diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..bea4e492df --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +# top-most EditorConfig file +root = true + +[*.cs] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index c950f83d8e..9f7ec8f882 100644 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,11 @@ x86/ bld/ [Bb]in/ [Oo]bj/ +build/ # Roslyn cache directories *.ide/ +.vs/ # MSTest test Results [Tt]est[Rr]esult*/ @@ -233,6 +235,5 @@ $RECYCLE.BIN/ AkavacheSqliteLinkerOverride.cs NuGetBuild WiX.Toolset.DummyFile.txt -nunit-UnitTests.xml -nunit-TrackingCollectionTests.xml +nunit-*.xml GitHubVS.sln.DotSettings diff --git a/.gitmodules b/.gitmodules index 1e655d0621..f37964cd09 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,21 +1,15 @@ -[submodule "submodules/rothko"] - path = submodules/rothko - url = https://github.com/Haacked/Rothko.git [submodule "submodules/reactiveui"] path = submodules/reactiveui - url = https://github.com/shana/ReactiveUI + url = https://github.com/editor-tools/ReactiveUI [submodule "submodules/octokit.net"] path = submodules/octokit.net - url = https://github.com/shana/Octokit.Net + url = https://github.com/editor-tools/Octokit.Net [submodule "submodules/splat"] path = submodules/splat - url = https://github.com/shana/splat.git + url = https://github.com/editor-tools/splat.git [submodule "submodules/akavache"] path = submodules/akavache - url = https://github.com/shana/Akavache -[submodule "submodules/libgit2sharp"] - path = submodules/libgit2sharp - url = https://github.com/shana/libgit2sharp.git + url = https://github.com/editor-tools/Akavache [submodule "script"] path = script - url = git@github.com:github/VisualStudioBuildScripts + url = git@github.com:github/VisualStudioBuildScripts \ No newline at end of file diff --git a/.ncrunch/Akavache.Sqlite3.v3.ncrunchproject b/.ncrunch/Akavache.Sqlite3.v3.ncrunchproject new file mode 100644 index 0000000000..161aafaa0b --- /dev/null +++ b/.ncrunch/Akavache.Sqlite3.v3.ncrunchproject @@ -0,0 +1,6 @@ +<ProjectConfiguration> + <Settings> + <PreventSigningOfAssembly>False</PreventSigningOfAssembly> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Akavache_Net45.v3.ncrunchproject b/.ncrunch/Akavache_Net45.v3.ncrunchproject new file mode 100644 index 0000000000..161aafaa0b --- /dev/null +++ b/.ncrunch/Akavache_Net45.v3.ncrunchproject @@ -0,0 +1,6 @@ +<ProjectConfiguration> + <Settings> + <PreventSigningOfAssembly>False</PreventSigningOfAssembly> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Akavache_Portable.v3.ncrunchproject b/.ncrunch/Akavache_Portable.v3.ncrunchproject new file mode 100644 index 0000000000..161aafaa0b --- /dev/null +++ b/.ncrunch/Akavache_Portable.v3.ncrunchproject @@ -0,0 +1,6 @@ +<ProjectConfiguration> + <Settings> + <PreventSigningOfAssembly>False</PreventSigningOfAssembly> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/CredentialManagement.v3.ncrunchproject b/.ncrunch/CredentialManagement.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/CredentialManagement.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/DesignTimeStyleHelper.v3.ncrunchproject b/.ncrunch/DesignTimeStyleHelper.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/DesignTimeStyleHelper.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.Api.v3.ncrunchproject b/.ncrunch/GitHub.Api.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.Api.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.App.v3.ncrunchproject b/.ncrunch/GitHub.App.v3.ncrunchproject new file mode 100644 index 0000000000..586daf0054 --- /dev/null +++ b/.ncrunch/GitHub.App.v3.ncrunchproject @@ -0,0 +1,6 @@ +<ProjectConfiguration> + <Settings> + <PreloadReferencedAssemblies>False</PreloadReferencedAssemblies> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.Exports.Reactive.v3.ncrunchproject b/.ncrunch/GitHub.Exports.Reactive.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.Exports.Reactive.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.Exports.v3.ncrunchproject b/.ncrunch/GitHub.Exports.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.Exports.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.Extensions.Reactive.v3.ncrunchproject b/.ncrunch/GitHub.Extensions.Reactive.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.Extensions.Reactive.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.Extensions.v3.ncrunchproject b/.ncrunch/GitHub.Extensions.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.Extensions.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.InlineReviews.UnitTests.v3.ncrunchproject b/.ncrunch/GitHub.InlineReviews.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..0cef203ae5 --- /dev/null +++ b/.ncrunch/GitHub.InlineReviews.UnitTests.v3.ncrunchproject @@ -0,0 +1,9 @@ +<ProjectConfiguration> + <Settings> + <CopyReferencedAssembliesToWorkspace>True</CopyReferencedAssembliesToWorkspace> + <HiddenComponentWarnings> + <Value>CopyReferencedAssembliesToWorkspaceIsOn</Value> + </HiddenComponentWarnings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.InlineReviews.v3.ncrunchproject b/.ncrunch/GitHub.InlineReviews.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.InlineReviews.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.Logging.v3.ncrunchproject b/.ncrunch/GitHub.Logging.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.Logging.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.StartPage.v3.ncrunchproject b/.ncrunch/GitHub.StartPage.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.StartPage.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.TeamFoundation.14.v3.ncrunchproject b/.ncrunch/GitHub.TeamFoundation.14.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.TeamFoundation.14.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.TeamFoundation.15.v3.ncrunchproject b/.ncrunch/GitHub.TeamFoundation.15.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.TeamFoundation.15.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.UI.Reactive.v3.ncrunchproject b/.ncrunch/GitHub.UI.Reactive.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.UI.Reactive.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.UI.UnitTests.v3.ncrunchproject b/.ncrunch/GitHub.UI.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..0cef203ae5 --- /dev/null +++ b/.ncrunch/GitHub.UI.UnitTests.v3.ncrunchproject @@ -0,0 +1,9 @@ +<ProjectConfiguration> + <Settings> + <CopyReferencedAssembliesToWorkspace>True</CopyReferencedAssembliesToWorkspace> + <HiddenComponentWarnings> + <Value>CopyReferencedAssembliesToWorkspaceIsOn</Value> + </HiddenComponentWarnings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.UI.v3.ncrunchproject b/.ncrunch/GitHub.UI.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.UI.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.VisualStudio.UI.v3.ncrunchproject b/.ncrunch/GitHub.VisualStudio.UI.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.VisualStudio.UI.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GitHub.VisualStudio.v3.ncrunchproject b/.ncrunch/GitHub.VisualStudio.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GitHub.VisualStudio.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/GithubVSTestAutomationIDs.v3.ncrunchproject b/.ncrunch/GithubVSTestAutomationIDs.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/GithubVSTestAutomationIDs.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Markdig.Wpf.v3.ncrunchproject b/.ncrunch/Markdig.Wpf.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Markdig.Wpf.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Octokit.Reactive.v3.ncrunchproject b/.ncrunch/Octokit.Reactive.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Octokit.Reactive.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Octokit.v3.ncrunchproject b/.ncrunch/Octokit.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Octokit.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/ReactiveUI.Events_Net45.v3.ncrunchproject b/.ncrunch/ReactiveUI.Events_Net45.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/ReactiveUI.Events_Net45.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/ReactiveUI.Testing_Net45.v3.ncrunchproject b/.ncrunch/ReactiveUI.Testing_Net45.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/ReactiveUI.Testing_Net45.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/ReactiveUI_Net45.v3.ncrunchproject b/.ncrunch/ReactiveUI_Net45.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/ReactiveUI_Net45.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Rothko.v3.ncrunchproject b/.ncrunch/Rothko.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Rothko.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Splat-Net45.v3.ncrunchproject b/.ncrunch/Splat-Net45.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Splat-Net45.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/Splat-Portable.v3.ncrunchproject b/.ncrunch/Splat-Portable.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Splat-Portable.v3.ncrunchproject @@ -0,0 +1,5 @@ +<ProjectConfiguration> + <Settings> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/TrackingCollectionTests.v3.ncrunchproject b/.ncrunch/TrackingCollectionTests.v3.ncrunchproject new file mode 100644 index 0000000000..74f25ff082 --- /dev/null +++ b/.ncrunch/TrackingCollectionTests.v3.ncrunchproject @@ -0,0 +1,15 @@ +<ProjectConfiguration> + <Settings> + <DefaultTestTimeout>5000</DefaultTestTimeout> + <HiddenComponentWarnings> + <Value>AbnormalReferenceResolution</Value> + </HiddenComponentWarnings> + <IgnoredTests> + <NamedTestSelector> + <TestName>TrackingTests.OrderByDoesntMatchOriginalOrderTimings</TestName> + </NamedTestSelector> + </IgnoredTests> + <PreloadReferencedAssemblies>True</PreloadReferencedAssemblies> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/.ncrunch/UnitTests.v3.ncrunchproject b/.ncrunch/UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..8400a07ba0 --- /dev/null +++ b/.ncrunch/UnitTests.v3.ncrunchproject @@ -0,0 +1,22 @@ +<ProjectConfiguration> + <Settings> + <CopyReferencedAssembliesToWorkspace>True</CopyReferencedAssembliesToWorkspace> + <DefaultTestTimeout>2000</DefaultTestTimeout> + <HiddenComponentWarnings> + <Value>AbnormalReferenceResolution</Value> + <Value>CopyReferencedAssembliesToWorkspaceIsOn</Value> + </HiddenComponentWarnings> + <IgnoredTests> + <FixtureTestSelector> + <FixtureName>SimpleRepositoryModelTests</FixtureName> + </FixtureTestSelector> + <FixtureTestSelector> + <FixtureName>UIControllerTests+PullRequestsFlow</FixtureName> + </FixtureTestSelector> + <FixtureTestSelector> + <FixtureName>UIProviderTests</FixtureName> + </FixtureTestSelector> + </IgnoredTests> + <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> + </Settings> +</ProjectConfiguration> \ No newline at end of file diff --git a/Build-Solution.cmd b/Build-Solution.cmd deleted file mode 100644 index 822884e5cc..0000000000 --- a/Build-Solution.cmd +++ /dev/null @@ -1 +0,0 @@ -Powershell -ExecutionPolicy Unrestricted %~dp0Build-Solution.ps1 \ No newline at end of file diff --git a/Build-Solution.ps1 b/Build-Solution.ps1 deleted file mode 100644 index 5e78326dce..0000000000 --- a/Build-Solution.ps1 +++ /dev/null @@ -1,147 +0,0 @@ -param( - [ValidateSet('Full', 'Tests', 'Build', 'Clean')] - [string] - $build = "Build" - , - [ValidateSet('Debug', 'Release')] - [string] - $config = "Release" - , - [ValidateSet('Any CPU', 'x86', 'x64')] - [string] - $platform = "Any CPU" - , - [string] - $verbosity = "minimal" -) - -$rootDirectory = Split-Path $MyInvocation.MyCommand.Path -$projFile = join-path $rootDirectory GitHubVS.msbuild -$msbuild = "C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" - -function Die([string]$message, [object[]]$output) { - if ($output) { - Write-Output $output - $message += ". See output above." - } - Throw (New-Object -TypeName ScriptException -ArgumentList $message) -} - -function Run-Command([scriptblock]$Command, [switch]$Fatal, [switch]$Quiet) { - $output = "" - if ($Quiet) { - $output = & $Command 2>&1 - } else { - & $Command - } - - if (!$Fatal) { - return - } - - $exitCode = 0 - if (!$? -and $LastExitCode -ne 0) { - $exitCode = $LastExitCode - } elseif (!$?) { - $exitCode = 1 - } else { - return - } - - Die "``$Command`` failed" $output -} - -function Run-XUnit([string]$project, [int]$timeoutDuration, [string]$configuration) { - $dll = "src\$project\bin\$configuration\$project.dll" - - $xunitDirectory = Join-Path $rootDirectory packages\xunit.runner.console.2.1.0\tools - $consoleRunner = Join-Path $xunitDirectory xunit.console.x86.exe - $xml = Join-Path $rootDirectory "nunit-$project.xml" - $outputPath = [System.IO.Path]::GetTempFileName() - - $args = $dll, "-noshadow", "-xml", $xml, "-parallel", "all" - [object[]] $output = "$consoleRunner " + ($args -join " ") - - $process = Start-Process -PassThru -NoNewWindow -RedirectStandardOutput $outputPath $consoleRunner ($args | %{ "`"$_`"" }) - Wait-Process -InputObject $process -Timeout $timeoutDuration -ErrorAction SilentlyContinue - if ($process.HasExited) { - $output += Get-Content $outputPath - $exitCode = $process.ExitCode - } else { - $output += "Tests timed out. Backtrace:" - $output += Get-DotNetStack $process.Id - $exitCode = 9999 - } - Stop-Process -InputObject $process - Remove-Item $outputPath - - $result = New-Object System.Object - $result | Add-Member -Type NoteProperty -Name Output -Value $output - $result | Add-Member -Type NoteProperty -Name ExitCode -Value $exitCode - $result -} - -function Run-NUnit([string]$project, [int]$timeoutDuration, [string]$configuration) { - $dll = "src\$project\bin\$configuration\$project.dll" - - $nunitDirectory = Join-Path $rootDirectory packages\NUnit.Runners.2.6.4\tools - $consoleRunner = Join-Path $nunitDirectory nunit-console-x86.exe - $xml = Join-Path $rootDirectory "nunit-$project.xml" - $outputPath = [System.IO.Path]::GetTempFileName() - - $args = "-noshadow", "-xml:$xml", "-framework:net-4.5", "-exclude:Timings", $dll - [object[]] $output = "$consoleRunner " + ($args -join " ") - - $process = Start-Process -PassThru -NoNewWindow -RedirectStandardOutput $outputPath $consoleRunner ($args | %{ "`"$_`"" }) - Wait-Process -InputObject $process -Timeout $timeoutDuration -ErrorAction SilentlyContinue - if ($process.HasExited) { - $output += Get-Content $outputPath - $exitCode = $process.ExitCode - } else { - $output += "Tests timed out. Backtrace:" - $output += Get-DotNetStack $process.Id - $exitCode = 9999 - } - - Stop-Process -InputObject $process - Remove-Item $outputPath - - $result = New-Object System.Object - $result | Add-Member -Type NoteProperty -Name Output -Value $output - $result | Add-Member -Type NoteProperty -Name ExitCode -Value $exitCode - $result -} - -function Build-Solution([string]$solution) { - Run-Command -Fatal { & $msbuild $solution /t:Build /property:Configuration=$config /verbosity:$verbosity /p:VisualStudioVersion=14.0 /p:DeployExtension=false } -} - -Write-Output "Building GitHub for Visual Studio..." -Write-Output "" - -Build-Solution GitHubVs.sln - -$exitCode = 0 - -Write-Output "Running Unit Tests..." -$result = Run-XUnit UnitTests 180 $config -if ($result.ExitCode -eq 0) { - # Print out the test result summary. - Write-Output $result.Output[-1] -} else { - $exitCode = $result.ExitCode - Write-Output $result.Output -} - -Write-Output "Running TrackingCollection Tests..." -$result = Run-NUnit TrackingCollectionTests 180 $config -if ($result.ExitCode -eq 0) { - # Print out the test result summary. - Write-Output $result.Output[-3] -} else { - $exitCode = $result.ExitCode - Write-Output $result.Output -} -Write-Output "" - -exit $exitCode \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 788b3848ea..15e2f4ad6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,28 @@ -## Contributing +## Contributing to the _GitHub Extension for Visual Studio_ [fork]: https://github.com/github/VisualStudio/fork [pr]: https://github.com/github/VisualStudio/compare [code-of-conduct]: http://todogroup.org/opencodeofconduct/#VisualStudio/opensource@github.com [readme]: https://github.com/github/VisualStudio#build -Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. +Hi there! We're thrilled that you'd like to contribute to the __GitHub Extension for Visual Studio__. Your help is essential for keeping it great. + +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.md). This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code. ## Submitting a pull request -0. [Fork][] and clone the repository (see Build Instructions in the [README][readme]) -0. Create a new branch: `git checkout -b my-branch-name` -0. Make your change, add tests, and make sure the tests still pass -0. Push to your fork and [submit a pull request][pr] -0. Pat your self on the back and wait for your pull request to be reviewed and merged. +1. [Fork][] and clone the repository (see Build Instructions in the [README][readme]) +2. Create a new branch: `git checkout -b my-branch-name` +3. Make your change, add tests, and make sure the tests still pass +4. Push to your fork and [submit a pull request][pr] +5. Pat your self on the back and wait for your pull request to be reviewed and merged. Here are a few things you can do that will increase the likelihood of your pull request being accepted: -- Follow the existing code's style. -- Write tests. +- Follow the style/format of the existing code. +- Write tests for your changes. - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). @@ -31,9 +33,33 @@ There are certain areas of the extension that are restricted in what they can do - Team Explorer content outside the Home page is slightly less restricted, but not by much - Dialogs and views that don't inherit from TeamExplorer classes are free to use what they need. +## Submitting an Issue + +### Bug Reporting + +Here are a few helpful tips when reporting a bug: +- Verify that the bug resides in the GitHub for Visual Studio extension + - A lot of functionality provided by this extension resides in the Team Explorer pane, alongside other non-GitHub tools to manage and collaborate on source code, including Visual Studio's Git support, which is owned by Microsoft. + - If this bug not is related to the GitHub extension, visit the [Visual Studio support page](https://www.visualstudio.com/support/support-overview-vs) for help +- Screenshots are very helpful in diagnosing bugs and understanding the state of the extension when it's experiencing problems. Please include them whenever possible. +- A log file is helpful in diagnosing bug issues. To include log files in your issue: + + 1. Close Visual Studio if it's open + 2. Open a Developer Command Prompt for VS2015 + 3. Run devenv /log + 4. Reproduce your issue + 5. Close VS + 6. Locate the following files on your system and email them to windows@github.com or create a gist and link it in the issue report: + - `%appdata%\Microsoft\VisualStudio\14.0\ActivityLog.xml` + - `%localappdata%\temp\extension.log` + - `%localappdata%\GitHubVisualStudio\extension.log` + +### Feature Requests +If you have a feature that you think would be a great addition to the extension, we might already have thought about it too, so be sure to check if your suggestion matches our [roadmap](#roadmap-and-future-feature-ideas) before making a request. Also take a peek at our [pull requests](https://github.com/github/VisualStudio/pulls) to see what we're currently working on. Additionally, someone might have already thought of your idea, so check out Issues labeled as [features](https://github.com/github/VisualStudio/issues?q=is%3Aopen+is%3Aissue+label%3Afeature) to see if it already exists. + ## Things to improve in the current version -- Localization +- [Localization](https://github.com/github/VisualStudio/issues/18) ## Roadmap and future feature ideas diff --git a/GitHubVS.sln b/GitHubVS.sln index cc8c0cb875..97eeb887a0 100644 --- a/GitHubVS.sln +++ b/GitHubVS.sln @@ -1,17 +1,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2035 MinimumVisualStudioVersion = 10.0.40219.1 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} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{72036B62-2FA6-4A22-8B33-69F698A18CF1}" ProjectSection(SolutionItems) = preProject README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "src\UnitTests\UnitTests.csproj", "{596595A6-2A3C-469E-9386-9E3767D863A5}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.UI", "src\GitHub.UI\GitHub.UI.csproj", "{346384DD-2445-4A28-AF22-B45F3957BD89}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.UI.Reactive", "src\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj", "{158B05E8-FDBC-4D71-B871-C96E28D5ADF5}" @@ -23,53 +24,41 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.App", "src\GitHub.App\GitHub.App.csproj", "{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Submodules", "Submodules", "{1E7F7253-A6AF-43C4-A955-37BEDDA01AB8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rothko", "submodules\Rothko\src\Rothko.csproj", "{4A84E568-CA86-4510-8CD0-90D3EF9B65F9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibGit2Sharp", "LibGit2Sharp", "{8446C785-A5B4-4676-9B38-560FCA0563E0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibGit2Sharp", "submodules\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj", "{EE6ED99F-CB12-4683-B055-D28FC7357A34}" + ProjectSection(SolutionItems) = preProject + 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 - script\Modules\BuildUtils.psm1 = script\Modules\BuildUtils.psm1 - script\Modules\Debugging.psm1 = script\Modules\Debugging.psm1 - script\Modules\Vsix.psm1 = script\Modules\Vsix.psm1 - script\Modules\WiX.psm1 = script\Modules\WiX.psm1 + 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}") = "Script", "Script", "{7B6C5F8D-14B3-443D-B044-0E209AE12BDF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{7B6C5F8D-14B3-443D-B044-0E209AE12BDF}" ProjectSection(SolutionItems) = preProject .gitattributes = .gitattributes .gitignore = .gitignore - script\Bootstrap.ps1 = script\Bootstrap.ps1 - build.cmd = build.cmd - script\Bump-Version.ps1 = script\Bump-Version.ps1 - script\cibuild.ps1 = script\cibuild.ps1 - script\common.ps1 = script\common.ps1 - script\Deploy.ps1 = script\Deploy.ps1 - script\Get-CheckedOutBranch.ps1 = script\Get-CheckedOutBranch.ps1 - script\HubotTell-NativeRoom.ps1 = script\HubotTell-NativeRoom.ps1 + 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 nuget.config = nuget.config - script\Require-CleanWorkTree.ps1 = script\Require-CleanWorkTree.ps1 - script\SolutionInfo.cs = script\SolutionInfo.cs - script\Upload-DirectoryToS3.ps1 = script\Upload-DirectoryToS3.ps1 + scripts\Require-CleanWorkTree.ps1 = scripts\Require-CleanWorkTree.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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Support", "Support", "{8A7DA2E7-262B-4581-807A-1C45CE79CDFF}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports", "src\GitHub.Exports\GitHub.Exports.csproj", "{9AEA02DB-02B5-409C-B0CA-115D05331A6B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Api", "src\GitHub.Api\GitHub.Api.csproj", "{B389ADAF-62CC-486E-85B4-2D8B078DF763}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports.Reactive", "src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj", "{E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DesignTimeStyleHelper", "src\DesignTimeStyleHelper\DesignTimeStyleHelper.csproj", "{B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}" -EndProject -Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "MsiInstaller", "src\MsiInstaller\MsiInstaller.wixproj", "{1ED83084-2A57-4F89-915C-8A2167C0D6BC}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Octokit", "Octokit", "{1E7F7253-A6AF-43C4-A955-37BEDDA01AC0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit", "submodules\octokit.net\Octokit\Octokit.csproj", "{08DD4305-7787-4823-A53F-4D0F725A07F3}" @@ -82,365 +71,472 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI_Net45", "submodu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Events_Net45", "submodules\reactiveui\ReactiveUI.Events\ReactiveUI.Events_Net45.csproj", "{600998C4-54DD-4755-BFA8-6F44544D8E2E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventBuilder", "submodules\reactiveui\ReactiveUI.Events\EventBuilder.csproj", "{3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Akavache", "Akavache", "{1E7F7253-A6AF-43C4-A955-37BEDDA01AC9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akavache_Net45", "submodules\akavache\Akavache\Akavache_Net45.csproj", "{B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akavache.Sqlite3", "submodules\akavache\Akavache.Sqlite3\Akavache.Sqlite3.csproj", "{241C47DF-CA8E-4296-AA03-2C48BB646ABD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akavache_Portable", "submodules\akavache\Akavache\Akavache_Portable.csproj", "{EB73ADDD-2FE9-44C0-A1AB-20709B979B64}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Splat", "Splat", "{1E7F7253-A6AF-43C4-A955-37BEDDA01AF9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Splat-Net45", "submodules\splat\Splat\Splat-Net45.csproj", "{252CE1C2-027A-4445-A3C2-E4D6C80A935A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Splat-Portable", "submodules\splat\Splat\Splat-Portable.csproj", "{0EC8DBA1-D745-4EE5-993A-6026440EC3BF}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialManagement", "src\CredentialManagement\CredentialManagement.csproj", "{41A47C5B-C606-45B4-B83C-22B9239E4DA0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Testing_Net45", "submodules\reactiveui\ReactiveUI.Testing\ReactiveUI.Testing_Net45.csproj", "{DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrackingCollectionTests", "test\TrackingCollectionTests\TrackingCollectionTests.csproj", "{7B835A7D-CF94-45E8-B191-96F5A4FE26A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.TeamFoundation.14", "src\GitHub.TeamFoundation.14\GitHub.TeamFoundation.14.csproj", "{161DBF01-1DBF-4B00-8551-C5C00F26720D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.TeamFoundation.15", "src\GitHub.TeamFoundation.15\GitHub.TeamFoundation.15.csproj", "{161DBF01-1DBF-4B00-8551-C5C00F26720E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.VisualStudio.UI", "src\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj", "{D1DFBB0C-B570-4302-8F1E-2E3A19C41961}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.StartPage", "src\GitHub.StartPage\GitHub.StartPage.csproj", "{50E277B8-8580-487A-8F8E-5C3B9FBF0F77}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrackingCollectionTests", "src\TrackingCollectionTests\TrackingCollectionTests.csproj", "{7B835A7D-CF94-45E8-B191-96F5A4FE26A8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.UI.UnitTests", "test\GitHub.UI.UnitTests\GitHub.UI.UnitTests.csproj", "{110B206F-8554-4B51-BF86-94DAA32F5E26}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.InlineReviews", "src\GitHub.InlineReviews\GitHub.InlineReviews.csproj", "{7F5ED78B-74A3-4406-A299-70CFB5885B8B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.InlineReviews.UnitTests", "test\GitHub.InlineReviews.UnitTests\GitHub.InlineReviews.UnitTests.csproj", "{17EB676B-BB91-48B5-AA59-C67695C647C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Logging", "src\GitHub.Logging\GitHub.Logging.csproj", "{8D73575A-A89F-47CC-B153-B47DD06837F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Services.Vssdk", "src\GitHub.Services.Vssdk\GitHub.Services.Vssdk.csproj", "{2D3D2834-33BE-45CA-B3CC-12F853557D7B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Metrics", "Metrics", "{C2D88962-BD6B-4F11-B914-535B38377962}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsServer", "test\MetricsTests\MetricsServer\MetricsServer.csproj", "{14FDEE91-7301-4247-846C-049647BF8E99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsTests", "test\MetricsTests\MetricsTests\MetricsTests.csproj", "{09313E65-7ADB-48C1-AD3A-572020C5BDCB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Api.UnitTests", "test\GitHub.Api.UnitTests\GitHub.Api.UnitTests.csproj", "{EFDE0798-ACDB-431D-B7F1-548A7231C853}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.App.UnitTests", "test\GitHub.App.UnitTests\GitHub.App.UnitTests.csproj", "{3525D819-6AEC-4879-89FB-56B41F026571}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports.UnitTests", "test\GitHub.Exports.UnitTests\GitHub.Exports.UnitTests.csproj", "{94509FCB-6C97-4ED6-AED6-6E74AB3CA336}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Exports.Reactive.UnitTests", "test\GitHub.Exports.Reactive.UnitTests\GitHub.Exports.Reactive.UnitTests.csproj", "{C59868FC-D8BC-4D47-B4F3-16908D2641C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Extensions.UnitTests", "test\GitHub.Extensions.UnitTests\GitHub.Extensions.UnitTests.csproj", "{DE704BBB-6EC6-4173-B695-D9EBF5AEB092}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Primitives.UnitTests", "test\GitHub.Primitives.UnitTests\GitHub.Primitives.UnitTests.csproj", "{E687457A-BEDC-422D-8D9D-2DA58099EBBA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.TeamFoundation.UnitTests", "test\GitHub.TeamFoundation.UnitTests\GitHub.TeamFoundation.UnitTests.csproj", "{93778A89-3E58-4853-B772-948EBB3F17BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.VisualStudio.UnitTests", "test\GitHub.VisualStudio.UnitTests\GitHub.VisualStudio.UnitTests.csproj", "{8B14F90B-0781-465D-AB94-19C8C56E3A94}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 - Publish|Any CPU = Publish|Any CPU - Publish|x86 = Publish|x86 + DebugCodeAnalysis|Any CPU = DebugCodeAnalysis|Any CPU + DebugWithoutVsix|Any CPU = DebugWithoutVsix|Any CPU Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 + ReleaseWithoutVsix|Any CPU = ReleaseWithoutVsix|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Debug|x86.Build.0 = Debug|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Publish|x86.ActiveCfg = Release|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Publish|x86.Build.0 = Release|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 {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Release|Any CPU.Build.0 = Release|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Release|x86.ActiveCfg = Release|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Release|x86.Build.0 = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Debug|x86.ActiveCfg = Debug|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Debug|x86.Build.0 = Debug|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Publish|x86.ActiveCfg = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Publish|x86.Build.0 = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Release|Any CPU.Build.0 = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Release|x86.ActiveCfg = Release|Any CPU - {596595A6-2A3C-469E-9386-9E3767D863A5}.Release|x86.Build.0 = Release|Any CPU + {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.ReleaseWithoutVsix|Any CPU.ActiveCfg = ReleaseWithoutVsix|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.Debug|x86.Build.0 = Debug|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.Publish|x86.ActiveCfg = Release|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.Publish|x86.Build.0 = Release|Any CPU + {346384DD-2445-4A28-AF22-B45F3957BD89}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {346384DD-2445-4A28-AF22-B45F3957BD89}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {346384DD-2445-4A28-AF22-B45F3957BD89}.Release|Any CPU.Build.0 = Release|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.Release|x86.ActiveCfg = Release|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.Release|x86.Build.0 = Release|Any CPU + {346384DD-2445-4A28-AF22-B45F3957BD89}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Debug|x86.Build.0 = Debug|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Publish|x86.ActiveCfg = Release|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Publish|x86.Build.0 = Release|Any CPU + {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Release|Any CPU.Build.0 = Release|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Release|x86.ActiveCfg = Release|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Release|x86.Build.0 = Release|Any CPU + {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Debug|x86.Build.0 = Debug|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Publish|x86.ActiveCfg = Release|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Publish|x86.Build.0 = Release|Any CPU + {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Release|Any CPU.Build.0 = Release|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Release|x86.ActiveCfg = Release|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Release|x86.Build.0 = Release|Any CPU + {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Debug|x86.Build.0 = Debug|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Publish|x86.ActiveCfg = Release|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Publish|x86.Build.0 = Release|Any CPU + {6559E128-8B40-49A5-85A8-05565ED0C7E3}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {6559E128-8B40-49A5-85A8-05565ED0C7E3}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Release|Any CPU.Build.0 = Release|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Release|x86.ActiveCfg = Release|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Release|x86.Build.0 = Release|Any CPU + {6559E128-8B40-49A5-85A8-05565ED0C7E3}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Debug|x86.Build.0 = Debug|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Publish|x86.ActiveCfg = Release|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Publish|x86.Build.0 = Release|Any CPU + {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Release|Any CPU.Build.0 = Release|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Release|x86.ActiveCfg = Release|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Release|x86.Build.0 = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Debug|Any CPU.Build.0 = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Debug|x86.ActiveCfg = Debug|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Debug|x86.Build.0 = Debug|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Publish|x86.ActiveCfg = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Publish|x86.Build.0 = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|Any CPU.Build.0 = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|x86.ActiveCfg = Release|Any CPU - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9}.Release|x86.Build.0 = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Debug|x86.ActiveCfg = Debug|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Debug|x86.Build.0 = Debug|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Publish|Any CPU.Build.0 = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Publish|x86.ActiveCfg = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Publish|x86.Build.0 = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Release|Any CPU.Build.0 = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Release|x86.ActiveCfg = Release|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Release|x86.Build.0 = Release|Any CPU + {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|x86.Build.0 = Debug|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Publish|x86.ActiveCfg = Release|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Publish|x86.Build.0 = Release|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|Any CPU.Build.0 = Release|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|x86.ActiveCfg = Release|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|x86.Build.0 = Release|Any CPU + {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Debug|x86.Build.0 = Debug|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Publish|x86.ActiveCfg = Release|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Publish|x86.Build.0 = Release|Any CPU + {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|Any CPU.Build.0 = Release|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|x86.ActiveCfg = Release|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|x86.Build.0 = Release|Any CPU + {B389ADAF-62CC-486E-85B4-2D8B078DF763}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Debug|x86.Build.0 = Debug|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Publish|x86.ActiveCfg = Release|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Publish|x86.Build.0 = Release|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Release|Any CPU.Build.0 = Release|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Release|x86.ActiveCfg = Release|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Release|x86.Build.0 = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Debug|x86.ActiveCfg = Debug|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Debug|x86.Build.0 = Debug|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Publish|x86.ActiveCfg = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Publish|x86.Build.0 = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Release|Any CPU.Build.0 = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Release|x86.ActiveCfg = Release|Any CPU - {B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}.Release|x86.Build.0 = Release|Any CPU - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Debug|Any CPU.ActiveCfg = Debug|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Debug|x86.ActiveCfg = Debug|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Debug|x86.Build.0 = Debug|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Publish|Any CPU.ActiveCfg = Publish|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Publish|Any CPU.Build.0 = Publish|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Publish|x86.ActiveCfg = Publish|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Publish|x86.Build.0 = Publish|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Release|Any CPU.ActiveCfg = Release|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Release|x86.ActiveCfg = Release|x86 - {1ED83084-2A57-4F89-915C-8A2167C0D6BC}.Release|x86.Build.0 = Release|x86 - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Debug|Any CPU.Build.0 = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Debug|x86.ActiveCfg = Debug|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Debug|x86.Build.0 = Debug|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Publish|x86.ActiveCfg = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Publish|x86.Build.0 = Release|Any CPU + {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 {08DD4305-7787-4823-A53F-4D0F725A07F3}.Release|Any CPU.Build.0 = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Release|x86.ActiveCfg = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.Release|x86.Build.0 = Release|Any CPU + {08DD4305-7787-4823-A53F-4D0F725A07F3}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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}.Debug|x86.ActiveCfg = Debug|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Debug|x86.Build.0 = Debug|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Publish|x86.ActiveCfg = Release|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Publish|x86.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 {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Release|Any CPU.Build.0 = Release|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Release|x86.ActiveCfg = Release|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Release|x86.Build.0 = Release|Any CPU + {674B69B8-0780-4D54-AE2B-C15821FA51CB}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {674B69B8-0780-4D54-AE2B-C15821FA51CB}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Debug|Any CPU.ActiveCfg = Release|Any CPU {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Debug|Any CPU.Build.0 = Release|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Debug|x86.ActiveCfg = Debug|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Debug|x86.Build.0 = Debug|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Publish|x86.ActiveCfg = Release|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Publish|x86.Build.0 = Release|Any CPU + {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Release|Any CPU.Build.0 = Release|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Release|x86.ActiveCfg = Release|Any CPU - {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.Release|x86.Build.0 = Release|Any CPU + {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {1CE2D235-8072-4649-BA5A-CFB1AF8776E0}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Debug|Any CPU.ActiveCfg = Release|Any CPU {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Debug|Any CPU.Build.0 = Release|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Debug|x86.ActiveCfg = Debug|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Debug|x86.Build.0 = Debug|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Publish|x86.ActiveCfg = Release|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Publish|x86.Build.0 = Release|Any CPU + {600998C4-54DD-4755-BFA8-6F44544D8E2E}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {600998C4-54DD-4755-BFA8-6F44544D8E2E}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {600998C4-54DD-4755-BFA8-6F44544D8E2E}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {600998C4-54DD-4755-BFA8-6F44544D8E2E}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Release|Any CPU.Build.0 = Release|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Release|x86.ActiveCfg = Release|Any CPU - {600998C4-54DD-4755-BFA8-6F44544D8E2E}.Release|x86.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|Any CPU.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|x86.ActiveCfg = Debug|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Debug|x86.Build.0 = Debug|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Publish|x86.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Publish|x86.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|Any CPU.Build.0 = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|x86.ActiveCfg = Release|Any CPU - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D}.Release|x86.Build.0 = Release|Any CPU + {600998C4-54DD-4755-BFA8-6F44544D8E2E}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {600998C4-54DD-4755-BFA8-6F44544D8E2E}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|Any CPU.ActiveCfg = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|Any CPU.Build.0 = Release|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|x86.ActiveCfg = Debug|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Debug|x86.Build.0 = Debug|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Publish|x86.ActiveCfg = Release|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Publish|x86.Build.0 = Release|Any CPU + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Release|Any CPU.Build.0 = Release|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Release|x86.ActiveCfg = Release|Any CPU - {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.Release|x86.Build.0 = Release|Any CPU + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Debug|Any CPU.ActiveCfg = Release|Any CPU {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Debug|Any CPU.Build.0 = Release|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Debug|x86.ActiveCfg = Debug|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Debug|x86.Build.0 = Debug|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Publish|x86.ActiveCfg = Release|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Publish|x86.Build.0 = Release|Any CPU + {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Release|Any CPU.Build.0 = Release|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Release|x86.ActiveCfg = Release|Any CPU - {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.Release|x86.Build.0 = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Debug|Any CPU.Build.0 = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Debug|x86.ActiveCfg = Debug|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Debug|x86.Build.0 = Debug|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Publish|Any CPU.Build.0 = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Publish|x86.ActiveCfg = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Publish|x86.Build.0 = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Release|Any CPU.Build.0 = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Release|x86.ActiveCfg = Release|Any CPU - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64}.Release|x86.Build.0 = Release|Any CPU + {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {241C47DF-CA8E-4296-AA03-2C48BB646ABD}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Debug|Any CPU.ActiveCfg = Release|Any CPU {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Debug|Any CPU.Build.0 = Release|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Debug|x86.ActiveCfg = Debug|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Debug|x86.Build.0 = Debug|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Publish|x86.ActiveCfg = Release|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Publish|x86.Build.0 = Release|Any CPU + {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Release|Any CPU.ActiveCfg = Release|Any CPU {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Release|Any CPU.Build.0 = Release|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Release|x86.ActiveCfg = Release|Any CPU - {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.Release|x86.Build.0 = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Debug|Any CPU.Build.0 = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Debug|x86.ActiveCfg = Debug|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Debug|x86.Build.0 = Debug|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Publish|x86.ActiveCfg = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Publish|x86.Build.0 = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Release|Any CPU.Build.0 = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Release|x86.ActiveCfg = Release|Any CPU - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF}.Release|x86.Build.0 = Release|Any CPU + {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {252CE1C2-027A-4445-A3C2-E4D6C80A935A}.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}.Debug|x86.ActiveCfg = Debug|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Debug|x86.Build.0 = Debug|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Publish|Any CPU.Build.0 = Release|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Publish|x86.ActiveCfg = Release|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Publish|x86.Build.0 = Release|Any CPU + {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Release|Any CPU.Build.0 = Release|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Release|x86.ActiveCfg = Release|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Release|x86.Build.0 = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Debug|x86.ActiveCfg = Debug|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Debug|x86.Build.0 = Debug|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Publish|Any CPU.Build.0 = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Publish|x86.ActiveCfg = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Publish|x86.Build.0 = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Release|Any CPU.Build.0 = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Release|x86.ActiveCfg = Release|Any CPU - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65}.Release|x86.Build.0 = Release|Any CPU + {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Debug|x86.ActiveCfg = Debug|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Debug|x86.Build.0 = Debug|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Publish|Any CPU.Build.0 = Release|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Publish|x86.ActiveCfg = Release|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Publish|x86.Build.0 = Release|Any CPU + {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU + {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Release|Any CPU.Build.0 = Release|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Release|x86.ActiveCfg = Release|Any CPU - {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.Release|x86.Build.0 = Release|Any CPU + {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {7B835A7D-CF94-45E8-B191-96F5A4FE26A8}.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 + {161DBF01-1DBF-4B00-8551-C5C00F26720D}.Release|Any CPU.Build.0 = Release|Any CPU + {161DBF01-1DBF-4B00-8551-C5C00F26720D}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {161DBF01-1DBF-4B00-8551-C5C00F26720E}.Release|Any CPU.Build.0 = Release|Any CPU + {161DBF01-1DBF-4B00-8551-C5C00F26720E}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 = DebugCodeAnalysis|Any CPU + {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 + {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.Release|Any CPU.Build.0 = Release|Any CPU + {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.Release|Any CPU.Build.0 = Release|Any CPU + {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {110B206F-8554-4B51-BF86-94DAA32F5E26}.Release|Any CPU.Build.0 = Release|Any CPU + {110B206F-8554-4B51-BF86-94DAA32F5E26}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.Release|Any CPU.Build.0 = Release|Any CPU + {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {17EB676B-BB91-48B5-AA59-C67695C647C2}.Release|Any CPU.Build.0 = Release|Any CPU + {17EB676B-BB91-48B5-AA59-C67695C647C2}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 = DebugCodeAnalysis|Any CPU + {8D73575A-A89F-47CC-B153-B47DD06837F0}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|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 + {8D73575A-A89F-47CC-B153-B47DD06837F0}.Release|Any CPU.Build.0 = Release|Any CPU + {8D73575A-A89F-47CC-B153-B47DD06837F0}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 = DebugCodeAnalysis|Any CPU + {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU + {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugWithoutVsix|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU + {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugWithoutVsix|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU + {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.Release|Any CPU.Build.0 = Release|Any CPU + {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {14FDEE91-7301-4247-846C-049647BF8E99}.Release|Any CPU.Build.0 = Release|Any CPU + {14FDEE91-7301-4247-846C-049647BF8E99}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.Release|Any CPU.Build.0 = Release|Any CPU + {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {EFDE0798-ACDB-431D-B7F1-548A7231C853}.Release|Any CPU.Build.0 = Release|Any CPU + {EFDE0798-ACDB-431D-B7F1-548A7231C853}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {3525D819-6AEC-4879-89FB-56B41F026571}.Release|Any CPU.Build.0 = Release|Any CPU + {3525D819-6AEC-4879-89FB-56B41F026571}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.Release|Any CPU.Build.0 = Release|Any CPU + {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.Release|Any CPU.Build.0 = Release|Any CPU + {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.Release|Any CPU.Build.0 = Release|Any CPU + {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.Release|Any CPU.Build.0 = Release|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {E687457A-BEDC-422D-8D9D-2DA58099EBBA}.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 + {93778A89-3E58-4853-B772-948EBB3F17BE}.Release|Any CPU.Build.0 = Release|Any CPU + {93778A89-3E58-4853-B772-948EBB3F17BE}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {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 + {8B14F90B-0781-465D-AB94-19C8C56E3A94}.Release|Any CPU.Build.0 = Release|Any CPU + {8B14F90B-0781-465D-AB94-19C8C56E3A94}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {8B14F90B-0781-465D-AB94-19C8C56E3A94}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {596595A6-2A3C-469E-9386-9E3767D863A5} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} - {4A84E568-CA86-4510-8CD0-90D3EF9B65F9} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} - {8446C785-A5B4-4676-9B38-560FCA0563E0} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} - {EE6ED99F-CB12-4683-B055-D28FC7357A34} = {8446C785-A5B4-4676-9B38-560FCA0563E0} {8E1F1B4E-AEA2-4AB1-8F73-423A903550A1} = {7B6C5F8D-14B3-443D-B044-0E209AE12BDF} - {1ED83084-2A57-4F89-915C-8A2167C0D6BC} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFF} {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} {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} {1CE2D235-8072-4649-BA5A-CFB1AF8776E0} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {600998C4-54DD-4755-BFA8-6F44544D8E2E} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} - {3D4AE5F9-A535-4D5C-8F30-1A35D7BA0A3D} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} {241C47DF-CA8E-4296-AA03-2C48BB646ABD} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} - {EB73ADDD-2FE9-44C0-A1AB-20709B979B64} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC9} {1E7F7253-A6AF-43C4-A955-37BEDDA01AF9} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} {252CE1C2-027A-4445-A3C2-E4D6C80A935A} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AF9} - {0EC8DBA1-D745-4EE5-993A-6026440EC3BF} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AF9} - {DD99FD0F-82F6-4C30-930E-4A1D0DF01D65} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {7B835A7D-CF94-45E8-B191-96F5A4FE26A8} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {110B206F-8554-4B51-BF86-94DAA32F5E26} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {17EB676B-BB91-48B5-AA59-C67695C647C2} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {C2D88962-BD6B-4F11-B914-535B38377962} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {14FDEE91-7301-4247-846C-049647BF8E99} = {C2D88962-BD6B-4F11-B914-535B38377962} + {09313E65-7ADB-48C1-AD3A-572020C5BDCB} = {C2D88962-BD6B-4F11-B914-535B38377962} + {EFDE0798-ACDB-431D-B7F1-548A7231C853} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {3525D819-6AEC-4879-89FB-56B41F026571} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {94509FCB-6C97-4ED6-AED6-6E74AB3CA336} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {C59868FC-D8BC-4D47-B4F3-16908D2641C6} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {DE704BBB-6EC6-4173-B695-D9EBF5AEB092} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {E687457A-BEDC-422D-8D9D-2DA58099EBBA} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {93778A89-3E58-4853-B772-948EBB3F17BE} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {8B14F90B-0781-465D-AB94-19C8C56E3A94} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {556014CF-5B35-4CE5-B3EF-6AB0007001AC} EndGlobalSection EndGlobal diff --git a/GitHubVS.v3.ncrunchsolution b/GitHubVS.v3.ncrunchsolution new file mode 100644 index 0000000000..2cd8e79788 --- /dev/null +++ b/GitHubVS.v3.ncrunchsolution @@ -0,0 +1,14 @@ +<SolutionConfiguration> + <Settings> + <AdditionalFilesForGridProcessing> + <Value>packages\Fody.1.29.4\**.*</Value> + <Value>packages\EntryExitDecorator.Fody.0.3.0\**.*</Value> + </AdditionalFilesForGridProcessing> + <AdditionalFilesToIncludeForSolution> + <Value>lib\**.*</Value> + </AdditionalFilesToIncludeForSolution> + <AllowParallelTestExecution>False</AllowParallelTestExecution> + <ProjectConfigStoragePathRelativeToSolutionDir>.ncrunch</ProjectConfigStoragePathRelativeToSolutionDir> + <SolutionConfigured>True</SolutionConfigured> + </Settings> +</SolutionConfiguration> \ No newline at end of file diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..2c895f02d6 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,24 @@ +<!-- Hello! Please read the [contributing guidelines](https://github.com/github/VisualStudio/blob/master/CONTRIBUTING.md) before submitting an issue regarding the GitHub Extension for Visual Studio. --> + +## Version + +- GitHub Extension for Visual Studio version: +- Visual Studio version: + +## What happened + +**Steps to Reproduce** + +1. [First Step] +2. [Second Step] +3. [and so on...] + +**Expected behavior:** [What you expect to happen] + +**Actual behavior:** [What actually happens] + +**Screenshot or GIF:** + +**Log file:** + +<!-- Include the log file. Logs can be found at: `%LOCALAPPDATA%\GitHubVisualStudio\extension.log` --> diff --git a/LICENSE.md b/LICENSE.md index 0b506974f9..a9872c8b69 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2015 GitHub Inc. +Copyright (c) GitHub Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index f6a804ccf2..9eedee0630 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,49 @@ # 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. 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). -[](https://gitter.im/github/VisualStudio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +[](https://ci.appveyor.com/project/github-windows/visualstudio/branch/master) +[](https://codecov.io/gh/GitHub/VisualStudio) + +[](http://webchat.freenode.net/?channels=%23github-vs) [](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. + +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`. + +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. ## Build requirements -* Visual Studio 2015 +* Visual Studio 2017 (15.7.4)+ * Visual Studio SDK ## Build Clone the repository and its submodules in a git GUI client or via the command line: -``` +```txt git clone https://github.com/github/VisualStudio cd VisualStudio git submodule init @@ -24,11 +51,48 @@ git submodule deinit script git submodule update ``` -Open the `GitHubVS.sln` solution with Visual Studio 2015. +Open the `GitHubVS.sln` solution with Visual Studio 2017+. To be able to use the GitHub API, you'll need to: - [Register a new developer application](https://github.com/settings/developers) in your profile. -- Open [src/GitHub.App/Api/ApiClientConfiguration.cs](src/GitHub.App/Api/ApiClientConfiguration.cs) and fill out the clientId/clientSecret fields for your application. +- 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 using Visual Studio 2017 or: + +```txt +build.cmd +``` + +Install in live (non-Experimental) instances of Visual Studio 2015 and 2017: + +```txt +install.cmd +``` + +Note, the script will only install in one instance of Visual Studio 2017 (Enterprise, Professional or Community). + +## Build Flavors + +The following can be executed via `cmd.exe`. + +To build and install a `Debug` configuration VSIX: +```txt +build.cmd Debug +install.cmd Debug +``` + +To build and install a `Release` configuration VSIX: +```txt +build.cmd Release +install.cmd Release +``` +## 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. ## Contributing @@ -36,6 +100,7 @@ Visit the [Contributor Guidelines](CONTRIBUTING.md) for details on how to contri ## Copyright -Copyright 2015 GitHub, Inc. +Copyright 2015 - 2018 GitHub, Inc. Licensed under the [MIT License](LICENSE.md) + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..d103ff2160 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,37 @@ +os: Visual Studio 2017 +version: '2.5.6.{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 +build_script: +- ps: scripts\build.ps1 -AppVeyor -BuildNumber:$env:APPVEYOR_BUILD_NUMBER +test: + categories: + except: + - Timings +after_test: +- ps: | + choco install opencover.portable codecov + OpenCover.Console.exe --% "-target:nunit3-console.exe" "-targetargs: test\GitHub.Api.UnitTests\bin\Release\GitHub.Api.UnitTests.dll test\GitHub.App.UnitTests\bin\Release\GitHub.App.UnitTests.dll test\GitHub.Exports.Reactive.UnitTests\bin\Release\GitHub.Exports.Reactive.UnitTests.dll test\GitHub.Exports.UnitTests\bin\Release\GitHub.Exports.UnitTests.dll test\GitHub.Extensions.UnitTests\bin\Release\GitHub.Extensions.UnitTests.dll test\GitHub.InlineReviews.UnitTests\bin\Release\GitHub.InlineReviews.UnitTests.dll test\GitHub.Primitives.UnitTests\bin\Release\GitHub.Primitives.UnitTests.dll test\GitHub.TeamFoundation.UnitTests\bin\Release\GitHub.TeamFoundation.UnitTests.dll test\GitHub.UI.UnitTests\bin\Release\GitHub.UI.UnitTests.dll test\GitHub.VisualStudio.UnitTests\bin\Release\GitHub.VisualStudio.UnitTests.dll test\MetricsTests\MetricsTests\bin\Release\MetricsTests.dll test\TrackingCollectionTests\bin\Release\TrackingCollectionTests.dll --where cat!=Timings" -filter:"+[GitHub*]* -[GitHub*]*UnitTests" -register:user -output:".\coverage.xml" + codecov -f "coverage.xml" + Push-AppveyorArtifact coverage.xml +on_success: +- ps: | + if ($full_build) { + script\Sign-Package -AppVeyor + } diff --git a/build.cmd b/build.cmd index 7b7f67072a..ed204a53a7 100644 --- a/build.cmd +++ b/build.cmd @@ -1 +1,2 @@ -powershell.exe .\script\cibuild.ps1 \ No newline at end of file +@if "%1" == "" echo Please specify Debug or Release && EXIT /B +powershell -ExecutionPolicy Unrestricted scripts\build.ps1 -Package:$true -Config:%1 diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..1727ea602d --- /dev/null +++ b/codecov.yml @@ -0,0 +1,29 @@ +codecov: + notify: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header, diff" + behavior: default + require_changes: no + +fixes: + - "C:\projects\visualstudio\::" diff --git a/deploy-local.cmd b/deploy-local.cmd new file mode 100644 index 0000000000..64f80141d9 --- /dev/null +++ b/deploy-local.cmd @@ -0,0 +1 @@ +powershell.exe .\script\deploy.ps1 -Force -NoChat -NoPush -NoUpload \ No newline at end of file diff --git a/deploy.cmd b/deploy.cmd deleted file mode 100644 index 0998692f9f..0000000000 --- a/deploy.cmd +++ /dev/null @@ -1 +0,0 @@ -powershell.exe .\script\deploy.ps1 staff development %1 -Force -NoCampfire \ No newline at end of file diff --git a/docs/contributing/cloning-a-repository-to-visual-studio.md b/docs/contributing/cloning-a-repository-to-visual-studio.md new file mode 100644 index 0000000000..1ccea26979 --- /dev/null +++ b/docs/contributing/cloning-a-repository-to-visual-studio.md @@ -0,0 +1,19 @@ +# Cloning a repository to Visual Studio + +After you provide your GitHub or GitHub Enterprise credentials to GitHub for Visual Studio, the extension automatically detects the personal and organization repositories you have access to on your account. + +1. Open **Team Explorer** by clicking on its tab next to *Solution Explorer*, or via the *View* menu. +2. Click the **Manage Connections** toolbar button. + + + +3. Next to the account you want to clone from, click **Clone**. + + + +4. In the list of repositories, click the repository you'd like to clone. + + + +5. If desired, change the local path where the repository will be cloned into, or leave the default as-is. Click **Clone**. +6. In Team Explorer, under the list of repositories, locate the repository and double-click to open the project in Visual Studio. diff --git a/docs/contributing/creating-a-pull-request.md b/docs/contributing/creating-a-pull-request.md new file mode 100644 index 0000000000..c2eb313cd0 --- /dev/null +++ b/docs/contributing/creating-a-pull-request.md @@ -0,0 +1,10 @@ +# Creating a pull request + +1. Open a solution in a GitHub repository. +2. Open **Team Explorer** and click the **Pull Requests** button to open the **GitHub** pane. + +3. Click the **Create New** link above the list of pull requests for the repository. +4. Select the target branch by clicking the link. If the current repository is a fork, then there will be two sets of branches in the dropdown - to submit a pull request upstream then select a branch with the `owner:` prefix of the upstream repository. + +5. Enter a pull request title and an optional description. +6. Click the **Create Pull Request** button. diff --git a/docs/contributing/creating-an-empty-repository-from-visual-studio.md b/docs/contributing/creating-an-empty-repository-from-visual-studio.md new file mode 100644 index 0000000000..4252863095 --- /dev/null +++ b/docs/contributing/creating-an-empty-repository-from-visual-studio.md @@ -0,0 +1,27 @@ +# Creating an empty repository from Visual Studio + +1. [Sign in](../getting-started/authenticating-to-github.md) to GitHub. + +2. Open **Team Explorer** by clicking on its tab next to *Solution Explorer*, or via the *View* menu. + +3. Click the **Manage Connections** toolbar button. + +  + +4. Click the **Create** link next to the account you want to create the repository in. + +  + +5. In the **Create a GitHub Repository** dialog, enter a name, description and local path for the repository. + +  + +6. Select a license for the repository. + +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. + +8. Click the **Create** button to create the repository + +9. When the repository is created, click the **Create a new Project or Solution** link in Team Explorer to create a project or solution in the repository. + +  diff --git a/docs/contributing/creating-gists.md b/docs/contributing/creating-gists.md new file mode 100644 index 0000000000..1a5547457b --- /dev/null +++ b/docs/contributing/creating-gists.md @@ -0,0 +1,23 @@ +# Creating gists + +GitHub for Visual Studio enables easy creation of gists directly from the Visual Studio Editor. + +1. [Sign in](../getting-started/authenticating-to-github.md) to GitHub. + +2. Open a file in the Visual Studio text editor. + +3. Select the section of text that you want to create a gist from. + +4. Right click and select **Create a GitHub Gist** from the **GitHub** submenu. + +  + +5. In the **Create a GitHub Gist** dialog, check that the filename is correct and optionally add a description. + +  + +6. If you want the gist to be private, check the **Private Gist** checkbox. + +7. Click **Create**. + +8. Once the gist is created it will be opened in your browser. diff --git a/docs/contributing/images/add-comment.png b/docs/contributing/images/add-comment.png new file mode 100644 index 0000000000..3bbc5244f4 Binary files /dev/null and b/docs/contributing/images/add-comment.png differ diff --git a/docs/contributing/images/add-to-source-control.png b/docs/contributing/images/add-to-source-control.png new file mode 100644 index 0000000000..7154c14a63 Binary files /dev/null and b/docs/contributing/images/add-to-source-control.png differ diff --git a/docs/contributing/images/clone-dialog.png b/docs/contributing/images/clone-dialog.png new file mode 100644 index 0000000000..2e44a61916 Binary files /dev/null and b/docs/contributing/images/clone-dialog.png differ diff --git a/docs/contributing/images/clone-link.png b/docs/contributing/images/clone-link.png new file mode 100644 index 0000000000..eb8a26bae8 Binary files /dev/null and b/docs/contributing/images/clone-link.png differ diff --git a/docs/contributing/images/create-dialog.png b/docs/contributing/images/create-dialog.png new file mode 100644 index 0000000000..16f3dfd013 Binary files /dev/null and b/docs/contributing/images/create-dialog.png differ diff --git a/docs/contributing/images/create-gist-dialog.png b/docs/contributing/images/create-gist-dialog.png new file mode 100644 index 0000000000..fbab9a2abf Binary files /dev/null and b/docs/contributing/images/create-gist-dialog.png differ diff --git a/docs/contributing/images/create-gist-menu.png b/docs/contributing/images/create-gist-menu.png new file mode 100644 index 0000000000..015aafbe54 Binary files /dev/null and b/docs/contributing/images/create-gist-menu.png differ diff --git a/docs/contributing/images/create-link.png b/docs/contributing/images/create-link.png new file mode 100644 index 0000000000..1636ec6ae2 Binary files /dev/null and b/docs/contributing/images/create-link.png differ diff --git a/docs/contributing/images/github-pane-toolbar.png b/docs/contributing/images/github-pane-toolbar.png new file mode 100644 index 0000000000..f146ac0b80 Binary files /dev/null and b/docs/contributing/images/github-pane-toolbar.png differ diff --git a/docs/contributing/images/hover-to-add-comment.png b/docs/contributing/images/hover-to-add-comment.png new file mode 100644 index 0000000000..231441e0f9 Binary files /dev/null and b/docs/contributing/images/hover-to-add-comment.png differ diff --git a/docs/contributing/images/manage-connections.png b/docs/contributing/images/manage-connections.png new file mode 100644 index 0000000000..fb8c01d58e Binary files /dev/null and b/docs/contributing/images/manage-connections.png differ diff --git a/docs/contributing/images/open-on-github.png b/docs/contributing/images/open-on-github.png new file mode 100644 index 0000000000..186bda86d9 Binary files /dev/null and b/docs/contributing/images/open-on-github.png differ diff --git a/docs/contributing/images/open-team-explorer.png b/docs/contributing/images/open-team-explorer.png new file mode 100644 index 0000000000..25a750c85f Binary files /dev/null and b/docs/contributing/images/open-team-explorer.png differ diff --git a/docs/contributing/images/pr-create.png b/docs/contributing/images/pr-create.png new file mode 100644 index 0000000000..02aededb85 Binary files /dev/null and b/docs/contributing/images/pr-create.png differ diff --git a/docs/contributing/images/pr-details-checkout-link.png b/docs/contributing/images/pr-details-checkout-link.png new file mode 100644 index 0000000000..543ac38e49 Binary files /dev/null and b/docs/contributing/images/pr-details-checkout-link.png differ diff --git a/docs/contributing/images/pr-details.png b/docs/contributing/images/pr-details.png new file mode 100644 index 0000000000..222b2edc1a Binary files /dev/null and b/docs/contributing/images/pr-details.png differ diff --git a/docs/contributing/images/pr-diff-files.png b/docs/contributing/images/pr-diff-files.png new file mode 100644 index 0000000000..5ae7800009 Binary files /dev/null and b/docs/contributing/images/pr-diff-files.png differ diff --git a/docs/contributing/images/pr-pull-changes.png b/docs/contributing/images/pr-pull-changes.png new file mode 100644 index 0000000000..3073e77536 Binary files /dev/null and b/docs/contributing/images/pr-pull-changes.png differ diff --git a/docs/contributing/images/publish-to-github.png b/docs/contributing/images/publish-to-github.png new file mode 100644 index 0000000000..59c3abb36b Binary files /dev/null and b/docs/contributing/images/publish-to-github.png differ diff --git a/docs/contributing/images/pull-request-list.png b/docs/contributing/images/pull-request-list.png new file mode 100644 index 0000000000..9b5d32b881 Binary files /dev/null and b/docs/contributing/images/pull-request-list.png differ diff --git a/docs/contributing/images/pull-requests-button.png b/docs/contributing/images/pull-requests-button.png new file mode 100644 index 0000000000..365494defb Binary files /dev/null and b/docs/contributing/images/pull-requests-button.png differ diff --git a/docs/contributing/images/pull-requests-button2.png b/docs/contributing/images/pull-requests-button2.png new file mode 100644 index 0000000000..4d03c1b0dc Binary files /dev/null and b/docs/contributing/images/pull-requests-button2.png differ diff --git a/docs/contributing/images/successful-creation-message.png b/docs/contributing/images/successful-creation-message.png new file mode 100644 index 0000000000..45fe34e34b Binary files /dev/null and b/docs/contributing/images/successful-creation-message.png differ diff --git a/docs/contributing/images/team-explorer-sync.png b/docs/contributing/images/team-explorer-sync.png new file mode 100644 index 0000000000..32d4bce4a3 Binary files /dev/null and b/docs/contributing/images/team-explorer-sync.png differ diff --git a/docs/contributing/index.md b/docs/contributing/index.md new file mode 100644 index 0000000000..5bf9eb0791 --- /dev/null +++ b/docs/contributing/index.md @@ -0,0 +1,29 @@ +# Contributing to Projects with GitHub for Visual Studio + +Use GitHub for Visual Studio to manage your projects and work with pull requests. + +### Table of Contents + +- Adding and cloning repositories + - [Publishing an existing project to GitHub](publishing-an-existing-project-to-github.md) + - [Creating an empty repository from Visual Studio](creating-an-empty-repository-from-visual-studio.md) + - [Cloning a repository to Visual Studio](cloning-a-repository-to-visual-studio.md) +- Working with pull requests + - [Viewing pull requests for a repository](viewing-the-pull-requests-for-a-repository.md) + - [Creating a pull request](creating-a-pull-request.md) + - [Reviewing a pull request in Visual Studio](reviewing-a-pull-request-in-visual-studio.md) + - [Making changes to a pull request](making-changes-to-a-pull-request.md) +- [Creating gists](creating-gists.md) +- [Viewing existing code on GitHub](viewing-code-on-github.md) + - [Viewing the selected code on GitHub](viewing-code-on-github.md#viewing-the-selected-code-on-github) + - [Copying the URL of the selected code's location on GitHub](viewing-code-on-github.md#copying-the-url-of-the-selected-codes-location-on-github) + - [Viewing the selected code in blame view on GitHub](viewing-code-on-github.md#viewing-the-selected-code-in-blame-view-on-github) +- [Using the GitHub toolbar](using-the-github-toolbar.md) +- Operations provided by Visual Studio + - [Committing](https://www.visualstudio.com/en-us/docs/git/tutorial/commits) + - [Pushing commits to the remote repository](https://www.visualstudio.com/en-us/docs/git/tutorial/pushing) + - [Fetching and pulling](https://www.visualstudio.com/en-us/docs/git/tutorial/pulling) + - [Working with branches](https://www.visualstudio.com/en-us/docs/git/tutorial/branches) + - [Viewing history](https://www.visualstudio.com/en-us/docs/git/tutorial/history) + - [Ignoring files](https://www.visualstudio.com/en-us/docs/git/tutorial/ignore-files) +- [Contact a human](https://github.com/contact) diff --git a/docs/contributing/making-changes-to-a-pull-request.md b/docs/contributing/making-changes-to-a-pull-request.md new file mode 100644 index 0000000000..d16d728dfb --- /dev/null +++ b/docs/contributing/making-changes-to-a-pull-request.md @@ -0,0 +1,17 @@ +# Making changes to a pull request + +When a topic branch is [checked out](review-a-pull-request-in-visual-studio.md), you can commit changes to it and push and pull like any other branch. If the pull request branch is located in a fork and was checked out from the Pull Request Details view in the GitHub pane, then a remote to that fork will be created automatically and the branch set to track the fork branch. + +## Pulling changes to your local clone + +If a Pull Request is checked out and the author adds new commits to the branch, then the option will be given to pull the changes locally. This works both for pull requests from the same repository and from a fork. + + + +## Pushing changes + +If you make commits locally to a topic branch, then you can push the changes to the remote branch. You can also do this from Git itself or from the Visual Studio Team Explorer **Sync** view. + +> Note: for this to work with Pull Requests that come from forks, then you must be a maintainer on the repository and the Pull Request submitter must have checked [Allow edits from maintainers](https://help.github.com/articles/allowing-changes-to-a-pull-request-branch-created-from-a-fork/) when submitting the Pull Request. + +If there are commits on the branch on the remote repository that you don't have on your local clone, you must pull them to your local clone and [resolve any conflicts](https://help.github.com/articles/addressing-merge-conflicts/) before you can push your local commits back to the remote repository. diff --git a/docs/contributing/publishing-an-existing-project-to-github.md b/docs/contributing/publishing-an-existing-project-to-github.md new file mode 100644 index 0000000000..7b296056ce --- /dev/null +++ b/docs/contributing/publishing-an-existing-project-to-github.md @@ -0,0 +1,14 @@ +# Publishing an existing project to GitHub + +1. Open a solution in Visual Studio. +2. If solution is not already initialized as a Git repository, select **Add to Source Control** from the **File** menu. + +3. Open **Team Explorer**. + +4. In Team Explorer, click **Sync**. + +5. Click the **Publish to GitHub** button. + +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. +8. Click the **Publish** button. diff --git a/docs/contributing/reviewing-a-pull-request-in-visual-studio.md b/docs/contributing/reviewing-a-pull-request-in-visual-studio.md new file mode 100644 index 0000000000..904af9dab3 --- /dev/null +++ b/docs/contributing/reviewing-a-pull-request-in-visual-studio.md @@ -0,0 +1,51 @@ +# Reviewing a pull request in Visual Studio + +GitHub for Visual Studio provides facilities for reviewing a pull request directly in Visual Studio. + +1. Open a solution in a GitHub repository. + +2. Open **Team Explorer** and click the **Pull Requests** button to open the **GitHub** pane. + +  + +3. Click the title of the pull request to be reviewed. + +## Viewing a pull request + +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, and the files changed. + + + +## 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. + + + +If the pull request is from a fork then a remote will be added to the forked repository and the branch checked out locally. This remote will automatically be cleaned up when the local branch is deleted. + +> Note that you cannot check out a pull request branch when your working directory has uncommitted changes. First commit or stash your changes and then refresh the Pull Request view. + +## Viewing Changes + +To view the changes in the pull request for a file, double click a file in the **Changed Files** tree. This will open the Visual Studio diff viewer. + + + +You can also right-click on a file in the changed files tree to get more options: + +- **View Changes**: This is the default option that is also triggered when the file is double-clicked. It shows the changes to the file that are introduced by the pull request. +- **View File**: This opens a read-only editor showing the contents of the file in the pull request. +- **View Changes in Solution**: This menu item is only available when the pull request branch is checked out. It shows the changes in the pull request, but the right hand side of the diff is the file in the working directory. This view allows you to use Visual Studio navigation commands such as **Go to Definition (F12)**. +- **Open File in Solution**: This menu item opens the working directory file in an editor. + +## Leaving Comments + +You can add comments to a pull request directly from Visual Studio. When a file is [open in the diff viewer](#viewing-changes) you can click the **Add Comment** icon in the margin to add a comment on a line. + + + +Then click the icon on the desired line and leave a comment. + + +Existing comments left by you or other reviewers will also show up in this margin. Click the icon to open an inline conversation view from which you can review and reply to comments: diff --git a/docs/contributing/using-the-github-toolbar.md b/docs/contributing/using-the-github-toolbar.md new file mode 100644 index 0000000000..0cc353babd --- /dev/null +++ b/docs/contributing/using-the-github-toolbar.md @@ -0,0 +1,29 @@ +# Using the GitHub pane toolbar + +The GitHub pane toolbar provides a way to navigate between views, refresh views, and open the current view on GitHub. + + + +1. Open a solution in a GitHub repository. +2. Open **Team Explorer** and click the **Pull Requests** button to open the **GitHub** pane. + +## Using the back navigation button +1. At the top of the **GitHub** pane, locate the button furthest to the left. This is the **Back** button. +2. Click **Back** to navigate to the previous view. If **Back** is grey, you have reached the initial point in the view navigation. + +## Using the forward navigation button +1. At the top of the **GitHub** pane, locate the second button from the left, which is the **Forward** button. +2. Click **Forward** to navigate to the next view. If **Forward** is grey, you have reached the furthest point in the view navigation. + +## Using the pull request toolbar icon +1. At the top of the **GitHub** pane, locate the third button from the left. This is the **Pull Requests** button. +2. Click **Pull Requests** to navigate to the list of pull requests in the repository. + +## Using the GitHub toolbar button +1. At the top of the **GitHub** pane, locate the fourth button from the left. This is the **View On GitHub** button. +2. While viewing the list of pull requests, click **View On Github**. Your browser will open and navigate to the repository's pull requests on GitHub. +3. While viewing the details of a pull request, click **View On Github**. Your browser will open and navigate to the pull request on GitHub. + +## Using the refresh toolbar button +1. At the top of the **GitHub** pane, locate the fifth button from the left. This is the **Refresh** button. +2. Click **Refresh** to refresh the current view in the **GitHub** pane. diff --git a/docs/contributing/viewing-code-on-github.md b/docs/contributing/viewing-code-on-github.md new file mode 100644 index 0000000000..2f3d587fc3 --- /dev/null +++ b/docs/contributing/viewing-code-on-github.md @@ -0,0 +1,21 @@ +# Viewing existing code on GitHub + +GitHub for Visual Studio enables easy navigation to code that exists on GitHub directly from the Visual Studio code editor. + +1. Open a solution in a GitHub repository. +2. Open *Solution Explorer* by clicking on its tab, or via the *View* menu. +3. In *Solution Explorer*, double click on a file to open it in Visual Studio code editor. +3. In the code editor, highlight the section of text that you want to view in the browser. + +## Viewing the selected code on GitHub +1. Right click and select **Open on GitHub** from the **GitHub** submenu. + +2. Your browser will open and navigate to the code on GitHub. + +## Copying the URL of the selected code's location on GitHub +1. Right click and select **Copy link to clipboard** from the **GitHub** submenu. The URL will be copied to the clipboard. +2. Paste the URL into your browser to view it on GitHub. + +## Viewing the selected code in blame view on GitHub +1. Right click and select **Blame** from the **GitHub** submenu. +2. Your browser will open and navigate to the code in blame view on GitHub. diff --git a/docs/contributing/viewing-the-pull-requests-for-a-repository.md b/docs/contributing/viewing-the-pull-requests-for-a-repository.md new file mode 100644 index 0000000000..83a2ae3e7c --- /dev/null +++ b/docs/contributing/viewing-the-pull-requests-for-a-repository.md @@ -0,0 +1,15 @@ +# Viewing the pull requests for a repository + +GitHub for Visual Studio exposes the pull requests for the current repository and lets you create new pull requests and review pull requests from other contributors. + +1. [Sign in](../getting-started/authenticating-to-github.md) to GitHub. +2. Open a solution in a GitHub repository. +3. Open **Team Explorer** and click the **Pull Requests** button to open the **GitHub** pane. + +4. The open pull requests will be displayed. + +5. Change the Open/Closed filter by clicking the **Open** link and selecting the filter you want to use from the dropdown. +6. Filter pull requests by Assignee by clicking the **Assignee** link and selecting the assignee you want to view from the dropdown. +7. Filter pull requests by Author by clicking the **Author** link and selecting the author you want to view from the dropdown. +8. Click on a pull request title to [view the pull request details and review the pull request](review-a-pull-request-in-visual-studio.md) +9. Click on the **Create New** link to [create a pull request from the current branch](sending-a-pull-request.md) diff --git a/docs/developer/dialog-views-with-connections.md b/docs/developer/dialog-views-with-connections.md new file mode 100644 index 0000000000..856b4ae67e --- /dev/null +++ b/docs/developer/dialog-views-with-connections.md @@ -0,0 +1,24 @@ +# Dialog Views with Connections + +Some dialog views need a connection to operate - if there is no connection, a login dialog should be shown: for example, clicking Create Gist without a connection will first prompt the user to log in. + +Achieving this is simple, first make your view model interface implement `IConnectionInitializedViewModel` and do any initialization that requires a connection in the `InitializeAsync` method in your view model: + +```csharp +public Task InitializeAsync(IConnection connection) +{ + // .. at this point, you're guaranteed to have a connection. +} +``` + +To show the dialog, call `IShowDialogService.ShowWithFirstConnection` instead of `Show`: + +```csharp +public async Task ShowExampleDialog() +{ + var viewModel = serviceProvider.ExportProvider.GetExportedValue<IExampleDialogViewModel>(); + await showDialog.ShowWithFirstConnection(viewModel); +} +``` + +`ShowFirstConnection` first checks if there are any logged in connections. If there are, the first logged in connection will be passed to `InitializeAsync` and the view shown immediately. If there are no logged in connections, the login view will first be shown. Once the user has successfully logged in, the new connection will be passed to `InitalizeAsync` and the view shown. \ No newline at end of file diff --git a/docs/developer/how-viewmodels-are-turned-into-views.md b/docs/developer/how-viewmodels-are-turned-into-views.md new file mode 100644 index 0000000000..a0df01241a --- /dev/null +++ b/docs/developer/how-viewmodels-are-turned-into-views.md @@ -0,0 +1,86 @@ +# How ViewModels are Turned into Views + +We make use of the [MVVM pattern](https://msdn.microsoft.com/en-us/library/ff798384.aspx), in which application level code is not aware of the view level. MVVM takes advantage of the fact that `DataTemplate`s can be used to create views from view models. + +## DataTemplates + +[`DataTemplate`](https://docs.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview)s are a WPF feature that allow you to define the presentation of your data. Consider a simple view model: + +```csharp +public class ViewModel +{ + public string Greeting => "Hello World!"; +} +``` + +And a window: + +```csharp +public class MainWindow : Window +{ + public MainWindow() + { + DataContext = new ViewModel(); + InitializeComponent(); + } +} +``` + +```xml +<Window x:Class="MyApp.MainWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:MyApp" + Content="{Binding}"> +<Window> + +``` + +Here we're binding the `Content` of the `Window` to the `Window.DataContext`, which we're setting in the constructor to be an instance of `ViewModel`. + +One can choose to display the `ViewModel` instance in any way we want by using a `DataTemplate`: + +```xml +<Window x:Class="MyApp.MainWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:MyApp" + Content="{Binding}"> + <Window.Resources> + <DataTemplate DataType="{x:Type local:ViewModel}"> + + <!-- Display ViewModel.Greeting in a red border with rounded corners --> + <Border Background="Red" CornerRadius="8"> + <TextBlock Binding="{Binding Greeting}"/> + </Border> + + </DataTemplate> + </Window.Resources> +</Window> +``` + +This is the basis for converting view models to views. + +## ViewLocator + +There are currently two top-level controls for our UI: + +- [GitHubDialogWindow](../../src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml) for the dialog which shows the login, clone, etc views +- [GitHubPaneView](../../src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml) for the GitHub pane + +In the resources for each of these top-level controls we define a `DataTemplate` like so: + +```xml +<views:ViewLocator x:Key="viewLocator"/> +<DataTemplate DataType="{x:Type vm:ViewModelBase}"> + <ContentControl Content="{Binding Converter={StaticResource viewLocator}}"/> +</DataTemplate> +``` + +The `DataTemplate.DataType` here applies the template to all classes inherited from [`GitHub.ViewModels.ViewModelBase`](../../src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs) [1]. The template defines a single `ContentControl` whose contents are created by a `ViewLocator`. + +The [`ViewLocator`](../../src/GitHub.VisualStudio/Views/ViewLocator.cs) class is an `IValueConverter` which then creates an instance of the appropriate view for the view model using MEF. + +And thus a view model becomes a view. + +[1]: it would be nice to make it apply to all classes that inherit `IViewModel` but unfortunately WPF's `DataTemplate`s don't work with interfaces. diff --git a/docs/developer/implementing-a-dialog-view.md b/docs/developer/implementing-a-dialog-view.md new file mode 100644 index 0000000000..332ddd4aa9 --- /dev/null +++ b/docs/developer/implementing-a-dialog-view.md @@ -0,0 +1,113 @@ +# Implementing a Dialog View + +GitHub for Visual Studio has a common dialog which is used to show the login, clone, create repository etc. operations. To add a new view to the dialog and show the dialog with this view, you need to do the following: + +## Create a View Model and Interface + +- Create an interface for the view model that implements `IDialogContentViewModel` in `GitHub.Exports.Reactive\ViewModels\Dialog` +- Create a view model that inherits from `NewViewModelBase` and implements the interface in `GitHub.App\ViewModels\Dialog` +- Export the view model with the interface as the contract and add a `[PartCreationPolicy(CreationPolicy.NonShared)]` attribute + +A minimal example that just exposes a command that will dismiss the dialog: + +```csharp +using System; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + public interface IExampleDialogViewModel : IDialogContentViewModel + { + ReactiveCommand<object> Dismiss { get; } + } +} +``` + +```csharp +using System; +using System.ComponentModel.Composition; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IExampleDialogViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ExampleDialogViewModel : ViewModelBase, IExampleDialogViewModel + { + [ImportingConstructor] + public ExampleDialogViewModel() + { + Dismiss = ReactiveCommand.Create(); + } + + public string Title => "Example Dialog"; + + public ReactiveCommand<object> Dismiss { get; } + + public IObservable<object> Done => Dismiss; + } +} +``` + +## Create a View + +- Create a WPF `UserControl` under `GitHub.VisualStudio\Views\Dialog` +- Add an `ExportViewFor` attribute with the type of the view model interface +- Add a `PartCreationPolicy(CreationPolicy.NonShared)]` attribute + +Continuing the example above: + +```xml +<UserControl x:Class="GitHub.VisualStudio.Views.Dialog.ExampleDialogView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <Button Command="{Binding Dismiss}" HorizontalAlignment="Center" VerticalAlignment="Center"> + Dismiss + </Button> +</UserControl> +``` + +```csharp +using System.ComponentModel.Composition; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + [ExportViewFor(typeof(IExampleDialogViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class ExampleDialogView : UserControl + { + public ExampleDialogView() + { + InitializeComponent(); + } + } +} +``` + +## Show the Dialog! + +To show the dialog you will need an instance of the `IShowDialogService` service. Once you have that, simply call the `Show` method with an instance of your view model. + +```csharp +var viewModel = new ExampleDialogViewModel(); +showDialog.Show(viewModel) +``` + +## Optional: Add a method to `DialogService` + +Creating a view model like this may be the right thing to do, but it's not very reusable or testable. If you want your dialog to be easy reusable, add a method to `DialogService`: + +```csharp +public async Task ShowExampleDialog() +{ + var viewModel = factory.CreateViewModel<IExampleDialogViewModel>(); + await showDialog.Show(viewModel); +} +``` + +Obviously, add this method to `IDialogService` too. + +Note that these methods are `async` - this means that if you need to do asynchronous initialization of your view model, you can do it here before calling `showDialog`. \ No newline at end of file diff --git a/docs/developer/implementing-github-pane-page.md b/docs/developer/implementing-github-pane-page.md new file mode 100644 index 0000000000..bd17b65af6 --- /dev/null +++ b/docs/developer/implementing-github-pane-page.md @@ -0,0 +1,122 @@ +# Implementing a GitHub Pane Page + +The GitHub pane displays GitHub-specific functionality in a dockable pane. To add a new page to the GitHub pane, you need to do the following: + +## Create a View Model and Interface + +- Create an interface for the view model that implements `IPanePageViewModel` in `GitHub.Exports.Reactive\ViewModels\GitHubPane` +- Create a view model that inherits from `PanePageViewModelBase` and implements the interface in `GitHub.App\ViewModels\GitHubPane` +- Export the view model with the interface as the contract and add a `[PartCreationPolicy(CreationPolicy.NonShared)]` attribute + +A minimal example that just exposes a command that will navigate to the pull request list: + +```csharp +using System; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + public interface IExamplePaneViewModel : IPanePageViewModel + { + ReactiveCommand<object> GoToPullRequests { get; } + } +} +``` + +```csharp +using System; +using System.ComponentModel.Composition; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + [Export(typeof(IExamplePaneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ExamplePaneViewModel : PanePageViewModelBase, IExamplePaneViewModel + { + [ImportingConstructor] + public ExamplePaneViewModel() + { + GoToPullRequests = ReactiveCommand.Create(); + GoToPullRequests.Subscribe(_ => NavigateTo("/pulls")); + } + + public ReactiveCommand<object> GoToPullRequests { get; } + } +} +``` + +## Create a View + +- Create a WPF `UserControl` under `GitHub.VisualStudio\ViewsGitHubPane` +- Add an `ExportViewFor` attribute with the type of the view model interface +- Add a `PartCreationPolicy(CreationPolicy.NonShared)]` attribute + +Continuing the example above: + +```xml +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.ExamplePaneView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <Button Command="{Binding GoToPullRequests}" + HorizontalAlignment="Center" + VerticalAlignment="Center"> + Go to Pull Requests + </Button> +</UserControl> + +``` + +```csharp +using System.ComponentModel.Composition; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + [ExportViewFor(typeof(IExampleDialogViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class ExampleDialogView : UserControl + { + public ExampleDialogView() + { + InitializeComponent(); + } + } +} +``` + +## Add a Route to GitHubPaneViewModel + +Now you need to add a route to the `GitHubPaneViewModel`. To add a route, you must do two things: + +- Add a method to `GitHubPaneViewModel` +- Add a URL handler to `GitHubPaneViewModel.NavigateTo` + +So lets add the `ShowExample` method to `GitHubPaneViewModel`: + +```csharp +public Task ShowExample() +{ + return NavigateTo<IExamplePaneViewModel>(x => Task.CompletedTask); +} +``` +Here we call `NavigateTo` with the type of the interface of our view model. We're passing a lambda that simply returns `Task.CompletedTask` as the parameter: usually here you'd call an async initialization method on the view model, but since we don't have one in our simple example we just return a completed task. + +Next we add a URL handler: our URL is going to be `github://pane/example` so we need to add a route that checks that the URL's `AbsolutePath` is `/example` and if so call the method we added above. This code is added to `GitHubPaneViewModel.NavigateTo`: + +```csharp +else if (uri.AbsolutePath == "/example") +{ + await ShowExample(); +} +``` + +For the sake of the example, we're going to show our new page as soon as the GitHub Pane is shown and the user is logged-in with an open repository. To do this, simply change `GitHubPaneViewModel.ShowDefaultPage` to the following: + +```csharp +public Task ShowDefaultPage() => ShowExample(); +``` + +When you run the extension and show the GitHub pane, our new example page should be shown. Clicking on the button in the page will navigate to the pull request list. \ No newline at end of file diff --git a/docs/developer/multi-paged-dialogs.md b/docs/developer/multi-paged-dialogs.md new file mode 100644 index 0000000000..1e014f516c --- /dev/null +++ b/docs/developer/multi-paged-dialogs.md @@ -0,0 +1,77 @@ +# Multi-paged Dialogs + +Some dialogs will be multi-paged - for example the login dialog has a credentials page and a 2Fa page that is shown if two-factor authorization is required. + +## The View Model + +To help implement view models for a multi-page dialog there is a useful base class called `PagedDialogViewModelBase`. The typical way of implementing this is as follows: + +- Define each page of the dialog as you would [implement a single dialog view model](implementing-a-dialog-view.md) +- Implement a "container" view model for the dialog that inherits from `PagedDialogViewModel` +- Import each page into the container view model +- Add logic to switch between pages by setting the `PagedDialogViewModelBase.Content` property +- Add a `Done` observable + +Here's a simple example of a container dialog that has two pages. The pages are switched using `ReactiveCommand`s: + +```csharp +using System; +using System.ComponentModel.Composition; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IExamplePagedDialogViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ExamplePagedDialogViewModel : PagedDialogViewModelBase, + IExamplePagedDialogViewModel + { + [ImportingConstructor] + public ExamplePagedDialogViewModel( + IPage1ViewModel page1, + IPage2ViewModel page2) + { + Content = page1; + page1.Next.Subscribe(_ => Content = page2); + page2.Previous.Subscribe(_ => Content = page1); + Done = Observable.Merge(page2.Done, page2.Done); + } + + public override IObservable<object> Done { get; } + } +} +``` + +## The View + +The view in this case is very simple: it just needs to display the `Content` property of the container view model: + +```xml +<UserControl x:Class="GitHub.VisualStudio.Views.Dialog.ExamplePagedDialogView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Content="{Binding Content}"> +</UserControl> +``` + +```csharp +using System; +using System.ComponentModel.Composition; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + [ExportViewFor(typeof(IExamplePagedDialogViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class ExamplePagedDialogView : UserControl + { + public NewLoginView() + { + InitializeComponent(); + } + } +} +``` + +> Note: this is such a common pattern, you don't actually need to define your own view! Simply add the `[ExportViewFor(...)]` attribute to the existing `ContentView` class. \ No newline at end of file diff --git a/docs/developer/readme.md b/docs/developer/readme.md new file mode 100644 index 0000000000..b0c06b2e81 --- /dev/null +++ b/docs/developer/readme.md @@ -0,0 +1,10 @@ +# Developer Documentation + +Documentation for hacking on GitHub for Visual Studio: + +- User Interface + - [How ViewModels are Turned into Views](how-viewmodels-are-turned-into-views.md) + - [Implementing a Dialog View](implementing-a-dialog-view.md) + - [Dialog Views with Connections](dialog-views-with-connections.md) + - [Multi-Paged Dialogs](multi-paged-dialogs.md) + diff --git a/docs/getting-started/authenticating-to-github.md b/docs/getting-started/authenticating-to-github.md new file mode 100644 index 0000000000..d3f83ba438 --- /dev/null +++ b/docs/getting-started/authenticating-to-github.md @@ -0,0 +1,50 @@ +# Authenticating to GitHub + +## How to login to GitHub or GitHub Enterprise + +1. In Visual Studio, select **Team Explorer** from the **View** menu. + <a href="images/view_team_explorer.png?raw=true" target="_blank"><div><img src="images/view_team_explorer.png" alt="Team Explorer in the view menu" width="500px"/></div></a> +1. In the Team Explorer pane, click the **Manage Connectios** toolbar icon. + <a href="images/manage_connections.png?raw=true" target="_blank"><div><img src="images/manage_connections.png" alt="Manage connections toolbar icon in the Team Explorer pane" width="500px"/></div></a> +1. Click the **Connect** link in the GitHub section. + <a href="images/sign-in-to-github-provider.png?raw=true" target="_blank"><div><img src="images/sign-in-to-github-provider.png" alt="Connect to GitHub" height="300px"/></div></a> + + If you're connected to a TFS instance, click on the **Sign in** link instead + <a href="images/sign-in-to-github.png?raw=true" target="_blank"><div><img src="images/sign-in-to-github.png" alt="Sign in to GitHub" width="300px"/></div></a> + + If none of these options are visible, click **Manage Connections** and then **Connect to GitHub**. + <a href="images/connect_to_github.png?raw=true" target="_blank"><div><img src="images/connect_to_github.png" alt="Connect to GitHub in the manage connections dropdown in the Team Explorer pane" width="500px"/></div></a> +1. In the **Connect to GitHub dialog** choose **GitHub** or **GitHub Enterprise**, depending on which product you're using. + +**GitHub option**: +<a href="images/connect-to-github-dialog.png?raw=true" target="_blank"><div><img src="images/connect-to-github-dialog.png" alt="Connect to GitHub dialog view" height="400px"/></div></a> + +- To sign in with credentials, enter either username or email and password. +- To sign in with SSO, select `Sign in with your browser`. + +**GitHub Enterprise option**: +<a href="images/connect-to-github-enterprise-dialog.png?raw=true" target="_blank"><div><img src="images/connect-to-github-enterprise-dialog.png" alt="Connect to GitHub Enterprise dialog view" height="400px"/></div></a> + +- To sign in with SSO, enter the GitHub Enterprise server address and select `Sign in with your browser`. +- To sign in with credentials, enter the GitHub Enterprise server address. + - If a `Password` field appears, enter your password. + - If a `Token` field appears, enter a valid token. You can create personal access tokens by [following the instructions in the section below](#personal_access_tokens). + +Before you authenticate, you must already have a GitHub or GitHub Enterprise account. + +- For more information on creating a GitHub account, see "[Signing up for a new GitHub account](https://help.github.com/articles/signing-up-for-a-new-github-account/)". +- For a GitHub Enterprise account, contact your GitHub Enterprise site administrator. + +### Personal access tokens + +If all signin options above fail, you can manually create a personal access token and use it as your password. + +The scopes for the personal access token are: `user`, `repo`, `gist`, and `write:public_key`. +- *user* scope: Grants access to the user profile data. We currently use this to display your avatar and check whether your plans lets you publish private repositories. +- *repo* scope: Grants read/write access to code, commit statuses, invitations, collaborators, adding team memberships, and deployment statuses for public and private repositories and organizations. This is needed for all git network operations (push, pull, fetch), and for getting information about the repository you're currently working on. +- *gist* scope: Grants write access to gists. We use this in our gist feature, so you can highlight code and create gists directly from Visual Studio +- *write:public_key* scope: Grants access to creating, listing, and viewing details for public keys. This will allows us to add ssh support to your repositories, if you are unable to go through https (this feature is not available yet, this scope is optional) + +For more information on creating personal access tokens, see "[Creating a personal access token for the command line](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). + +For more information on authenticating with SAML single sign-on, see "[About authentication with SAML single sign-on](https://help.github.com/articles/about-authentication-with-saml-single-sign-on)." diff --git a/docs/getting-started/configuring-git-in-visual-studio.md b/docs/getting-started/configuring-git-in-visual-studio.md new file mode 100644 index 0000000000..1c122ba3c4 --- /dev/null +++ b/docs/getting-started/configuring-git-in-visual-studio.md @@ -0,0 +1,17 @@ +# Configuring Git in Visual Studio + +# Setting your name and email + +The name and email that will be displayed with your commits can be set from Team Explorer's settings. + +1. Open **Team Explorer** by clicking on its tab next to *Solution Explorer*, or via the *View* menu. + +2. Click the **Settings** button in Team Explorer. + +  + +3. Click **Global Settings** in the Team Explorer Settings page + +  + +4. Enter the name and email that you would like to appear in commits. diff --git a/docs/getting-started/images/connect-to-github-dialog.png b/docs/getting-started/images/connect-to-github-dialog.png new file mode 100644 index 0000000000..c7ea91774a Binary files /dev/null and b/docs/getting-started/images/connect-to-github-dialog.png differ diff --git a/docs/getting-started/images/connect-to-github-enterprise-dialog.png b/docs/getting-started/images/connect-to-github-enterprise-dialog.png new file mode 100644 index 0000000000..4957025721 Binary files /dev/null and b/docs/getting-started/images/connect-to-github-enterprise-dialog.png differ diff --git a/docs/getting-started/images/connect_to_github.png b/docs/getting-started/images/connect_to_github.png new file mode 100644 index 0000000000..49cf9a707d Binary files /dev/null and b/docs/getting-started/images/connect_to_github.png differ diff --git a/docs/getting-started/images/global-settings-link.png b/docs/getting-started/images/global-settings-link.png new file mode 100644 index 0000000000..88ca43b6ea Binary files /dev/null and b/docs/getting-started/images/global-settings-link.png differ diff --git a/docs/getting-started/images/install-from-gallery.png b/docs/getting-started/images/install-from-gallery.png new file mode 100644 index 0000000000..5731d67192 Binary files /dev/null and b/docs/getting-started/images/install-from-gallery.png differ diff --git a/docs/getting-started/images/manage_connections.png b/docs/getting-started/images/manage_connections.png new file mode 100644 index 0000000000..a3f5a7c71a Binary files /dev/null and b/docs/getting-started/images/manage_connections.png differ diff --git a/docs/getting-started/images/settings-button.png b/docs/getting-started/images/settings-button.png new file mode 100644 index 0000000000..4e7b577faf Binary files /dev/null and b/docs/getting-started/images/settings-button.png differ diff --git a/docs/getting-started/images/sign-in-to-github-provider.png b/docs/getting-started/images/sign-in-to-github-provider.png new file mode 100644 index 0000000000..e4a9b1748e Binary files /dev/null and b/docs/getting-started/images/sign-in-to-github-provider.png differ diff --git a/docs/getting-started/images/sign-in-to-github.png b/docs/getting-started/images/sign-in-to-github.png new file mode 100644 index 0000000000..1ea95fb069 Binary files /dev/null and b/docs/getting-started/images/sign-in-to-github.png differ diff --git a/docs/getting-started/images/update-from-gallery.png b/docs/getting-started/images/update-from-gallery.png new file mode 100644 index 0000000000..f7cdb7c954 Binary files /dev/null and b/docs/getting-started/images/update-from-gallery.png differ diff --git a/docs/getting-started/images/view_team_explorer.png b/docs/getting-started/images/view_team_explorer.png new file mode 100644 index 0000000000..364934e038 Binary files /dev/null and b/docs/getting-started/images/view_team_explorer.png differ diff --git a/docs/getting-started/images/vs2015-installer.png b/docs/getting-started/images/vs2015-installer.png new file mode 100644 index 0000000000..5f442547ec Binary files /dev/null and b/docs/getting-started/images/vs2015-installer.png differ diff --git a/docs/getting-started/images/vs2017-installer.png b/docs/getting-started/images/vs2017-installer.png new file mode 100644 index 0000000000..b5c730fff2 Binary files /dev/null and b/docs/getting-started/images/vs2017-installer.png differ diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 0000000000..dc0575d1a9 --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,13 @@ +# Getting Started with GitHub for Visual Studio + +## [Installing GitHub for Visual Studio](installing-github-for-visual-studio.md) + +GitHub for Visual Studio is available for Visual Studio 2015 and later. The Community, Professional and Enterprise editions are supported. + +## [Authenticating to GitHub](authenticating-to-github.md) + +Add your GitHub.com or GitHub Enterprise account information to Visual Studio so you can access your repositories. + +### [Configuring Git in Visual Studio](configuring-git-in-visual-studio.md) + +Configure how Visual Studio interacts with GitHub \ No newline at end of file diff --git a/docs/getting-started/installing-github-for-visual-studio.md b/docs/getting-started/installing-github-for-visual-studio.md new file mode 100644 index 0000000000..11decbfb7e --- /dev/null +++ b/docs/getting-started/installing-github-for-visual-studio.md @@ -0,0 +1,71 @@ +# Installing the GitHub Extension for Visual Studio + +GitHub for Visual Studio is an extension for Microsoft Visual Studio 2015 and later. It is not supported on Visual Studio Code, Visual Studio Express or Visual Studio for Mac. + +## Installing for all versions of Visual Studio 2015 and higher + +If you already have Visual Studio 2015 or higher installed, you can install the extension into all your versions of Visual Studio with the following steps: + +1. Visit the [GitHub for Visual Studio](https://visualstudio.github.com/) site. +2. Click the **Download GitHub Extension for Visual Studio** button. +3. In your computer's **Downloads** folder, double-click **GitHub.VisualStudio.vsix**. +4. In the pop-up window, click **Install**. +5. After the installation is completed, run Visual Studio. + +## Installing from the Visual Studio gallery + +If you're currently running Visual Studio 2015 or higher, you can install the extension from the Visual Studio gallery. + +1. In Visual Studio, open the **Tools** menu and click **Extensions and Updates** + +2. On the left side of the **Extensions and Updates** dialog, select **Online - Visual Studio gallery** + +3. In the search box on the top right side, type **GitHub** + +4. Select the **GitHub Extension for Visual Studio** entry and click **Download** + +  + +5. After installation is completed, restart Visual Studio. + +## If you do not have Visual Studio installed yet + +When you install Visual Studio, you can include the GitHub Extension for Visual Studio for installation, as it is available in the Visual Studio installer. + +### Visual Studio 2015 + +**Note:** The Visual Studio 2015 installer is not guaranteed to install the latest version of the extension. Once the Visual Studio installation is complete, [update the extension](#updating-the-extension) from the Visual Studio gallery, or [run the installer](#installing-for-all-versions-of-visual-studio-2015-and-higher) from our website. + +1. Start the Visual Studio 2015 installer. +2. Scroll down to **Common Tools** and check **GitHub Extension for Visual Studio**. +  +3. Click the **Install** button. +4. Once installation is complete, run Visual Studio 2015 and [update the extension](#updating-the-extension) + +### Visual Studio 2017 + +**Note:** The Visual Studio 2017 installer is not guaranteed to install the latest version of the extension. Once the Visual Studio installation is complete, [update the extension](#updating-the-extension) from the Visual Studio gallery, or [run the installer](#installing-for-all-versions-of-visual-studio-2015-and-higher) from our website. + +1. Start the Visual Studio 2017 installer. + +2. Select the **Individual components** tab at the top. + +3. Scroll down to **Code tools** and check **GitHub Extension for Visual Studio**. + +  + +4. Click the **Modify** button. + +5. Once installation is complete, run Visual Studio 2017 and [update the extension](#updating-the-extension) + +## Updating the extension + +**Note:** If you're currently running Visual Studio 2015 or higher and the extension is already installed, Visual Studio will check for and install updates automatically every 24 hours. For this update process to run, Visual Studio must not be running. + +Visual Studio 2017 will not run automatic updates until you update the extension at least once. + +1. In Visual Studio, open the **Tools** menu and click **Extensions and Updates** +2. On the left side of the **Extensions and Updates** dialog, select **Updates - Visual Studio gallery** +3. If there are updates available, an entry titled **GitHub Extension for Visual Studio** will appear on the list. Select it and click **Update** +  +4. After installation is completed, restart Visual Studio. diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000000..6916ea3c3c --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,9 @@ +# GitHub for Visual Studio Documentation + +### [Getting Started with GitHub for Visual Studio](getting-started/index.md) + +Get GitHub for Visual Studio set up to bring the GitHub flow to Visual Studio. Authenticate to GitHub.com or GitHub Enterprise, keep the extension up-to-date, and review your preferred settings. + +### [Contributing to Projects with GitHub for Visual Studio](contributing/index.md) + +Use GitHub for Visual Studio to manage your projects and work with pull requests. \ No newline at end of file diff --git a/documentation/manifest.md b/documentation/manifest.md deleted file mode 100644 index 3a68a88220..0000000000 --- a/documentation/manifest.md +++ /dev/null @@ -1,112 +0,0 @@ -# First Launch - - **If last solution was in a git repo hosted on GitHub** - - **Team Explorer Home page shows:** - - [ ] GitHub header, repo information - - [ ] Pull Requests button - - [ ] Pulse button - - [ ] Graphs button - - [ ] Issues button - - **If last solution was in a git repo not hosted on GitHub** - - [ ] Team Explorer Home page does not show any github information - - **Go to Team Explorer Connect page** - - [ ] GitHub invitation section in Hosted Service Providers area is visible with Connect... and Sign up links - - [ ] **Click on Connect** - - [ ] Connect to GitHub dialog appears - - [ ] GitHub option underlined - - [ ] Cursor on username field - - [ ] Login button disabled - - [ ] Link to sign up at the bottom - - [ ] **Fill out login information** - - [ ] **On successful login** - - [ ] Connect dialog disappears - - [ ] GitHub invitation section in Connect page disappears - - [ ] GitHub connection appears above Hosted Service Providers area with Clone, Create and Sign out action links. As long as it's above Local Git Repositories, it's good - -# In Team Explorer Connect page (logged in) - - [ ] **Click on Clone action link** - - [ ] Clone dialog appears - - [ ] List of user repositories is populated - - [ ] Path field contains default cloning path C:\Users\[user]\Source\Repos - - [ ] Cursor is in Search Repositories field - - [ ] Clone button is disabled - - [ ] Typing in the Search Repositories field filters the list - - [ ] Clicking on the browse action link opens a file explorer - - [ ] Selecting a directory in the file explorer changes the contents of the Path field to the new path - - [ ] Selecting a repository from the list enables the clone button - - [ ] ctrl-clicking a selection in the list removes the selection and disables the clone button - - [ ] Hovering over the clone button (when enabled) animates the button (reversing colors) - - [ ] **Select a repository and click Clone** - - [ ] Clone dialog disappears - - [ ] Progress bar appears in the Team Explorer Connect page with cloning progress (depending on repo size) - - [ ] Notification appears in Team Explorer Connect page: "The repository was cloned successfully. username/repo-name has been successfully created. Create a new project or solution." with proper links displayed. - - [ ] Repository shows up in the "Local Git Repositories" list - - [ ] **Double-click the cloned repository in the "Local Git Repositories" list** - - [ ] Team Explorer view changes to Home page - - [ ] GitHub header and repo information is shown - - [ ] Click Clone in "Local Git Repositories" List and copy/paste a repo from .com. Clone and verify the message displays, "The repository was cloned successfully." - - [ ] **Click on Create action link** - - [ ] Create dialog appears - - [ ] Cursor is on the Name field - - [ ] Create button is disabled - - [ ] Local path is set to default cloning path C:\Users\[user]\Source\Repos - - [ ] Git ignore is set to VisualStudio - - [ ] User is set to current logged user - - [ ] Tabbing through the fields follows visual placement of fields - - [ ] Filling the name field enables the Create button - - [ ] Hovering over the Create button animates it (reversing colors) - - [ ] Clicking on the browse action link opens a file explorer - - [ ] Selecting a directory in the file explorer changes the contents of the Path field to the new path - - [ ] **Fill out the name field and click Create** - - [ ] Dialog disappears - - [ ] Notification appears in Team Explorer Connect page: "The repository was created successfully" - - [ ] Repository shows up in the "Local Git Repositories" list - - [ ] **Double-click the created repository in the "Local Git Repositories" list** - - [ ] Team Explorer view changes to Home page - - [ ] GitHub header and repo information is shown -- [ ] **Publishing a local repo** - - [ ] File - New - Project - Console Application (or any type of project, doesn't matter much) - - [ ] Select "Add to source control" from the dialog and click Ok - - [ ] Select "Git" from the Choose Source Control dialog - - [ ] Verify that Team Explorer home page does *not* have a GitHub section -- [ ] **Click "Sync"** - - [ ] Synchronization page opens with "Publish to GitHub" section -- [ ] **Click "Get Started" in the "Publish to GitHub" section** - - [ ] Contents of section change to a publish form with: - - [ ] User dropdown - - [ ] Pre-filled name field with project name - - [ ] Description field - - [ ] Private checkbox - - [ ] Publish button -- [ ] **Publish button is enabled and private checkbox is unchecked (and disabled if user cannot create private repos)** - - [ ] Click on "Publish" - - [ ] Form becomes disabled - - [ ] Progress bar appears above Synchronization title - - [ ] Team Explorer view changes to Home page - - [ ] Notification appears: "Repository published successfully" - - [ ] Publish a private repo and verify on .com that it's private -- [ ] **Project section (Home button)*** - - [ ] Click on "Home" icon - - [ ] Verify Project has the following sections/buttons when signed into GitHub.com and the Repository is enabled: Pull Requests, Pulse, Graphs, Issues, Wikis - - [ ] Verify Pulse button navigates to Pulse page on Github.com - - [ ] Verify Graphs button navigates to Graphs page on GitHub.com - - [ ] Verify Wikis button navigates to Wikis page on GitHub.com (when logged in and the repository is enabled) - - [ ] Verify Issues button navigates to Issues page on GitHub.com (when logged in and the repository is enabled) - - -# Connect page when logged in to TFS - - [ ] **Connect to a TFS project** - - [ ] Login to GitHub - - [ ] Team Explorer Connect page: GitHub section appears above TFS section with Clone | Create | Sign out links - - [ ] Log out of GitHub - - [ ] Team Explorer Connect page: GitHub section appears above TFS section with Clone | Create | Login links - - [ ] Disconnect from TFS (right click on project and "Remove" - - [ ] Team Explorer Connect page: GitHub invitation section appears in Hosted Service Providers with Connect.. and Sign up links - -# Connections -- [ ] **Login to GitHub.com, then click on the "Manage Connections" header and "Connect to GitHub"** - - [ ] Login dialog appears - - [ ] GitHub Enterprise is underlined - - [ ] Form has 3 fields - username, password and address - - [ ] Login button is disabled -- [ ] **Login to an enterprise instance** - - [ ] Team Explorer Connect page shows two github connections - one titled GitHub, another with the enterprise url diff --git a/install.cmd b/install.cmd new file mode 100644 index 0000000000..0f46241066 --- /dev/null +++ b/install.cmd @@ -0,0 +1,3 @@ +@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/.gitignore b/lib/.gitignore new file mode 100644 index 0000000000..296234c026 --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1 @@ +!*.nupkg diff --git a/lib/Microsoft.TeamFoundation.Client.dll b/lib/14.0/Microsoft.TeamFoundation.Client.dll similarity index 100% rename from lib/Microsoft.TeamFoundation.Client.dll rename to lib/14.0/Microsoft.TeamFoundation.Client.dll diff --git a/lib/Microsoft.TeamFoundation.Common.dll b/lib/14.0/Microsoft.TeamFoundation.Common.dll similarity index 100% rename from lib/Microsoft.TeamFoundation.Common.dll rename to lib/14.0/Microsoft.TeamFoundation.Common.dll diff --git a/lib/Microsoft.TeamFoundation.Controls.dll b/lib/14.0/Microsoft.TeamFoundation.Controls.dll similarity index 100% rename from lib/Microsoft.TeamFoundation.Controls.dll rename to lib/14.0/Microsoft.TeamFoundation.Controls.dll diff --git a/lib/Microsoft.TeamFoundation.Git.Client.dll b/lib/14.0/Microsoft.TeamFoundation.Git.Client.dll similarity index 100% rename from lib/Microsoft.TeamFoundation.Git.Client.dll rename to lib/14.0/Microsoft.TeamFoundation.Git.Client.dll diff --git a/lib/Microsoft.TeamFoundation.Git.Controls.dll b/lib/14.0/Microsoft.TeamFoundation.Git.Controls.dll similarity index 100% rename from lib/Microsoft.TeamFoundation.Git.Controls.dll rename to lib/14.0/Microsoft.TeamFoundation.Git.Controls.dll diff --git a/lib/Microsoft.TeamFoundation.Git.Provider.dll b/lib/14.0/Microsoft.TeamFoundation.Git.Provider.dll similarity index 100% rename from lib/Microsoft.TeamFoundation.Git.Provider.dll rename to lib/14.0/Microsoft.TeamFoundation.Git.Provider.dll diff --git a/lib/15.0/Microsoft.TeamFoundation.Client.dll b/lib/15.0/Microsoft.TeamFoundation.Client.dll new file mode 100644 index 0000000000..ff4164ee7f Binary files /dev/null and b/lib/15.0/Microsoft.TeamFoundation.Client.dll differ diff --git a/lib/15.0/Microsoft.TeamFoundation.Common.dll b/lib/15.0/Microsoft.TeamFoundation.Common.dll new file mode 100644 index 0000000000..7ab1fb2525 Binary files /dev/null and b/lib/15.0/Microsoft.TeamFoundation.Common.dll differ diff --git a/lib/15.0/Microsoft.TeamFoundation.Controls.dll b/lib/15.0/Microsoft.TeamFoundation.Controls.dll new file mode 100644 index 0000000000..65d4237f55 Binary files /dev/null and b/lib/15.0/Microsoft.TeamFoundation.Controls.dll differ diff --git a/lib/15.0/Microsoft.TeamFoundation.Git.Client.dll b/lib/15.0/Microsoft.TeamFoundation.Git.Client.dll new file mode 100644 index 0000000000..f77b7d6a56 Binary files /dev/null and b/lib/15.0/Microsoft.TeamFoundation.Git.Client.dll differ diff --git a/lib/15.0/Microsoft.TeamFoundation.Git.Controls.dll b/lib/15.0/Microsoft.TeamFoundation.Git.Controls.dll new file mode 100644 index 0000000000..d077a0816c Binary files /dev/null and b/lib/15.0/Microsoft.TeamFoundation.Git.Controls.dll differ diff --git a/lib/15.0/Microsoft.TeamFoundation.Git.Provider.dll b/lib/15.0/Microsoft.TeamFoundation.Git.Provider.dll new file mode 100644 index 0000000000..f8908bf622 Binary files /dev/null and b/lib/15.0/Microsoft.TeamFoundation.Git.Provider.dll differ diff --git a/lib/Markdig.Wpf.Signed.0.2.1.nupkg b/lib/Markdig.Wpf.Signed.0.2.1.nupkg new file mode 100644 index 0000000000..70561c66c4 Binary files /dev/null and b/lib/Markdig.Wpf.Signed.0.2.1.nupkg differ diff --git a/lib/Microsoft.TextTemplating.Build.Tasks.dll b/lib/Microsoft.TextTemplating.Build.Tasks.dll new file mode 100644 index 0000000000..7426b3b3e1 Binary files /dev/null and b/lib/Microsoft.TextTemplating.Build.Tasks.dll differ diff --git a/lib/Microsoft.TextTemplating.targets b/lib/Microsoft.TextTemplating.targets new file mode 100644 index 0000000000..f7727d5fc0 --- /dev/null +++ b/lib/Microsoft.TextTemplating.targets @@ -0,0 +1,535 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <!-- + This targets file allows you to regenerate T4-generated files using MsBuild. + Files can be regenerated either as part of a normal build, or by calling a specific + build target directly. + + + Using this targets file + - - - - - - - - - - - - + To use this targets file: + + 1) Import this targets file into your project by adding the appropriate <Import ...> + e.g. <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" /> + + This import statement must be included *after* the standard VB/C# targets + import, as it appends itself to the $(BuildDependsOn) property. + + 2) Set the properties to control how the build is carried out e.g. + + <TransformOnBuild>true</TransformOnBuild> + : files should be transformation automatically as part of the build. + + <TransformOutOfDateOnly>true</TransformOutOfDateOnly> + : only out-of-date files should be tranformed. + + <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles> + : whether read-only output files should be over-written or not. + + <IncludeDslT4Settings>true</IncludeDslT4Settings> + : set the additional properties required to transform DSL text templates + (i.e. add PublicAssemblies to the reference search path, + specify the include folder location for the DSL .tt files, and + specify the settings for the DSL directive processor). + + + User-callable targets: + - - - - - - - - - - - - + This file also contains the following targets that you can call directly + (e.g. using msbuild /t:TransformAll) : + + * Transform: transforms a single T4 template. + * TransformAll: transforms all T4 templates. + + + Specifying the output file + - - - - - - - - - - - - - - + By default, the generated file will have the same file name and location as if + it were created by the TextTransformationFileGenerator tool i.e. the generated + file will be in the same location and will have the same name but with a different + file extension. + + However, you can specify in metadata the location and file name to use instead e.g. + + <None Include="GeneratedCode\SerializationHelper.tt"> + ... + <OutputFilePath>SomeOtherDir</OutputFilePath> + <OutputFileName>SomeOtherFileName</OutputFileName> + ... + </None> + + The output path can be absolute or relative. Either the output path or file name or + both can be supplied. + + + Transforming custom model files + - - - - - - - - - - - - - - - - + This target file contains the settings and properties to transform DSL definition model files. + To transform model files for your custom DSLs, you will need to add a <DirectiveProcessor> + item specifying the details of the generated directive processor. See the example for the + DslDirectiveProcessor below. + + + Source control integration + - - - - - - - - - - - - - - + There is no source control integration as such. If the task enounters a read-only output file, + it's behaviour is determined by the $(OverwriteReadOnlyOutputFiles) property. If the property is + true, the file is overwritten and the metadata is added to the GeneratedFiles task entry to + indicate this. + + If the property is false, the file is not over-written, and an entry is added to + othe NonGeneratedFiles list. + + By default, warnings will be logged for non-writable / over-written files, and the task + will fail if there are non-writable files. + + You can customise this behaviour e.g. to check files out before or after transformation. + See the section "Customising the transformation process" below for more information. + + + What is an "out of date" file? + - - - - - - - - - - - - - - - - + An out-of-date file is one where any of the input files used to generated the file were + modified more recently than the file itself. The input files are any "include" files that + were used (i.e. those imported into the template using the <#@ include ... #> directive + processor), and any files reference by custom directive processors. This includes any + directive processors generated for target DSLs by the DSL Tools. + + In addition, a file that contains only invalid T4 output (i.e. the message "ErrorGeneratingOutput" + that is written to the file when transformation fails) is treated as being out of date. + + The targets use the file tracking feature in MsBuild. This works by logging all of the file + read / writes that occur when generating an output file. If the tracking logs do not exist for + a project, the task will not be able to determine whether a file is out of date or not. In this case, + the file will be tranformed. + +--> + + <!-- ################################################################################ --> + <!-- Defaults --> + <!-- ################################################################################ --> + <!-- Set default properties --> + <PropertyGroup> + + <!-- Location of the assembly containing the tasks. --> + <!-- Currently, the assembly is in the same folder as this targets file, so the $(T4BuildTasksPath) does not need to be set. --> + <T4BuildTasksAssemblyFile>$(T4BuildTasksPath)Microsoft.TextTemplating.Build.Tasks.dll</T4BuildTasksAssemblyFile> + + <!-- Indicates whether T4 templates should be transformed automatically as part of the build. --> + <TransformOnBuild Condition=" $(TransformOnBuild)=='' ">false</TransformOnBuild> + + <!-- Indicates whether only out of date templates should be transformed, or whether all + templates should be transformed. --> + <TransformOutOfDateOnly Condition=" $(TransformOutOfDateOnly)=='' ">true</TransformOutOfDateOnly> + + <!-- Specifies how read-only output files will be handled. --> + <OverwriteReadOnlyOutputFiles Condition=" $(OverwriteReadOnlyOutputFiles)=='' ">false</OverwriteReadOnlyOutputFiles> + + <!-- Whether to include the additional settings required to transform standard DSL projects --> + <IncludeDslT4Settings Condition=" $(IncludeDslT4Settings)=='' ">false</IncludeDslT4Settings> + + <!-- Directory into which .tlog files will be written. --> + <TrackerLogDirectory Condition=" $(TrackerLogDirectory)=='' ">$(IntermediateOutputPath)</TrackerLogDirectory> + + <TrackFileAccess Condition=" $(TrackFileAccess)=='' ">true</TrackFileAccess> + + </PropertyGroup> + + <!-- Set default metadata item for GeneratedFiles. + This enables conditions to refer to this metadata, even if it hasn't be set by the task. --> + <ItemDefinitionGroup> + <GeneratedFiles> + <ReadOnlyFileOverwritten>false</ReadOnlyFileOverwritten> + </GeneratedFiles> + </ItemDefinitionGroup> + + + + <!-- ################################################################################ --> + <!-- Dsl Tools properties --> + <!-- ################################################################################ --> + <!-- Add settings so that ".dsl" files can be transformed --> + + <PropertyGroup Condition=" $(IncludeDslT4Settings)=='true' "> + <!-- Path to VS\Common7\IDE --> + <!-- Check the 32bit location first ; if that is empty, use the 64bit location. --> + <VsIdePath>$(Registry:HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0@InstallDir)</VsIdePath> + <VsIdePath Condition=" $(VsIdePath) == ''" >$(Registry:HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\VisualStudio\14.0@InstallDir)</VsIdePath> + + <!-- Set the default location for the Dsl Designer install. --> + <DslDesignerInstallPath Condition=" $(DslDesignerInstallPath)=='' ">$(VsIdePath)Extensions\Microsoft\DSL SDK\Dsl Designer\$(VisualStudioVersion)\</DslDesignerInstallPath> + + <!-- Add the standard DSL templates folder --> + <IncludeFolders>$(IncludeFolders);$(DslDesignerInstallPath)TextTemplates\</IncludeFolders> + </PropertyGroup> + + <ItemGroup Condition=" $(IncludeDslT4Settings)=='true' "> + <!-- Add a ref to the DSL directive processor. --> + <DirectiveProcessor Include="DslDirectiveProcessor" > + <Class>Microsoft.VisualStudio.Modeling.DslDefinition.DslDirectiveProcessor</Class> + <!-- Specify the default install location of the DslDesigner assembly --> + <CodeBase>$(Registry:HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0@InstallDir)Extensions\Microsoft\DSL SDK\DSL Designer\$(VisualStudioVersion)\Microsoft.VisualStudio.Modeling.Sdk.DslDefinition.$(VisualStudioVersion).dll</CodeBase> + </DirectiveProcessor> + <!-- Add VS\...\PublicAssemblies to the list of places to look for assemblies used by templates. --> + <T4ReferencePath Include="$(VsIdePath)PublicAssemblies\" /> + </ItemGroup> + + <!-- Set the default namespace to use for T4 Transformations. + The DSL Defintition directive processor asks the host for the project namespace, + so it needs to be specified here as a paramater value. + By default, the root project namespace will be returned. If you want to use + a different value, set the MSBuild property $(T4DslToolsNamespace). + --> + <PropertyGroup Condition=" $(IncludeDslT4Settings)=='true' "> + <T4DslToolsNamespace Condition=" $(T4DslToolsNamespace) == '' ">$(RootNamespace)</T4DslToolsNamespace> + </PropertyGroup> + <ItemGroup Condition=" $(IncludeDslT4Settings)=='true' "> + <!-- Set the value to return as the project namespace --> + <T4ParameterValues Include="ProjectDefaultNamespace" > + <Value>$(T4DslToolsNamespace)</Value> + </T4ParameterValues> + </ItemGroup> + + <!-- ################################################################################ --> + <!-- Task definitions --> + <!-- ################################################################################ --> + <!-- Uses Assembly.LoadFrom --> + <UsingTask TaskName="TransformTemplates" AssemblyFile="$(T4BuildTasksAssemblyFile)" /> + <UsingTask TaskName="PreprocessTemplates" AssemblyFile="$(T4BuildTasksAssemblyFile)" /> + + + <!-- ################################################################################ --> + <!-- User-callable targets / customisation points --> + <!-- ################################################################################ --> + + <!-- Customising the transformation process: + The transformation process can be customised by specifying targets to execute before + and after the transformation. This is done by setting the properties $(BeforeTransform) + and $(AfterTransform). + + The transformation task sets the output item lists + @(GeneratedFiles) and @(NonGeneratedFiles). These list contain respectively the + list of files that were generated by transformation, and the list of files that could + not be generated because a read-only version of the output file already existed. + + The default post-processing target "ProcessTransformResults" shows how these lists can be manipulated. + --> + + <PropertyGroup> + <!-- List of targets that should be run before the transforming. + By default, no pre-processing is carried out. + The next line is effectively a no-op: it is only here as placeholder to document this + customisation point. --> + <BeforeTransform Condition=" $(BeforeTransform) == '' "></BeforeTransform> + + <!-- List of targets that should be run after transforming. + By default, a target is called that will issue warnings for all read-only files that + were overwritten, and for all files that were not overwritten. + You can override this behaviour by providing a custom target to handle post-processing + e.g. in a source code control scenario, you could check out files that have been overwritten. + --> + <AfterTransform Condition=" $(AfterTransform) == '' ">ProcessTransformResults</AfterTransform> + </PropertyGroup> + + + <!-- + ================================================================ + Tranform + ================================================================ + Description: Transforms the specified T4 templates. + The $(TransformFile) property is used to specify the file or files + to transform. It can contain wildcards. + Examples: + (1) transform file "foo.tt" + msbuild myproj.proj /t:Transform /p:TransformFile=foo.tt + + (2) transform all .tt files start with "domain" under the folder "GeneratedCode", recursively + msbuild dsl.csproj /t:Transform /p:TransformFile="GeneratedCode\**\domain*.tt" + + The actual work of the transformations is carried out by ExecuteTransformations. This target + calls @(CreateCandidateT4ItemList) then @(SelectItemsForTransform) to detemine which files should + be transformed/pre-processed. + --> + <Target Name="Transform" DependsOnTargets="CreateCandidateT4ItemList;SelectItemsForTransform;$(BeforeTransform);ExecuteTransformations;$(AfterTransform)"> + </Target> + + <!-- + ================================================================ + SelectItemsForTransform + ================================================================ + Description: Selects the items that will be passed as inputs to + the CreateT4ItemLists target. + The TransformFile property is used to select the file or files + to transform. It can contain wildcards. + --> + <Target Name="SelectItemsForTransform" > + <Error Condition="$(TransformFile)==''" Text="Specify the file to be transformed in the property 'TransformFile'" /> + + <!-- Convert the property into an item. This is done so that any + wildcards in the property are expanded.--> + <ItemGroup> + <ExpandedTransformFile Include="$(TransformFile)" /> + </ItemGroup> + + <!-- However, the TransformFileItem task items are newly-created, and they may or may not + refer to files that are in the project. Also, the newly-created items will not have any + custom metadata. We want to (1) limit this to files that are in the project and (2) get + the corresponding task items from the project (since they may have custom metadata). + --> + <FilterCandidatesBasedOnItemSpec CandidateItemList="@(CandidateT4ItemList)" ItemSpecList="@(ExpandedTransformFile)" > + <Output ItemName="CreateT4ItemListsInputs" TaskParameter="Result" /> + </FilterCandidatesBasedOnItemSpec> + + </Target> + + <!-- + ================================================================ + TransformAll + ================================================================ + Description: Transforms all T4 templates in the project + + The actual work of the transformations is carried out by ExecuteTransformations. This target + calls @(CreateCandidateT4ItemList) then @(SelectItemsForTransformAll) to detemine which files should + be transformed/pre-processed. + --> + <Target Name="TransformAll" DependsOnTargets="CreateCandidateT4ItemList;SelectItemsForTransformAll;$(BeforeTransform);ExecuteTransformations;$(AfterTransform)"> + </Target> + + <!-- + ================================================================ + SelectItemsForTransformAll + ================================================================ + Description: Selects the items that will be passed as inputs to + the CreateT4ItemLists target. This target selects all of the + items in the groups commonly generated with T4. + --> + <Target Name="SelectItemsForTransformAll" > + <!-- Create a list of all files (we need this to work out which file are generated and which + files they depend on. --> + <CreateItem Condition="@(CreateT4ItemListsInputs)==''" Include="@(CandidateT4ItemList)"> + <Output ItemName="CreateT4ItemListsInputs" TaskParameter="Include"/> + </CreateItem> + + </Target> + + + + <!-- ################################################################################ --> + <!-- Internal target(s) --> + <!-- ################################################################################ + These targets would not normally be called directly from outside this file. + --> + + <!-- + ================================================================ + CreateCandidateT4ItemList + ================================================================ + Description: creates an item list @(CandidateT4ItemList) that contains + all of the items that should be considered when deciding which ones + should be processed by T4. + --> + <Target Name="CreateCandidateT4ItemList" > + <Message Importance="low" Text="Creating a list of candidate items that might need to be processed by T4 items" /> + + <!-- Create a list of all files if only hasn't been created already (we need this to + work out which file are generated and which files they depend on. --> + <CreateItem Condition="@(CandidateT4ItemList)==''" Include="@(Compile);@(None);@(Content);@(EmbeddedResource)"> + <Output ItemName="CandidateT4ItemList" TaskParameter="Include"/> + </CreateItem> + </Target> + + + <!-- + ================================================================ + CreateT4ItemLists + ================================================================ + Description: filters the items in @(CreateT4ItemListsInputs) to select those that + have a T4 custom tool as the custom tool for the item. The matching items will + be placed in either the $(T4TransformInputs) or $(T4PreprocessInputs) item groups, + depending on the custom tool that is specified. + + The @(T4TransformInputs) group will contain the items that are use the T4 transform + custom tool, together with any items that in in the @(T4Transform) group. + + Similarly, the @(T4TransformInputs) group will contain the items that are use the T4 + preprocess custom tool, together with any items that in in the @(T4Preprocess) group. + + The item groups, @(T4TranformInputs) and @(T4PreprocessInputs) will be passed to + the transform and preprocess targets, respectively. + + --> + <Target Name="CreateT4ItemLists" > + <Message Importance="low" Text="Creating T4 items lists for project $(ProjectName) ($(ProjectPath))..." /> + + <!-- Specify the names of the T4 custom tools if they haven't already been set elsewhere. --> + <PropertyGroup> + <T4TransformCustomToolName Condition=" $(T4TransformCustomToolName)=='' ">TextTemplatingFileGenerator</T4TransformCustomToolName> + <T4PreprocessCustomToolName Condition=" $(T4PreprocessCustomToolName)=='' ">TextTemplatingFilePreprocessor</T4PreprocessCustomToolName> + </PropertyGroup> + + <!-- Work out which of the files in the @(CreateT4ItemListsInputs) group are T4 files. --> + <ItemGroup> + <!-- Add any files that have the T4 transformation custom tool--> + <T4TransformInputs Include="@(CreateT4ItemListsInputs)" Condition=" %(CreateT4ItemListsInputs.Generator) == $(T4TransformCustomToolName) " /> + <!-- Add any files that are in the gruop T4Transform --> + <T4TransformInputs Include="@(T4Transform)" /> + + <!-- Now repeat for the pre-processed files --> + <T4PreprocessInputs Include="@(CreateT4ItemListsInputs)" Condition=" %(CreateT4ItemListsInputs.Generator) == $(T4PreprocessCustomToolName) " /> + <T4PreprocessInputs Include="@(T4Preprocess)" /> + </ItemGroup> + + </Target> + + + <!-- + ================================================================ + ExecuteTransformations + ================================================================ + Description: Transforms the T4 templates in the item groups T4TransformInputs and + T4PreProcessInputs. The item groups are created by the target "CreateT4ItemLists". + Includes both transformation and preprocess templates. + --> + <Target Name="ExecuteTransformations" DependsOnTargets="CreateT4ItemLists"> + + <TransformTemplates + TemplatesToProcess="@(T4TransformInputs)" + IncludeFolders="$(IncludeFolders)" + DirectiveProcessors="@(DirectiveProcessor)" + AssemblyReferences="@(T4AssemblyReference)" + ReferencePaths="@(T4ReferencePath)" + TrackerLogDirectory="$(TrackerLogDirectory)" + TrackFileAccess="$(TrackFileAccess)" + MinimalRebuildFromTracking="$(TransformOutOfDateOnly)" + OverwriteReadOnlyOutputFiles="$(OverwriteReadOnlyOutputFiles)" + ParameterValues="@(T4ParameterValues)" + > + + <!-- List of output files that were generated by the task. + If a read-only version of the file was overwritten by the task, the + task item for that file will have the following metadata entry: + <ReadOnlyFileOverwritten>true<ReadOnlyFileOverwritten> + --> + <Output ItemName="GeneratedFiles" TaskParameter="GeneratedFiles"/> + + <!-- List of output files that could not be generated because the file file + already exists and is read-only. --> + <Output ItemName="NonGeneratedFiles" TaskParameter="NonGeneratedFiles"/> + </TransformTemplates> + + + <PropertyGroup> + <!-- Unless another namespace has been specified, use the project namespace as the + default namespace from pre-processed files. --> + <PreprocessTemplateDefaultNamespace Condition=" $(PreprocessTemplateDefaultNamespace)=='' ">$(RootNamespace)</PreprocessTemplateDefaultNamespace> + </PropertyGroup> + + <PreprocessTemplates + DefaultNamespace="$(PreprocessTemplateDefaultNamespace)" + TemplatesToProcess="@(T4PreprocessInputs)" + IncludeFolders="$(IncludeFolders)" + DirectiveProcessors="@(DirectiveProcessor)" + AssemblyReferences="@(T4AssemblyReference)" + ReferencePaths="@(T4ReferencePath)" + TrackerLogDirectory="$(TrackerLogDirectory)" + TrackFileAccess="$(TrackFileAccess)" + MinimalRebuildFromTracking="$(TransformOutOfDateOnly)" + OverwriteReadOnlyOutputFiles="$(OverwriteReadOnlyOutputFiles)" + ParameterValues="@(T4ParameterValues)" + > + + <!-- List of output files that were generated by the task. + If a read-only version of the file was overwritten by the task, the + task item for that file will have the following metadata entry: + <ReadOnlyFileOverwritten>true<ReadOnlyFileOverwritten> + --> + <Output ItemName="GeneratedFiles" TaskParameter="GeneratedFiles"/> + + <!-- List of output files that could not be generated because the file file + already exists and is read-only. --> + <Output ItemName="NonGeneratedFiles" TaskParameter="NonGeneratedFiles"/> + + <!-- List of assemblies that are required to run the processed code. --> + <Output ItemName="T4RequiredAssemblies" TaskParameter="RequiredAssemblies"/> + </PreprocessTemplates> + + </Target> + + <!-- + ================================================================ + FilterCandidatesBasedOnItemSpec + ================================================================ + Description: + Returns the items from the CandidateItemList for which there is + an item with the same itemspec in the ItemSpecList. + --> + <UsingTask TaskName="FilterCandidatesBasedOnItemSpec" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" > + <ParameterGroup> + <CandidateItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> + <ItemSpecList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> + <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> + </ParameterGroup> + + <Task> + <Code Type="Fragment" Language="cs"> + <![CDATA[ + var matches = from candidate in CandidateItemList + join spec in ItemSpecList + on candidate.ItemSpec.ToUpperInvariant() equals spec.ItemSpec.ToUpperInvariant() + select candidate; + + Result = matches.ToArray();]]> + </Code> + </Task> + + </UsingTask> + + + <!-- + ================================================================ + ProcessTransformResults + ================================================================ + Description: processes the results of a transformation to log + warnings for any read-only output files that were *not* overwritten, and + for any read-only files that *were* overwritten. In addition, if there + were any read-only files that were not overwritten, the target will log + an error, causing the build to fail. + --> + <Target Name="ProcessTransformResults"> + + <Warning Condition=" %(GeneratedFiles.ReadOnlyFileOverwritten) == true " + Text="Over-writing read-only output file: %(GeneratedFiles.Identity)" /> + + <Warning Condition=" %(NonGeneratedFiles.Identity) != '' " + Text="Output file is read-only: %(NonGeneratedFiles.Identity)"/> + + <Error Condition=" @(NonGeneratedFiles) != '' " + Text="One or more output files were read-only" /> + + </Target> + + + + <!-- ################################################################################ --> + <!-- Integration into the normal build process --> + <!-- ################################################################################ --> + + <PropertyGroup> + <!-- Insert the transform task into the normal project build sequence --> + <BuildDependsOn>TransformDuringBuild;$(BuildDependsOn)</BuildDependsOn> + </PropertyGroup> + + <!-- + ================================================================ + TransformDuringBuild + ================================================================ + Description: if TransformOnBuild is true, this target will be executed as part of the normal + build process, and will transform all of the templates in the project. + --> + <Target Name="TransformDuringBuild" Condition=" $(TransformOnBuild)==true"> + <CallTarget Targets="TransformAll"/> + </Target> + + +</Project> diff --git a/lib/Microsoft.VisualStudio.TextTemplating.Sdk.Host.14.0.dll b/lib/Microsoft.VisualStudio.TextTemplating.Sdk.Host.14.0.dll new file mode 100644 index 0000000000..64579110ca Binary files /dev/null and b/lib/Microsoft.VisualStudio.TextTemplating.Sdk.Host.14.0.dll differ diff --git a/lib/Octokit.GraphQL.0.1.1-beta.nupkg b/lib/Octokit.GraphQL.0.1.1-beta.nupkg new file mode 100644 index 0000000000..ac26783672 Binary files /dev/null and b/lib/Octokit.GraphQL.0.1.1-beta.nupkg differ diff --git a/lib/Rothko.0.0.3-ghfvs.nupkg b/lib/Rothko.0.0.3-ghfvs.nupkg new file mode 100644 index 0000000000..522e3c7b54 Binary files /dev/null and b/lib/Rothko.0.0.3-ghfvs.nupkg differ diff --git a/nuget.config b/nuget.config index ac070021ef..f9e15995d3 100644 --- a/nuget.config +++ b/nuget.config @@ -7,4 +7,7 @@ <activePackageSource> <add key="All" value="(Aggregate source)" /> </activePackageSource> -</configuration> \ No newline at end of file + <bindingRedirects> + <add key="skip" value="True" /> + </bindingRedirects> +</configuration> diff --git a/script b/script index e8bd8e1bfd..5ed9b3d7bc 160000 --- a/script +++ b/script @@ -1 +1 @@ -Subproject commit e8bd8e1bfdff5d3355f884af1e6f3b9f7f26716f +Subproject commit 5ed9b3d7bceee50d27a4a5838d4c0265bd35cc8e diff --git a/scripts/Bump-Version.ps1 b/scripts/Bump-Version.ps1 new file mode 100644 index 0000000000..33a89cac8c --- /dev/null +++ b/scripts/Bump-Version.ps1 @@ -0,0 +1,92 @@ +<# +.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 + +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 new file mode 100644 index 0000000000..38a961c2e3 --- /dev/null +++ b/scripts/Get-CheckedOutBranch.ps1 @@ -0,0 +1,31 @@ +<# +.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 new file mode 100644 index 0000000000..741a05ab26 --- /dev/null +++ b/scripts/Require-CleanWorkTree.ps1 @@ -0,0 +1,57 @@ +<# +.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-NUnit.ps1 b/scripts/Run-NUnit.ps1 new file mode 100644 index 0000000000..ac4662198a --- /dev/null +++ b/scripts/Run-NUnit.ps1 @@ -0,0 +1,54 @@ +<# +.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 new file mode 100644 index 0000000000..aa44a88934 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,84 @@ +<# +.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 new file mode 100644 index 0000000000..9a18480a67 --- /dev/null +++ b/scripts/clearerror.cmd @@ -0,0 +1 @@ +@echo off \ No newline at end of file diff --git a/scripts/common.ps1 b/scripts/common.ps1 new file mode 100644 index 0000000000..3637124792 --- /dev/null +++ b/scripts/common.ps1 @@ -0,0 +1,69 @@ +$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 new file mode 100644 index 0000000000..f0430ddc49 --- /dev/null +++ b/scripts/modules.ps1 @@ -0,0 +1,199 @@ +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 += "Tests 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" + } + 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=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 } + } + + 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 new file mode 100644 index 0000000000..49470283d0 --- /dev/null +++ b/scripts/modules/AppVeyor.ps1 @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000000..f93d6eecb2 --- /dev/null +++ b/scripts/modules/BuildUtils.psm1 @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000000..2ca851ec0a --- /dev/null +++ b/scripts/modules/Debugging.psm1 @@ -0,0 +1,26 @@ +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/SolutionInfo.ps1 b/scripts/modules/SolutionInfo.ps1 new file mode 100644 index 0000000000..4e1d6e1d0f --- /dev/null +++ b/scripts/modules/SolutionInfo.ps1 @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000000..22f94a8656 --- /dev/null +++ b/scripts/modules/Versioning.ps1 @@ -0,0 +1,68 @@ +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 + 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 new file mode 100644 index 0000000000..63563d3f00 --- /dev/null +++ b/scripts/modules/Vsix.ps1 @@ -0,0 +1,35 @@ +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 new file mode 100644 index 0000000000..6e64d9df63 --- /dev/null +++ b/scripts/test.ps1 @@ -0,0 +1,103 @@ +<# +.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 new file mode 100644 index 0000000000..371008d5a6 Binary files /dev/null and b/signingkey.snk differ diff --git a/src/CredentialManagement/Credential.cs b/src/CredentialManagement/Credential.cs index 56618c3256..170bd479d0 100644 --- a/src/CredentialManagement/Credential.cs +++ b/src/CredentialManagement/Credential.cs @@ -4,7 +4,7 @@ using System.Security; using System.Security.Permissions; using System.Text; -using NullGuard; +using GitHub.Extensions; namespace GitHub.Authentication.CredentialManagement { @@ -35,9 +35,9 @@ public Credential() : this(null, (string)null) {} public Credential( - [AllowNull]string username, - [AllowNull]SecureString password, - [AllowNull]string target = null) + string username, + SecureString password, + string target = null) { Username = username; SecurePassword = password; @@ -48,18 +48,40 @@ public Credential( } public Credential( - [AllowNull]string username, - [AllowNull]string password, - [AllowNull]string target = null) + string username, + string password, + string target = null) { Username = username; Password = password; Target = target; Type = CredentialType.Generic; - PersistenceType = PersistenceType.Session; + PersistenceType = PersistenceType.LocalComputer; _lastWriteTime = DateTime.MinValue; } + public static Credential Load(string key) + { + var result = new Credential(); + result.Target = key; + result.Type = CredentialType.Generic; + return result.Load() ? result : null; + } + + public static void Save(string key, string username, string password) + { + var result = new Credential(username, password, key); + result.Save(); + } + + public static void Delete(string key) + { + var result = new Credential(); + result.Target = key; + result.Type = CredentialType.Generic; + result.Delete(); + } + bool disposed; void Dispose(bool disposing) { @@ -86,10 +108,8 @@ private void CheckNotDisposed() } } - [AllowNull] public string Username { - [return: AllowNull] get { CheckNotDisposed(); @@ -102,10 +122,8 @@ public string Username } } - [AllowNull] public string Password { - [return: AllowNull] get { return SecureStringHelper.CreateString(SecurePassword); @@ -117,7 +135,6 @@ public string Password } } - [AllowNull] public SecureString SecurePassword { get @@ -138,10 +155,8 @@ public SecureString SecurePassword } } - [AllowNull] public string Target { - [return: AllowNull] get { CheckNotDisposed(); @@ -154,10 +169,8 @@ public string Target } } - [AllowNull] public string Description { - [return: AllowNull] get { CheckNotDisposed(); @@ -245,6 +258,8 @@ public bool Save() public bool Save(byte[] passwordBytes) { + Guard.ArgumentNotNull(passwordBytes, nameof(passwordBytes)); + CheckNotDisposed(); _unmanagedCodePermission.Demand(); @@ -340,6 +355,8 @@ internal void LoadInternal(NativeMethods.CREDENTIAL credential) static void ValidatePasswordLength(byte[] passwordBytes) { + Guard.ArgumentNotNull(passwordBytes, nameof(passwordBytes)); + if (passwordBytes.Length > maxPasswordLengthInBytes) { var message = string.Format(CultureInfo.InvariantCulture, diff --git a/src/CredentialManagement/CredentialManagement.csproj b/src/CredentialManagement/CredentialManagement.csproj index d6d3e5685d..07648cf7aa 100644 --- a/src/CredentialManagement/CredentialManagement.csproj +++ b/src/CredentialManagement/CredentialManagement.csproj @@ -9,40 +9,44 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>CredentialManagement</RootNamespace> <AssemblyName>GitHub.CredentialManagement</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <NuGetPackageImportStamp> - </NuGetPackageImportStamp> - <TargetFrameworkProfile /> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> - </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> - <Private>True</Private> - </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> @@ -53,9 +57,6 @@ <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> </Compile> @@ -67,12 +68,6 @@ <Compile Include="SecureStringHelper.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> - <ItemGroup> - <Content Include="FodyWeavers.xml" /> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> - </ItemGroup> <ItemGroup> <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> @@ -80,13 +75,6 @@ </ProjectReference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>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}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> - </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/CredentialManagement/FodyWeavers.xml b/src/CredentialManagement/FodyWeavers.xml deleted file mode 100644 index 9344211210..0000000000 --- a/src/CredentialManagement/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard ExcludeRegex="^GitHub.Authentication.CredentialManagement.*$"/> -</Weavers> \ No newline at end of file diff --git a/src/CredentialManagement/packages.config b/src/CredentialManagement/packages.config deleted file mode 100644 index bf46b8c898..0000000000 --- a/src/CredentialManagement/packages.config +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="Fody" version="1.28.0" targetFramework="net452" userInstalled="true" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net452" userInstalled="true" /> -</packages> \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/App.config b/src/DesignTimeStyleHelper/App.config deleted file mode 100644 index 8e15646352..0000000000 --- a/src/DesignTimeStyleHelper/App.config +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<configuration> - <startup> - <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> - </startup> -</configuration> \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/App.xaml b/src/DesignTimeStyleHelper/App.xaml deleted file mode 100644 index 81f0c67e14..0000000000 --- a/src/DesignTimeStyleHelper/App.xaml +++ /dev/null @@ -1,14 +0,0 @@ -<Application x:Class="DesignTimeStyleHelper.App" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="clr-namespace:DesignTimeStyleHelper" - StartupUri="MainWindow.xaml"> - <Application.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <ResourceDictionary Source="/GitHub.UI;component/SharedDictionary.xaml" /> - <ResourceDictionary Source="/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </Application.Resources> -</Application> diff --git a/src/DesignTimeStyleHelper/App.xaml.cs b/src/DesignTimeStyleHelper/App.xaml.cs deleted file mode 100644 index 10c9f38a7e..0000000000 --- a/src/DesignTimeStyleHelper/App.xaml.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.ComponentModel.Composition.Hosting; -using System.ComponentModel.Composition.Primitives; -using System.Globalization; -using System.Linq; -using System.Windows; -using GitHub.Services; -using GitHub.VisualStudio; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; -using Moq; -using GitHub.Models; - -namespace DesignTimeStyleHelper -{ - /// <summary> - /// Interaction logic for App.xaml - /// </summary> - public partial class App : Application - { - public static Holder ExportHolder { get; set; } - public static CustomServiceProvider ServiceProvider { get { return (CustomServiceProvider) ExportHolder.ServiceProvider; } } - - - public App() - { - var s = new CustomServiceProvider(); - ExportHolder = new Holder(s.DefaultCompositionService); - } - } - - public class Holder - { - [Import] - public SVsServiceProvider ServiceProvider; - - [Import] - public SComponentModel sc; - - [Import] - public IVisualStudioBrowser Browser; - - [Import] - public IExportFactoryProvider ExportFactoryProvider; - - [Import] - public IUIProvider UIProvider; - - public Holder(ICompositionService cc) - { - cc.SatisfyImportsOnce(this); - } - } - - - [Export(typeof(SVsServiceProvider))] - [Export(typeof(SComponentModel))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class CustomServiceProvider : SVsServiceProvider, IServiceProvider, - SComponentModel, IComponentModel - { - readonly CompositionContainer container; - public CompositionContainer Container { get { return container; } } - AggregateCatalog catalog; - - public CustomServiceProvider() - { - catalog = new AggregateCatalog( - new AssemblyCatalog(typeof(CustomServiceProvider).Assembly), - new AssemblyCatalog(typeof(Program).Assembly), // GitHub.VisualStudio - new AssemblyCatalog(typeof(GitHub.Api.ApiClient).Assembly), // GitHub.App - new AssemblyCatalog(typeof(GitHub.Api.SimpleApiClient).Assembly), // GitHub.Api - new AssemblyCatalog(typeof(Rothko.Environment).Assembly), // Rothko - new AssemblyCatalog(typeof(EnterpriseProbeTask).Assembly) // GitHub.Exports - ); - container = new CompositionContainer(catalog, CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); - - DefaultCatalog = catalog; - DefaultExportProvider = container; - DefaultCompositionService = DefaultCatalog.CreateCompositionService(); - - var batch = new CompositionBatch(); - batch.AddExportedValue<SVsServiceProvider>(this); - batch.AddExportedValue<SComponentModel>(this); - batch.AddExportedValue<ICompositionService>(DefaultCompositionService); - container.Compose(batch); - } - - public object GetService(Type serviceType) - { - string contract = AttributedModelServices.GetContractName(serviceType); - var instance = container.GetExportedValues<object>(contract).FirstOrDefault(); - - if (instance != null) - return instance; - - instance = Create(serviceType); - - if (instance != null) - return instance; - - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "Could not locate any instances of contract {0}.", contract)); - } - - T Create<T>() where T : class - { - return new Mock<T>().Object; - } - - static object Create(Type t) - { - var moq = typeof(Mock<>).MakeGenericType(t); - var ctor = moq.GetConstructor(new Type[] { }); - var m = ctor?.Invoke(new object[] { }) as Mock; - return m?.Object; - } - - public ExportProvider DefaultExportProvider { get; set; } - public ComposablePartCatalog DefaultCatalog { get; set; } - public ICompositionService DefaultCompositionService { get; set; } - - public ComposablePartCatalog GetCatalog(string catalogName) - { - throw new NotImplementedException(); - } - - public IEnumerable<T> GetExtensions<T>() where T : class - { - throw new NotImplementedException(); - } - - public T GetService<T>() where T : class - { - throw new NotImplementedException(); - } - } -} diff --git a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj b/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj deleted file mode 100644 index f16a881b5c..0000000000 --- a/src/DesignTimeStyleHelper/DesignTimeStyleHelper.csproj +++ /dev/null @@ -1,191 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{B1F5C227-456F-437D-BD5F-4C11B7A8D1A0}</ProjectGuid> - <OutputType>WinExe</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>DesignTimeStyleHelper</RootNamespace> - <AssemblyName>DesignTimeStyleHelper</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <WarningLevel>4</WarningLevel> - <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <PlatformTarget>AnyCPU</PlatformTarget> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - </PropertyGroup> - <ItemGroup> - <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0" /> - <Reference Include="Moq"> - <HintPath>..\..\packages\Moq.4.2.1312.1319\lib\net40\Moq.dll</HintPath> - </Reference> - <Reference Include="System" /> - <Reference Include="System.ComponentModel.Composition" /> - <Reference Include="System.Data" /> - <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.Windows.Threading, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Xml" /> - <Reference Include="Microsoft.CSharp" /> - <Reference Include="System.Core" /> - <Reference Include="System.Xml.Linq" /> - <Reference Include="System.Data.DataSetExtensions" /> - <Reference Include="System.Xaml"> - <RequiredTargetFramework>4.0</RequiredTargetFramework> - </Reference> - <Reference Include="WindowsBase" /> - <Reference Include="PresentationCore" /> - <Reference Include="PresentationFramework" /> - <Reference Include="Microsoft.TeamFoundation.Git.Provider, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> - <Private>True</Private> - </Reference> - </ItemGroup> - <ItemGroup> - <ApplicationDefinition Include="App.xaml"> - <Generator>MSBuild:Compile</Generator> - </ApplicationDefinition> - <Compile Include="TwoFactorInputTester.xaml.cs"> - <DependentUpon>TwoFactorInputTester.xaml</DependentUpon> - </Compile> - <Compile Include="WindowController.xaml.cs"> - <DependentUpon>WindowController.xaml</DependentUpon> - </Compile> - <Page Include="MainWindow.xaml"> - <Generator>MSBuild:Compile</Generator> - <SubType>Designer</SubType> - </Page> - <Compile Include="App.xaml.cs"> - <DependentUpon>App.xaml</DependentUpon> - <SubType>Code</SubType> - </Compile> - <Compile Include="MainWindow.xaml.cs"> - <DependentUpon>MainWindow.xaml</DependentUpon> - <SubType>Code</SubType> - </Compile> - <Page Include="TwoFactorInputTester.xaml"> - <SubType>Designer</SubType> - <Generator>MSBuild:Compile</Generator> - </Page> - <Page Include="WindowController.xaml"> - <Generator>MSBuild:Compile</Generator> - </Page> - </ItemGroup> - <ItemGroup> - <Compile Include="Properties\AssemblyInfo.cs"> - <SubType>Code</SubType> - </Compile> - <Compile Include="Properties\Resources.Designer.cs"> - <AutoGen>True</AutoGen> - <DesignTime>True</DesignTime> - <DependentUpon>Resources.resx</DependentUpon> - </Compile> - <Compile Include="Properties\Settings.Designer.cs"> - <AutoGen>True</AutoGen> - <DependentUpon>Settings.settings</DependentUpon> - <DesignTimeSharedInput>True</DesignTimeSharedInput> - </Compile> - <EmbeddedResource Include="Properties\Resources.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>Resources.Designer.cs</LastGenOutput> - </EmbeddedResource> - <None Include="packages.config" /> - <None Include="Properties\Settings.settings"> - <Generator>SettingsSingleFileGenerator</Generator> - <LastGenOutput>Settings.Designer.cs</LastGenOutput> - </None> - <AppDesigner Include="Properties\" /> - </ItemGroup> - <ItemGroup> - <None Include="App.config" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\..\submodules\Rothko\src\Rothko.csproj"> - <Project>{4a84e568-ca86-4510-8cd0-90d3ef9b65f9}</Project> - <Name>Rothko</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> - <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> - <Name>GitHub.Api</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> - <Project>{1a1da411-8d1f-4578-80a6-04576bea2dc5}</Project> - <Name>GitHub.App</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> - <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> - <Name>GitHub.Exports</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> - <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> - <Name>GitHub.Exports.Reactive</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj"> - <Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project> - <Name>GitHub.UI.Reactive</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> - <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> - <Name>GitHub.UI</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.VisualStudio\GitHub.VisualStudio.csproj"> - <Project>{11569514-5ae5-4b5b-92a2-f10b0967de5f}</Project> - <Name>GitHub.VisualStudio</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> - <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> - <Name>ReactiveUI_Net45</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> - <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> - <Name>Splat-Net45</Name> - </ProjectReference> - </ItemGroup> - <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> -</Project> \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml b/src/DesignTimeStyleHelper/MainWindow.xaml deleted file mode 100644 index 5e3283aa83..0000000000 --- a/src/DesignTimeStyleHelper/MainWindow.xaml +++ /dev/null @@ -1,36 +0,0 @@ -<Window x:Class="DesignTimeStyleHelper.MainWindow" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:gh="clr-namespace:GitHub.VisualStudio.UI.Views;assembly=GitHub.VisualStudio" - xmlns:local="clr-namespace:DesignTimeStyleHelper" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:ctl="clr-namespace:GitHub.VisualStudio.UI.Views.Controls;assembly=GitHub.VisualStudio" - mc:Ignorable="d" - Title="MainWindow" Height="350" Width="525"> - <Window.Resources> - <Style TargetType="{x:Type TextBlock}"> - <Setter Property="Margin" Value="10,0,0,0" /> - </Style> - </Window.Resources> - <StackPanel> - <Border Background="#F5F5F5" > - <gh:GitHubHomeContent x:Name="gitHubHomeSection" Margin="0,10,0,10" /> - </Border> - <Grid> - <WrapPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,6"> - <TextBlock><Hyperlink x:Name="loginLink" Click="loginLink_Click">Login</Hyperlink></TextBlock> - <TextBlock><Hyperlink x:Name="cloneLink" Click="cloneLink_Click">Clone</Hyperlink></TextBlock> - <TextBlock><Hyperlink x:Name="createLink" Click="createLink_Click">Create</Hyperlink></TextBlock> - <TextBlock><Hyperlink x:Name="publishLink" Click="publishLink_Click">Publish</Hyperlink></TextBlock> - <TextBlock><Hyperlink x:Name="twoFactorTester" Click="twoFactorTester_Click">Two Factor</Hyperlink></TextBlock> - </WrapPanel> - </Grid> - - <StackPanel x:Name="container"> - - </StackPanel> - - </StackPanel> -</Window> diff --git a/src/DesignTimeStyleHelper/MainWindow.xaml.cs b/src/DesignTimeStyleHelper/MainWindow.xaml.cs deleted file mode 100644 index 9ca122416f..0000000000 --- a/src/DesignTimeStyleHelper/MainWindow.xaml.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Windows; -using GitHub.SampleData; -using GitHub.Services; -using GitHub.UI; -using GitHub.Extensions; -using GitHub.Models; - -namespace DesignTimeStyleHelper -{ - /// <summary> - /// Interaction logic for MainWindow.xaml - /// </summary> - public partial class MainWindow : Window - { - public MainWindow() - { - InitializeComponent(); - gitHubHomeSection.DataContext = new GitHubHomeSectionDesigner(); - } - - private void loginLink_Click(object sender, RoutedEventArgs e) - { - ShowDialog(UIControllerFlow.Authentication); - } - - private void cloneLink_Click(object sender, RoutedEventArgs e) - { - ShowDialog(UIControllerFlow.Clone); - } - - private void createLink_Click(object sender, RoutedEventArgs e) - { - ShowDialog(UIControllerFlow.Create); - } - - private void publishLink_Click(object sender, RoutedEventArgs e) - { - ShowDialog(UIControllerFlow.Publish); - } - - private void twoFactorTester_Click(object sender, RoutedEventArgs e) - { - var twoFactorTester = new TwoFactorInputTester(); - twoFactorTester.ShowDialog(); - } - - void ShowDialog(UIControllerFlow flow) - { - var ui = App.ServiceProvider.GetExportedValue<IUIProvider>(); - - var factory = ui.GetService<IExportFactoryProvider>(); - var d = factory.UIControllerFactory.CreateExport(); - var userControlObservable = d.Value.SelectFlow(flow); - var x = new WindowController(userControlObservable); - userControlObservable.Subscribe(_ => { }, _ => x.Close()); - x.Show(); - d.Value.Start(null); - } - } -} diff --git a/src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs b/src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs deleted file mode 100644 index ccdfc3b4fe..0000000000 --- a/src/DesignTimeStyleHelper/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Windows; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DesignTimeStyleHelper")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DesignTimeStyleHelper")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -//In order to begin building localizable applications, set -//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file -//inside a <PropertyGroup>. For example, if you are using US english -//in your source files, set the <UICulture> to en-US. Then uncomment -//the NeutralResourceLanguage attribute below. Update the "en-US" in -//the line below to match the UICulture setting in the project file. - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] - - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DesignTimeStyleHelper/Properties/Resources.Designer.cs b/src/DesignTimeStyleHelper/Properties/Resources.Designer.cs deleted file mode 100644 index 7d10cee3a3..0000000000 --- a/src/DesignTimeStyleHelper/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// <auto-generated> -// This code was generated by a tool. -// Runtime Version:4.0.30319.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// </auto-generated> -//------------------------------------------------------------------------------ - -namespace DesignTimeStyleHelper.Properties -{ - - - /// <summary> - /// A strongly-typed resource class, for looking up localized strings, etc. - /// </summary> - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// <summary> - /// Returns the cached ResourceManager instance used by this class. - /// </summary> - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DesignTimeStyleHelper.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// <summary> - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// </summary> - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/src/DesignTimeStyleHelper/Properties/Resources.resx b/src/DesignTimeStyleHelper/Properties/Resources.resx deleted file mode 100644 index af7dbebbac..0000000000 --- a/src/DesignTimeStyleHelper/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<root> - <!-- - Microsoft ResX Schema - - Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes - associated with the data types. - - Example: - - ... ado.net/XML headers & schema ... - <resheader name="resmimetype">text/microsoft-resx</resheader> - <resheader name="version">2.0</resheader> - <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> - <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> - <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> - <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> - <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> - <value>[base64 mime encoded serialized .NET Framework object]</value> - </data> - <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> - <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> - <comment>This is a comment</comment> - </data> - - There are any number of "resheader" rows that contain simple - name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the - mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can - read any of the formats listed below. - - mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with - : System.Serialization.Formatters.Binary.BinaryFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Soap.SoapFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array - : using a System.ComponentModel.TypeConverter - : and then encoded with base64 encoding. - --> - <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:element name="root" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="metadata"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" /> - <xsd:attribute name="type" type="xsd:string" /> - <xsd:attribute name="mimetype" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="assembly"> - <xsd:complexType> - <xsd:attribute name="alias" type="xsd:string" /> - <xsd:attribute name="name" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="data"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> - <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> - <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="resheader"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" /> - </xsd:complexType> - </xsd:element> - </xsd:choice> - </xsd:complexType> - </xsd:element> - </xsd:schema> - <resheader name="resmimetype"> - <value>text/microsoft-resx</value> - </resheader> - <resheader name="version"> - <value>2.0</value> - </resheader> - <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> -</root> \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/Properties/Settings.Designer.cs b/src/DesignTimeStyleHelper/Properties/Settings.Designer.cs deleted file mode 100644 index a3392b1a2f..0000000000 --- a/src/DesignTimeStyleHelper/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// <auto-generated> -// This code was generated by a tool. -// Runtime Version:4.0.30319.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// </auto-generated> -//------------------------------------------------------------------------------ - -namespace DesignTimeStyleHelper.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/src/DesignTimeStyleHelper/Properties/Settings.settings b/src/DesignTimeStyleHelper/Properties/Settings.settings deleted file mode 100644 index 033d7a5e9e..0000000000 --- a/src/DesignTimeStyleHelper/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> - <Profiles> - <Profile Name="(Default)" /> - </Profiles> - <Settings /> -</SettingsFile> \ No newline at end of file diff --git a/src/DesignTimeStyleHelper/TwoFactorInputTester.xaml b/src/DesignTimeStyleHelper/TwoFactorInputTester.xaml deleted file mode 100644 index 5186888afc..0000000000 --- a/src/DesignTimeStyleHelper/TwoFactorInputTester.xaml +++ /dev/null @@ -1,14 +0,0 @@ -<Window x:Class="DesignTimeStyleHelper.TwoFactorInputTester" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:local="clr-namespace:DesignTimeStyleHelper" - mc:Ignorable="d" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - - Title="TwoFactorInputTester" Height="300" Width="300"> - <Grid> - <uirx:TwoFactorInput x:Name="twoFactor" /> - </Grid> -</Window> diff --git a/src/DesignTimeStyleHelper/TwoFactorInputTester.xaml.cs b/src/DesignTimeStyleHelper/TwoFactorInputTester.xaml.cs deleted file mode 100644 index 581dc40f34..0000000000 --- a/src/DesignTimeStyleHelper/TwoFactorInputTester.xaml.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Windows; - -namespace DesignTimeStyleHelper -{ - /// <summary> - /// Interaction logic for TwoFactorInputTester.xaml - /// </summary> - public partial class TwoFactorInputTester : Window - { - public TwoFactorInputTester() - { - InitializeComponent(); - } - } -} diff --git a/src/DesignTimeStyleHelper/WindowController.xaml b/src/DesignTimeStyleHelper/WindowController.xaml deleted file mode 100644 index 3e88df1b23..0000000000 --- a/src/DesignTimeStyleHelper/WindowController.xaml +++ /dev/null @@ -1,16 +0,0 @@ -<Window x:Class="DesignTimeStyleHelper.WindowController" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:local="clr-namespace:DesignTimeStyleHelper" - mc:Ignorable="d" - Title="WindowController" - Height="440" - Width="414" - MinWidth="414" - MinHeight="440"> - <Grid x:Name="Container"> - - </Grid> -</Window> diff --git a/src/DesignTimeStyleHelper/WindowController.xaml.cs b/src/DesignTimeStyleHelper/WindowController.xaml.cs deleted file mode 100644 index 81ae1f53ad..0000000000 --- a/src/DesignTimeStyleHelper/WindowController.xaml.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; - -namespace DesignTimeStyleHelper -{ - /// <summary> - /// Interaction logic for WindowController.xaml - /// </summary> - public partial class WindowController : Window - { - IDisposable disposable; - - public WindowController(IObservable<UserControl> controls) - { - InitializeComponent(); - - disposable = controls.Subscribe(c => Load(c), - Close - ); - } - - protected override void OnClosed(EventArgs e) - { - disposable.Dispose(); - base.OnClosed(e); - } - - public void Load(UserControl control) - { - Container.Children.Clear(); - Container.Children.Add(control); - } - } -} diff --git a/src/DesignTimeStyleHelper/packages.config b/src/DesignTimeStyleHelper/packages.config deleted file mode 100644 index 59062b8c38..0000000000 --- a/src/DesignTimeStyleHelper/packages.config +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="Moq" version="4.2.1312.1319" targetFramework="net45" userInstalled="true" /> - <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> - <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> - <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> - <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> - <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> - <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> -</packages> \ No newline at end of file diff --git a/src/GitHub.Api/ApiClientConfiguration.cs b/src/GitHub.Api/ApiClientConfiguration.cs new file mode 100644 index 0000000000..fc8cba3eb7 --- /dev/null +++ b/src/GitHub.Api/ApiClientConfiguration.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Security.Cryptography; +using System.Text; + +namespace GitHub.Api +{ + /// <summary> + /// Holds the configuration for API clients. + /// </summary> + public static partial class ApiClientConfiguration + { + /// <summary> + /// Initializes static members of the <see cref="ApiClientConfiguration"/> class. + /// </summary> + static ApiClientConfiguration() + { + Configure(); + } + + /// <summary> + /// Gets the application's OAUTH client ID. + /// </summary> + public static string ClientId { get; private set; } + + /// <summary> + /// Gets the application's OAUTH client secret. + /// </summary> + public static string ClientSecret { get; private set; } + + /// <summary> + /// Gets the scopes required by the application. + /// </summary> + public static IReadOnlyList<string> RequiredScopes { get; } = new[] { "user", "repo", "gist", "write:public_key" }; + + /// <summary> + /// Gets a note that will be stored with the OAUTH token. + /// </summary> + public static string AuthorizationNote + { + get { return Info.ApplicationInfo.ApplicationDescription + " on " + GetMachineNameSafe(); } + } + + /// <summary> + /// Gets the machine fingerprint that will be registered with the OAUTH token, allowing + /// multiple authorizations to be created for a single user. + /// </summary> + public static string MachineFingerprint + { + get + { + return GetSha256Hash( + Info.ApplicationInfo.ApplicationDescription + ":" + + GetMachineIdentifier() + ":" + + GetMachineNameSafe()); + } + } + + static partial void Configure(); + + static string GetMachineIdentifier() + { + try + { + // adapted from http://stackoverflow.com/a/1561067 + var fastestValidNetworkInterface = NetworkInterface.GetAllNetworkInterfaces() + .OrderByDescending(nic => nic.Speed) + .Where(nic => nic.OperationalStatus == OperationalStatus.Up) + .Select(nic => nic.GetPhysicalAddress().ToString()) + .FirstOrDefault(address => address.Length >= 12); + + return fastestValidNetworkInterface ?? GetMachineNameSafe(); + } + catch (Exception) + { + //log.Info("Could not retrieve MAC address. Fallback to using machine name.", e); + return GetMachineNameSafe(); + } + } + + static string GetMachineNameSafe() + { + try + { + return Dns.GetHostName(); + } + catch (Exception) + { + //log.Info("Failed to retrieve host name using `DNS.GetHostName`.", e); + + try + { + return Environment.MachineName; + } + catch (Exception) + { + //log.Info("Failed to retrieve host name using `Environment.MachineName`.", ex); + return "(unknown)"; + } + } + } + + static string GetSha256Hash(string input) + { + try + { + using (var sha256 = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha256.ComputeHash(bytes); + + return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + } + } + catch (Exception) + { + //log.Error("IMPOSSIBLE! Generating Sha256 hash caused an exception.", e); + return null; + } + } + } +} diff --git a/src/GitHub.Api/ApiClientConfiguration_User.cs b/src/GitHub.Api/ApiClientConfiguration_User.cs new file mode 100644 index 0000000000..fdffb967e8 --- /dev/null +++ b/src/GitHub.Api/ApiClientConfiguration_User.cs @@ -0,0 +1,16 @@ +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/GitHub.Api.csproj b/src/GitHub.Api/GitHub.Api.csproj index ad15b3a88e..28f1263421 100644 --- a/src/GitHub.Api/GitHub.Api.csproj +++ b/src/GitHub.Api/GitHub.Api.csproj @@ -9,41 +9,57 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub.Api</RootNamespace> <AssemblyName>GitHub.Api</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> + <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath> + </Reference> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.Core" /> @@ -55,10 +71,22 @@ <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> - <Compile Include="SimpleCredentialStore.cs" /> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> + <Compile Include="ApiClientConfiguration.cs" /> + <Compile Include="..\..\script\src\ApiClientConfiguration_User.cs" Condition="$(Buildtype) == 'Internal'"> + <Link>ApiClientConfiguration_User.cs</Link> + </Compile> + <Compile Include="ApiClientConfiguration_User.cs" Condition="$(Buildtype) != 'Internal'" /> + <Compile Include="GraphQLKeychainCredentialStore.cs" /> + <Compile Include="IGraphQLClientFactory.cs" /> + <Compile Include="GraphQLClientFactory.cs" /> + <Compile Include="IKeychain.cs" /> + <Compile Include="ILoginManager.cs" /> + <Compile Include="IOAuthCallbackListener.cs" /> + <Compile Include="IncorrectScopesException.cs" /> + <Compile Include="ITwoFactorChallengeHandler.cs" /> + <Compile Include="LoginManager.cs" /> + <Compile Include="KeychainCredentialStore.cs" /> + <Compile Include="WindowsKeychain.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> @@ -85,6 +113,13 @@ <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/src/GitHub.Api/GraphQLClientFactory.cs b/src/GitHub.Api/GraphQLClientFactory.cs new file mode 100644 index 0000000000..cd91295935 --- /dev/null +++ b/src/GitHub.Api/GraphQLClientFactory.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using Octokit.GraphQL; + +namespace GitHub.Api +{ + /// <summary> + /// Creates GraphQL <see cref="Octokit.GraphQL.IConnection"/>s for querying the + /// GitHub GraphQL API. + /// </summary> + [Export(typeof(IGraphQLClientFactory))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class GraphQLClientFactory : IGraphQLClientFactory + { + readonly IKeychain keychain; + readonly IProgram program; + + /// <summary> + /// Initializes a new instance of the <see cref="GraphQLClientFactory"/> class. + /// </summary> + /// <param name="keychain">The <see cref="IKeychain"/> to use.</param> + /// <param name="program">The program details.</param> + [ImportingConstructor] + public GraphQLClientFactory(IKeychain keychain, IProgram program) + { + this.keychain = keychain; + this.program = program; + } + + /// <inheirtdoc/> + public Task<Octokit.GraphQL.IConnection> CreateConnection(HostAddress address) + { + var credentials = new GraphQLKeychainCredentialStore(keychain, address); + var header = new ProductHeaderValue(program.ProductHeader.Name, program.ProductHeader.Version); + return Task.FromResult<Octokit.GraphQL.IConnection>(new Connection(header, address.GraphQLUri, credentials)); + } + } +} diff --git a/src/GitHub.Api/GraphQLKeychainCredentialStore.cs b/src/GitHub.Api/GraphQLKeychainCredentialStore.cs new file mode 100644 index 0000000000..4ad122fb88 --- /dev/null +++ b/src/GitHub.Api/GraphQLKeychainCredentialStore.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Primitives; +using Octokit.GraphQL; + +namespace GitHub.Api +{ + /// <summary> + /// An Octokit.GraphQL credential store that reads from an <see cref="IKeychain"/>. + /// </summary> + public class GraphQLKeychainCredentialStore : ICredentialStore + { + readonly IKeychain keychain; + readonly HostAddress address; + + public GraphQLKeychainCredentialStore(IKeychain keychain, HostAddress address) + { + Guard.ArgumentNotNull(keychain, nameof(keychain)); + Guard.ArgumentNotNull(address, nameof(keychain)); + + this.keychain = keychain; + this.address = address; + } + + public async Task<string> GetCredentials(CancellationToken cancellationToken = default) + { + var userPass = await keychain.Load(address).ConfigureAwait(false); + return userPass?.Item2; + } + } +} diff --git a/src/GitHub.Api/IGraphQLClientFactory.cs b/src/GitHub.Api/IGraphQLClientFactory.cs new file mode 100644 index 0000000000..464fab0de8 --- /dev/null +++ b/src/GitHub.Api/IGraphQLClientFactory.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using GitHub.Primitives; + +namespace GitHub.Api +{ + /// <summary> + /// Creates GraphQL <see cref="Octokit.GraphQL.IConnection"/>s for querying the + /// GitHub GraphQL API. + /// </summary> + public interface IGraphQLClientFactory + { + /// <summary> + /// Creates a new <see cref="Octokit.GraphQL.IConnection"/>. + /// </summary> + /// <param name="address">The address of the server.</param> + /// <returns>A task returning the created connection.</returns> + Task<Octokit.GraphQL.IConnection> CreateConnection(HostAddress address); + } +} \ No newline at end of file diff --git a/src/GitHub.Api/IKeychain.cs b/src/GitHub.Api/IKeychain.cs new file mode 100644 index 0000000000..88d824c98a --- /dev/null +++ b/src/GitHub.Api/IKeychain.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; +using GitHub.Primitives; + +namespace GitHub.Api +{ + /// <summary> + /// Represents a keychain used to store credentials. + /// </summary> + public interface IKeychain + { + /// <summary> + /// Loads the credentials for the specified host address. + /// </summary> + /// <param name="address">The host address.</param> + /// <returns> + /// A task returning a tuple consisting of the retrieved username and password or null + /// if the credentials were not found. + /// </returns> + Task<Tuple<string, string>> Load(HostAddress address); + + /// <summary> + /// Saves the credentials for the specified host address. + /// </summary> + /// <param name="userName">The username.</param> + /// <param name="password">The password.</param> + /// <param name="address">The host address.</param> + /// <returns>A task tracking the operation.</returns> + Task Save(string userName, string password, HostAddress address); + + /// <summary> + /// Deletes the login details for the specified host address. + /// </summary> + /// <param name="address"></param> + /// <returns>A task tracking the operation.</returns> + Task Delete(HostAddress address); + } +} diff --git a/src/GitHub.Api/ILoginManager.cs b/src/GitHub.Api/ILoginManager.cs new file mode 100644 index 0000000000..d17408a6aa --- /dev/null +++ b/src/GitHub.Api/ILoginManager.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Primitives; +using GitHub.VisualStudio; +using Octokit; + +namespace GitHub.Api +{ + /// <summary> + /// Provides services for logging into a GitHub server. + /// </summary> + [Guid(Guids.LoginManagerId)] + public interface ILoginManager + { + /// <summary> + /// Attempts to log into a GitHub server. + /// </summary> + /// <param name="hostAddress">The address of the server.</param> + /// <param name="client">An octokit client configured to access the server.</param> + /// <param name="userName">The username.</param> + /// <param name="password">The password.</param> + /// <returns>The logged in user.</returns> + /// <exception cref="AuthorizationException"> + /// The login authorization failed. + /// </exception> + Task<User> Login(HostAddress hostAddress, IGitHubClient client, string userName, string password); + + /// <summary> + /// Attempts to log into a GitHub server via OAuth in the browser. + /// </summary> + /// <param name="hostAddress">The address of the server.</param> + /// <param name="client">An octokit client configured to access the server.</param> + /// <param name="oauthClient">An octokit OAuth client configured to access the server.</param> + /// <param name="openBrowser">A callback that should open a browser at the requested URL.</param> + /// <param name="cancel">A cancellation token used to cancel the operation.</param> + /// <returns>The logged in user.</returns> + /// <exception cref="AuthorizationException"> + /// The login authorization failed. + /// </exception> + Task<User> LoginViaOAuth( + HostAddress hostAddress, + IGitHubClient client, + IOauthClient oauthClient, + Action<Uri> openBrowser, + CancellationToken cancel); + + /// <summary> + /// Attempts to log into a GitHub server with a token. + /// </summary> + /// <param name="hostAddress">The address of the server.</param> + /// <param name="client">An octokit client configured to access the server.</param> + /// <param name="token">The token.</param> + Task<User> LoginWithToken( + HostAddress hostAddress, + IGitHubClient client, + string token); + + /// <summary> + /// Attempts to log into a GitHub server using existing credentials. + /// </summary> + /// <param name="hostAddress">The address of the server.</param> + /// <param name="client">An octokit client configured to access the server.</param> + /// <returns>The logged in user.</returns> + /// <exception cref="AuthorizationException"> + /// The login authorization failed. + /// </exception> + Task<User> LoginFromCache(HostAddress hostAddress, IGitHubClient client); + + /// <summary> + /// Logs out of GitHub server. + /// </summary> + /// <param name="hostAddress">The address of the server.</param> + /// <param name="client">An octokit client configured to access the server.</param> + Task Logout(HostAddress hostAddress, IGitHubClient client); + } +} \ No newline at end of file diff --git a/src/GitHub.Api/IOAuthCallbackListener.cs b/src/GitHub.Api/IOAuthCallbackListener.cs new file mode 100644 index 0000000000..4c8d29b85c --- /dev/null +++ b/src/GitHub.Api/IOAuthCallbackListener.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace GitHub.Api +{ + /// <summary> + /// Listens for a callback from the OAuth endpoint signalling successful login. + /// </summary> + public interface IOAuthCallbackListener + { + /// <summary> + /// Listens for a callback with a `state` matching <paramref name="id"/>. + /// </summary> + /// <param name="id">The ID of the operation.</param> + /// <param name="cancel">A cancellation token.</param> + /// <returns>The temporary code included in the callback.</returns> + Task<string> Listen(string id, CancellationToken cancel); + } +} diff --git a/src/GitHub.Api/ITwoFactorChallengeHandler.cs b/src/GitHub.Api/ITwoFactorChallengeHandler.cs new file mode 100644 index 0000000000..8edfcf5fda --- /dev/null +++ b/src/GitHub.Api/ITwoFactorChallengeHandler.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Octokit; + +namespace GitHub.Api +{ + /// <summary> + /// Interface for handling Two Factor Authentication challenges in a + /// <see cref="LoginManager"/> operation. + /// </summary> + public interface ITwoFactorChallengeHandler + { + /// <summary> + /// Called when the GitHub API responds to a login request with a 2FA challenge. + /// </summary> + /// <param name="exception">The 2FA exception that initiated the challenge.</param> + /// <returns>A task returning a <see cref="TwoFactorChallengeResult"/>.</returns> + Task<TwoFactorChallengeResult> HandleTwoFactorException(TwoFactorAuthorizationException exception); + + /// <summary> + /// Called when an error occurs sending the 2FA challenge response. + /// </summary> + /// <param name="e">The exception that occurred.</param> + /// <remarks> + /// This method is called when, on sending the challenge response returned by + /// <see cref="HandleTwoFactorException(TwoFactorAuthorizationException)"/>, an exception of + /// a type other than <see cref="TwoFactorAuthorizationException"/> is thrown. This + /// indicates that the login attempt is over and any 2FA dialog being shown should close. + /// </remarks> + Task ChallengeFailed(Exception e); + } +} diff --git a/src/GitHub.Api/IncorrectScopesException.cs b/src/GitHub.Api/IncorrectScopesException.cs new file mode 100644 index 0000000000..5cdddec7d7 --- /dev/null +++ b/src/GitHub.Api/IncorrectScopesException.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.Serialization; + +namespace GitHub.Api +{ + /// <summary> + /// Thrown when the login for a user does not have the required API scopes. + /// </summary> + [Serializable] + public class IncorrectScopesException : Exception + { + public IncorrectScopesException() + : this("Incorrect API scopes.") + { + } + + public IncorrectScopesException(string message) + : base(message) + { + } + + public IncorrectScopesException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected IncorrectScopesException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/GitHub.Api/KeychainCredentialStore.cs b/src/GitHub.Api/KeychainCredentialStore.cs new file mode 100644 index 0000000000..1a039a52ec --- /dev/null +++ b/src/GitHub.Api/KeychainCredentialStore.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Primitives; +using Octokit; + +namespace GitHub.Api +{ + /// <summary> + /// An Octokit credential store that reads from an <see cref="IKeychain"/>. + /// </summary> + public class KeychainCredentialStore : ICredentialStore + { + readonly IKeychain keychain; + readonly HostAddress address; + + public KeychainCredentialStore(IKeychain keychain, HostAddress address) + { + Guard.ArgumentNotNull(keychain, nameof(keychain)); + Guard.ArgumentNotNull(address, nameof(keychain)); + + this.keychain = keychain; + this.address = address; + } + + public async Task<Credentials> GetCredentials() + { + var userPass = await keychain.Load(address).ConfigureAwait(false); + return userPass != null ? new Credentials(userPass.Item1, userPass.Item2) : Credentials.Anonymous; + } + } +} diff --git a/src/GitHub.Api/LoginManager.cs b/src/GitHub.Api/LoginManager.cs new file mode 100644 index 0000000000..7dcfbbda24 --- /dev/null +++ b/src/GitHub.Api/LoginManager.cs @@ -0,0 +1,416 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Primitives; +using Octokit; +using Serilog; + +namespace GitHub.Api +{ + /// <summary> + /// Provides services for logging into a GitHub server. + /// </summary> + public class LoginManager : ILoginManager + { + const string ScopesHeader = "X-OAuth-Scopes"; + static readonly ILogger log = LogManager.ForContext<LoginManager>(); + static readonly Uri UserEndpoint = new Uri("user", UriKind.Relative); + readonly IKeychain keychain; + readonly Lazy<ITwoFactorChallengeHandler> twoFactorChallengeHandler; + readonly string clientId; + readonly string clientSecret; + readonly IReadOnlyList<string> scopes; + readonly string authorizationNote; + readonly string fingerprint; + IOAuthCallbackListener oauthListener; + + /// <summary> + /// Initializes a new instance of the <see cref="LoginManager"/> class. + /// </summary> + /// <param name="keychain">The keychain in which to store credentials.</param> + /// <param name="twoFactorChallengeHandler">The handler for 2FA challenges.</param> + /// <param name="oauthListener">The callback listener to signal successful login.</param> + /// <param name="clientId">The application's client API ID.</param> + /// <param name="clientSecret">The application's client API secret.</param> + /// <param name="scopes">List of scopes to authenticate for</param> + /// <param name="authorizationNote">An note to store with the authorization.</param> + /// <param name="fingerprint">The machine fingerprint.</param> + public LoginManager( + IKeychain keychain, + Lazy<ITwoFactorChallengeHandler> twoFactorChallengeHandler, + IOAuthCallbackListener oauthListener, + string clientId, + string clientSecret, + IReadOnlyList<string> scopes, + string authorizationNote = null, + string fingerprint = null) + { + Guard.ArgumentNotNull(keychain, nameof(keychain)); + Guard.ArgumentNotNull(twoFactorChallengeHandler, nameof(twoFactorChallengeHandler)); + Guard.ArgumentNotEmptyString(clientId, nameof(clientId)); + Guard.ArgumentNotEmptyString(clientSecret, nameof(clientSecret)); + + this.keychain = keychain; + this.twoFactorChallengeHandler = twoFactorChallengeHandler; + this.oauthListener = oauthListener; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.scopes = scopes; + this.authorizationNote = authorizationNote; + this.fingerprint = fingerprint; + } + + /// <inheritdoc/> + public async Task<User> Login( + HostAddress hostAddress, + IGitHubClient client, + string userName, + string password) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + Guard.ArgumentNotNull(client, nameof(client)); + Guard.ArgumentNotEmptyString(userName, nameof(userName)); + Guard.ArgumentNotEmptyString(password, nameof(password)); + + // Start by saving the username and password, these will be used by the `IGitHubClient` + // until an authorization token has been created and acquired: + await keychain.Save(userName, password, hostAddress).ConfigureAwait(false); + + var newAuth = new NewAuthorization + { + Scopes = scopes, + Note = authorizationNote, + Fingerprint = fingerprint, + }; + + ApplicationAuthorization auth = null; + + do + { + try + { + auth = await CreateAndDeleteExistingApplicationAuthorization(client, newAuth, null) + .ConfigureAwait(false); + EnsureNonNullAuthorization(auth); + } + catch (TwoFactorAuthorizationException e) + { + auth = await HandleTwoFactorAuthorization(hostAddress, client, newAuth, e) + .ConfigureAwait(false); + } + catch (Exception e) + { + // Some enterpise instances don't support OAUTH, so fall back to using the + // supplied password - on intances that don't support OAUTH the user should + // be using a personal access token as the password. + if (EnterpriseWorkaround(hostAddress, e)) + { + auth = new ApplicationAuthorization(password); + } + else + { + await keychain.Delete(hostAddress).ConfigureAwait(false); + throw; + } + } + } while (auth == null); + + await keychain.Save(userName, auth.Token, hostAddress).ConfigureAwait(false); + return await ReadUserWithRetry(client); + } + + /// <inheritdoc/> + public async Task<User> LoginViaOAuth( + HostAddress hostAddress, + IGitHubClient client, + IOauthClient oauthClient, + Action<Uri> openBrowser, + CancellationToken cancel) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + Guard.ArgumentNotNull(client, nameof(client)); + Guard.ArgumentNotNull(oauthClient, nameof(oauthClient)); + Guard.ArgumentNotNull(openBrowser, nameof(openBrowser)); + + var state = Guid.NewGuid().ToString(); + var loginUrl = GetLoginUrl(oauthClient, state); + var listen = oauthListener.Listen(state, cancel); + + openBrowser(loginUrl); + + var code = await listen; + var request = new OauthTokenRequest(clientId, clientSecret, code); + var token = await oauthClient.CreateAccessToken(request); + + await keychain.Save("[oauth]", token.AccessToken, hostAddress).ConfigureAwait(false); + var user = await ReadUserWithRetry(client); + await keychain.Save(user.Login, token.AccessToken, hostAddress).ConfigureAwait(false); + return user; + } + + /// <inheritdoc/> + public async Task<User> LoginWithToken( + HostAddress hostAddress, + IGitHubClient client, + string token) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + Guard.ArgumentNotNull(client, nameof(client)); + Guard.ArgumentNotEmptyString(token, nameof(token)); + + await keychain.Save("[token]", token, hostAddress).ConfigureAwait(false); + + try + { + var user = await ReadUserWithRetry(client); + await keychain.Save(user.Login, token, hostAddress).ConfigureAwait(false); + return user; + } + catch + { + await keychain.Delete(hostAddress); + throw; + } + } + + /// <inheritdoc/> + public Task<User> LoginFromCache(HostAddress hostAddress, IGitHubClient client) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + Guard.ArgumentNotNull(client, nameof(client)); + + return ReadUserWithRetry(client); + } + + /// <inheritdoc/> + public async Task Logout(HostAddress hostAddress, IGitHubClient client) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + Guard.ArgumentNotNull(client, nameof(client)); + + await keychain.Delete(hostAddress); + } + + /// <summary> + /// Tests if received API scopes match the required API scopes. + /// </summary> + /// <param name="required">The required API scopes.</param> + /// <param name="received">The received API scopes.</param> + /// <returns>True if all required scopes are present, otherwise false.</returns> + public static bool ScopesMatch(IReadOnlyList<string> required, IReadOnlyList<string> received) + { + foreach (var scope in required) + { + var found = received.Contains(scope); + + if (!found && + (scope.StartsWith("read:", StringComparison.Ordinal) || + scope.StartsWith("write:", StringComparison.Ordinal))) + { + // NOTE: Scopes are actually more complex than this, for example + // `user` encompasses `read:user` and `user:email` but just use + // this simple rule for now as it works for the scopes we require. + var adminScope = scope + .Replace("read:", "admin:") + .Replace("write:", "admin:"); + found = received.Contains(adminScope); + } + + if (!found) + { + return false; + } + } + + return true; + } + + async Task<ApplicationAuthorization> CreateAndDeleteExistingApplicationAuthorization( + IGitHubClient client, + NewAuthorization newAuth, + string twoFactorAuthenticationCode) + { + ApplicationAuthorization result; + var retry = 0; + + do + { + if (twoFactorAuthenticationCode == null) + { + result = await client.Authorization.GetOrCreateApplicationAuthentication( + clientId, + clientSecret, + newAuth).ConfigureAwait(false); + } + else + { + result = await client.Authorization.GetOrCreateApplicationAuthentication( + clientId, + clientSecret, + newAuth, + twoFactorAuthenticationCode).ConfigureAwait(false); + } + + if (result.Token == string.Empty) + { + if (twoFactorAuthenticationCode == null) + { + await client.Authorization.Delete(result.Id); + } + else + { + await client.Authorization.Delete(result.Id, twoFactorAuthenticationCode); + } + } + } while (result.Token == string.Empty && retry++ == 0); + + return result; + } + + async Task<ApplicationAuthorization> HandleTwoFactorAuthorization( + HostAddress hostAddress, + IGitHubClient client, + NewAuthorization newAuth, + TwoFactorAuthorizationException exception) + { + for (;;) + { + var challengeResult = await twoFactorChallengeHandler.Value.HandleTwoFactorException(exception); + + if (challengeResult == null) + { + throw new InvalidOperationException( + "ITwoFactorChallengeHandler.HandleTwoFactorException returned null."); + } + + if (!challengeResult.ResendCodeRequested) + { + try + { + var auth = await CreateAndDeleteExistingApplicationAuthorization( + client, + newAuth, + challengeResult.AuthenticationCode).ConfigureAwait(false); + return EnsureNonNullAuthorization(auth); + } + catch (TwoFactorAuthorizationException e) + { + exception = e; + } + catch (Exception e) + { + await twoFactorChallengeHandler.Value.ChallengeFailed(e); + await keychain.Delete(hostAddress).ConfigureAwait(false); + throw; + } + } + else + { + return null; + } + } + } + + 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() + // so prevent that. + if (auth == null) + { + throw new InvalidOperationException("IGutHubClient.Authorization.Create returned null."); + } + + return auth; + } + + bool EnterpriseWorkaround(HostAddress hostAddress, Exception e) + { + // Older Enterprise hosts either don't have the API end-point to PUT an authorization, or they + // return 422 because they haven't white-listed our client ID. In that case, we just ignore + // the failure, using basic authentication (with username and password) instead of trying + // to get an authorization token. + // Since enterprise 2.1 and https://github.com/github/github/pull/36669 the API returns 403 + // instead of 404 to signal that it's not allowed. In the name of backwards compatibility we + // test for both 404 (NotFoundException) and 403 (ForbiddenException) here. + var apiException = e as ApiException; + return !hostAddress.IsGitHubDotCom() && + (e is NotFoundException || + e is ForbiddenException || + apiException?.StatusCode == (HttpStatusCode)422); + } + + async Task<User> ReadUserWithRetry(IGitHubClient client) + { + var retry = 0; + + while (true) + { + try + { + return await GetUserAndCheckScopes(client).ConfigureAwait(false); + } + catch (AuthorizationException) + { + if (retry++ == 3) throw; + } + + // It seems that attempting to use a token immediately sometimes fails, retry a few + // times with a delay of of 1s to allow the token to propagate. + await Task.Delay(1000); + } + } + + async Task<User> GetUserAndCheckScopes(IGitHubClient client) + { + var response = await client.Connection.Get<User>( + UserEndpoint, null, null).ConfigureAwait(false); + + if (response.HttpResponse.Headers.ContainsKey(ScopesHeader)) + { + var returnedScopes = response.HttpResponse.Headers[ScopesHeader] + .Split(',') + .Select(x => x.Trim()) + .ToArray(); + + if (ScopesMatch(scopes, returnedScopes)) + { + return response.Body; + } + else + { + log.Error("Incorrect API scopes: require {RequiredScopes} but got {Scopes}", scopes, returnedScopes); + } + } + else + { + log.Error("Error reading scopes: /user succeeded but scopes header was not present"); + } + + throw new IncorrectScopesException( + "Incorrect API scopes. Required: " + string.Join(",", scopes)); + } + + Uri GetLoginUrl(IOauthClient client, string state) + { + var request = new OauthLoginRequest(ApiClientConfiguration.ClientId); + + request.State = state; + + foreach (var scope in scopes) + { + request.Scopes.Add(scope); + } + + var uri = client.GetGitHubLoginUrl(request); + + // OauthClient.GetGitHubLoginUrl seems to give the wrong URL. Fix this. + return new Uri(uri.ToString().Replace("/api/v3", "")); + } + } +} diff --git a/src/GitHub.Api/SimpleApiClient.cs b/src/GitHub.Api/SimpleApiClient.cs index 2723c42956..5ae5de86c9 100644 --- a/src/GitHub.Api/SimpleApiClient.cs +++ b/src/GitHub.Api/SimpleApiClient.cs @@ -14,8 +14,7 @@ public class SimpleApiClient : ISimpleApiClient public HostAddress HostAddress { get; } public UriString OriginalUrl { get; } - readonly IGitHubClient client; - readonly Lazy<IEnterpriseProbeTask> enterpriseProbe; + readonly Lazy<IEnterpriseProbe> enterpriseProbe; readonly Lazy<IWikiProbe> wikiProbe; static readonly SemaphoreSlim sem = new SemaphoreSlim(1); @@ -25,18 +24,20 @@ public class SimpleApiClient : ISimpleApiClient bool? hasWiki; public SimpleApiClient(UriString repoUrl, IGitHubClient githubClient, - Lazy<IEnterpriseProbeTask> enterpriseProbe, Lazy<IWikiProbe> wikiProbe) + Lazy<IEnterpriseProbe> enterpriseProbe, Lazy<IWikiProbe> wikiProbe) { Guard.ArgumentNotNull(repoUrl, nameof(repoUrl)); Guard.ArgumentNotNull(githubClient, nameof(githubClient)); HostAddress = HostAddress.Create(repoUrl); OriginalUrl = repoUrl; - client = githubClient; + Client = githubClient; this.enterpriseProbe = enterpriseProbe; this.wikiProbe = wikiProbe; } + public IGitHubClient Client { get; } + public async Task<Repository> GetRepository() { // fast path to avoid locking when the cache has already been set @@ -46,6 +47,14 @@ public async Task<Repository> GetRepository() return repositoryCache; return await GetRepositoryInternal(); } + + public bool IsAuthenticated() + { + // this doesn't account for auth revoke on the server but its much faster + // than doing the API hit. + var authType = Client.Connection.Credentials?.AuthenticationType ?? AuthenticationType.Anonymous; + return authType != AuthenticationType.Anonymous; + } async Task<Repository> GetRepositoryInternal() { @@ -59,11 +68,11 @@ async Task<Repository> GetRepositoryInternal() if (ownerLogin != null && repositoryName != null) { - var repo = await client.Repository.Get(ownerLogin, repositoryName); + var repo = await Client.Repository.Get(ownerLogin, repositoryName); if (repo != null) { hasWiki = await HasWikiInternal(repo); - isEnterprise = await IsEnterpriseInternal(); + isEnterprise = isEnterprise ?? await IsEnterpriseInternal(); repositoryCache = repo; } owner = ownerLogin; @@ -71,10 +80,10 @@ async Task<Repository> GetRepositoryInternal() } } // it'll throw if it's private or an enterprise instance requiring authentication - catch (ApiException) + catch (ApiException apiex) { if (!HostAddress.IsGitHubDotComUri(OriginalUrl.ToRepositoryUrl())) - isEnterprise = true; + isEnterprise = apiex.IsGitHubApiException(); } catch {} finally @@ -90,9 +99,14 @@ public bool HasWiki() return hasWiki.HasValue && hasWiki.Value; } - public bool IsEnterprise() + public async Task<bool> IsEnterprise() { - return isEnterprise.HasValue && isEnterprise.Value; + if (!isEnterprise.HasValue) + { + isEnterprise = await IsEnterpriseInternal(); + } + + return isEnterprise ?? false; } async Task<bool> HasWikiInternal(Repository repo) @@ -118,13 +132,16 @@ async Task<bool> HasWikiInternal(Repository repo) async Task<bool> IsEnterpriseInternal() { + if (HostAddress == HostAddress.GitHubDotComHostAddress) + return false; + var probe = enterpriseProbe.Value; Debug.Assert(probe != null, "Lazy<Enterprise> probe is not set, something is wrong."); #if !DEBUG if (probe == null) return false; #endif - var ret = await probe.ProbeAsync(HostAddress.WebUri); + var ret = await probe.Probe(HostAddress.WebUri); return (ret == EnterpriseProbeResult.Ok); } } diff --git a/src/GitHub.Api/SimpleApiClientFactory.cs b/src/GitHub.Api/SimpleApiClientFactory.cs index 503cc09668..9ad8d90cb5 100644 --- a/src/GitHub.Api/SimpleApiClientFactory.cs +++ b/src/GitHub.Api/SimpleApiClientFactory.cs @@ -1,11 +1,11 @@ using System; -using System.Collections.Generic; using System.ComponentModel.Composition; using GitHub.Models; using GitHub.Primitives; using GitHub.Services; using Octokit; using System.Collections.Concurrent; +using System.Threading.Tasks; namespace GitHub.Api { @@ -13,26 +13,34 @@ namespace GitHub.Api [PartCreationPolicy(CreationPolicy.Shared)] public class SimpleApiClientFactory : ISimpleApiClientFactory { + readonly IKeychain keychain; readonly ProductHeaderValue productHeader; - readonly Lazy<IEnterpriseProbeTask> lazyEnterpriseProbe; + readonly Lazy<IEnterpriseProbe> lazyEnterpriseProbe; readonly Lazy<IWikiProbe> lazyWikiProbe; static readonly ConcurrentDictionary<UriString, ISimpleApiClient> cache = new ConcurrentDictionary<UriString, ISimpleApiClient>(); [ImportingConstructor] - public SimpleApiClientFactory(IProgram program, Lazy<IEnterpriseProbeTask> enterpriseProbe, Lazy<IWikiProbe> wikiProbe) + public SimpleApiClientFactory( + IProgram program, + IKeychain keychain, + Lazy<IEnterpriseProbe> enterpriseProbe, + Lazy<IWikiProbe> wikiProbe) { + this.keychain = keychain; productHeader = program.ProductHeader; lazyEnterpriseProbe = enterpriseProbe; lazyWikiProbe = wikiProbe; } - public ISimpleApiClient Create(UriString repositoryUrl) + public Task<ISimpleApiClient> Create(UriString repositoryUrl) { var hostAddress = HostAddress.Create(repositoryUrl); - return cache.GetOrAdd(repositoryUrl, new SimpleApiClient(repositoryUrl, - new GitHubClient(productHeader, new SimpleCredentialStore(hostAddress), hostAddress.ApiUri), + var credentialStore = new KeychainCredentialStore(keychain, hostAddress); + var result = cache.GetOrAdd(repositoryUrl, new SimpleApiClient(repositoryUrl, + new GitHubClient(productHeader, credentialStore, hostAddress.ApiUri), lazyEnterpriseProbe, lazyWikiProbe)); + return Task.FromResult(result); } public void ClearFromCache(ISimpleApiClient client) diff --git a/src/GitHub.Api/SimpleCredentialStore.cs b/src/GitHub.Api/SimpleCredentialStore.cs deleted file mode 100644 index a50e080152..0000000000 --- a/src/GitHub.Api/SimpleCredentialStore.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Threading.Tasks; -using GitHub.Primitives; -using Octokit; -using System; -using GitHub.Authentication.CredentialManagement; - -namespace GitHub.Api -{ - public class SimpleCredentialStore : ICredentialStore - { - readonly HostAddress hostAddress; - - public SimpleCredentialStore(HostAddress hostAddress) - { - this.hostAddress = hostAddress; - } - - public Task<Credentials> GetCredentials() - { - var keyHost = GetKeyHost(hostAddress.CredentialCacheKeyHost); - using (var credential = new Credential()) - { - credential.Target = keyHost; - credential.Type = CredentialType.Generic; - if (credential.Load()) - return Task.FromResult(new Credentials(credential.Username, credential.Password)); - } - return Task.FromResult(Credentials.Anonymous); - } - - public static Task<bool> RemoveCredentials(string key) - { - var keyGit = GetKeyGit(key); - if (!DeleteKey(keyGit)) - return Task.FromResult(false); - - var keyHost = GetKeyHost(key); - if (!DeleteKey(keyHost)) - return Task.FromResult(false); - - return Task.FromResult(true); - } - - static bool DeleteKey(string key) - { - using (var credential = new Credential()) - { - credential.Target = key; - if (!credential.Load()) - return false; - return credential.Delete(); - } - } - - static string GetKeyHost(string key) - { - key = FormatKey(key); - if (key.StartsWith("git:", StringComparison.Ordinal)) - key = key.Substring("git:".Length); - if (!key.EndsWith("/", StringComparison.Ordinal)) - key += '/'; - return key; - } - - static string FormatKey(string key) - { - if (key.StartsWith("login:", StringComparison.Ordinal)) - key = key.Substring("login:".Length); - return key; - } - - static string GetKeyGit(string key) - { - key = FormatKey(key); - // it appears this is how MS expects the host key - if (!key.StartsWith("git:", StringComparison.Ordinal)) - key = "git:" + key; - if (key.EndsWith("/", StringComparison.Ordinal)) - key = key.Substring(0, key.Length - 1); - return key; - } - } -} diff --git a/src/GitHub.Api/WindowsKeychain.cs b/src/GitHub.Api/WindowsKeychain.cs new file mode 100644 index 0000000000..5e44d21f2a --- /dev/null +++ b/src/GitHub.Api/WindowsKeychain.cs @@ -0,0 +1,127 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Authentication.CredentialManagement; +using GitHub.Extensions; +using GitHub.Primitives; + +namespace GitHub.Api +{ + /// <summary> + /// A keychain that stores logins in the windows credential store. + /// </summary> + [Export(typeof(IKeychain))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class WindowsKeychain : IKeychain + { + /// <inheritdoc/> + public Task<Tuple<string, string>> Load(HostAddress hostAddress) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + + var key = GetKey(hostAddress.CredentialCacheKeyHost); + var keyGit = GetKeyGit(hostAddress.CredentialCacheKeyHost); + var keyHost = GetKeyHost(hostAddress.CredentialCacheKeyHost); + Tuple<string, string> result = null; + + // Visual Studio requires two credentials, keyed as "{hostname}" (e.g. "https://github.com/") and + // "git:{hostname}" (e.g. "git:https://github.com"). We have a problem in that these credentials can + // potentially be overwritten by other applications, so we store an extra "master" key as + // "GitHub for Visual Studio - {hostname}". Whenever we read the credentials we overwrite the other + // two keys with the value from the master key. Older versions of GHfVS did not store this master key + // so if it does not exist, try to get the value from the "{hostname}" key. + using (var credential = Credential.Load(key)) + using (var credentialGit = Credential.Load(keyGit)) + using (var credentialHost = Credential.Load(keyHost)) + { + if (credential != null) + { + result = Tuple.Create(credential.Username, credential.Password); + } + else if (credentialHost != null) + { + result = Tuple.Create(credentialHost.Username, credentialHost.Password); + } + + if (result != null) + { + Save(result.Item1, result.Item2, hostAddress); + } + } + + return Task.FromResult(result); + } + + /// <inheritdoc/> + public Task Save(string userName, string password, HostAddress hostAddress) + { + Guard.ArgumentNotEmptyString(userName, nameof(userName)); + Guard.ArgumentNotEmptyString(password, nameof(password)); + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + + var key = GetKey(hostAddress.CredentialCacheKeyHost); + var keyGit = GetKeyGit(hostAddress.CredentialCacheKeyHost); + var keyHost = GetKeyHost(hostAddress.CredentialCacheKeyHost); + + Credential.Save(key, userName, password); + Credential.Save(keyGit, userName, password); + Credential.Save(keyHost, userName, password); + + return Task.CompletedTask; + } + + /// <inheritdoc/> + public Task Delete(HostAddress hostAddress) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + + var key = GetKey(hostAddress.CredentialCacheKeyHost); + var keyGit = GetKeyGit(hostAddress.CredentialCacheKeyHost); + var keyHost = GetKeyHost(hostAddress.CredentialCacheKeyHost); + + Credential.Delete(key); + Credential.Delete(keyGit); + Credential.Delete(keyHost); + + return Task.CompletedTask; + } + + static string GetKey(string key) + { + key = FormatKey(key); + if (key.StartsWith("git:", StringComparison.Ordinal)) + key = key.Substring("git:".Length); + if (!key.EndsWith("/", StringComparison.Ordinal)) + key += '/'; + return "GitHub for Visual Studio - " + key; + } + + static string GetKeyGit(string key) + { + key = FormatKey(key); + // it appears this is how MS expects the host key + if (!key.StartsWith("git:", StringComparison.Ordinal)) + key = "git:" + key; + if (key.EndsWith("/", StringComparison.Ordinal)) + key = key.Substring(0, key.Length - 1); + return key; + } + + static string GetKeyHost(string key) + { + key = FormatKey(key); + if (key.StartsWith("git:", StringComparison.Ordinal)) + key = key.Substring("git:".Length); + if (!key.EndsWith("/", StringComparison.Ordinal)) + key += '/'; + return key; + } + + static string FormatKey(string key) + { + if (key.StartsWith("login:", StringComparison.Ordinal)) + key = key.Substring("login:".Length); + return key; + } + } +} diff --git a/src/GitHub.Api/packages.config b/src/GitHub.Api/packages.config new file mode 100644 index 0000000000..c6373f1d86 --- /dev/null +++ b/src/GitHub.Api/packages.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> + <package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs index efb954e35c..f9fe4055ee 100644 --- a/src/GitHub.App/Api/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -7,42 +7,38 @@ using System.Reactive.Linq; using System.Security.Cryptography; using System.Text; +using GitHub.Extensions; +using GitHub.Logging; using GitHub.Primitives; -using NLog; -using NullGuard; using Octokit; using Octokit.Reactive; -using ReactiveUI; +using Serilog; namespace GitHub.Api { public partial class ApiClient : IApiClient { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - + const string ScopesHeader = "X-OAuth-Scopes"; const string ProductName = Info.ApplicationInfo.ApplicationDescription; + static readonly ILogger log = LogManager.ForContext<ApiClient>(); readonly IObservableGitHubClient gitHubClient; - // There are two sets of authorization scopes, old and new: - // The old scopes must be used by older versions of Enterprise that don't support the new scopes: - readonly string[] oldAuthorizationScopes = { "user", "repo" }; - // These new scopes include write:public_key, which allows us to add public SSH keys to an account: - readonly string[] newAuthorizationScopes = { "user", "repo", "write:public_key" }; - readonly static Lazy<string> lazyNote = new Lazy<string>(() => ProductName + " on " + GetMachineNameSafe()); - readonly static Lazy<string> lazyFingerprint = new Lazy<string>(GetFingerprint); string ClientId { get; set; } string ClientSecret { get; set; } public ApiClient(HostAddress hostAddress, IObservableGitHubClient gitHubClient) { - Configure(); + ClientId = ApiClientConfiguration.ClientId; + ClientSecret = ApiClientConfiguration.ClientSecret; HostAddress = hostAddress; this.gitHubClient = gitHubClient; } partial void Configure(); + public IGitHubClient GitHubClient => new GitHubClient(gitHubClient.Connection); + public IObservable<Repository> CreateRepository(NewRepository repository, string login, bool isUser) { Guard.ArgumentNotEmptyString(login, nameof(login)); @@ -52,50 +48,116 @@ public IObservable<Repository> CreateRepository(NewRepository repository, string return (isUser ? client.Create(repository) : client.Create(login, repository)); } - public IObservable<User> GetUser() + public IObservable<Repository> ForkRepository(string owner, string name, NewRepositoryFork repository) { - return gitHubClient.User.Current(); + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + Guard.ArgumentNotNull(repository, nameof(repository)); + + var client = gitHubClient.Repository.Forks; + + return client.Create(owner, name, repository); } - public IObservable<ApplicationAuthorization> GetOrCreateApplicationAuthenticationCode( - Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHander, - string authenticationCode = null, - bool useOldScopes = false, - bool useFingerPrint = true) + public IObservable<PullRequestReview> PostPullRequestReview( + string owner, + string name, + int number, + string commitId, + string body, + PullRequestReviewEvent e) { - var newAuthorization = new NewAuthorization + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + var review = new PullRequestReviewCreate { - Scopes = useOldScopes - ? oldAuthorizationScopes - : newAuthorizationScopes, - Note = lazyNote.Value, - Fingerprint = useFingerPrint ? lazyFingerprint.Value : null + Body = body, + CommitId = commitId, + Event = e, }; - Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> dispatchedHandler = - ex => Observable.Start(() => twoFactorChallengeHander(ex), RxApp.MainThreadScheduler).Merge(); + return gitHubClient.PullRequest.Review.Create(owner, name, number, review); + } + + public IObservable<PullRequestReviewComment> CreatePullRequestReviewComment( + string owner, + string name, + int number, + string body, + string commitId, + string path, + int position) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + Guard.ArgumentNotEmptyString(body, nameof(body)); + Guard.ArgumentNotEmptyString(commitId, nameof(commitId)); + Guard.ArgumentNotEmptyString(path, nameof(path)); + + var comment = new PullRequestReviewCommentCreate(body, commitId, path, position); + return gitHubClient.PullRequest.ReviewComment.Create(owner, name, number, comment); + } + + public IObservable<PullRequestReviewComment> CreatePullRequestReviewComment( + string owner, + string name, + int number, + string body, + int inReplyTo) + { + var comment = new PullRequestReviewCommentReplyCreate(body, inReplyTo); + return gitHubClient.PullRequest.ReviewComment.CreateReply(owner, name, number, comment); + } + + public IObservable<PullRequestReviewComment> EditPullRequestReviewComment( + string owner, + string name, + int number, + string body) + { + var pullRequestReviewCommentEdit = new PullRequestReviewCommentEdit(body); + return gitHubClient.PullRequest.ReviewComment.Edit(owner, name, number, pullRequestReviewCommentEdit); + } + + public IObservable<Unit> DeletePullRequestReviewComment( + string owner, + string name, + int number) + { + return gitHubClient.PullRequest.ReviewComment.Delete(owner, name, number); + } + + public IObservable<Gist> CreateGist(NewGist newGist) + { + return gitHubClient.Gist.Create(newGist); + } - var authorizationsClient = gitHubClient.Authorization; + public IObservable<Repository> GetForks(string owner, string name) + { + return gitHubClient.Repository.Forks.GetAll(owner, name); + } - return string.IsNullOrEmpty(authenticationCode) - ? authorizationsClient.CreateAndDeleteExistingApplicationAuthorization( - ClientId, - ClientSecret, - newAuthorization, - dispatchedHandler, - true) - : authorizationsClient.CreateAndDeleteExistingApplicationAuthorization( - ClientId, - ClientSecret, - newAuthorization, - dispatchedHandler, - authenticationCode, - true); + public IObservable<User> GetUser() + { + return gitHubClient.User.Current(); + } + + public IObservable<User> GetUser(string login) + { + return gitHubClient.User.Get(login); } public IObservable<Organization> GetOrganizations() { - return gitHubClient.Organization.GetAllForCurrent(); + // Organization.GetAllForCurrent doesn't return all of the information we need (we + // need information about the plan the organization is on in order to enable/disable + // the "Private Repository" checkbox in the "Create Repository" dialog). To get this + // we have to do an Organization.Get on each repository received. + return gitHubClient.Organization + .GetAllForCurrent() + .Select(x => gitHubClient.Organization.Get(x.Login)) + .Merge(); } public IObservable<Repository> GetUserRepositories(RepositoryType repositoryType) @@ -121,28 +183,10 @@ public IObservable<LicenseMetadata> GetLicenses() public HostAddress HostAddress { get; } - static string GetSha256Hash(string input) - { - try - { - using (var sha256 = SHA256.Create()) - { - var bytes = Encoding.UTF8.GetBytes(input); - var hash = sha256.ComputeHash(bytes); - - return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); - } - } - catch (Exception e) - { - log.Error("IMPOSSIBLE! Generating Sha256 hash caused an exception.", e); - return null; - } - } - static string GetFingerprint() { - return GetSha256Hash(ProductName + ":" + GetMachineIdentifier()); + var fingerprint = ProductName + ":" + GetMachineIdentifier(); + return fingerprint.GetSha256Hash(); } static string GetMachineNameSafe() @@ -153,14 +197,14 @@ static string GetMachineNameSafe() } catch (Exception e) { - log.Info("Failed to retrieve host name using `DNS.GetHostName`.", e); + log.Warning(e, "Failed to retrieve host name using `DNS.GetHostName`"); try { return Environment.MachineName; } catch (Exception ex) { - log.Info("Failed to retrieve host name using `Environment.MachineName`.", ex); + log.Warning(ex, "Failed to retrieve host name using `Environment.MachineName`"); return "(unknown)"; } } @@ -181,29 +225,109 @@ static string GetMachineIdentifier() } catch (Exception e) { - log.Info("Could not retrieve MAC address. Fallback to using machine name.", e); + log.Warning(e, "Could not retrieve MAC address. Fallback to using machine name"); return GetMachineNameSafe(); } } public IObservable<Repository> GetRepositoriesForOrganization(string organization) { + Guard.ArgumentNotEmptyString(organization, nameof(organization)); + return gitHubClient.Repository.GetAllForOrg(organization); } - public IObservable<Unit> DeleteApplicationAuthorization(int id, [AllowNull]string twoFactorAuthorizationCode) + public IObservable<Unit> DeleteApplicationAuthorization(int id, string twoFactorAuthorizationCode) { + Guard.ArgumentNotEmptyString(twoFactorAuthorizationCode, nameof(twoFactorAuthorizationCode)); + return gitHubClient.Authorization.Delete(id, twoFactorAuthorizationCode); } + public IObservable<IssueComment> GetIssueComments(string owner, string name, int number) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + return gitHubClient.Issue.Comment.GetAllForIssue(owner, name, number); + } + + public IObservable<PullRequest> GetPullRequest(string owner, string name, int number) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + return gitHubClient.PullRequest.Get(owner, name, number); + } + + public IObservable<PullRequestFile> GetPullRequestFiles(string owner, string name, int number) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + return gitHubClient.PullRequest.Files(owner, name, number); + } + + public IObservable<PullRequestReviewComment> GetPullRequestReviewComments(string owner, string name, int number) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + return gitHubClient.PullRequest.ReviewComment.GetAll(owner, name, number); + } + public IObservable<PullRequest> GetPullRequestsForRepository(string owner, string name) { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + return gitHubClient.PullRequest.GetAllForRepository(owner, name, - new PullRequestRequest { - State = ItemState.All, + new PullRequestRequest + { + State = ItemStateFilter.All, SortProperty = PullRequestSort.Updated, SortDirection = SortDirection.Descending }); } + + public IObservable<PullRequest> CreatePullRequest(NewPullRequest pullRequest, string owner, string repo) + { + Guard.ArgumentNotNull(pullRequest, nameof(pullRequest)); + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(repo, nameof(repo)); + + return gitHubClient.PullRequest.Create(owner, repo, pullRequest); + } + + public IObservable<Repository> GetRepositories() + { + return gitHubClient.Repository.GetAllForCurrent(); + } + + public IObservable<Branch> GetBranches(string owner, string repo) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(repo, nameof(repo)); + + return gitHubClient.Repository.Branch.GetAll(owner, repo); + } + + public IObservable<Repository> GetRepository(string owner, string repo) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(repo, nameof(repo)); + + return gitHubClient.Repository.Get(owner, repo); + } + + public IObservable<RepositoryContent> GetFileContents(string owner, string name, string reference, string path) + { + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + Guard.ArgumentNotEmptyString(reference, nameof(reference)); + Guard.ArgumentNotEmptyString(path, nameof(path)); + + return gitHubClient.Repository.Content.GetAllContentsByRef(owner, name, reference, path); + } } } diff --git a/src/GitHub.App/Api/ApiClientConfiguration.cs b/src/GitHub.App/Api/ApiClientConfiguration.cs deleted file mode 100644 index 16f7eb2e97..0000000000 --- a/src/GitHub.App/Api/ApiClientConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace GitHub.Api -{ - public partial class ApiClient : IApiClient - { - const string clientId = "YOUR CLIENT ID HERE"; - const string clientSecret = "YOUR CLIENT SECRET HERE"; - - partial void Configure() - { - ClientId = clientId; - ClientSecret = clientSecret; - } - } -} diff --git a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs index 952a6235c3..74214a1741 100644 --- a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs +++ b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs @@ -4,37 +4,54 @@ using GitHub.ViewModels; using Octokit; using ReactiveUI; -using NullGuard; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Helpers; +using GitHub.Extensions; +using GitHub.ViewModels.Dialog; namespace GitHub.Authentication { [Export(typeof(ITwoFactorChallengeHandler))] + [Export(typeof(IDelegatingTwoFactorChallengeHandler))] [PartCreationPolicy(CreationPolicy.Shared)] - public class TwoFactorChallengeHandler : ReactiveObject, ITwoFactorChallengeHandler + public class TwoFactorChallengeHandler : ReactiveObject, IDelegatingTwoFactorChallengeHandler { - ITwoFactorDialogViewModel twoFactorDialog; - [AllowNull] + ILogin2FaViewModel twoFactorDialog; public IViewModel CurrentViewModel { - [return:AllowNull] get { return twoFactorDialog; } - private set { this.RaiseAndSetIfChanged(ref twoFactorDialog, (ITwoFactorDialogViewModel)value); } + private set { this.RaiseAndSetIfChanged(ref twoFactorDialog, (ILogin2FaViewModel)value); } } - public void SetViewModel([AllowNull]IViewModel vm) + public void SetViewModel(IViewModel vm) { CurrentViewModel = vm; } - public IObservable<TwoFactorChallengeResult> HandleTwoFactorException(TwoFactorAuthorizationException exception) + public async Task<TwoFactorChallengeResult> HandleTwoFactorException(TwoFactorAuthorizationException exception) { + Guard.ArgumentNotNull(exception, nameof(exception)); + + await ThreadingHelper.SwitchToMainThreadAsync(); + var userError = new TwoFactorRequiredUserError(exception); - return twoFactorDialog.Show(userError) - .ObserveOn(RxApp.MainThreadScheduler) - .SelectMany(x => - x == RecoveryOptionResult.RetryOperation - ? Observable.Return(userError.ChallengeResult) - : Observable.Throw<TwoFactorChallengeResult>(exception)); + var result = await twoFactorDialog.Show(userError); + + if (result != null) + { + return result; + } + else + { + throw exception; + } + } + + public async Task ChallengeFailed(Exception exception) + { + await ThreadingHelper.SwitchToMainThreadAsync(); + twoFactorDialog.Cancel(); } } } \ No newline at end of file diff --git a/src/GitHub.App/Authentication/TwoFactorRequiredUserError.cs b/src/GitHub.App/Authentication/TwoFactorRequiredUserError.cs index 1695673eb9..726d074600 100644 --- a/src/GitHub.App/Authentication/TwoFactorRequiredUserError.cs +++ b/src/GitHub.App/Authentication/TwoFactorRequiredUserError.cs @@ -1,5 +1,5 @@ using System; -using NullGuard; +using GitHub.Extensions; using Octokit; using ReactiveUI; @@ -8,9 +8,18 @@ namespace GitHub.Authentication public class TwoFactorRequiredUserError : UserError { public TwoFactorRequiredUserError(TwoFactorAuthorizationException exception) + : this(exception, exception.TwoFactorType) + { + } + + public TwoFactorRequiredUserError( + TwoFactorAuthorizationException exception, + TwoFactorType twoFactorType) : base(exception.Message, innerException: exception) { - TwoFactorType = exception.TwoFactorType; + Guard.ArgumentNotNull(exception, nameof(exception)); + + TwoFactorType = twoFactorType; RetryFailed = exception is TwoFactorChallengeFailedException; } @@ -18,14 +27,6 @@ public TwoFactorRequiredUserError(TwoFactorAuthorizationException exception) public TwoFactorType TwoFactorType { get; private set; } - [AllowNull] - public TwoFactorChallengeResult ChallengeResult - { - [return: AllowNull] - get; - set; - } - public IObservable<RecoveryOptionResult> Throw() { return Throw(this); diff --git a/src/GitHub.App/Caches/CacheIndex.cs b/src/GitHub.App/Caches/CacheIndex.cs index a9016470e1..d3ad77de7f 100644 --- a/src/GitHub.App/Caches/CacheIndex.cs +++ b/src/GitHub.App/Caches/CacheIndex.cs @@ -4,12 +4,18 @@ using System.Linq; using System.Reactive.Linq; using Akavache; -using NullGuard; +using GitHub.Extensions; namespace GitHub.Caches { public class CacheIndex { + public const string PRPrefix = "index:pr"; + public const string RepoPrefix = "index:repos"; + public const string GitIgnoresPrefix = "index:ignores"; + public const string LicensesPrefix = "index:licenses"; + public const string FileContentsPrefix = "index:filecontents"; + public static CacheIndex Create(string key) { return new CacheIndex { IndexKey = key }; @@ -21,21 +27,42 @@ public CacheIndex() OldKeys = new List<string>(); } + public CacheIndex Add(string indexKey, CacheItem item) + { + Guard.ArgumentNotEmptyString(indexKey, nameof(indexKey)); + Guard.ArgumentNotNull(item, nameof(item)); + + var k = string.Format(CultureInfo.InvariantCulture, "{0}|{1}", IndexKey, item.Key); + if (!Keys.Contains(k)) + Keys.Add(k); + UpdatedAt = DateTimeOffset.UtcNow; + return this; + } + public IObservable<CacheIndex> AddAndSave(IBlobCache cache, string indexKey, CacheItem item, DateTimeOffset? absoluteExpiration = null) { + Guard.ArgumentNotNull(cache, nameof(cache)); + Guard.ArgumentNotEmptyString(indexKey, nameof(indexKey)); + Guard.ArgumentNotNull(item, nameof(item)); + var k = string.Format(CultureInfo.InvariantCulture, "{0}|{1}", IndexKey, item.Key); if (!Keys.Contains(k)) Keys.Add(k); UpdatedAt = DateTimeOffset.UtcNow; return cache.InsertObject(IndexKey, this, absoluteExpiration) - .Select(x => this); + .Select(x => this); } public static IObservable<CacheIndex> AddAndSaveToIndex(IBlobCache cache, string indexKey, CacheItem item, DateTimeOffset? absoluteExpiration = null) { + Guard.ArgumentNotNull(cache, nameof(cache)); + Guard.ArgumentNotEmptyString(indexKey, nameof(indexKey)); + Guard.ArgumentNotNull(item, nameof(item)); + return cache.GetOrCreateObject(indexKey, () => Create(indexKey)) + .Select(x => x.IndexKey == null ? Create(indexKey) : x) .Do(index => { var k = string.Format(CultureInfo.InvariantCulture, "{0}|{1}", index.IndexKey, item.Key); @@ -47,19 +74,24 @@ public static IObservable<CacheIndex> AddAndSaveToIndex(IBlobCache cache, string .Select(x => index)); } - public IObservable<CacheIndex> Clear(IBlobCache cache, string indexKey, DateTimeOffset? absoluteExpiration = null) + public CacheIndex Clear() { OldKeys = Keys.ToList(); Keys.Clear(); UpdatedAt = DateTimeOffset.UtcNow; - return cache - .InvalidateObject<CacheIndex>(indexKey) - .SelectMany(_ => cache.InsertObject(indexKey, this, absoluteExpiration)) - .Select(_ => this); + return this; + } + + public IObservable<CacheIndex> Save(IBlobCache cache, + DateTimeOffset? absoluteExpiration = null) + { + Guard.ArgumentNotNull(cache, nameof(cache)); + + return cache.InsertObject(IndexKey, this, absoluteExpiration) + .Select(x => this); } - [AllowNull] - public string IndexKey {[return: AllowNull] get; set; } + public string IndexKey { get; set; } public List<string> Keys { get; set; } public DateTimeOffset UpdatedAt { get; set; } public List<string> OldKeys { get; set; } diff --git a/src/GitHub.App/Caches/CacheItem.cs b/src/GitHub.App/Caches/CacheItem.cs index fa18dafcb5..33e92d1b18 100644 --- a/src/GitHub.App/Caches/CacheItem.cs +++ b/src/GitHub.App/Caches/CacheItem.cs @@ -2,19 +2,21 @@ using System.Globalization; using System.Reactive.Linq; using Akavache; -using NullGuard; +using GitHub.Extensions; namespace GitHub.Caches { public class CacheItem { - [AllowNull] - public string Key {[return: AllowNull] get; set; } + public string Key { get; set; } public DateTimeOffset Timestamp { get; set; } public IObservable<T> Save<T>(IBlobCache cache, string key, DateTimeOffset? absoluteExpiration = null) where T : CacheItem { + Guard.ArgumentNotNull(cache, nameof(cache)); + Guard.ArgumentNotEmptyString(key, nameof(key)); + var k = string.Format(CultureInfo.InvariantCulture, "{0}|{1}", key, Key); return cache .InvalidateObject<T>(k) diff --git a/src/GitHub.App/Caches/CredentialCache.cs b/src/GitHub.App/Caches/CredentialCache.cs deleted file mode 100644 index 9d51d4fab1..0000000000 --- a/src/GitHub.App/Caches/CredentialCache.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Text; -using Akavache; -using GitHub.Helpers; -using GitHub.Authentication.CredentialManagement; -using System.Security; - -namespace GitHub.Caches -{ - public class CredentialCache : ISecureBlobCache, IObjectBlobCache - { - public IScheduler Scheduler { get; protected set; } - - readonly AsyncSubject<Unit> shutdown = new AsyncSubject<Unit>(); - public IObservable<Unit> Shutdown => shutdown; - - public IObservable<Unit> Flush() - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<Unit>("CredentialCache"); - return Observable.Return(Unit.Default); - } - - public IObservable<byte[]> Get(string key) - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<byte[]>("CredentialCache"); - - var keyHost = GetKeyHost(key); - using (var credential = new Credential()) - { - credential.Target = keyHost; - if (credential.Load()) - return Observable.Return(Encoding.Unicode.GetBytes(credential.Password)); - } - return ExceptionHelper.ObservableThrowKeyNotFoundException<byte[]>(key); - } - - public IObservable<IEnumerable<string>> GetAllKeys() - { - throw new NotImplementedException(); - } - - public IObservable<DateTimeOffset?> GetCreatedAt(string key) - { - throw new NotImplementedException(); - } - - public IObservable<Unit> Insert(string key, byte[] data, DateTimeOffset? absoluteExpiration = default(DateTimeOffset?)) - { - return ExceptionHelper.ObservableThrowInvalidOperationException<Unit>(key); - } - - public IObservable<Unit> Invalidate(string key) - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<Unit>("CredentialCache"); - - var keyGit = GetKeyGit(key); - if (!DeleteKey(keyGit)) - return ExceptionHelper.ObservableThrowKeyNotFoundException<Unit>(keyGit); - - var keyHost = GetKeyHost(key); - if (!DeleteKey(keyHost)) - return ExceptionHelper.ObservableThrowKeyNotFoundException<Unit>(keyHost); - - return Observable.Return(Unit.Default); - } - - public IObservable<Unit> InvalidateAll() - { - throw new NotImplementedException(); - } - - public IObservable<Unit> Vacuum() - { - throw new NotImplementedException(); - } - - public IObservable<Unit> InsertObject<T>(string key, T value, DateTimeOffset? absoluteExpiration = default(DateTimeOffset?)) - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<Unit>("CredentialCache"); - - if (value is Tuple<string, string>) - return InsertTuple(key, value as Tuple<string, string>); - if (value is Tuple<string, SecureString>) - return InsertTuple(key, value as Tuple<string, SecureString>); - - return ExceptionHelper.ObservableThrowInvalidOperationException<Unit>(key); - } - - static IObservable<Unit> InsertTuple(string key, Tuple<string, string> values) - { - var keyGit = GetKeyGit(key); - if (!SaveKey(keyGit, values.Item1, values.Item2)) - return ExceptionHelper.ObservableThrowInvalidOperationException<Unit>(keyGit); - - var keyHost = GetKeyHost(key); - if (!SaveKey(keyHost, values.Item1, values.Item2)) - return ExceptionHelper.ObservableThrowInvalidOperationException<Unit>(keyGit); - - return Observable.Return(Unit.Default); - } - - static IObservable<Unit> InsertTuple(string key, Tuple<string, SecureString> values) - { - var keyGit = GetKeyGit(key); - if (!SaveKey(keyGit, values.Item1, values.Item2)) - return ExceptionHelper.ObservableThrowInvalidOperationException<Unit>(keyGit); - - var keyHost = GetKeyHost(key); - if (!SaveKey(keyHost, values.Item1, values.Item2)) - return ExceptionHelper.ObservableThrowInvalidOperationException<Unit>(keyGit); - - return Observable.Return(Unit.Default); - } - - public IObservable<T> GetObject<T>(string key) - { - if (typeof(T) == typeof(Tuple<string, string>)) - return (IObservable<T>) GetTuple(key); - if (typeof(T) == typeof(Tuple<string, SecureString>)) - return (IObservable<T>)GetSecureTuple(key); - return ExceptionHelper.ObservableThrowInvalidOperationException<T>(key); - } - - IObservable<Tuple<string, string>> GetTuple(string key) - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<Tuple<string, string>>("CredentialCache"); - - var keyHost = GetKeyHost(key); - var ret = GetKey(keyHost); - return ret != null - ? Observable.Return(ret) - : ExceptionHelper.ObservableThrowKeyNotFoundException<Tuple<string, string>>(keyHost); - } - - IObservable<Tuple<string, SecureString>> GetSecureTuple(string key) - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<Tuple<string, SecureString>>("CredentialCache"); - - var keyHost = GetKeyHost(key); - var ret = GetSecureKey(keyHost); - return ret != null - ? Observable.Return(ret) - : ExceptionHelper.ObservableThrowKeyNotFoundException<Tuple<string, SecureString>>(keyHost); - } - - public IObservable<IEnumerable<T>> GetAllObjects<T>() - { - throw new NotImplementedException(); - } - - public IObservable<DateTimeOffset?> GetObjectCreatedAt<T>(string key) - { - throw new NotImplementedException(); - } - - public IObservable<Unit> InvalidateObject<T>(string key) - { - if (disposed) return ExceptionHelper.ObservableThrowObjectDisposedException<Unit>("CredentialCache"); - - var keyGit = GetKeyGit(key); - if (!DeleteKey(keyGit)) - return ExceptionHelper.ObservableThrowKeyNotFoundException<Unit>(keyGit); - - var keyHost = GetKeyHost(key); - if (!DeleteKey(keyHost)) - return ExceptionHelper.ObservableThrowKeyNotFoundException<Unit>(key); - - return Observable.Return(Unit.Default); - } - - public IObservable<Unit> InvalidateAllObjects<T>() - { - throw new NotImplementedException(); - } - - static string FormatKey(string key) - { - if (key.StartsWith("login:", StringComparison.Ordinal)) - key = key.Substring("login:".Length); - return key; - } - - static string GetKeyGit(string key) - { - key = FormatKey(key); - // it appears this is how MS expects the host key - if (!key.StartsWith("git:", StringComparison.Ordinal)) - key = "git:" + key; - if (key.EndsWith("/", StringComparison.Ordinal)) - key = key.Substring(0, key.Length - 1); - return key; - } - - static string GetKeyHost(string key) - { - key = FormatKey(key); - if (key.StartsWith("git:", StringComparison.Ordinal)) - key = key.Substring("git:".Length); - if (!key.EndsWith("/", StringComparison.Ordinal)) - key += '/'; - return key; - } - - static bool DeleteKey(string key) - { - using (var credential = new Credential()) - { - credential.Target = key; - return credential.Load() && credential.Delete(); - } - } - - static bool SaveKey(string key, string user, string pwd) - { - using (var credential = new Credential(user, pwd, key)) - { - return credential.Save(); - } - } - - static bool SaveKey(string key, string user, SecureString pwd) - { - using (var credential = new Credential(user, pwd, key)) - { - return credential.Save(); - } - } - - static Tuple<string, string> GetKey(string key) - { - using (var credential = new Credential()) - { - credential.Target = key; - return credential.Load() - ? new Tuple<string, string>(credential.Username, credential.Password) - : null; - } - } - - static Tuple<string, SecureString> GetSecureKey(string key) - { - using (var credential = new Credential()) - { - credential.Target = key; - return credential.Load() - ? new Tuple<string, SecureString>(credential.Username, credential.SecurePassword) - : null; - } - } - - bool disposed; - void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - Scheduler = null; - shutdown.OnNext(Unit.Default); - shutdown.OnCompleted(); - disposed = true; - } - } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/GitHub.App/Caches/ImageCache.cs b/src/GitHub.App/Caches/ImageCache.cs index 079ce0bc80..175911018a 100644 --- a/src/GitHub.App/Caches/ImageCache.cs +++ b/src/GitHub.App/Caches/ImageCache.cs @@ -11,8 +11,10 @@ using GitHub.Extensions; using GitHub.Extensions.Reactive; using GitHub.Factories; +using GitHub.Logging; using GitHub.Services; using Rothko; +using Serilog; namespace GitHub.Caches { @@ -20,7 +22,7 @@ namespace GitHub.Caches [PartCreationPolicy(CreationPolicy.Shared)] public sealed class ImageCache : IImageCache { - static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); + static readonly ILogger log = LogManager.ForContext<ImageCache>(); public const string ImageCacheFileName = "images.cache.db"; readonly IObservable<IBlobCache> cacheFactory; @@ -159,7 +161,7 @@ static IObservable<Unit> VacuumIfNecessary(IBlobCache blobCache, bool force = fa .SelectMany(Observable.Defer(() => blobCache.Insert(key, new byte[] { 1 }))) .Catch<Unit, Exception>(ex => { - log.Error("Could not vacuum image cache", ex); + log.Error(ex, "Could not vacuum image cache"); return Observable.Return(Unit.Default); }) .AsCompletion(); @@ -184,7 +186,7 @@ IObservable<BitmapSource> LoadImage(byte[] x, Uri url) static IObservable<BitmapImage> LoadImage(Stream sourceStream, float? desiredWidth = null, float? desiredHeight = null) { - Debug.Assert(sourceStream != null, "Cannot load image from a null sourceStream"); + Guard.ArgumentNotNull(sourceStream, nameof(sourceStream)); return Observable.Defer(() => { @@ -213,33 +215,8 @@ static string GetCacheKey(Uri url) return url.ToString(); } - bool disposed; - void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - try - { - cacheFactory.Select(x => - { - x.Dispose(); - return x.Shutdown; - }).Wait(); - } - catch (Exception e) - { - log.Warn("Exception occured while disposing ImageCache", e); - } - disposed = true; - } - } - public void Dispose() - { - Dispose(true); - } + {} class UriComparer : IEqualityComparer<Uri> { diff --git a/src/GitHub.App/Caches/LoginCache.cs b/src/GitHub.App/Caches/LoginCache.cs deleted file mode 100644 index e748d86428..0000000000 --- a/src/GitHub.App/Caches/LoginCache.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Globalization; -using System.Reactive; -using System.Reactive.Linq; -using Akavache; -using GitHub.Primitives; -using NLog; - -namespace GitHub.Caches -{ - [Export(typeof(ILoginCache))] - [PartCreationPolicy(CreationPolicy.Shared)] - public sealed class LoginCache : ILoginCache - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - readonly ISharedCache cache; - - static readonly LoginInfo empty = new LoginInfo("", ""); - - [ImportingConstructor] - public LoginCache(ISharedCache cache) - { - this.cache = cache; - } - - public static LoginInfo EmptyLoginInfo - { - get { return empty; } - } - - public IObservable<LoginInfo> GetLoginAsync(HostAddress hostAddress) - { - return cache.Secure.GetLoginAsync(hostAddress.CredentialCacheKeyHost) - .Catch<LoginInfo, Exception>(e => Observable.Return(empty)); - } - - public IObservable<Unit> SaveLogin(string user, string password, HostAddress hostAddress) - { - Guard.ArgumentNotEmptyString(user, nameof(user)); - Guard.ArgumentNotEmptyString(password, nameof(password)); - - return cache.Secure.SaveLogin(user, password, hostAddress.CredentialCacheKeyHost); - } - - public IObservable<Unit> EraseLogin(HostAddress hostAddress) - { - log.Info(CultureInfo.CurrentCulture, "Erasing the git credential cache for host '{0}'", - hostAddress.CredentialCacheKeyHost); - return cache.Secure.EraseLogin(hostAddress.CredentialCacheKeyHost); - } - - public IObservable<Unit> Flush() - { - log.Info("Flushing the login cache"); - return cache.Secure.Flush(); - } - - bool disposed; - void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - try - { - cache.Dispose(); - } - catch (Exception e) - { - log.Warn("Exception occurred while disposing shared cache", e); - } - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - } - } -} diff --git a/src/GitHub.App/Caches/SharedCache.cs b/src/GitHub.App/Caches/SharedCache.cs index 51c2fe0a97..fb1e420a0c 100644 --- a/src/GitHub.App/Caches/SharedCache.cs +++ b/src/GitHub.App/Caches/SharedCache.cs @@ -4,7 +4,8 @@ using System.Reactive; using System.Reactive.Linq; using Akavache; -using NLog; +using GitHub.Logging; +using Serilog; namespace GitHub.Caches { @@ -16,7 +17,7 @@ namespace GitHub.Caches public class SharedCache : ISharedCache { const string enterpriseHostApiBaseUriCacheKey = "enterprise-host-api-base-uri"; - static readonly Logger log = LogManager.GetCurrentClassLogger(); + static readonly ILogger log = LogManager.ForContext<SharedCache>(); static SharedCache() { @@ -26,37 +27,22 @@ static SharedCache() } catch (Exception e) { - log.Error("Error while running the static inializer for SharedCache", e); + log.Error(e, "Error while running the static inializer for SharedCache"); } } - public SharedCache() : this(null, null, null) + public SharedCache() : this(null, null) { } - protected SharedCache(IBlobCache userAccountCache, IBlobCache localMachineCache, ISecureBlobCache secureCache) + protected SharedCache(IBlobCache userAccountCache, IBlobCache localMachineCache) { - if (secureCache == null) - { - try - { - BlobCache.Secure = new CredentialCache(); - secureCache = BlobCache.Secure; - } - catch (Exception e) - { - log.Error("Failed to set up secure cache.", e); - secureCache = new InMemoryBlobCache(); - } - } UserAccount = userAccountCache ?? GetBlobCacheWithFallback(() => BlobCache.UserAccount, "UserAccount"); LocalMachine = localMachineCache ?? GetBlobCacheWithFallback(() => BlobCache.LocalMachine, "LocalMachine"); - Secure = secureCache; } public IBlobCache UserAccount { get; private set; } public IBlobCache LocalMachine { get; private set; } - public ISecureBlobCache Secure { get; private set; } public IObservable<Uri> GetEnterpriseHostUri() { @@ -84,8 +70,6 @@ protected virtual void Dispose(bool disposing) UserAccount.Shutdown.Wait(); LocalMachine.Dispose(); LocalMachine.Shutdown.Wait(); - Secure.Dispose(); - Secure.Shutdown.Wait(); disposed = true; } } @@ -104,7 +88,7 @@ static IBlobCache GetBlobCacheWithFallback(Func<IBlobCache> blobCacheFunc, strin } catch (Exception e) { - log.Error(string.Format(CultureInfo.InvariantCulture, "Failed to set the {0} cache.", cacheName), e); + log.Error(e, "Failed to set the {CacheName} cache", cacheName); return new InMemoryBlobCache(); } } diff --git a/src/GitHub.App/Collections/IVirtualizingListSource.cs b/src/GitHub.App/Collections/IVirtualizingListSource.cs new file mode 100644 index 0000000000..f8480b1e28 --- /dev/null +++ b/src/GitHub.App/Collections/IVirtualizingListSource.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace GitHub.Collections +{ + /// <summary> + /// A loader for a virtualizing list. + /// </summary> + /// <typeparam name="T">The item type.</typeparam> + /// <remarks> + /// This interface is used by the <see cref="VirtualizingList{T}"/> class to load pages of data. + /// </remarks> + public interface IVirtualizingListSource<T> : IDisposable, INotifyPropertyChanged + { + /// <summary> + /// Gets a value that indicates where loading is in progress. + /// </summary> + bool IsLoading { get; } + + /// <summary> + /// Gets the page size of the list source. + /// </summary> + int PageSize { get; } + + /// <summary> + /// Gets the total number of items in the list. + /// </summary> + /// <returns>A task returning the count.</returns> + Task<int> GetCount(); + + /// <summary> + /// Gets the numbered page of items. + /// </summary> + /// <param name="pageNumber">The page number.</param> + /// <returns>A task returning the page contents.</returns> + Task<IReadOnlyList<T>> GetPage(int pageNumber); + } +} \ No newline at end of file diff --git a/src/GitHub.App/Collections/SequentialListSource.cs b/src/GitHub.App/Collections/SequentialListSource.cs new file mode 100644 index 0000000000..97baec70b1 --- /dev/null +++ b/src/GitHub.App/Collections/SequentialListSource.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using GitHub.Logging; +using GitHub.Models; +using ReactiveUI; +using Serilog; + +namespace GitHub.Collections +{ + /// <summary> + /// An <see cref="IVirtualizingListSource{T}"/> that loads GraphQL pages sequentially, and + /// transforms items into a view model after reading. + /// </summary> + /// <typeparam name="TModel">The type of the model read from the remote data source.</typeparam> + /// <typeparam name="TViewModel">The type of the transformed view model.</typeparam> + /// <remarks> + /// GraphQL can only read pages of data sequentally, so in order to read item 450 (assuming a + /// page size of 100), the list source must read pages 1, 2, 3 and 4 in that order. Classes + /// deriving from this class only need to implement <see cref="LoadPage(string)"/> to load a + /// single page and this class will handle the rest. + /// + /// In addition, items will usually need to be transformed into a view model after reading. The + /// implementing class overrides <see cref="CreateViewModel(TModel)"/> to carry out that + /// transformation. + /// </remarks> + public abstract class SequentialListSource<TModel, TViewModel> : ReactiveObject, IVirtualizingListSource<TViewModel> + { + static readonly ILogger log = LogManager.ForContext<SequentialListSource<TModel, TViewModel>>(); + + readonly Dispatcher dispatcher; + readonly object loadLock = new object(); + Dictionary<int, Page<TModel>> pages = new Dictionary<int, Page<TModel>>(); + Task loading = Task.CompletedTask; + bool disposed; + bool isLoading; + int? count; + int nextPage; + int loadTo; + string after; + + /// <summary> + /// Initializes a new instance of the <see cref="SequentialListSource{TModel, TViewModel}"/> class. + /// </summary> + public SequentialListSource() + { + dispatcher = Application.Current?.Dispatcher; + } + + /// <inheritdoc/> + public bool IsLoading + { + get { return isLoading; } + private set { this.RaiseAndSetIfChanged(ref isLoading, value); } + } + + /// <inheritdoc/> + public virtual int PageSize => 100; + + event EventHandler PageLoaded; + + public void Dispose() => disposed = true; + + /// <inheritdoc/> + public async Task<int> GetCount() + { + dispatcher?.VerifyAccess(); + + if (!count.HasValue) + { + count = (await EnsureLoaded(0).ConfigureAwait(false)).TotalCount; + } + + return count.Value; + } + + /// <inheritdoc/> + public async Task<IReadOnlyList<TViewModel>> GetPage(int pageNumber) + { + dispatcher?.VerifyAccess(); + + var page = await EnsureLoaded(pageNumber); + + if (page == null) + { + return null; + } + + var result = page.Items + .Select(CreateViewModel) + .ToList(); + pages.Remove(pageNumber); + return result; + } + + /// <summary> + /// When overridden in a derived class, transforms a model into a view model after loading. + /// </summary> + /// <param name="model">The model.</param> + /// <returns>The view model.</returns> + protected abstract TViewModel CreateViewModel(TModel model); + + /// <summary> + /// When overridden in a derived class reads a page of results from GraphQL. + /// </summary> + /// <param name="after">The GraphQL after cursor.</param> + /// <returns>A task which returns the page of results.</returns> + protected abstract Task<Page<TModel>> LoadPage(string after); + + /// <summary> + /// Called when the source begins loading pages. + /// </summary> + protected virtual void OnBeginLoading() + { + IsLoading = true; + } + + /// <summary> + /// Called when the source finishes loading pages. + /// </summary> + protected virtual void OnEndLoading() + { + IsLoading = false; + } + + async Task<Page<TModel>> EnsureLoaded(int pageNumber) + { + if (pageNumber < nextPage) + { + return pages[pageNumber]; + } + + var pageLoaded = WaitPageLoaded(pageNumber); + loadTo = Math.Max(loadTo, pageNumber); + + while (!disposed) + { + lock (loadLock) + { + if (loading.IsCompleted) + { + loading = Load(); + } + } + + var completed = await Task.WhenAny(loading, pageLoaded).ConfigureAwait(false); + + if (completed.IsFaulted) + { + throw completed.Exception; + } + + if (pageLoaded.IsCompleted) + { + // A previous waiting task may have already returned the page. If so, return null. + pages.TryGetValue(pageNumber, out var result); + return result; + } + } + + return null; + } + + Task WaitPageLoaded(int page) + { + var tcs = new TaskCompletionSource<bool>(); + EventHandler handler = null; + handler = (s, e) => + { + if (nextPage > page) + { + tcs.SetResult(true); + PageLoaded -= handler; + } + }; + PageLoaded += handler; + return tcs.Task; + } + + async Task Load() + { + OnBeginLoading(); + + try + { + while (nextPage <= loadTo && !disposed) + { + await LoadNextPage().ConfigureAwait(false); + PageLoaded?.Invoke(this, EventArgs.Empty); + } + } + finally + { + OnEndLoading(); + } + } + + async Task LoadNextPage() + { + log.Debug("Loading page {Number} of {ModelType}", nextPage, typeof(TModel)); + + var page = await LoadPage(after).ConfigureAwait(false); + pages[nextPage++] = page; + after = page.EndCursor; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/Collections/VirtualizingList.cs b/src/GitHub.App/Collections/VirtualizingList.cs new file mode 100644 index 0000000000..b283c3f56f --- /dev/null +++ b/src/GitHub.App/Collections/VirtualizingList.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using GitHub.Logging; +using Serilog; + +namespace GitHub.Collections +{ + /// <summary> + /// A virtualizing list that loads data only when needed. + /// </summary> + /// <typeparam name="T">The list item type.</typeparam> + /// <remarks> + /// This class exposes a read-only list where the data is fetched as needed. When the indexer + /// getter is called, if the requested item is not yet available it calls the associated + /// <see cref="IVirtualizingListSource{T}"/> to load the page of data containing the requested + /// item. While the data is being read, <see cref="Placeholder"/> is returned and when the + /// data is read <see cref="CollectionChanged"/> is raised. + /// + /// Note that this implementation currently represents the minimum required for interaction + /// with WPF and as such many members are not yet implemented. In addition, if filtering is + /// required in the UI then the collection can be wrapped in a + /// <see cref="VirtualizingListCollectionView{T}"/>. + /// </remarks> + public class VirtualizingList<T> : IReadOnlyList<T>, IList, INotifyCollectionChanged, INotifyPropertyChanged + { + static readonly ILogger log = LogManager.ForContext<VirtualizingList<T>>(); + readonly Dictionary<int, IReadOnlyList<T>> pages = new Dictionary<int, IReadOnlyList<T>>(); + readonly IVirtualizingListSource<T> source; + readonly IList<T> emptyPage; + readonly IReadOnlyList<T> placeholderPage; + readonly Dispatcher dispatcher; + int? count; + + /// <summary> + /// Initializes a new instance of the <see cref="VirtualizingList{T}"/> class. + /// </summary> + /// <param name="source">The list source.</param> + /// <param name="placeholder">The placeholder item.</param> + public VirtualizingList( + IVirtualizingListSource<T> source, + T placeholder) + { + this.source = source; + Placeholder = placeholder; + emptyPage = Enumerable.Repeat(default(T), PageSize).ToList(); + placeholderPage = Enumerable.Repeat(placeholder, PageSize).ToList(); + dispatcher = Application.Current?.Dispatcher; + } + + /// <summary> + /// Gets an item by index. + /// </summary> + /// <param name="index">The index of the item.</param> + /// <returns>The item, or <see cref="Placeholder"/> if the item is not yet loaded.</returns> + public T this[int index] + { + get + { + var pageNumber = index / PageSize; + var pageIndex = index % PageSize; + IReadOnlyList<T> page; + + if (pages.TryGetValue(pageNumber, out page)) + { + return page[pageIndex]; + } + else + { + LoadPage(pageNumber); + + if (pages.TryGetValue(pageNumber, out page)) + { + return page[pageIndex]; + } + else + { + return placeholderPage[0]; + } + } + } + + set { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the total count of the collection, including not-yet-loaded items. + /// </summary> + /// <remarks> + /// If the count has not yet been loaded, this will return 0 and then raise a + /// <see cref="PropertyChanged"/> event when the count is loaded. + /// </remarks> + public int Count + { + get + { + if (!count.HasValue) + { + count = 0; + LoadCount(); + } + + return count.Value; + } + } + + /// <summary> + /// Gets the placeholder item that will be displayed while an item is loading. + /// </summary> + public T Placeholder { get; } + + /// <summary> + /// Gets the loaded pages of data. + /// </summary> + public IReadOnlyDictionary<int, IReadOnlyList<T>> Pages => pages; + + /// <summary> + /// Gets the page size of the associated <see cref="IVirtualizingListSource{T}"/>. + /// </summary> + public int PageSize => source.PageSize; + + object IList.this[int index] + { + get { return this[index]; } + set { this[index] = (T)value; } + } + + bool IList.IsReadOnly => true; + bool IList.IsFixedSize => false; + int ICollection.Count => Count; + object ICollection.SyncRoot => null; + bool ICollection.IsSynchronized => false; + + public event NotifyCollectionChangedEventHandler CollectionChanged; + public event PropertyChangedEventHandler PropertyChanged; + public event EventHandler<ErrorEventArgs> InitializationError; + + public IEnumerator<T> GetEnumerator() + { + var i = 0; + while (i < Count) yield return this[i++]; + } + + int IList.Add(object value) => throw new NotImplementedException(); + void IList.Clear() => throw new NotImplementedException(); + bool IList.Contains(object value) => throw new NotImplementedException(); + int IList.IndexOf(object value) => throw new NotImplementedException(); + void IList.Insert(int index, object value) => throw new NotImplementedException(); + void IList.Remove(object value) => throw new NotImplementedException(); + void IList.RemoveAt(int index) => throw new NotImplementedException(); + void ICollection.CopyTo(Array array, int index) => throw new NotImplementedException(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void LoadCount() + { + dispatcher?.VerifyAccess(); + + try + { + var countTask = source.GetCount(); + + if (countTask.IsCompleted) + { + // Don't send a Reset if the count is available immediately, as this causes + // a NullReferenceException in ListCollectionView. + count = countTask.Result; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + } + else + { + countTask.ContinueWith(x => + { + if (x.IsFaulted) + { + RaiseInitializationError(x.Exception); + } + else if (!x.IsCanceled) + { + count = x.Result; + SendReset(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + } + }, TaskScheduler.FromCurrentSynchronizationContext()); + } + } + catch (Exception ex) + { + RaiseInitializationError(ex); + log.Error(ex, "Error loading virtualizing list count"); + } + } + + async void LoadPage(int number) + { + dispatcher?.VerifyAccess(); + + try + { + pages.Add(number, placeholderPage); + var page = await source.GetPage(number); + + if (page != null) + { + pages[number] = page; + SendReset(); + } + } + catch (Exception ex) + { + log.Error(ex, "Error loading virtualizing list page {Number}", number); + pages.Remove(number); + } + } + + void RaiseInitializationError(Exception e) + { + if (InitializationError != null) + { + if (e is AggregateException ae) + { + e = ae = ae.Flatten(); + + if (ae.InnerExceptions.Count == 1) + { + e = ae.InnerException; + } + } + + InitializationError(this, new ErrorEventArgs(e)); + } + } + + void SendReset() + { + // ListCollectionView (which is used internally by the WPF list controls) doesn't + // support multi-item Replace notifications, so sending a Reset is actually the + // best thing we can do to notify of items being loaded. + CollectionChanged?.Invoke( + this, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/Collections/VirtualizingListCollectionView.cs b/src/GitHub.App/Collections/VirtualizingListCollectionView.cs new file mode 100644 index 0000000000..9aa5900b24 --- /dev/null +++ b/src/GitHub.App/Collections/VirtualizingListCollectionView.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Windows.Data; + +namespace GitHub.Collections +{ + /// <summary> + /// A <see cref="CollectionView"/> that adds filtering to a <see cref="VirtualizingList{T}"/>. + /// </summary> + /// <typeparam name="T">The item type.</typeparam> + public class VirtualizingListCollectionView<T> : CollectionView, IList + { + List<int> filtered; + + /// <summary> + /// Initializes a new instance of the <see cref="VirtualizingListCollectionView{T}"/> class. + /// </summary> + /// <param name="inner">The inner virtualizing list.</param> + public VirtualizingListCollectionView(VirtualizingList<T> inner) + : base(inner) + { + } + + /// <summary> + /// Gets the count of the filtered items. + /// </summary> + public override int Count => filtered?.Count ?? Inner.Count; + + /// <inheritdoc/> + public override bool IsEmpty => Count == 0; + + bool IList.IsReadOnly => true; + bool IList.IsFixedSize => false; + object ICollection.SyncRoot => null; + bool ICollection.IsSynchronized => false; + + /// <summary> + /// Gets the inner virtualizing list. + /// </summary> + protected VirtualizingList<T> Inner => (VirtualizingList<T>)SourceCollection; + + object IList.this[int index] + { + get { return GetItemAt(index); } + set { throw new NotImplementedException(); } + } + + /// <inheritdoc/> + public override object GetItemAt(int index) + { + if (filtered == null) + { + return Inner[index]; + } + else + { + return Inner[filtered[index]]; + } + } + + int IList.Add(object value) => throw new NotSupportedException(); + bool IList.Contains(object value) => throw new NotImplementedException(); + void IList.Clear() => throw new NotSupportedException(); + int IList.IndexOf(object value) => throw new NotImplementedException(); + void IList.Insert(int index, object value) => throw new NotSupportedException(); + void IList.Remove(object value) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); + void ICollection.CopyTo(Array array, int index) => throw new NotImplementedException(); + + protected override void RefreshOverride() + { + if (Filter != null) + { + var result = new List<int>(); + var count = Inner.Count; + var pageCount = (int)Math.Ceiling((double)count / Inner.PageSize); + + for (var i = 0; i < pageCount; ++i) + { + IReadOnlyList<T> page; + + if (Inner.Pages.TryGetValue(i, out page)) + { + var j = 0; + + foreach (var item in page) + { + if (Equals(item, Inner.Placeholder) || Filter(item)) + { + result.Add((i * Inner.PageSize) + j); + } + + ++j; + } + } + else + { + for (var j = 0; j < Inner.PageSize; ++j) + { + result.Add((i * Inner.PageSize) + j); + } + } + } + + filtered = result; + } + else + { + filtered = null; + } + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/Commands/UsageTrackingCommand.cs b/src/GitHub.App/Commands/UsageTrackingCommand.cs new file mode 100644 index 0000000000..8115f83c23 --- /dev/null +++ b/src/GitHub.App/Commands/UsageTrackingCommand.cs @@ -0,0 +1,51 @@ +using System; +using System.Windows.Input; +using System.Linq.Expressions; +using GitHub.Models; +using GitHub.Services; +using GitHub.Extensions; + +namespace GitHub.Commands +{ + /// <summary> + /// A proxy <see cref="ICommand"/> that increments a usage counter after executing the command. + /// </summary> + public class UsageTrackingCommand : ICommand + { + readonly ICommand command; + readonly Lazy<IUsageTracker> usageTracker; + readonly Expression<Func<UsageModel.MeasuresModel, int>> counter; + + /// <summary> + /// The usage tracker and counter to increment after the target command is executed. + /// </summary> + /// <param name="usageTracker">The usage tracker.</param> + /// <param name="counter">The counter to increment.</param> + /// <param name="command">The target command.</param> + public UsageTrackingCommand( + Lazy<IUsageTracker> usageTracker, Expression<Func<UsageModel.MeasuresModel, int>> counter, + ICommand command) + { + this.command = command; + this.usageTracker = usageTracker; + this.counter = counter; + } + + public event EventHandler CanExecuteChanged + { + add { command.CanExecuteChanged += value; } + remove { command.CanExecuteChanged -= value; } + } + + public bool CanExecute(object parameter) + { + return command.CanExecute(parameter); + } + + public void Execute(object parameter) + { + command.Execute(parameter); + usageTracker.Value.IncrementCounter(counter).Forget(); + } + } +} diff --git a/src/GitHub.App/Controllers/UIController.cs b/src/GitHub.App/Controllers/UIController.cs deleted file mode 100644 index 2a1dd1c14d..0000000000 --- a/src/GitHub.App/Controllers/UIController.cs +++ /dev/null @@ -1,315 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Windows; -using System.Windows.Controls; -using GitHub.Authentication; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using GitHub.ViewModels; -using NullGuard; -using ReactiveUI; -using Stateless; -using System.Collections.Specialized; - -namespace GitHub.Controllers -{ - [Export(typeof(IUIController))] - public class UIController : IUIController, IDisposable - { - enum Trigger { Cancel = 0, Auth = 1, Create = 2, Clone = 3, Publish = 4, Next, Finish } - - readonly IExportFactoryProvider factory; - readonly IUIProvider uiProvider; - readonly IRepositoryHosts hosts; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - readonly IConnectionManager connectionManager; - readonly Lazy<ITwoFactorChallengeHandler> lazyTwoFactorChallengeHandler; - - readonly CompositeDisposable disposables = new CompositeDisposable(); - readonly StateMachine<UIViewType, Trigger> machine; - Subject<UserControl> transition; - Subject<bool> completion; - UIControllerFlow currentFlow; - NotifyCollectionChangedEventHandler connectionAdded; - - [ImportingConstructor] - public UIController(IUIProvider uiProvider, IRepositoryHosts hosts, IExportFactoryProvider factory, - IConnectionManager connectionManager, Lazy<ITwoFactorChallengeHandler> lazyTwoFactorChallengeHandler) - { - this.factory = factory; - this.uiProvider = uiProvider; - this.hosts = hosts; - this.connectionManager = connectionManager; - this.lazyTwoFactorChallengeHandler = lazyTwoFactorChallengeHandler; - -#if DEBUG - if (Application.Current != null && !Splat.ModeDetector.InUnitTestRunner()) - { - var waitDispatcher = RxApp.MainThreadScheduler as WaitForDispatcherScheduler; - if (waitDispatcher != null) - { - Debug.Assert(DispatcherScheduler.Current.Dispatcher == Application.Current.Dispatcher, - "DispatcherScheduler is set correctly"); - } - else - { - Debug.Assert(((DispatcherScheduler)RxApp.MainThreadScheduler).Dispatcher == Application.Current.Dispatcher, - "The MainThreadScheduler is using the wrong dispatcher"); - } - } -#endif - machine = new StateMachine<UIViewType, Trigger>(UIViewType.None); - - machine.Configure(UIViewType.Login) - .OnEntry(() => - { - RunView(UIViewType.Login); - }) - .Permit(Trigger.Next, UIViewType.TwoFactor) - // Added the following line to make it easy to login to both GitHub and GitHub Enterprise - // in DesignTimeStyleHelper in order to test Publish. - .Permit(Trigger.Cancel, UIViewType.End) - .PermitIf(Trigger.Finish, UIViewType.End, () => currentFlow == UIControllerFlow.Authentication) - .PermitIf(Trigger.Finish, UIViewType.Create, () => currentFlow == UIControllerFlow.Create) - .PermitIf(Trigger.Finish, UIViewType.Clone, () => currentFlow == UIControllerFlow.Clone) - .PermitIf(Trigger.Finish, UIViewType.Publish, () => currentFlow == UIControllerFlow.Publish); - - machine.Configure(UIViewType.TwoFactor) - .SubstateOf(UIViewType.Login) - .OnEntry(() => - { - RunView(UIViewType.TwoFactor); - }) - .Permit(Trigger.Cancel, UIViewType.End) - .PermitIf(Trigger.Next, UIViewType.End, () => currentFlow == UIControllerFlow.Authentication) - .PermitIf(Trigger.Next, UIViewType.Create, () => currentFlow == UIControllerFlow.Create) - .PermitIf(Trigger.Next, UIViewType.Clone, () => currentFlow == UIControllerFlow.Clone) - .PermitIf(Trigger.Next, UIViewType.Publish, () => currentFlow == UIControllerFlow.Publish); - - machine.Configure(UIViewType.Create) - .OnEntry(() => - { - RunView(UIViewType.Create); - }) - .Permit(Trigger.Cancel, UIViewType.End) - .Permit(Trigger.Next, UIViewType.End); - - machine.Configure(UIViewType.Clone) - .OnEntry(() => - { - RunView(UIViewType.Clone); - }) - .Permit(Trigger.Cancel, UIViewType.End) - .Permit(Trigger.Next, UIViewType.End); - - machine.Configure(UIViewType.Publish) - .OnEntry(() => - { - RunView(UIViewType.Publish); - }) - .Permit(Trigger.Cancel, UIViewType.End) - .Permit(Trigger.Next, UIViewType.End); - - machine.Configure(UIViewType.End) - .OnEntryFrom(Trigger.Cancel, () => End(false)) - .OnEntryFrom(Trigger.Next, () => End(true)) - .Permit(Trigger.Next, UIViewType.Finished); - - machine.Configure(UIViewType.Finished); - } - - public IObservable<UserControl> SelectFlow(UIControllerFlow choice) - { - currentFlow = choice; - - transition = new Subject<UserControl>(); - transition.Subscribe(_ => { }, _ => Fire(Trigger.Next)); - - return transition; - } - - /// <summary> - /// Allows listening to the completion state of the ui flow - whether - /// it was completed because it was cancelled or whether it succeeded. - /// </summary> - /// <returns>true for success, false for cancel</returns> - public IObservable<bool> ListenToCompletionState() - { - if (completion == null) - completion = new Subject<bool>(); - return completion; - } - - void End(bool success) - { - uiProvider.RemoveService(typeof(IConnection)); - transition.OnCompleted(); - completion?.OnNext(success); - completion?.OnCompleted(); - completion = null; - } - - void RunView(UIViewType viewType) - { - var view = CreateViewAndViewModel(viewType); - transition.OnNext(view as UserControl); - SetupView(viewType, view); - } - - void SetupView(UIViewType viewType, IView view) - { - if (viewType == UIViewType.Login) - { - // we're setting up the login dialog, we need to setup the 2fa as - // well to continue the flow if it's needed, since the - // authenticationresult callback won't happen until - // everything is done - var dvm = factory.GetViewModel(UIViewType.TwoFactor); - disposables.Add(dvm); - var twofa = dvm.Value; - disposables.Add(twofa.WhenAny(x => x.IsShowing, x => x.Value) - .Where(x => x) - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => Fire(Trigger.Next))); - - disposables.Add(view.Done - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => Fire(Trigger.Finish))); - } - else if (viewType != UIViewType.TwoFactor) - { - disposables.Add(view.Done - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => Fire(Trigger.Next))); - } - disposables.Add(view.Cancel.Subscribe(_ => Stop())); - } - - IView CreateViewAndViewModel(UIViewType viewType) - { - IViewModel viewModel; - if (viewType == UIViewType.TwoFactor) - { - viewModel = lazyTwoFactorChallengeHandler.Value.CurrentViewModel; - } - else - { - var dvm = factory.GetViewModel(viewType); - disposables.Add(dvm); - viewModel = dvm.Value; - } - - var dv = factory.GetView(viewType); - disposables.Add(dv); - var view = dv.Value; - - view.ViewModel = viewModel; - - return view; - } - - void Fire(Trigger next) - { - Debug.WriteLine("Firing {0} ({1})", next, GetHashCode()); - machine.Fire(next); - } - - public void Start([AllowNull] IConnection connection) - { - if (connection != null) - { - uiProvider.AddService(typeof(IConnection), connection); - connection.Login() - .Select(c => hosts.LookupHost(connection.HostAddress)) - .Do(host => - { - machine.Configure(UIViewType.None) - .Permit(Trigger.Auth, UIViewType.Login) - .PermitIf(Trigger.Create, UIViewType.Create, () => host.IsLoggedIn) - .PermitIf(Trigger.Create, UIViewType.Login, () => !host.IsLoggedIn) - .PermitIf(Trigger.Clone, UIViewType.Clone, () => host.IsLoggedIn) - .PermitIf(Trigger.Clone, UIViewType.Login, () => !host.IsLoggedIn) - .PermitIf(Trigger.Publish, UIViewType.Publish, () => host.IsLoggedIn) - .PermitIf(Trigger.Publish, UIViewType.Login, () => !host.IsLoggedIn); - }) - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => { }, () => - { - Debug.WriteLine("Start ({0})", GetHashCode()); - Fire((Trigger)(int)currentFlow); - }); - } - else - { - connectionManager - .IsLoggedIn(hosts) - .Do(loggedin => - { - if (!loggedin && currentFlow != UIControllerFlow.Authentication) - { - connectionAdded = (s, e) => { - if (e.Action == NotifyCollectionChangedAction.Add) - uiProvider.AddService(typeof(IConnection), e.NewItems[0]); - }; - connectionManager.Connections.CollectionChanged += connectionAdded; - } - - machine.Configure(UIViewType.None) - .Permit(Trigger.Auth, UIViewType.Login) - .PermitIf(Trigger.Create, UIViewType.Create, () => loggedin) - .PermitIf(Trigger.Create, UIViewType.Login, () => !loggedin) - .PermitIf(Trigger.Clone, UIViewType.Clone, () => loggedin) - .PermitIf(Trigger.Clone, UIViewType.Login, () => !loggedin) - .PermitIf(Trigger.Publish, UIViewType.Publish, () => loggedin) - .PermitIf(Trigger.Publish, UIViewType.Login, () => !loggedin); - }) - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => { }, () => - { - Debug.WriteLine("Start ({0})", GetHashCode()); - Fire((Trigger)(int)currentFlow); - }); - } - } - - public void Stop() - { - Debug.WriteLine("Stop ({0})", GetHashCode()); - Fire(machine.IsInState(UIViewType.End) ? Trigger.Next : Trigger.Cancel); - } - - bool disposed; // To detect redundant calls - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - Debug.WriteLine("Disposing ({0})", GetHashCode()); - disposables.Dispose(); - transition?.Dispose(); - completion?.Dispose(); - if (connectionAdded != null) - connectionManager.Connections.CollectionChanged -= connectionAdded; - connectionAdded = null; - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public bool IsStopped => machine.IsInState(UIViewType.Finished); - } -} diff --git a/src/GitHub.App/Extensions/AkavacheExtensions.cs b/src/GitHub.App/Extensions/AkavacheExtensions.cs index 87fa112fc2..f1adcb1ef6 100644 --- a/src/GitHub.App/Extensions/AkavacheExtensions.cs +++ b/src/GitHub.App/Extensions/AkavacheExtensions.cs @@ -4,6 +4,7 @@ using System.Reactive.Linq; using Akavache; using GitHub.Caches; +using System.Threading.Tasks; namespace GitHub.Extensions { @@ -199,39 +200,82 @@ static IObservable<T> GetAndFetchLatestFromIndex<T>(this IBlobCache This, bool shouldInvalidateOnError = false) where T : CacheItem { - var fetch = Observable.Defer(() => This.GetOrCreateObject(key, () => CacheIndex.Create(key)) + var idx = Observable.Defer(() => This + .GetOrCreateObject(key, () => CacheIndex.Create(key))) + .Select(x => x.IndexKey == null ? CacheIndex.Create(key) : x) + .Replay() + .RefCount(); + + + var fetch = idx .Select(x => Tuple.Create(x, fetchPredicate == null || !x.Keys.Any() || fetchPredicate(x.UpdatedAt))) .Where(predicateIsTrue => predicateIsTrue.Item2) .Select(x => x.Item1) - .SelectMany(index => index.Clear(This, key, absoluteExpiration)) - .SelectMany(index => - { - var fetchObs = fetchFunc().Catch<T, Exception>(ex => - { - var shouldInvalidate = shouldInvalidateOnError ? - This.InvalidateObject<CacheIndex>(key) : - Observable.Return(Unit.Default); - return shouldInvalidate.SelectMany(__ => Observable.Throw<T>(ex)); - }); - - return fetchObs - .SelectMany(x => x.Save<T>(This, key, absoluteExpiration)) - .Do(x => index.AddAndSave(This, key, x, absoluteExpiration)) - .Finally(() => + .Select(index => index.Clear()) + .SelectMany(index => fetchFunc() + .Catch<T, Exception>(ex => { - This.GetObjects<T>(index.OldKeys.Except(index.Keys)) - .Do(dict => This.InvalidateObjects<T>(dict.Keys)) - .SelectMany(dict => dict.Values) - .Do(removedItemsCallback) - .Subscribe(); - }); - })); - - var cache = Observable.Defer(() => This.GetOrCreateObject(key, () => CacheIndex.Create(key)) - .SelectMany(index => This.GetObjects<T>(index.Keys)) - .SelectMany(dict => dict.Values)); - - return cache.Merge(fetch).Replay().RefCount(); + var shouldInvalidate = shouldInvalidateOnError ? + This.InvalidateObject<CacheIndex>(key) : + Observable.Return(Unit.Default); + return shouldInvalidate.SelectMany(__ => Observable.Throw<T>(ex)); + }) + .SelectMany(x => x.Save<T>(This, key, absoluteExpiration)) + .Do(x => index.Add(key, x)) + ); + + var cache = idx + .SelectMany(index => This.GetObjects<T>(index.Keys.ToList())) + .SelectMany(dict => dict.Values); + + return cache.Merge(fetch) + .Finally(async () => + { + var index = await idx; + await index.Save(This); + + var list = index.OldKeys.Except(index.Keys); + if (!list.Any()) + return; + var removed = await This.GetObjects<T>(list); + foreach (var d in removed.Values) + removedItemsCallback(d); + await This.InvalidateObjects<T>(list); + }) + .Replay().RefCount(); + } + + /// <summary> + /// This method adds a new object to the database and updates the + /// corresponding index. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="blobCache">The cache to retrieve the object from.</param> + /// <param name="key">The key to look up the cache value with.</param> + /// <param name="fetchFunc">The fetch function.</param> + /// <param name="maxCacheDuration"> + /// The maximum age of a cache object before the object is treated as + /// expired and unusable. Cache objects older than this will be treated + /// as a cache miss. + /// </param> + /// <returns></returns> + public static IObservable<T> PutAndUpdateIndex<T>(this IBlobCache blobCache, + string key, + Func<IObservable<T>> fetchFunc, + TimeSpan maxCacheDuration) + where T : CacheItem + { + return Observable.Defer(() => + { + var absoluteExpiration = blobCache.Scheduler.Now + maxCacheDuration; + return blobCache.GetOrCreateObject(key, () => CacheIndex.Create(key)) + .Select(x => x.IndexKey == null ? CacheIndex.Create(key) : x) + .SelectMany(index => fetchFunc() + .Catch<T, Exception>(Observable.Throw<T>) + .SelectMany(x => x.Save<T>(blobCache, key, absoluteExpiration)) + .Do(x => index.AddAndSave(blobCache, key, x, absoluteExpiration)) + ); + }); } static bool IsExpired(IBlobCache blobCache, DateTimeOffset itemCreatedAt, TimeSpan cacheDuration) diff --git a/src/GitHub.App/Extensions/ValidationExtensions.cs b/src/GitHub.App/Extensions/ValidationExtensions.cs deleted file mode 100644 index 1c1407a5ec..0000000000 --- a/src/GitHub.App/Extensions/ValidationExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using GitHub.Validation; -using GitHub.ViewModels; -using ReactiveUI; - -namespace GitHub.Extensions -{ - public static class ValidationExtensions - { - public static ReactivePropertyValidator<string> CreateBaseRepositoryPathValidator( - this IRepositoryCreationTarget target) - { - return ReactivePropertyValidator.ForObservable(target.WhenAny(x => x.BaseRepositoryPath, x => x.Value)) - .IfNullOrEmpty("Please enter a repository path") - .IfTrue(x => x.Length > 200, "Path too long") - .IfContainsInvalidPathChars("Path contains invalid characters") - .IfPathNotRooted("Please enter a valid path"); - } - } -} diff --git a/src/GitHub.App/Factories/ApiClientFactory.cs b/src/GitHub.App/Factories/ApiClientFactory.cs index 78dbc2bc96..f2d55a2e51 100644 --- a/src/GitHub.App/Factories/ApiClientFactory.cs +++ b/src/GitHub.App/Factories/ApiClientFactory.cs @@ -1,13 +1,13 @@ -using System.ComponentModel.Composition; +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; using GitHub.Api; -using GitHub.Caches; +using GitHub.Infrastructure; using GitHub.Models; using GitHub.Primitives; -using GitHub.Services; using Octokit; using Octokit.Reactive; using ApiClient = GitHub.Api.ApiClient; -using GitHub.Infrastructure; namespace GitHub.Factories { @@ -18,22 +18,26 @@ public class ApiClientFactory : IApiClientFactory readonly ProductHeaderValue productHeader; [ImportingConstructor] - public ApiClientFactory(ILoginCache loginCache, IProgram program, ILoggingConfiguration config) + public ApiClientFactory(IKeychain keychain, IProgram program) { - LoginCache = loginCache; + Keychain = keychain; productHeader = program.ProductHeader; - config.Configure(); } - public IApiClient Create(HostAddress hostAddress) + public Task<IGitHubClient> CreateGitHubClient(HostAddress hostAddress) { - var apiBaseUri = hostAddress.ApiUri; + var credentialStore = new KeychainCredentialStore(Keychain, hostAddress); + var result = new GitHubClient(productHeader, credentialStore, hostAddress.ApiUri); + return Task.FromResult<IGitHubClient>(result); + } + public async Task<IApiClient> Create(HostAddress hostAddress) + { return new ApiClient( hostAddress, - new ObservableGitHubClient(new GitHubClient(productHeader, new GitHubCredentialStore(hostAddress, LoginCache), apiBaseUri))); + new ObservableGitHubClient(await CreateGitHubClient(hostAddress))); } - protected ILoginCache LoginCache { get; private set; } + protected IKeychain Keychain { get; private set; } } } diff --git a/src/GitHub.App/Factories/HostCacheFactory.cs b/src/GitHub.App/Factories/HostCacheFactory.cs index a5978eda75..ad96e64a82 100644 --- a/src/GitHub.App/Factories/HostCacheFactory.cs +++ b/src/GitHub.App/Factories/HostCacheFactory.cs @@ -1,10 +1,12 @@ using System; using System.ComponentModel.Composition; using System.IO; +using System.Reactive.Linq; using Akavache; using GitHub.Primitives; using Rothko; using GitHub.Extensions; +using System.Threading.Tasks; namespace GitHub.Factories { @@ -12,6 +14,9 @@ namespace GitHub.Factories [PartCreationPolicy(CreationPolicy.Shared)] public class HostCacheFactory : IHostCacheFactory { + const int CacheVersion = 1; + const string CacheVersionKey = "cacheVersion"; + readonly Lazy<IBlobCacheFactory> blobCacheFactory; readonly Lazy<IOperatingSystem> operatingSystem; @@ -22,7 +27,7 @@ public HostCacheFactory(Lazy<IBlobCacheFactory> blobCacheFactory, Lazy<IOperatin this.operatingSystem = operatingSystem; } - public IBlobCache Create(HostAddress hostAddress) + public async Task<IBlobCache> Create(HostAddress hostAddress) { var environment = OperatingSystem.Environment; // For GitHub.com, the cache file name should be "api.github.com.cache.db" @@ -34,10 +39,29 @@ public IBlobCache Create(HostAddress hostAddress) // CreateDirectory is a noop if the directory already exists. OperatingSystem.Directory.CreateDirectory(Path.GetDirectoryName(userAccountPath)); - return BlobCacheFactory.CreateBlobCache(userAccountPath); + + var cache = BlobCacheFactory.CreateBlobCache(userAccountPath); + var version = await cache.GetOrCreateObject<int>(CacheVersionKey, () => 0); + + if (version != CacheVersion) + { + await cache.InvalidateAll(); + await cache.InsertObject(CacheVersionKey, CacheVersion); + } + + return cache; } IOperatingSystem OperatingSystem { get { return operatingSystem.Value; } } IBlobCacheFactory BlobCacheFactory { get { return blobCacheFactory.Value; } } + + protected virtual void Dispose(bool disposing) + {} + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/GitHub.App/Factories/IBlobCacheFactory.cs b/src/GitHub.App/Factories/IBlobCacheFactory.cs index 3f068b7e5e..dde86b2f64 100644 --- a/src/GitHub.App/Factories/IBlobCacheFactory.cs +++ b/src/GitHub.App/Factories/IBlobCacheFactory.cs @@ -1,8 +1,9 @@ using Akavache; +using System; namespace GitHub.Factories { - public interface IBlobCacheFactory + public interface IBlobCacheFactory : IDisposable { IBlobCache CreateBlobCache(string path); } diff --git a/src/GitHub.App/Factories/IHostCacheFactory.cs b/src/GitHub.App/Factories/IHostCacheFactory.cs index a2e526bcbb..2e29ad2b10 100644 --- a/src/GitHub.App/Factories/IHostCacheFactory.cs +++ b/src/GitHub.App/Factories/IHostCacheFactory.cs @@ -1,10 +1,12 @@ using Akavache; using GitHub.Primitives; +using System; +using System.Threading.Tasks; namespace GitHub.Factories { - public interface IHostCacheFactory + public interface IHostCacheFactory : IDisposable { - IBlobCache Create(HostAddress hostAddress); + Task<IBlobCache> Create(HostAddress hostAddress); } } diff --git a/src/GitHub.App/Factories/ModelServiceFactory.cs b/src/GitHub.App/Factories/ModelServiceFactory.cs new file mode 100644 index 0000000000..a5ef967749 --- /dev/null +++ b/src/GitHub.App/Factories/ModelServiceFactory.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Caches; +using GitHub.Models; +using GitHub.Services; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.Factories +{ + [Export(typeof(IModelServiceFactory))] + [PartCreationPolicy(CreationPolicy.Shared)] + public sealed class ModelServiceFactory : IModelServiceFactory, IDisposable + { + readonly IApiClientFactory apiClientFactory; + readonly IHostCacheFactory hostCacheFactory; + readonly IAvatarProvider avatarProvider; + readonly Dictionary<IConnection, ModelService> cache = new Dictionary<IConnection, ModelService>(); + readonly SemaphoreSlim cacheLock = new SemaphoreSlim(1); + + [ImportingConstructor] + public ModelServiceFactory( + IApiClientFactory apiClientFactory, + IHostCacheFactory hostCacheFactory, + IAvatarProvider avatarProvider) + { + this.apiClientFactory = apiClientFactory; + this.hostCacheFactory = hostCacheFactory; + this.avatarProvider = avatarProvider; + } + + public async Task<IModelService> CreateAsync(IConnection connection) + { + ModelService result; + + await cacheLock.WaitAsync(); + + try + { + if (!cache.TryGetValue(connection, out result)) + { + result = new ModelService( + await apiClientFactory.Create(connection.HostAddress), + await hostCacheFactory.Create(connection.HostAddress), + avatarProvider); + result.InsertUser(AccountCacheItem.Create(connection.User)); + cache.Add(connection, result); + } + } + finally + { + cacheLock.Release(); + } + + return result; + } + + public IModelService CreateBlocking(IConnection connection) + { + return ThreadHelper.JoinableTaskFactory.Run(() => CreateAsync(connection)); + } + + public void Dispose() => cacheLock.Dispose(); + } +} diff --git a/src/GitHub.App/Factories/RepositoryHostFactory.cs b/src/GitHub.App/Factories/RepositoryHostFactory.cs deleted file mode 100644 index 1b83c80f61..0000000000 --- a/src/GitHub.App/Factories/RepositoryHostFactory.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using GitHub.Authentication; -using GitHub.Caches; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; - -namespace GitHub.Factories -{ - [Export(typeof(IRepositoryHostFactory))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class RepositoryHostFactory : IRepositoryHostFactory - { - readonly IApiClientFactory apiClientFactory; - readonly IHostCacheFactory hostCacheFactory; - readonly ILoginCache loginCache; - readonly IAvatarProvider avatarProvider; - readonly ITwoFactorChallengeHandler twoFactorChallengeHandler; - - [ImportingConstructor] - public RepositoryHostFactory( - IApiClientFactory apiClientFactory, - IHostCacheFactory hostCacheFactory, - ILoginCache loginCache, - IAvatarProvider avatarProvider, - ITwoFactorChallengeHandler twoFactorChallengeHandler) - { - this.apiClientFactory = apiClientFactory; - this.hostCacheFactory = hostCacheFactory; - this.loginCache = loginCache; - this.avatarProvider = avatarProvider; - this.twoFactorChallengeHandler = twoFactorChallengeHandler; - } - - public IRepositoryHost Create(HostAddress hostAddress) - { - var apiClient = apiClientFactory.Create(hostAddress); - var hostCache = hostCacheFactory.Create(hostAddress); - var modelService = new ModelService(apiClient, hostCache, avatarProvider); - - return new RepositoryHost(apiClient, modelService, loginCache, twoFactorChallengeHandler); - } - - bool disposed; - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - loginCache.Dispose(); - avatarProvider.Dispose(); - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs b/src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs index df5ad012c0..8d75c97948 100644 --- a/src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs +++ b/src/GitHub.App/Factories/SqlitePersistentBlobCacheFactory.cs @@ -1,8 +1,13 @@ using Akavache; using Akavache.Sqlite3; -using NLog; using System; +using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Logging; +using Serilog; namespace GitHub.Factories { @@ -10,21 +15,51 @@ namespace GitHub.Factories [PartCreationPolicy(CreationPolicy.Shared)] public class SqlitePersistentBlobCacheFactory : IBlobCacheFactory { - static readonly Logger log = LogManager.GetCurrentClassLogger(); + static readonly ILogger log = LogManager.ForContext<SqlitePersistentBlobCacheFactory>(); + Dictionary<string, IBlobCache> cache = new Dictionary<string, IBlobCache>(); public IBlobCache CreateBlobCache(string path) { Guard.ArgumentNotEmptyString(path, nameof(path)); + if (cache.ContainsKey(path)) + return cache[path]; try { - return new SQLitePersistentBlobCache(path); + var c = new SQLitePersistentBlobCache(path); + cache.Add(path, c); + return c; } catch(Exception ex) { - log.Error("Error while creating SQLitePersistentBlobCache for {0}. {1}", path, ex); + log.Error(ex, "Error while creating SQLitePersistentBlobCache for {Path}", path); return new InMemoryBlobCache(); } } + + bool disposed; + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (disposed) return; + disposed = true; + Task.Run(() => + { + foreach (var c in cache.Values) + { + c.Dispose(); + c.Shutdown.Wait(); + } + }).Wait(500); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } } \ No newline at end of file diff --git a/src/GitHub.App/Factories/ViewViewModelFactory.cs b/src/GitHub.App/Factories/ViewViewModelFactory.cs new file mode 100644 index 0000000000..4d96df5064 --- /dev/null +++ b/src/GitHub.App/Factories/ViewViewModelFactory.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Windows; +using GitHub.Exports; +using GitHub.Services; +using GitHub.ViewModels; + +namespace GitHub.Factories +{ + /// <summary> + /// Factory for creating views and view models. + /// </summary> + [Export(typeof(IViewViewModelFactory))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class ViewViewModelFactory : IViewViewModelFactory + { + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + public ViewViewModelFactory( + IGitHubServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + [ImportMany] + IEnumerable<ExportFactory<FrameworkElement, IViewModelMetadata>> Views { get; set; } + + /// <inheritdoc/> + public TViewModel CreateViewModel<TViewModel>() where TViewModel : IViewModel + { + return serviceProvider.ExportProvider.GetExport<TViewModel>().Value; + } + + /// <inheritdoc/> + public FrameworkElement CreateView<TViewModel>() where TViewModel : IViewModel + { + return CreateView(typeof(TViewModel)); + } + + /// <inheritdoc/> + public FrameworkElement CreateView(Type viewModel) + { + var f = Views.FirstOrDefault(x => x.Metadata.ViewModelType.Contains(viewModel.FullName)); + return f?.CreateExport().Value; + } + } +} diff --git a/src/GitHub.App/FodyWeavers.xml b/src/GitHub.App/FodyWeavers.xml deleted file mode 100644 index 92f9336e03..0000000000 --- a/src/GitHub.App/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard ExcludeRegex="^GitHub.SampleData.*$"/> -</Weavers> \ No newline at end of file diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index 4dbae7ae7d..18a58a0710 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -1,87 +1,172 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</ProjectGuid> <OutputType>Library</OutputType> + <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>GitHub</RootNamespace> + <RootNamespace>GitHub.App</RootNamespace> <AssemblyName>GitHub.App</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <NuGetPackageImportStamp> - </NuGetPackageImportStamp> - <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>true</RunCodeAnalysis> <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> - <Reference Include="Microsoft.TeamFoundation.Git.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> - <Private>False</Private> + <Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Provider, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> - <Private>False</Private> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0" /> - <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath> - <Private>False</Private> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6071\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6072\lib\net11\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="NLog"> - <HintPath>..\..\packages\NLog.3.1.0.0\lib\net45\NLog.dll</HintPath> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30320\lib\net20\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61031\lib\net20\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30111\lib\net20\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6071\lib\net11\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50728\lib\net11\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath> </Reference> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> + <Reference Include="rothko, Version=0.0.3.0, Culture=neutral, PublicKeyToken=9f664c41f503810a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rothko.0.0.3-ghfvs\lib\net45\rothko.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="SQLitePCL.raw, Version=0.7.3.0, Culture=neutral, PublicKeyToken=d89a3d1cc066b805, processorArchitecture=MSIL"> <HintPath>..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\lib\net45\SQLitePCL.raw.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Stateless"> - <HintPath>..\..\packages\Stateless.2.5.11.0\lib\portable-net40+sl50+win+wp80\Stateless.dll</HintPath> + <Reference Include="Stateless, Version=2.5.56.0, Culture=neutral, PublicKeyToken=93038f0927583c9a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Stateless.2.5.56.0\lib\portable-net40+sl50+win+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Stateless.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.Composition" /> @@ -108,6 +193,11 @@ <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> + </Reference> + <Reference Include="System.Web" /> + <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xaml" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> @@ -117,40 +207,110 @@ <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> + <Compile Include="Collections\IVirtualizingListSource.cs" /> + <Compile Include="Collections\SequentialListSource.cs" /> + <Compile Include="Collections\VirtualizingList.cs" /> + <Compile Include="Collections\VirtualizingListCollectionView.cs" /> + <Compile Include="Commands\UsageTrackingCommand.cs" /> + <Compile Include="Factories\ViewViewModelFactory.cs" /> + <Compile Include="Factories\ModelServiceFactory.cs" /> + <Compile Include="Models\PullRequestDetailArgument.cs" /> + <Compile Include="Models\PullRequestModel.cs" /> + <Compile Include="SampleData\ActorViewModelDesigner.cs" /> + <Compile Include="SampleData\ForkRepositoryExecuteViewModelDesigner.cs" /> + <Compile Include="SampleData\ForkRepositorySelectViewModelDesigner.cs" /> + <Compile Include="SampleData\GitServiceDesigner.cs" /> + <Compile Include="SampleData\ForkRepositorySwitchViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestFilesViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestListItemViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestReviewAuthoringViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestReviewFileCommentViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestReviewViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestUserReviewsViewModelDesigner.cs" /> + <Compile Include="SampleData\UserFilterViewModelDesigner.cs" /> + <Compile Include="Services\EnterpriseCapabilitiesService.cs" /> + <Compile Include="Services\FromGraphQlExtensions.cs" /> + <Compile Include="Services\GitHubContextService.cs" /> + <Compile Include="Services\GlobalConnection.cs" /> + <Compile Include="Services\RepositoryForkService.cs" /> + <Compile Include="Services\RepositoryService.cs" /> + <Compile Include="ViewModels\ActorViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ForkRepositoryExecuteViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ForkRepositorySwitchViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ForkRepositoryViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ForkRepositorySelectViewModel.cs" /> + <Compile Include="Services\PullRequestEditorService.cs" /> + <Compile Include="Services\TeamExplorerContext.cs" /> + <Compile Include="Services\OAuthCallbackListener.cs" /> + <Compile Include="Services\TextViewCommandDispatcher.cs" /> + <Compile Include="ViewModels\Dialog\GistCreationViewModel.cs" /> + <Compile Include="ViewModels\Dialog\GitHubDialogWindowViewModel.cs" /> + <Compile Include="ViewModels\Dialog\LoginCredentialsViewModel.cs" /> + <Compile Include="ViewModels\Dialog\LoginTabViewModel.cs" /> + <Compile Include="ViewModels\Dialog\LoginToGitHubForEnterpriseViewModel.cs" /> + <Compile Include="ViewModels\Dialog\LoginToGitHubViewModel.cs" /> + <Compile Include="ViewModels\Dialog\LoginViewModel.cs" /> + <Compile Include="ViewModels\Dialog\Login2FaViewModel.cs" /> + <Compile Include="ViewModels\Dialog\RepositoryCloneViewModel.cs" /> + <Compile Include="ViewModels\Dialog\RepositoryCreationViewModel.cs" /> + <Compile Include="ViewModels\Dialog\PagedDialogViewModelBase.cs" /> + <Compile Include="ViewModels\Dialog\RepositoryRecloneViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IssueListViewModelBase.cs" /> + <Compile Include="ViewModels\GitHubPane\LoggedOutViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\NavigationViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\GitHubPaneViewModel.cs" /> + <Compile Include="SampleData\PullRequestCheckViewModelDesigner.cs" /> + <Compile Include="ViewModels\GitHubPane\LoginFailedViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestCheckType.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestFilesViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestListItemViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestListViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PanePageViewModelBase.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestDetailViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestCreationViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\NotAGitHubRepositoryViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\NotAGitRepositoryViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestReviewAuthoringViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestReviewCommentViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestCheckViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestReviewSummaryViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestReviewViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestUserReviewsViewModel.cs" /> + <Compile Include="ViewModels\RepositoryFormViewModel.cs" /> + <Compile Include="ViewModels\TeamExplorer\RepositoryPublishViewModel.cs" /> <Compile Include="Caches\CacheIndex.cs" /> <Compile Include="Caches\CacheItem.cs" /> <Compile Include="Caches\ImageCache.cs" /> <Compile Include="Extensions\AkavacheExtensions.cs" /> <Compile Include="Extensions\EnvironmentExtensions.cs" /> - <Compile Include="Extensions\ValidationExtensions.cs" /> <Compile Include="GlobalSuppressions.cs" /> - <Compile Include="Infrastructure\LoggingConfiguration.cs" /> - <Compile Include="Models\PullRequestModel.cs" /> <Compile Include="Resources.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>Resources.resx</DependentUpon> </Compile> + <Compile Include="SampleData\AccountDesigner.cs" /> + <Compile Include="SampleData\LocalRepositoryModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestCreationViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestDetailViewModelDesigner.cs" /> + <Compile Include="SampleData\PullRequestListViewModelDesigner.cs" /> + <Compile Include="SampleData\RemoteRepositoryModelDesigner.cs" /> + <Compile Include="SampleData\RepositoryRecloneViewModelDesigner.cs" /> <Compile Include="Services\AvatarProvider.cs" /> + <Compile Include="Services\DialogService.cs" /> <Compile Include="Services\GitHubCredentialProvider.cs" /> <Compile Include="Services\IGitHubCredentialProvider.cs" /> <Compile Include="Services\ImageDownloader.cs" /> <Compile Include="Services\GitClient.cs" /> <Compile Include="Services\ModelService.cs" /> + <Compile Include="Services\PullRequestService.cs" /> <Compile Include="Services\RepositoryCloneService.cs" /> <Compile Include="Extensions\FileExtensions.cs" /> - <Compile Include="Caches\CredentialCache.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> </Compile> - <Compile Include="..\..\script\ApiClientConfiguration.cs" Condition="$(Buildtype) == 'Internal'"> - <Link>Api\ApiClientConfiguration.cs</Link> - </Compile> - <Compile Include="Api\ApiClientConfiguration.cs" Condition="$(Buildtype) != 'Internal'" /> + <Compile Include="ViewModels\UserFilterViewModel.cs" /> </ItemGroup> <ItemGroup> <None Include="AkavacheSqliteLinkerOverride.cs" /> @@ -159,58 +319,38 @@ <Compile Include="Factories\HostCacheFactory.cs" /> <Compile Include="Factories\IBlobCacheFactory.cs" /> <Compile Include="Factories\IHostCacheFactory.cs" /> - <Compile Include="Caches\LoginCache.cs" /> <Compile Include="Caches\SharedCache.cs" /> <Compile Include="Factories\SqlitePersistentBlobCacheFactory.cs" /> <Compile Include="Info\GitHubUrls.cs" /> <Compile Include="Infrastructure\ExportWrappers.cs" /> <Compile Include="Models\Account.cs" /> - <Compile Include="Models\DisconnectedRepositoryHosts.cs" /> - <Compile Include="Models\RepositoryHost.cs" /> - <Compile Include="Models\RepositoryHosts.cs" /> - <Compile Include="Models\RepositoryModel.cs" /> + <Compile Include="Models\RemoteRepositoryModel.cs" /> <Compile Include="SampleData\SampleViewModels.cs" /> <Compile Include="Api\ApiClient.cs" /> <Compile Include="Factories\ApiClientFactory.cs" /> - <None Include="Services\Browser.cs" /> - <Compile Include="Services\EnterpriseProbe.cs" /> <Compile Include="Services\ErrorMap.cs" /> <Compile Include="Services\ErrorMessage.cs" /> <Compile Include="Services\ErrorMessageTranslator.cs" /> - <Compile Include="Services\GitHubCredentialStore.cs" /> - <Compile Include="Services\IEnterpriseProbe.cs" /> - <Compile Include="Factories\RepositoryHostFactory.cs" /> <Compile Include="Services\RepositoryCreationService.cs" /> + <Compile Include="Services\GistPublishService.cs" /> <Compile Include="Services\RepositoryPublishService.cs" /> <Compile Include="Services\SerializedObjectProvider.cs" /> <Compile Include="Services\StandardUserErrors.cs" /> - <Compile Include="Controllers\UIController.cs" /> <Compile Include="Services\Translation.cs" /> <Compile Include="UserErrors\PublishRepositoryUserError.cs" /> <Compile Include="UserErrors\PrivateRepositoryOnFreeAccountUserError.cs" /> <Compile Include="UserErrors\PrivateRepositoryQuotaExceededUserError.cs" /> - <Compile Include="ViewModels\BaseViewModel.cs" /> - <Compile Include="Models\ConnectionRepositoryHostMap.cs" /> - <Compile Include="ViewModels\RepositoryCreationViewModel.cs" /> - <Compile Include="ViewModels\RepositoryCloneViewModel.cs" /> - <Compile Include="ViewModels\LoginControlViewModel.cs" /> - <Compile Include="ViewModels\LoginTabViewModel.cs" /> - <Compile Include="ViewModels\LoginToGitHubForEnterpriseViewModel.cs" /> - <Compile Include="ViewModels\LoginToGitHubViewModel.cs" /> - <Compile Include="ViewModels\RepositoryFormViewModel.cs" /> - <Compile Include="ViewModels\RepositoryPublishViewModel.cs" /> - <Compile Include="ViewModels\TwoFactorDialogViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestDirectoryNode.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestFileNode.cs" /> </ItemGroup> <ItemGroup> - <Content Include="FodyWeavers.xml"> - <SubType>Designer</SubType> - </Content> <Resource Include="Images\default_org_avatar.png" /> <Resource Include="Images\default_user_avatar.png" /> </ItemGroup> <ItemGroup> - <None Include="app.config" /> - <None Include="packages.config" /> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\submodules\akavache\Akavache.Sqlite3\Akavache.Sqlite3.csproj"> @@ -239,15 +379,14 @@ <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> <Name>Splat-Net45</Name> </ProjectReference> - <ProjectReference Include="..\..\submodules\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj"> - <Project>{EE6ED99F-CB12-4683-B055-D28FC7357A34}</Project> - <Name>LibGit2Sharp</Name> - <Private>False</Private> - </ProjectReference> <ProjectReference Include="..\CredentialManagement\CredentialManagement.csproj"> <Project>{41a47c5b-c606-45b4-b83c-22b9239e4da0}</Project> <Name>CredentialManagement</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> + <Project>{B389ADAF-62CC-486E-85B4-2D8B078DF763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> <Name>GitHub.Exports.Reactive</Name> @@ -265,30 +404,37 @@ <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Services.Vssdk\GitHub.Services.Vssdk.csproj"> + <Project>{2D3D2834-33BE-45CA-B3CC-12F853557D7B}</Project> + <Name>GitHub.Services.Vssdk</Name> + </ProjectReference> <ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj"> <Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project> <Name>GitHub.UI.Reactive</Name> </ProjectReference> - <ProjectReference Include="..\..\submodules\Rothko\src\Rothko.csproj"> - <Project>{4a84e568-ca86-4510-8cd0-90d3ef9b65f9}</Project> - <Name>Rothko</Name> - </ProjectReference> </ItemGroup> <ItemGroup> <EmbeddedResource Include="Resources.resx"> <Generator>PublicResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.cs</LastGenOutput> + <SubType>Designer</SubType> </EmbeddedResource> </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> <Error Condition="!Exists('..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets'))" /> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> </Target> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> <Import Project="..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets" Condition="Exists('..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets')" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/src/GitHub.App/GlobalSuppressions.cs b/src/GitHub.App/GlobalSuppressions.cs index 02f590d1ee..f635f8ea1d 100644 --- a/src/GitHub.App/GlobalSuppressions.cs +++ b/src/GitHub.App/GlobalSuppressions.cs @@ -3,6 +3,9 @@ [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<System.DateTimeOffset>)")] +[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 diff --git a/src/GitHub.App/Info/GitHubUrls.cs b/src/GitHub.App/Info/GitHubUrls.cs index 5db9afa582..97705bc203 100644 --- a/src/GitHub.App/Info/GitHubUrls.cs +++ b/src/GitHub.App/Info/GitHubUrls.cs @@ -94,5 +94,10 @@ public static string Billing(this IAccount account) : string.Format(CultureInfo.InvariantCulture, GitHub + "/organizations/{0}/settings/billing", account.Login); } + + /// <summary> + /// The URL for the GitHub for Visual Studio documentation. + /// </summary> + public const string Documentation = "https://github.com/github/VisualStudio/tree/master/docs"; } } diff --git a/src/GitHub.App/Infrastructure/ExportWrappers.cs b/src/GitHub.App/Infrastructure/ExportWrappers.cs index 5dfa07ff6f..a4dd184b2c 100644 --- a/src/GitHub.App/Infrastructure/ExportWrappers.cs +++ b/src/GitHub.App/Infrastructure/ExportWrappers.cs @@ -1,7 +1,10 @@ -using System.ComponentModel.Composition; +using System; +using System.ComponentModel.Composition; +using System.Net; +using GitHub.Models; +using Octokit; using Octokit.Internal; -using System; -using System.Net.Http; +using Rothko; namespace GitHub.Infrastructure { @@ -16,4 +19,23 @@ public ExportedHttpClient() : base(HttpMessageHandlerFactory.CreateDefault) {} } + + [Export(typeof(IHttpListener))] + public class ExportedHttpListener : HttpListenerWrapper + { + public ExportedHttpListener() + : base(new HttpListener()) + { + } + } + + [Export(typeof(IEnterpriseProbe))] + public class ExportedEnterpriseProbe : EnterpriseProbe + { + [ImportingConstructor] + public ExportedEnterpriseProbe(IProgram program, IHttpClient client) + : base(program.ProductHeader, client) + { + } + } } diff --git a/src/GitHub.App/Infrastructure/LoggingConfiguration.cs b/src/GitHub.App/Infrastructure/LoggingConfiguration.cs deleted file mode 100644 index d079d320bc..0000000000 --- a/src/GitHub.App/Infrastructure/LoggingConfiguration.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Globalization; -using System.IO; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Services; -using NLog; -using NLog.Config; -using NLog.Targets; -using Rothko; - -namespace GitHub.Infrastructure -{ - public interface ILoggingConfiguration - { - void Configure(); - } - [Export(typeof(ILoggingConfiguration))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class LoggingConfiguration : ILoggingConfiguration - { - // See http://nlog-project.org/wiki/Layout_Renderers for logging layout options. - const string layout = "${longdate}|${level:uppercase=true}|thread:${threadid:padding=2}|${logger:shortName=true}|${message}${onexception:inner=${newline}${exception:innerformat=ToString,StackTrace:format=ToString,StackTrace}}"; - - [ImportingConstructor] - public LoggingConfiguration(IProgram program, IOperatingSystem os, IVSServices vsservice) - { - NLog.Config.LoggingConfiguration conf; - string assemblyFolder = program.ExecutingAssemblyDirectory; - try - { - conf = new XmlLoggingConfiguration(Path.Combine(assemblyFolder, "NLog.config"), true); - } - catch (Exception ex) - { - vsservice.ActivityLogError(string.Format(CultureInfo.InvariantCulture, "Error loading nlog.config. {0}", ex)); - conf = new NLog.Config.LoggingConfiguration(); - } - - var fileTarget = conf.FindTargetByName("file") as FileTarget; - if (fileTarget == null) - { - fileTarget = new FileTarget(); - conf.AddTarget(Path.GetRandomFileName(), fileTarget); - conf.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, fileTarget)); - } - fileTarget.FileName = Path.Combine(os.Environment.GetLocalGitHubApplicationDataPath(), "extension.log"); - fileTarget.Layout = layout; - - try - { - LogManager.Configuration = conf; - } - catch (Exception ex) - { - vsservice.ActivityLogError(string.Format(CultureInfo.InvariantCulture, "Error configuring the log. {0}", ex)); - } - } - - public void Configure() - { - } - } -} diff --git a/src/GitHub.App/Models/Account.cs b/src/GitHub.App/Models/Account.cs index 2d58ca0041..09a318dc6d 100644 --- a/src/GitHub.App/Models/Account.cs +++ b/src/GitHub.App/Models/Account.cs @@ -3,7 +3,9 @@ using System.Globalization; using System.Reactive.Linq; using System.Windows.Media.Imaging; -using NullGuard; +using GitHub.Extensions; +using GitHub.Primitives; +using Octokit; using ReactiveUI; namespace GitHub.Models @@ -11,7 +13,9 @@ namespace GitHub.Models [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Account : ReactiveObject, IAccount { - readonly ObservableAsPropertyHelper<BitmapSource> avatar; + BitmapSource avatar; + IObservable<BitmapSource> bitmapSource; + IDisposable bitmapSourceSubscription; public Account( string login, @@ -19,8 +23,11 @@ public Account( bool isEnterprise, int ownedPrivateRepositoryCount, long privateRepositoryInPlanCount, + string avatarUrl, IObservable<BitmapSource> bitmapSource) { + Guard.ArgumentNotEmptyString(login, nameof(login)); + Login = login; IsUser = isUser; IsEnterprise = isEnterprise; @@ -28,9 +35,35 @@ public Account( PrivateReposInPlan = privateRepositoryInPlanCount; IsOnFreePlan = privateRepositoryInPlanCount == 0; HasMaximumPrivateRepositories = OwnedPrivateRepos >= PrivateReposInPlan; + AvatarUrl = avatarUrl; + this.bitmapSource = bitmapSource; + + bitmapSourceSubscription = bitmapSource + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => Avatar = x); + } - avatar = bitmapSource.ObserveOn(RxApp.MainThreadScheduler) - .ToProperty(this, a => a.Avatar); + public Account(Octokit.Account account) + { + Guard.ArgumentNotNull(account, nameof(account)); + + Login = account.Login; + IsUser = (account as User) != null; + Uri htmlUrl; + IsEnterprise = Uri.TryCreate(account.HtmlUrl, UriKind.Absolute, out htmlUrl) + && !HostAddress.IsGitHubDotComUri(htmlUrl); + PrivateReposInPlan = account.Plan != null ? account.Plan.PrivateRepos : 0; + OwnedPrivateRepos = account.OwnedPrivateRepos; + IsOnFreePlan = PrivateReposInPlan == 0; + HasMaximumPrivateRepositories = OwnedPrivateRepos >= PrivateReposInPlan; + AvatarUrl = account.AvatarUrl; + } + + public Account(Octokit.Account account, IObservable<BitmapSource> bitmapSource) + : this(account) + { + bitmapSource.ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => Avatar = x); } public bool IsOnFreePlan { get; private set; } @@ -47,15 +80,91 @@ public Account( public long PrivateReposInPlan { get; private set; } + public string AvatarUrl { get; set; } + public BitmapSource Avatar { - [return: AllowNull] - get + get { return avatar; } + set { avatar = value; this.RaisePropertyChanged(); } + } + + #region Equality things + public void CopyFrom(IAccount other) + { + if (!Equals(other)) + throw new ArgumentException("Instance to copy from doesn't match this instance. this:(" + this + ") other:(" + other + ")", nameof(other)); + OwnedPrivateRepos = other.OwnedPrivateRepos; + PrivateReposInPlan = other.PrivateReposInPlan; + IsOnFreePlan = other.IsOnFreePlan; + HasMaximumPrivateRepositories = other.HasMaximumPrivateRepositories; + Avatar = other.Avatar; + + var otherAccount = other as Account; + if (otherAccount != null) { - return avatar.Value; + bitmapSourceSubscription.Dispose(); + + bitmapSourceSubscription = otherAccount.bitmapSource + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => Avatar = x); } } + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + return true; + var other = obj as Account; + return other != null && Login == other.Login && IsUser == other.IsUser && IsEnterprise == other.IsEnterprise; + } + + public override int GetHashCode() + { + return (Login?.GetHashCode() ?? 0) ^ IsUser.GetHashCode() ^ IsEnterprise.GetHashCode(); + } + + bool IEquatable<IAccount>.Equals(IAccount other) + { + if (ReferenceEquals(this, other)) + return true; + return other != null && Login == other.Login && IsUser == other.IsUser && IsEnterprise == other.IsEnterprise; + } + + public int CompareTo(IAccount other) + { + return other != null ? String.Compare(Login, other.Login, StringComparison.CurrentCulture) : 1; + } + + public static bool operator >(Account lhs, Account rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return lhs?.CompareTo(rhs) > 0; + } + + public static bool operator <(Account lhs, Account rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return (object)lhs == null || lhs.CompareTo(rhs) < 0; + } + + public static bool operator ==(Account lhs, Account rhs) + { + return Equals(lhs, rhs) && ((object)lhs == null || lhs.CompareTo(rhs) == 0); + } + + public static bool operator !=(Account lhs, Account rhs) + { + return !(lhs == rhs); + } + #endregion + + public override string ToString() + { + return Login; + } + internal string DebuggerDisplay { get diff --git a/src/GitHub.App/Models/ConnectionRepositoryHostMap.cs b/src/GitHub.App/Models/ConnectionRepositoryHostMap.cs deleted file mode 100644 index a3deba2496..0000000000 --- a/src/GitHub.App/Models/ConnectionRepositoryHostMap.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.ComponentModel.Composition; -using GitHub.Models; -using GitHub.Services; - -namespace GitHub.ViewModels -{ - [Export(typeof(IConnectionRepositoryHostMap))] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class ConnectionRepositoryHostMap : IConnectionRepositoryHostMap - { - [ImportingConstructor] - public ConnectionRepositoryHostMap(IUIProvider provider, IRepositoryHosts hosts) - : this(provider.GetService<IConnection>(), hosts) - { - } - - public ConnectionRepositoryHostMap(IRepositoryHost repositoryHost) - { - CurrentRepositoryHost = repositoryHost; - } - - protected ConnectionRepositoryHostMap(IConnection connection, IRepositoryHosts hosts) - : this(hosts.LookupHost(connection.HostAddress)) - { - } - - /// <summary> - /// The current repository host. This is set in the MEF sub-container when the user clicks on an action - /// related to a host such as clone or create. - /// </summary> - public IRepositoryHost CurrentRepositoryHost { get; private set; } - } -} diff --git a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs b/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs deleted file mode 100644 index 6262c6e729..0000000000 --- a/src/GitHub.App/Models/DisconnectedRepositoryHosts.cs +++ /dev/null @@ -1,55 +0,0 @@ - -using System; -using System.Reactive; -using System.Reactive.Linq; -using GitHub.Api; -using GitHub.Authentication; -using GitHub.Primitives; -using GitHub.Services; -using NullGuard; -using ReactiveUI; - -namespace GitHub.Models -{ - public class DisconnectedRepositoryHost : ReactiveObject, IRepositoryHost - { - public DisconnectedRepositoryHost() - { - Address = HostAddress.Create(new Uri("https://null/")); - Organizations = new ReactiveList<IAccount>(); - Accounts = new ReactiveList<IAccount>(); - } - - public HostAddress Address { get; private set; } - public IApiClient ApiClient { get; private set; } - - [AllowNull] - public bool IsLoggedIn { get; private set; } - public bool IsLoggingIn { get; private set; } - public ReactiveList<IAccount> Organizations { get; private set; } - public ReactiveList<IAccount> Accounts { get; private set; } - public string Title { get; private set; } - public IAccount User { get; private set; } - public IModelService ModelService { get; private set; } - - public IObservable<AuthenticationResult> LogIn(string usernameOrEmail, string password) - { - return Observable.Return(AuthenticationResult.CredentialFailure); - } - - public IObservable<AuthenticationResult> LogInFromCache() - { - return Observable.Return(AuthenticationResult.CredentialFailure); - } - - public IObservable<Unit> LogOut() - { - return Observable.Return(Unit.Default); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - } - } -} diff --git a/src/GitHub.App/Models/IHttpListenerContext.cs b/src/GitHub.App/Models/IHttpListenerContext.cs new file mode 100644 index 0000000000..2cf98860f4 --- /dev/null +++ b/src/GitHub.App/Models/IHttpListenerContext.cs @@ -0,0 +1,154 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.App.Models +{ + public interface IHttpListenerContext + { + // + // Summary: + // Gets the System.Net.HttpListenerRequest that represents a client's request for + // a resource. + // + // Returns: + // An System.Net.HttpListenerRequest object that represents the client request. + public HttpListenerRequest Request { get; } + + // + // Summary: + // Gets the System.Net.HttpListenerResponse object that will be sent to the client + // in response to the client's request. + // + // Returns: + // An System.Net.HttpListenerResponse object used to send a response back to the + // client. + public HttpListenerResponse Response { get; } + + // + // Summary: + // Gets an object used to obtain identity, authentication information, and security + // roles for the client whose request is represented by this System.Net.HttpListenerContext + // object. + // + // Returns: + // An System.Security.Principal.IPrincipal object that describes the client, or + // null if the System.Net.HttpListener that supplied this System.Net.HttpListenerContext + // does not require authentication. + public IPrincipal User { get; } + + // + // Summary: + // Accept a WebSocket connection as an asynchronous operation. + // + // Parameters: + // subProtocol: + // The supported WebSocket sub-protocol. + // + // Returns: + // Returns System.Threading.Tasks.Task`1.The task object representing the asynchronous + // operation. The System.Threading.Tasks.Task`1.Result property on the task object + // returns an System.Net.WebSockets.HttpListenerWebSocketContext object. + // + // Exceptions: + // T:System.ArgumentException: + // subProtocol is an empty string-or- subProtocol contains illegal characters. + // + // T:System.Net.WebSockets.WebSocketException: + // An error occurred when sending the response to complete the WebSocket handshake. + public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol); + // + // Summary: + // Accept a WebSocket connection specifying the supported WebSocket sub-protocol + // and WebSocket keep-alive interval as an asynchronous operation. + // + // Parameters: + // subProtocol: + // The supported WebSocket sub-protocol. + // + // keepAliveInterval: + // The WebSocket protocol keep-alive interval in milliseconds. + // + // Returns: + // Returns System.Threading.Tasks.Task`1.The task object representing the asynchronous + // operation. The System.Threading.Tasks.Task`1.Result property on the task object + // returns an System.Net.WebSockets.HttpListenerWebSocketContext object. + // + // Exceptions: + // T:System.ArgumentException: + // subProtocol is an empty string-or- subProtocol contains illegal characters. + // + // T:System.ArgumentOutOfRangeException: + // keepAliveInterval is too small. + // + // T:System.Net.WebSockets.WebSocketException: + // An error occurred when sending the response to complete the WebSocket handshake. + public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval); + // + // Summary: + // Accept a WebSocket connection specifying the supported WebSocket sub-protocol, + // receive buffer size, and WebSocket keep-alive interval as an asynchronous operation. + // + // Parameters: + // subProtocol: + // The supported WebSocket sub-protocol. + // + // receiveBufferSize: + // The receive buffer size in bytes. + // + // keepAliveInterval: + // The WebSocket protocol keep-alive interval in milliseconds. + // + // Returns: + // Returns System.Threading.Tasks.Task`1.The task object representing the asynchronous + // operation. The System.Threading.Tasks.Task`1.Result property on the task object + // returns an System.Net.WebSockets.HttpListenerWebSocketContext object. + // + // Exceptions: + // T:System.ArgumentException: + // subProtocol is an empty string-or- subProtocol contains illegal characters. + // + // T:System.ArgumentOutOfRangeException: + // keepAliveInterval is too small.-or- receiveBufferSize is less than 16 bytes-or- + // receiveBufferSize is greater than 64K bytes. + // + // T:System.Net.WebSockets.WebSocketException: + // An error occurred when sending the response to complete the WebSocket handshake. + public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval); + // + // Summary: + // Accept a WebSocket connection specifying the supported WebSocket sub-protocol, + // receive buffer size, WebSocket keep-alive interval, and the internal buffer as + // an asynchronous operation. + // + // Parameters: + // subProtocol: + // The supported WebSocket sub-protocol. + // + // receiveBufferSize: + // The receive buffer size in bytes. + // + // keepAliveInterval: + // The WebSocket protocol keep-alive interval in milliseconds. + // + // internalBuffer: + // An internal buffer to use for this operation. + // + // Returns: + // Returns System.Threading.Tasks.Task`1.The task object representing the asynchronous + // operation. The System.Threading.Tasks.Task`1.Result property on the task object + // returns an System.Net.WebSockets.HttpListenerWebSocketContext object. + // + // Exceptions: + // T:System.ArgumentException: + // subProtocol is an empty string-or- subProtocol contains illegal characters. + // + // T:System.ArgumentOutOfRangeException: + // keepAliveInterval is too small.-or- receiveBufferSize is less than 16 bytes-or- + // receiveBufferSize is greater than 64K bytes. + // + // T:System.Net.WebSockets.WebSocketException: + // An error occurred when sending the response to complete the WebSocket handshake. + [EditorBrowsable(EditorBrowsableState.Never)] + public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment<byte> internalBuffer); + } +} diff --git a/src/GitHub.App/Models/PullRequestDetailArgument.cs b/src/GitHub.App/Models/PullRequestDetailArgument.cs new file mode 100644 index 0000000000..3710e75a68 --- /dev/null +++ b/src/GitHub.App/Models/PullRequestDetailArgument.cs @@ -0,0 +1,22 @@ +using System; +using GitHub.ViewModels; +using GitHub.Primitives; + +namespace GitHub.Models +{ + /// <summary> + /// Passes arguments to a <see cref="PullRequestDetailViewModel"/> + /// </summary> + public class PullRequestDetailArgument + { + /// <summary> + /// Gets or sets the owner of the repository containing the pull request. + /// </summary> + public string RepositoryOwner { get; set; } + + /// <summary> + /// Gets or sets the number of the pull request. + /// </summary> + public int Number { get; set; } + } +} diff --git a/src/GitHub.App/Models/PullRequestModel.cs b/src/GitHub.App/Models/PullRequestModel.cs index aac3514e36..6a560fa2ea 100644 --- a/src/GitHub.App/Models/PullRequestModel.cs +++ b/src/GitHub.App/Models/PullRequestModel.cs @@ -2,13 +2,19 @@ using System.Globalization; using GitHub.Primitives; using GitHub.VisualStudio.Helpers; -using NullGuard; +using System.Diagnostics; +using System.Collections.Generic; +using GitHub.Extensions; namespace GitHub.Models { - public sealed class PullRequestModel : NotificationAwareObject, IPullRequestModel + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public sealed class PullRequestModel : NotificationAwareObject, IPullRequestModel, + IEquatable<PullRequestModel>, + IComparable<PullRequestModel> { - public PullRequestModel(int number, string title, IAccount author, DateTimeOffset createdAt, DateTimeOffset? updatedAt = null) + public PullRequestModel(int number, string title, IAccount author, + DateTimeOffset createdAt, DateTimeOffset? updatedAt = null) { Number = number; Title = title; @@ -22,12 +28,14 @@ public void CopyFrom(IPullRequestModel other) if (!Equals(other)) throw new ArgumentException("Instance to copy from doesn't match this instance. this:(" + this + ") other:(" + other + ")", nameof(other)); Title = other.Title; + State = other.State; UpdatedAt = other.UpdatedAt; CommentCount = other.CommentCount; HasNewComments = other.HasNewComments; + Assignee = other.Assignee; } - public override bool Equals([AllowNull]object obj) + public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) return true; @@ -37,41 +45,53 @@ public override bool Equals([AllowNull]object obj) public override int GetHashCode() { - return Number; + return Number.GetHashCode(); } - bool IEquatable<IPullRequestModel>.Equals([AllowNull]IPullRequestModel other) + bool IEquatable<IPullRequestModel>.Equals(IPullRequestModel other) { if (ReferenceEquals(this, other)) return true; return other != null && Number == other.Number; } - public int CompareTo([AllowNull]IPullRequestModel other) + bool IEquatable<PullRequestModel>.Equals(PullRequestModel other) + { + if (ReferenceEquals(this, other)) + return true; + return other != null && Number == other.Number; + } + + public int CompareTo(IPullRequestModel other) { return other != null ? UpdatedAt.CompareTo(other.UpdatedAt) : 1; } - public static bool operator >([AllowNull]PullRequestModel lhs, [AllowNull]PullRequestModel rhs) + public int CompareTo(PullRequestModel other) + { + return other != null ? UpdatedAt.CompareTo(other.UpdatedAt) : 1; + } + + public static bool operator >(PullRequestModel lhs, PullRequestModel rhs) { if (ReferenceEquals(lhs, rhs)) return false; return lhs?.CompareTo(rhs) > 0; } - public static bool operator <([AllowNull]PullRequestModel lhs, [AllowNull]PullRequestModel rhs) + public static bool operator <(PullRequestModel lhs, PullRequestModel rhs) { if (ReferenceEquals(lhs, rhs)) return false; return (object)lhs == null || lhs.CompareTo(rhs) < 0; } - public static bool operator ==([AllowNull]PullRequestModel lhs, [AllowNull]PullRequestModel rhs) + public static bool operator ==(PullRequestModel lhs, PullRequestModel rhs) { - return Equals(lhs, rhs) && ((object)lhs == null || lhs.CompareTo(rhs) == 0); + return ReferenceEquals(lhs, rhs); } - public static bool operator !=([AllowNull]PullRequestModel lhs, [AllowNull]PullRequestModel rhs) + public static bool operator !=(PullRequestModel lhs, PullRequestModel rhs) { return !(lhs == rhs); } @@ -82,9 +102,33 @@ public int CompareTo([AllowNull]IPullRequestModel other) public string Title { get { return title; } - set { title = value; this.RaisePropertyChange(); } + set + { + Guard.ArgumentNotNull(value, nameof(value)); + title = value; + this.RaisePropertyChange(); + } + } + + PullRequestStateEnum status; + public PullRequestStateEnum State + { + get { return status; } + set + { + status = value; + this.RaisePropertyChange(); + + // TODO: These notifications will be removed once maintainer workflow has been merged to master. + this.RaisePropertyChange(nameof(IsOpen)); + this.RaisePropertyChange(nameof(Merged)); + } } + // TODO: Remove these property once maintainer workflow has been merged to master. + public bool IsOpen => State == PullRequestStateEnum.Open; + public bool Merged => State == PullRequestStateEnum.Merged; + int commentCount; public int CommentCount { @@ -92,6 +136,13 @@ public int CommentCount set { commentCount = value; this.RaisePropertyChange(); } } + int commitCount; + public int CommitCount + { + get { return commitCount; } + set { commitCount = value; this.RaisePropertyChange(); } + } + bool hasNewComments; public bool HasNewComments { @@ -99,15 +150,37 @@ public bool HasNewComments set { hasNewComments = value; this.RaisePropertyChange(); } } + string body; + public string Body + { + get { return body; } + set { body = value; this.RaisePropertyChange(); } + } + + public GitReferenceModel Base { get; set; } + public GitReferenceModel Head { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; } public IAccount Author { get; set; } + IAccount assignee; + public IAccount Assignee + { + get { return assignee; } + set { assignee = value; this.RaisePropertyChange(); } + } - [return: AllowNull] // nullguard thinks a string.Format can return null. sigh. public override string ToString() { return string.Format(CultureInfo.InvariantCulture, "id:{0} title:{1} created:{2:u} updated:{3:u}", Number, Title, CreatedAt, UpdatedAt); } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "id:{0} title:{1} created:{2:u} updated:{3:u}", Number, Title, CreatedAt, UpdatedAt); + } + } } } diff --git a/src/GitHub.App/Models/RemoteRepositoryModel.cs b/src/GitHub.App/Models/RemoteRepositoryModel.cs new file mode 100644 index 0000000000..64ec5bda4a --- /dev/null +++ b/src/GitHub.App/Models/RemoteRepositoryModel.cs @@ -0,0 +1,173 @@ +using GitHub.Primitives; +using System; +using System.Globalization; +using GitHub.Extensions; + +namespace GitHub.Models +{ + /// <summary> + /// A repository read from the GitHub API. + /// </summary> + public class RemoteRepositoryModel : RepositoryModel, IRemoteRepositoryModel, + IEquatable<RemoteRepositoryModel>, IComparable<RemoteRepositoryModel> + { + /// <summary> + /// Initializes a new instance of the <see cref="RemoteRepositoryModel"/> class. + /// </summary> + /// <param name="id">The API ID of the repository.</param> + /// <param name="name">The repository name.</param> + /// <param name="cloneUrl">The repository's clone URL.</param> + /// <param name="isPrivate">Whether the repository is private.</param> + /// <param name="isFork">Whether the repository is a fork.</param> + /// <param name="ownerAccount">The repository owner account.</param> + /// <param name="parent">The parent repository if this repository is a fork.</param> + public RemoteRepositoryModel(long id, string name, UriString cloneUrl, bool isPrivate, bool isFork, IAccount ownerAccount, IRemoteRepositoryModel parent) + : base(name, cloneUrl) + { + Guard.ArgumentNotEmptyString(name, nameof(name)); + Guard.ArgumentNotNull(ownerAccount, nameof(ownerAccount)); + + Id = id; + OwnerAccount = ownerAccount; + IsFork = isFork; + SetIcon(isPrivate, isFork); + // this is an assumption, we'd have to load the repo information from octokit to know for sure + // probably not worth it for this ctor + DefaultBranch = new BranchModel("master", this); + Parent = parent; + } + + /// <summary> + /// Initializes a new instance of the <see cref="RemoteRepositoryModel"/> class. + /// </summary> + /// <param name="repository">The source octokit repository.</param> + public RemoteRepositoryModel(Octokit.Repository repository) + : base(repository.Name, repository.CloneUrl) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + + Id = repository.Id; + IsFork = repository.Fork; + SetIcon(repository.Private, IsFork); + OwnerAccount = new Account(repository.Owner); + DefaultBranch = new BranchModel(repository.DefaultBranch, this); + Parent = repository.Parent != null ? new RemoteRepositoryModel(repository.Parent) : null; + if (Parent != null) + Parent.DefaultBranch.DisplayName = Parent.DefaultBranch.Id; + } + +#region Equality Things + public void CopyFrom(IRemoteRepositoryModel other) + { + if (!Equals(other)) + throw new ArgumentException("Instance to copy from doesn't match this instance. this:(" + this + ") other:(" + other + ")", nameof(other)); + Icon = other.Icon; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + return true; + var other = obj as RemoteRepositoryModel; + return Equals(other); + } + + public bool Equals(IRemoteRepositoryModel other) + { + if (ReferenceEquals(this, other)) + return true; + return other != null && Id == other.Id; + } + + public bool Equals(RemoteRepositoryModel other) + { + if (ReferenceEquals(this, other)) + return true; + return other != null && Id == other.Id; + } + + public int CompareTo(IRemoteRepositoryModel other) + { + return other != null ? UpdatedAt.CompareTo(other.UpdatedAt) : 1; + } + + public int CompareTo(RemoteRepositoryModel other) + { + return other != null ? UpdatedAt.CompareTo(other.UpdatedAt) : 1; + } + + public static bool operator >(RemoteRepositoryModel lhs, RemoteRepositoryModel rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return lhs?.CompareTo(rhs) > 0; + } + + public static bool operator <(RemoteRepositoryModel lhs, RemoteRepositoryModel rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return (object)lhs == null || lhs.CompareTo(rhs) < 0; + } + + public static bool operator ==(RemoteRepositoryModel lhs, RemoteRepositoryModel rhs) + { + return ReferenceEquals(lhs, rhs); + } + + public static bool operator !=(RemoteRepositoryModel lhs, RemoteRepositoryModel rhs) + { + return !(lhs == rhs); + } +#endregion + + /// <summary> + /// Gets the account that is the ower of the repository. + /// </summary> + public IAccount OwnerAccount { get; } + + /// <summary> + /// Gets the repository's API ID. + /// </summary> + public long Id { get; } + + /// <summary> + /// Gets the date and time at which the repository was created. + /// </summary> + public DateTimeOffset CreatedAt { get; set; } + + /// <summary> + /// Gets the repository's last update date and time. + /// </summary> + public DateTimeOffset UpdatedAt { get; set; } + + /// <summary> + /// Gets a value indicating whether the repository is a fork. + /// </summary> + public bool IsFork { get; } + + /// <summary> + /// Gets the repository from which this repository was forked, if any. + /// </summary> + public IRemoteRepositoryModel Parent { get; } + + /// <summary> + /// Gets the default branch for the repository. + /// </summary> + public IBranch DefaultBranch { get; } + + internal string DebuggerDisplay + { + get + { + return String.Format(CultureInfo.InvariantCulture, + "{4}\tId: {0} Name: {1} CloneUrl: {2} Account: {3}", Id, Name, CloneUrl, Owner, GetHashCode()); + } + } + } +} diff --git a/src/GitHub.App/Models/RepositoryHost.cs b/src/GitHub.App/Models/RepositoryHost.cs deleted file mode 100644 index 84d9a0c4ba..0000000000 --- a/src/GitHub.App/Models/RepositoryHost.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Net; -using System.Reactive; -using System.Reactive.Linq; -using GitHub.Api; -using GitHub.Authentication; -using GitHub.Caches; -using GitHub.Extensions.Reactive; -using GitHub.Primitives; -using GitHub.Services; -using NLog; -using Octokit; -using ReactiveUI; - -namespace GitHub.Models -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class RepositoryHost : ReactiveObject, IRepositoryHost - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - static readonly AccountCacheItem unverifiedUser = new AccountCacheItem(); - - readonly ITwoFactorChallengeHandler twoFactorChallengeHandler; - readonly HostAddress hostAddress; - readonly ILoginCache loginCache; - - bool isLoggedIn; - readonly bool isEnterprise; - - public RepositoryHost( - IApiClient apiClient, - IModelService modelService, - ILoginCache loginCache, - ITwoFactorChallengeHandler twoFactorChallengeHandler) - { - ApiClient = apiClient; - ModelService = modelService; - this.loginCache = loginCache; - this.twoFactorChallengeHandler = twoFactorChallengeHandler; - - Debug.Assert(apiClient.HostAddress != null, "HostAddress of an api client shouldn't be null"); - Address = apiClient.HostAddress; - hostAddress = apiClient.HostAddress; - isEnterprise = !hostAddress.IsGitHubDotCom(); - Title = apiClient.HostAddress.Title; - } - - public HostAddress Address { get; private set; } - - public IApiClient ApiClient { get; private set; } - - public bool IsLoggedIn - { - get { return isLoggedIn; } - private set { this.RaiseAndSetIfChanged(ref isLoggedIn, value); } - } - - public string Title { get; private set; } - - [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] - public IObservable<AuthenticationResult> LogInFromCache() - { - return GetUserFromApi() - .ObserveOn(RxApp.MainThreadScheduler) - .Catch<AccountCacheItem, Exception>(ex => - { - if (ex is AuthorizationException) - { - log.Warn("Got an authorization exception", ex); - return Observable.Return<AccountCacheItem>(null); - } - return ModelService.GetUserFromCache() - .Catch<AccountCacheItem, Exception>(e => - { - log.Warn("User does not exist in cache", e); - return Observable.Return<AccountCacheItem>(null); - }) - .ObserveOn(RxApp.MainThreadScheduler); - }) - .SelectMany(LoginWithApiUser) - .PublishAsync(); - } - - public IObservable<AuthenticationResult> LogIn(string usernameOrEmail, string password) - { - Guard.ArgumentNotEmptyString(usernameOrEmail, nameof(usernameOrEmail)); - Guard.ArgumentNotEmptyString(password, nameof(password)); - - // If we need to retry on fallback, we'll store the 2FA token - // from the first request to re-use: - string authenticationCode = null; - - // We need to intercept the 2FA handler to get the token: - var interceptingTwoFactorChallengeHandler = - new Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>(ex => - twoFactorChallengeHandler.HandleTwoFactorException(ex) - .Do(twoFactorChallengeResult => - authenticationCode = twoFactorChallengeResult.AuthenticationCode)); - - // Keep the function to save the authorization token here because it's used - // in multiple places in the chain below: - var saveAuthorizationToken = new Func<ApplicationAuthorization, IObservable<Unit>>(authorization => - { - var token = authorization?.Token; - if (string.IsNullOrWhiteSpace(token)) - return Observable.Return(Unit.Default); - - return loginCache.SaveLogin(usernameOrEmail, token, Address) - .ObserveOn(RxApp.MainThreadScheduler); - }); - - // Start be saving the username and password, as they will be used for older versions of Enterprise - // that don't support authorization tokens, and for the API client to use until an authorization - // token has been created and acquired: - return loginCache.SaveLogin(usernameOrEmail, password, Address) - .ObserveOn(RxApp.MainThreadScheduler) - // Try to get an authorization token, save it, then get the user to log in: - .SelectMany(fingerprint => ApiClient.GetOrCreateApplicationAuthenticationCode(interceptingTwoFactorChallengeHandler)) - .SelectMany(saveAuthorizationToken) - .SelectMany(_ => GetUserFromApi()) - .Catch<AccountCacheItem, ApiException>(firstTryEx => - { - var exception = firstTryEx as AuthorizationException; - if (isEnterprise - && exception != null - && exception.Message == "Bad credentials") - { - return Observable.Throw<AccountCacheItem>(exception); - } - - // If the Enterprise host doesn't support the write:public_key scope, it'll return a 422. - // EXCEPT, there's a bug where it doesn't, and instead creates a bad token, and in - // that case we'd get a 401 here from the GetUser invocation. So to be safe (and consistent - // with the Mac app), we'll just retry after any API error for Enterprise hosts: - if (isEnterprise && !(firstTryEx is TwoFactorChallengeFailedException)) - { - // Because we potentially have a bad authorization token due to the Enterprise bug, - // we need to reset to using username and password authentication: - return loginCache.SaveLogin(usernameOrEmail, password, Address) - .ObserveOn(RxApp.MainThreadScheduler) - .SelectMany(_ => - { - // Retry with the old scopes. If we have a stashed 2FA token, we use it: - if (authenticationCode != null) - { - return ApiClient.GetOrCreateApplicationAuthenticationCode( - interceptingTwoFactorChallengeHandler, - authenticationCode, - useOldScopes: true, - useFingerprint: false); - } - - // Otherwise, we use the default handler: - return ApiClient.GetOrCreateApplicationAuthenticationCode( - interceptingTwoFactorChallengeHandler, - useOldScopes: true, - useFingerprint: false); - }) - // Then save the authorization token (if there is one) and get the user: - .SelectMany(saveAuthorizationToken) - .SelectMany(_ => GetUserFromApi()); - } - - return Observable.Throw<AccountCacheItem>(firstTryEx); - }) - .Catch<AccountCacheItem, ApiException>(retryEx => - { - // Older Enterprise hosts either don't have the API end-point to PUT an authorization, or they - // return 422 because they haven't white-listed our client ID. In that case, we just ignore - // the failure, using basic authentication (with username and password) instead of trying - // to get an authorization token. - // Since enterprise 2.1 and https://github.com/github/github/pull/36669 the API returns 403 - // instead of 404 to signal that it's not allowed. In the name of backwards compatibility we - // test for both 404 (NotFoundException) and 403 (ForbiddenException) here. - if (isEnterprise && (retryEx is NotFoundException || retryEx is ForbiddenException || retryEx.StatusCode == (HttpStatusCode)422)) - return GetUserFromApi(); - - // Other errors are "real" so we pass them along: - return Observable.Throw<AccountCacheItem>(retryEx); - }) - .ObserveOn(RxApp.MainThreadScheduler) - .Catch<AccountCacheItem, Exception>(ex => - { - // If we get here, we have an actual login failure: - if (ex is TwoFactorChallengeFailedException) - { - return Observable.Return(unverifiedUser); - } - if (ex is AuthorizationException) - { - return Observable.Return(default(AccountCacheItem)); - } - return Observable.Throw<AccountCacheItem>(ex); - }) - .SelectMany(LoginWithApiUser) - .PublishAsync(); - } - - public IObservable<Unit> LogOut() - { - if (!IsLoggedIn) return Observable.Return(Unit.Default); - - log.Info(CultureInfo.InvariantCulture, "Logged off of host '{0}'", hostAddress.ApiUri); - - return loginCache.EraseLogin(Address) - .Catch<Unit, Exception>(e => - { - log.Warn("ASSERT! Failed to erase login. Going to invalidate cache anyways.", e); - return Observable.Return(Unit.Default); - }) - .SelectMany(_ => ModelService.InvalidateAll()) - .Catch<Unit, Exception>(e => - { - log.Warn("ASSERT! Failed to invaldiate caches", e); - return Observable.Return(Unit.Default); - }) - .ObserveOn(RxApp.MainThreadScheduler) - .Finally(() => - { - IsLoggedIn = false; - }); - } - - static IObservable<AuthenticationResult> GetAuthenticationResultForUser(AccountCacheItem account) - { - return Observable.Return(account == null ? AuthenticationResult.CredentialFailure - : account == unverifiedUser - ? AuthenticationResult.VerificationFailure - : AuthenticationResult.Success); - } - - IObservable<AuthenticationResult> LoginWithApiUser(AccountCacheItem user) - { - return GetAuthenticationResultForUser(user) - .SelectMany(result => - { - if (result.IsSuccess()) - { - return ModelService.InsertUser(user).Select(_ => result); - } - - if (result == AuthenticationResult.VerificationFailure) - { - return loginCache.EraseLogin(Address).Select(_ => result); - } - return Observable.Return(result); - }) - .ObserveOn(RxApp.MainThreadScheduler) - .Do(result => - { - if (result.IsSuccess()) - { - IsLoggedIn = true; - } - - log.Info("Log in from cache for login '{0}' to host '{1}' {2}", - user != null ? user.Login : "(null)", - hostAddress.ApiUri, - result.IsSuccess() ? "SUCCEEDED" : "FAILED"); - }); - } - - IObservable<AccountCacheItem> GetUserFromApi() - { - return Observable.Defer(() => ApiClient.GetUser().WhereNotNull() - .Select(user => new AccountCacheItem(user))); - } - - bool disposed; - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - try - { - ModelService.Dispose(); - } - catch (Exception e) - { - log.Warn("Exception occured while disposing RepositoryHost's ModelService", e); - } - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - internal string DebuggerDisplay - { - get - { - return string.Format(CultureInfo.InvariantCulture, "RepositoryHost: {0} {1}", Title, hostAddress.ApiUri); - } - } - - public IModelService ModelService - { - get; - private set; - } - } -} diff --git a/src/GitHub.App/Models/RepositoryHosts.cs b/src/GitHub.App/Models/RepositoryHosts.cs deleted file mode 100644 index c30b73cad9..0000000000 --- a/src/GitHub.App/Models/RepositoryHosts.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Globalization; -using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Akavache; -using GitHub.Authentication; -using GitHub.Caches; -using GitHub.Extensions.Reactive; -using GitHub.Factories; -using GitHub.Primitives; -using NullGuard; -using ReactiveUI; - -namespace GitHub.Models -{ - [Export(typeof(IRepositoryHosts))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class RepositoryHosts : ReactiveObject, IRepositoryHosts - { - static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); - - public static DisconnectedRepositoryHost DisconnectedRepositoryHost = new DisconnectedRepositoryHost(); - public const string EnterpriseHostApiBaseUriCacheKey = "enterprise-host-api-base-uri"; - readonly ObservableAsPropertyHelper<bool> isLoggedInToAnyHost; - readonly IConnectionManager connectionManager; - readonly CompositeDisposable disposables = new CompositeDisposable(); - - [ImportingConstructor] - public RepositoryHosts( - IRepositoryHostFactory repositoryHostFactory, - ISharedCache sharedCache, - IConnectionManager connectionManager) - { - this.connectionManager = connectionManager; - - RepositoryHostFactory = repositoryHostFactory; - disposables.Add(repositoryHostFactory); - GitHubHost = DisconnectedRepositoryHost; - EnterpriseHost = DisconnectedRepositoryHost; - - var initialCacheLoadObs = sharedCache.UserAccount.GetObject<Uri>(EnterpriseHostApiBaseUriCacheKey) - .Catch<Uri, KeyNotFoundException>(_ => Observable.Return<Uri>(null)) - .Catch<Uri, Exception>(ex => - { - log.Warn("Failed to get Enterprise host URI from cache.", ex); - return Observable.Return<Uri>(null); - }) - .WhereNotNull() - .Select(HostAddress.Create) - .Where(x => connectionManager.Connections.Any(c => c.HostAddress.Equals(x))) - .Select(repositoryHostFactory.Create) - .Do(x => EnterpriseHost = x) - .Do(disposables.Add) - .SelectUnit(); - - var persistEntepriseHostObs = this.WhenAny(x => x.EnterpriseHost, x => x.Value) - .Skip(1) // The first value will be null or something already in the db - .SelectMany(enterpriseHost => - { - if (!enterpriseHost.IsLoggedIn) - { - return sharedCache.UserAccount - .InvalidateObject<Uri>(EnterpriseHostApiBaseUriCacheKey) - .Catch<Unit, Exception>(ex => - { - log.Warn("Failed to invalidate enterprise host uri", ex); - return Observable.Return(Unit.Default); - }); - } - - return sharedCache.UserAccount - .InsertObject(EnterpriseHostApiBaseUriCacheKey, enterpriseHost.Address.ApiUri) - .Catch<Unit, Exception>(ex => - { - log.Warn("Failed to persist enterprise host uri", ex); - return Observable.Return(Unit.Default); - }); - }); - - isLoggedInToAnyHost = this.WhenAny( - x => x.GitHubHost.IsLoggedIn, - x => x.EnterpriseHost.IsLoggedIn, - (githubLoggedIn, enterpriseLoggedIn) => githubLoggedIn.Value || enterpriseLoggedIn.Value) - .ToProperty(this, x => x.IsLoggedInToAnyHost); - - // This part is strictly to support having the IConnectionManager request that a connection - // be logged in. It doesn't know about hosts or load anything reactive, so it gets - // informed of logins by an observable returned by the event - connectionManager.DoLogin += RunLoginHandler; - - // monitor the list of connections so we can log out hosts when connections are removed - disposables.Add( - connectionManager.Connections.CreateDerivedCollection(x => x) - .ItemsRemoved - .Select(x => - { - var host = LookupHost(x.HostAddress); - if (host.Address != x.HostAddress) - { - host = RepositoryHostFactory.Create(x.HostAddress); - } - return host; - }) - .Select(h => LogOut(h)) - .Merge().ToList().Select(_ => Unit.Default).Subscribe()); - - - // Wait until we've loaded (or failed to load) an enterprise uri from the db and then - // start tracking changes to the EnterpriseHost property and persist every change to the db - disposables.Add(Observable.Concat(initialCacheLoadObs, persistEntepriseHostObs).Subscribe()); - } - - IObservable<IConnection> RunLoginHandler(IConnection connection) - { - var handler = new ReplaySubject<IConnection>(); - var address = connection.HostAddress; - var host = LookupHost(address); - if (host == DisconnectedRepositoryHost) - LogInFromCache(address) - .Subscribe(c => handler.OnNext(connection), () => handler.OnCompleted()); - else - { - handler.OnNext(connection); - handler.OnCompleted(); - } - return handler; - } - - public IRepositoryHost LookupHost([AllowNull] HostAddress address) - { - if (address == GitHubHost.Address) - return GitHubHost; - if (address == EnterpriseHost.Address) - return EnterpriseHost; - return DisconnectedRepositoryHost; - } - - public IObservable<AuthenticationResult> LogIn( - HostAddress address, - string usernameOrEmail, - string password) - { - var isDotCom = HostAddress.GitHubDotComHostAddress == address; - var host = RepositoryHostFactory.Create(address); - disposables.Add(host); - return host.LogIn(usernameOrEmail, password) - .Catch<AuthenticationResult, Exception>(Observable.Throw<AuthenticationResult>) - .Do(result => - { - bool successful = result.IsSuccess(); - log.Info(CultureInfo.InvariantCulture, "Log in to {3} host '{0}' with username '{1}' {2}", - address.ApiUri, - usernameOrEmail, - successful ? "SUCCEEDED" : "FAILED", - isDotCom ? "GitHub.com" : address.WebUri.Host - ); - if (successful) - { - if (isDotCom) - GitHubHost = host; - else - EnterpriseHost = host; - connectionManager.AddConnection(address, usernameOrEmail); - } - }); - } - - /// <summary> - /// This is only called by the connection manager when logging in connections - /// that already exist so we don't have to add the connection. - /// </summary> - /// <param name="address"></param> - /// <returns></returns> - public IObservable<AuthenticationResult> LogInFromCache(HostAddress address) - { - var isDotCom = HostAddress.GitHubDotComHostAddress == address; - var host = RepositoryHostFactory.Create(address); - disposables.Add(host); - return host.LogInFromCache() - .Catch<AuthenticationResult, Exception>(Observable.Throw<AuthenticationResult>) - .Do(result => - { - bool successful = result.IsSuccess(); - if (successful) - { - if (isDotCom) - GitHubHost = host; - else - EnterpriseHost = host; - } - }); - } - - public IObservable<Unit> LogOut(IRepositoryHost host) - { - var address = host.Address; - var isDotCom = HostAddress.GitHubDotComHostAddress == address; - return host.LogOut() - .Do(result => - { - // reset the logged out host property to null - // it'll know what to do - if (isDotCom) - GitHubHost = null; - else - EnterpriseHost = null; - connectionManager.RemoveConnection(address); - disposables.Remove(host); - }); - } - - bool disposed; - protected void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - try - { - connectionManager.DoLogin -= RunLoginHandler; - disposables.Dispose(); - } - catch (Exception e) - { - log.Warn("Exception occured while disposing RepositoryHosts", e); - } - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - IRepositoryHost githubHost; - [AllowNull] - public IRepositoryHost GitHubHost - { - get { return githubHost; } - private set - { - var newHost = value ?? DisconnectedRepositoryHost; - this.RaiseAndSetIfChanged(ref githubHost, newHost); - } - } - - IRepositoryHost enterpriseHost; - [AllowNull] - public IRepositoryHost EnterpriseHost - { - get { return enterpriseHost; } - set - { - var newHost = value ?? DisconnectedRepositoryHost; - this.RaiseAndSetIfChanged(ref enterpriseHost, newHost); - } - } - - public IRepositoryHostFactory RepositoryHostFactory { get; private set; } - - public bool IsLoggedInToAnyHost { get { return isLoggedInToAnyHost.Value; } } - } -} \ No newline at end of file diff --git a/src/GitHub.App/Models/RepositoryModel.cs b/src/GitHub.App/Models/RepositoryModel.cs deleted file mode 100644 index 5260eea2b5..0000000000 --- a/src/GitHub.App/Models/RepositoryModel.cs +++ /dev/null @@ -1,47 +0,0 @@ -using GitHub.Primitives; -using NullGuard; -using System; -using System.Globalization; - -namespace GitHub.Models -{ - public class RepositoryModel : SimpleRepositoryModel, IRepositoryModel, IEquatable<RepositoryModel> - { - public RepositoryModel(string name, UriString cloneUrl, bool isPrivate, bool isFork, IAccount ownerAccount) - : base(name, cloneUrl) - { - Owner = ownerAccount; - SetIcon(isPrivate, isFork); - } - - public IAccount Owner { get; private set; } - public override int GetHashCode() - { - return (Owner?.GetHashCode() ?? 0) ^ base.GetHashCode(); - } - - public override bool Equals([AllowNull]object obj) - { - if (ReferenceEquals(this, obj)) - return true; - var other = obj as RepositoryModel; - return other != null && Equals(Owner, other.Owner) && base.Equals(obj); - } - - bool IEquatable<RepositoryModel>.Equals([AllowNull]RepositoryModel other) - { - if (ReferenceEquals(this, other)) - return true; - return other != null && Equals(Owner, other.Owner) && base.Equals(other as SimpleRepositoryModel); - } - - internal string DebuggerDisplay - { - get - { - return String.Format(CultureInfo.InvariantCulture, - "{4}\tName: {0} CloneUrl: {1} LocalPath: {2} Account: {3}", Name, CloneUrl, LocalPath, Owner, GetHashCode()); - } - } - } -} diff --git a/src/GitHub.App/Properties/AssemblyInfo.cs b/src/GitHub.App/Properties/AssemblyInfo.cs index 07fe8b166e..78e12d3418 100644 --- a/src/GitHub.App/Properties/AssemblyInfo.cs +++ b/src/GitHub.App/Properties/AssemblyInfo.cs @@ -1,6 +1,12 @@ using System.Reflection; using System.Runtime.InteropServices; +using System.Windows.Markup; [assembly: AssemblyTitle("GitHub.App")] [assembly: AssemblyDescription("Provides the view models for the GitHub for Visual Studio extension")] [assembly: Guid("a8b9a236-d238-4733-b116-716872a1e8e0")] + +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.SampleData")] +[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.GitHubPane")] diff --git a/src/GitHub.App/Resources.Designer.cs b/src/GitHub.App/Resources.Designer.cs index d29a014386..67ebbf80fb 100644 --- a/src/GitHub.App/Resources.Designer.cs +++ b/src/GitHub.App/Resources.Designer.cs @@ -8,7 +8,7 @@ // </auto-generated> //------------------------------------------------------------------------------ -namespace GitHub { +namespace GitHub.App { using System; @@ -19,7 +19,7 @@ namespace GitHub { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -39,7 +39,7 @@ internal Resources() { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GitHub.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GitHub.App.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -60,6 +60,24 @@ internal Resources() { } } + /// <summary> + /// Looks up a localized string similar to add. + /// </summary> + public static string AddedFileStatus { + get { + return ResourceManager.GetString("AddedFileStatus", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Approved. + /// </summary> + public static string Approved { + get { + return ResourceManager.GetString("Approved", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Select a containing folder for your new repository.. /// </summary> @@ -69,6 +87,33 @@ public static string BrowseForDirectory { } } + /// <summary> + /// Looks up a localized string similar to Are you sure you want to cancel this review? You will lose all your pending comments.. + /// </summary> + public static string CancelPendingReviewConfirmation { + get { + return ResourceManager.GetString("CancelPendingReviewConfirmation", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cancel Review. + /// </summary> + public static string CancelPendingReviewConfirmationCaption { + get { + return ResourceManager.GetString("CancelPendingReviewConfirmationCaption", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Changes Requested. + /// </summary> + public static string ChangesRequested { + get { + return ResourceManager.GetString("ChangesRequested", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Clone a {0} Repository. /// </summary> @@ -78,6 +123,45 @@ public static string CloneTitle { } } + /// <summary> + /// Looks up a localized string similar to Commented. + /// </summary> + public static string Commented { + get { + return ResourceManager.GetString("Commented", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Could not connect to github.com. + /// </summary> + public static string CouldNotConnectToGitHub { + get { + return ResourceManager.GetString("CouldNotConnectToGitHub", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Couldn't find Git.exe on PATH. + /// + ///Please install Git for Windows from: + ///https://git-scm.com/download/win. + /// </summary> + public static string CouldntFindGitOnPath { + get { + return ResourceManager.GetString("CouldntFindGitOnPath", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Create a GitHub Gist. + /// </summary> + public static string CreateGistTitle { + get { + return ResourceManager.GetString("CreateGistTitle", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Create a {0} Repository. /// </summary> @@ -87,6 +171,15 @@ public static string CreateTitle { } } + /// <summary> + /// Looks up a localized string similar to GistFromVisualStudio.cs. + /// </summary> + public static string DefaultGistFileName { + get { + return ResourceManager.GetString("DefaultGistFileName", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Please enter an Enterprise URL. /// </summary> @@ -115,7 +208,52 @@ public static string EnterpriseUrlValidatorNotAGitHubHost { } /// <summary> - /// Looks up a localized string similar to Make sure to use your password and not a Personal Access token to log in.. + /// Looks up a localized string similar to (forgot your password?). + /// </summary> + public static string ForgotPasswordLink { + get { + return ResourceManager.GetString("ForgotPasswordLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to fork. + /// </summary> + public static string Fork { + get { + return ResourceManager.GetString("Fork", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Fork Repository. + /// </summary> + public static string ForkRepositoryTitle { + get { + return ResourceManager.GetString("ForkRepositoryTitle", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to InProgress. + /// </summary> + public static string InProgress { + get { + return ResourceManager.GetString("InProgress", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to [invalid]. + /// </summary> + public static string InvalidBranchName { + get { + return ResourceManager.GetString("InvalidBranchName", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Make sure to use your password and not a Personal Access token to sign in.. /// </summary> public static string LoginFailedForbiddenMessage { get { @@ -132,6 +270,15 @@ public static string LoginFailedMessage { } } + /// <summary> + /// Looks up a localized string similar to Sign in failed.. + /// </summary> + public static string LoginFailedText { + get { + return ResourceManager.GetString("LoginFailedText", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Connect To GitHub. /// </summary> @@ -141,6 +288,69 @@ public static string LoginTitle { } } + /// <summary> + /// Looks up a localized string similar to You must pull before you can push. + /// </summary> + public static string MustPullBeforePush { + get { + return ResourceManager.GetString("MustPullBeforePush", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Checkout PR branch before navigating to Editor. + /// </summary> + public static string NavigateToEditorNotCheckedOutInfoMessage { + get { + return ResourceManager.GetString("NavigateToEditorNotCheckedOutInfoMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Press Enter to navigate to Editor (PR branch must be checked out). + /// </summary> + public static string NavigateToEditorNotCheckedOutStatusMessage { + get { + return ResourceManager.GetString("NavigateToEditorNotCheckedOutStatusMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Press Enter to navigate to Editor. + /// </summary> + public static string NavigateToEditorStatusMessage { + get { + return ResourceManager.GetString("NavigateToEditorStatusMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No commits to pull. + /// </summary> + public static string NoCommitsToPull { + get { + return ResourceManager.GetString("NoCommitsToPull", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No commits to push. + /// </summary> + public static string NoCommitsToPush { + get { + return ResourceManager.GetString("NoCommitsToPush", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to *No description provided.*. + /// </summary> + public static string NoDescriptionProvidedMarkdown { + get { + return ResourceManager.GetString("NoDescriptionProvidedMarkdown", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Please enter your password. /// </summary> @@ -150,6 +360,15 @@ public static string PasswordValidatorEmpty { } } + /// <summary> + /// Looks up a localized string similar to Pull Request for branch **{0}** created successfully at [{1}]({2}). + /// </summary> + public static string PRCreatedUpstream { + get { + return ResourceManager.GetString("PRCreatedUpstream", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Publish repository. /// </summary> @@ -168,6 +387,150 @@ public static string PublishToTitle { } } + /// <summary> + /// Looks up a localized string similar to Please enter a title for the Pull Request. + /// </summary> + public static string PullRequestCreationTitleValidatorEmpty { + get { + return ResourceManager.GetString("PullRequestCreationTitleValidatorEmpty", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Checkout {0}. + /// </summary> + public static string PullRequestDetailsCheckout { + get { + return ResourceManager.GetString("PullRequestDetailsCheckout", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Checkout to {0}. + /// </summary> + public static string PullRequestDetailsCheckoutTo { + get { + return ResourceManager.GetString("PullRequestDetailsCheckoutTo", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Pull from {0} branch {1}. + /// </summary> + public static string PullRequestDetailsPullToolTip { + get { + return ResourceManager.GetString("PullRequestDetailsPullToolTip", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Push to {0} branch {1}. + /// </summary> + public static string PullRequestDetailsPushToolTip { + get { + return ResourceManager.GetString("PullRequestDetailsPushToolTip", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Pull Request. + /// </summary> + public static string PullRequestNavigationItemText { + get { + return ResourceManager.GetString("PullRequestNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Pull Requests. + /// </summary> + public static string PullRequestsNavigationItemText { + get { + return ResourceManager.GetString("PullRequestsNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Source and target branch cannot be the same. + /// </summary> + public static string PullRequestSourceAndTargetBranchTheSame { + get { + return ResourceManager.GetString("PullRequestSourceAndTargetBranchTheSame", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Source branch doesn't exist remotely, have you pushed it?. + /// </summary> + public static string PullRequestSourceBranchDoesNotExist { + get { + return ResourceManager.GetString("PullRequestSourceBranchDoesNotExist", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to remote. + /// </summary> + public static string Remote { + get { + return ResourceManager.GetString("Remote", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to rename. + /// </summary> + public static string RenamedFileStatus { + get { + return ResourceManager.GetString("RenamedFileStatus", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No selected repository.. + /// </summary> + public static string RepositoryCloneFailedNoSelectedRepo { + get { + return ResourceManager.GetString("RepositoryCloneFailedNoSelectedRepo", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Please enter a repository path. + /// </summary> + public static string RepositoryCreationClonePathEmpty { + get { + return ResourceManager.GetString("RepositoryCreationClonePathEmpty", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Please enter a valid path. + /// </summary> + public static string RepositoryCreationClonePathInvalid { + get { + return ResourceManager.GetString("RepositoryCreationClonePathInvalid", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Path contains invalid characters. + /// </summary> + public static string RepositoryCreationClonePathInvalidCharacters { + get { + return ResourceManager.GetString("RepositoryCreationClonePathInvalidCharacters", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Path too long. + /// </summary> + public static string RepositoryCreationClonePathTooLong { + get { + return ResourceManager.GetString("RepositoryCreationClonePathTooLong", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Repository '{0}/{1}' already exists.. /// </summary> @@ -231,6 +594,33 @@ public static string SafeRepositoryNameWarning { } } + /// <summary> + /// Looks up a localized string similar to The source repository is no longer available.. + /// </summary> + public static string SourceRepositoryNoLongerAvailable { + get { + return ResourceManager.GetString("SourceRepositoryNoLongerAvailable", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Switch Origin. + /// </summary> + public static string SwitchOriginTitle { + get { + return ResourceManager.GetString("SwitchOriginTitle", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sync {0} submodules. + /// </summary> + public static string SyncSubmodules { + get { + return ResourceManager.GetString("SyncSubmodules", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Open the two-factor authentication app on your device to view your authentication code.. /// </summary> @@ -259,7 +649,7 @@ public static string TwoFactorTitle { } /// <summary> - /// Looks up a localized string similar to Enter a login authentication code here. + /// Looks up a localized string similar to Enter a sign in authentication code here. /// </summary> public static string TwoFactorUnknown { get { @@ -284,5 +674,14 @@ public static string UsernameOrEmailValidatorSpaces { return ResourceManager.GetString("UsernameOrEmailValidatorSpaces", resourceCulture); } } + + /// <summary> + /// Looks up a localized string similar to Cannot checkout as your working directory has uncommitted changes.. + /// </summary> + public static string WorkingDirectoryHasUncommittedCHanges { + get { + return ResourceManager.GetString("WorkingDirectoryHasUncommittedCHanges", resourceCulture); + } + } } } diff --git a/src/GitHub.App/Resources.resx b/src/GitHub.App/Resources.resx index 0bec3f4be7..124f512308 100644 --- a/src/GitHub.App/Resources.resx +++ b/src/GitHub.App/Resources.resx @@ -123,9 +123,18 @@ <data name="CloneTitle" xml:space="preserve"> <value>Clone a {0} Repository</value> </data> + <data name="CouldNotConnectToGitHub" xml:space="preserve"> + <value>Could not connect to github.com</value> + </data> + <data name="CreateGistTitle" xml:space="preserve"> + <value>Create a GitHub Gist</value> + </data> <data name="CreateTitle" xml:space="preserve"> <value>Create a {0} Repository</value> </data> + <data name="DefaultGistFileName" xml:space="preserve"> + <value>GistFromVisualStudio.cs</value> + </data> <data name="EnterpriseUrlValidatorEmpty" xml:space="preserve"> <value>Please enter an Enterprise URL</value> </data> @@ -135,24 +144,57 @@ <data name="EnterpriseUrlValidatorNotAGitHubHost" xml:space="preserve"> <value>Not an Enterprise server. Please enter an Enterprise URL</value> </data> + <data name="ForgotPasswordLink" xml:space="preserve"> + <value>(forgot your password?)</value> + </data> <data name="LoginFailedForbiddenMessage" xml:space="preserve"> - <value>Make sure to use your password and not a Personal Access token to log in.</value> + <value>Make sure to use your password and not a Personal Access token to sign in.</value> </data> <data name="LoginFailedMessage" xml:space="preserve"> <value>Check your username and password, then try again</value> </data> + <data name="LoginFailedText" xml:space="preserve"> + <value>Sign in failed.</value> + </data> <data name="LoginTitle" xml:space="preserve"> <value>Connect To GitHub</value> </data> <data name="PasswordValidatorEmpty" xml:space="preserve"> <value>Please enter your password</value> </data> + <data name="PRCreatedUpstream" xml:space="preserve"> + <value>Pull Request for branch **{0}** created successfully at [{1}]({2})</value> + </data> <data name="PublishTitle" xml:space="preserve"> <value>Publish repository</value> </data> <data name="PublishToTitle" xml:space="preserve"> <value>Publish repository to {0}</value> </data> + <data name="PullRequestCreationTitleValidatorEmpty" xml:space="preserve"> + <value>Please enter a title for the Pull Request</value> + </data> + <data name="PullRequestSourceAndTargetBranchTheSame" xml:space="preserve"> + <value>Source and target branch cannot be the same</value> + </data> + <data name="PullRequestSourceBranchDoesNotExist" xml:space="preserve"> + <value>Source branch doesn't exist remotely, have you pushed it?</value> + </data> + <data name="RepositoryCloneFailedNoSelectedRepo" xml:space="preserve"> + <value>No selected repository.</value> + </data> + <data name="RepositoryCreationClonePathEmpty" xml:space="preserve"> + <value>Please enter a repository path</value> + </data> + <data name="RepositoryCreationClonePathInvalid" xml:space="preserve"> + <value>Please enter a valid path</value> + </data> + <data name="RepositoryCreationClonePathInvalidCharacters" xml:space="preserve"> + <value>Path contains invalid characters</value> + </data> + <data name="RepositoryCreationClonePathTooLong" xml:space="preserve"> + <value>Path too long</value> + </data> <data name="RepositoryCreationFailedAlreadyExists" xml:space="preserve"> <value>Repository '{0}/{1}' already exists.</value> </data> @@ -184,7 +226,7 @@ <value>Two-Factor authentication required</value> </data> <data name="TwoFactorUnknown" xml:space="preserve"> - <value>Enter a login authentication code here</value> + <value>Enter a sign in authentication code here</value> </data> <data name="UsernameOrEmailValidatorEmpty" xml:space="preserve"> <value>Please enter your username or email address</value> @@ -192,4 +234,97 @@ <data name="UsernameOrEmailValidatorSpaces" xml:space="preserve"> <value>Username or email address must not have spaces</value> </data> + <data name="PullRequestsNavigationItemText" xml:space="preserve"> + <value>Pull Requests</value> + </data> + <data name="PullRequestNavigationItemText" xml:space="preserve"> + <value>Pull Request</value> + </data> + <data name="AddedFileStatus" xml:space="preserve"> + <value>add</value> + </data> + <data name="Fork" xml:space="preserve"> + <value>fork</value> + </data> + <data name="InvalidBranchName" xml:space="preserve"> + <value>[invalid]</value> + </data> + <data name="MustPullBeforePush" xml:space="preserve"> + <value>You must pull before you can push</value> + </data> + <data name="NoCommitsToPull" xml:space="preserve"> + <value>No commits to pull</value> + </data> + <data name="NoCommitsToPush" xml:space="preserve"> + <value>No commits to push</value> + </data> + <data name="NoDescriptionProvidedMarkdown" xml:space="preserve"> + <value>*No description provided.*</value> + </data> + <data name="PullRequestDetailsCheckout" xml:space="preserve"> + <value>Checkout {0}</value> + </data> + <data name="PullRequestDetailsCheckoutTo" xml:space="preserve"> + <value>Checkout to {0}</value> + </data> + <data name="PullRequestDetailsPullToolTip" xml:space="preserve"> + <value>Pull from {0} branch {1}</value> + </data> + <data name="PullRequestDetailsPushToolTip" xml:space="preserve"> + <value>Push to {0} branch {1}</value> + </data> + <data name="Remote" xml:space="preserve"> + <value>remote</value> + </data> + <data name="RenamedFileStatus" xml:space="preserve"> + <value>rename</value> + </data> + <data name="SourceRepositoryNoLongerAvailable" xml:space="preserve"> + <value>The source repository is no longer available.</value> + </data> + <data name="WorkingDirectoryHasUncommittedCHanges" xml:space="preserve"> + <value>Cannot checkout as your working directory has uncommitted changes.</value> + </data> + <data name="SyncSubmodules" xml:space="preserve"> + <value>Sync {0} submodules</value> + </data> + <data name="CouldntFindGitOnPath" xml:space="preserve"> + <value>Couldn't find Git.exe on PATH. + +Please install Git for Windows from: +https://git-scm.com/download/win</value> + </data> + <data name="Approved" xml:space="preserve"> + <value>Approved</value> + </data> + <data name="ChangesRequested" xml:space="preserve"> + <value>Changes Requested</value> + </data> + <data name="Commented" xml:space="preserve"> + <value>Commented</value> + </data> + <data name="InProgress" xml:space="preserve"> + <value>InProgress</value> + </data> + <data name="NavigateToEditorStatusMessage" xml:space="preserve"> + <value>Press Enter to navigate to Editor</value> + </data> + <data name="NavigateToEditorNotCheckedOutInfoMessage" xml:space="preserve"> + <value>Checkout PR branch before navigating to Editor</value> + </data> + <data name="NavigateToEditorNotCheckedOutStatusMessage" xml:space="preserve"> + <value>Press Enter to navigate to Editor (PR branch must be checked out)</value> + </data> + <data name="ForkRepositoryTitle" xml:space="preserve"> + <value>Fork Repository</value> + </data> + <data name="SwitchOriginTitle" xml:space="preserve"> + <value>Switch Origin</value> + </data> + <data name="CancelPendingReviewConfirmation" xml:space="preserve"> + <value>Are you sure you want to cancel this review? You will lose all your pending comments.</value> + </data> + <data name="CancelPendingReviewConfirmationCaption" xml:space="preserve"> + <value>Cancel Review</value> + </data> </root> \ No newline at end of file diff --git a/src/GitHub.App/SampleData/AccountDesigner.cs b/src/GitHub.App/SampleData/AccountDesigner.cs new file mode 100644 index 0000000000..68770f8336 --- /dev/null +++ b/src/GitHub.App/SampleData/AccountDesigner.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Media.Imaging; +using GitHub.Models; +using GitHub.Services; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public sealed class AccountDesigner : IAccount + { + public AccountDesigner() + { + Login = "octocat"; + IsUser = true; + } + + public BitmapSource Avatar + { + get + { + return IsUser + ? AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png") + : AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_org_avatar.png"); + } + } + + public bool HasMaximumPrivateRepositories { get; set; } + public bool IsEnterprise { get; set; } + public bool IsOnFreePlan { get; set; } + public bool IsUser { get; set; } + public string Login { get; set; } + public int OwnedPrivateRepos { get; set; } + public long PrivateReposInPlan { get; set; } + public string AvatarUrl { get; set; } + + public override string ToString() + { + return Login; + } + + #region Equality things + public void CopyFrom(IAccount other) + { + if (!Equals(other)) + throw new ArgumentException("Instance to copy from doesn't match this instance. this:(" + this + ") other:(" + other + ")", nameof(other)); + OwnedPrivateRepos = other.OwnedPrivateRepos; + PrivateReposInPlan = other.PrivateReposInPlan; + IsOnFreePlan = other.IsOnFreePlan; + HasMaximumPrivateRepositories = other.HasMaximumPrivateRepositories; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + return true; + var other = obj as Account; + return other != null && Login == other.Login && IsUser == other.IsUser && IsEnterprise == other.IsEnterprise; + } + + public override int GetHashCode() + { + return (Login?.GetHashCode() ?? 0) ^ IsUser.GetHashCode() ^ IsEnterprise.GetHashCode(); + } + + bool IEquatable<IAccount>.Equals(IAccount other) + { + if (ReferenceEquals(this, other)) + return true; + return other != null && Login == other.Login && IsUser == other.IsUser && IsEnterprise == other.IsEnterprise; + } + + public int CompareTo(IAccount other) + { + return other != null ? String.Compare(Login, other.Login, StringComparison.CurrentCulture) : 1; + } + + public static bool operator >(AccountDesigner lhs, AccountDesigner rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return lhs?.CompareTo(rhs) > 0; + } + + public static bool operator <(AccountDesigner lhs, AccountDesigner rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return (object)lhs == null || lhs.CompareTo(rhs) < 0; + } + + public static bool operator ==(AccountDesigner lhs, AccountDesigner rhs) + { + return Equals(lhs, rhs) && ((object)lhs == null || lhs.CompareTo(rhs) == 0); + } + + public static bool operator !=(AccountDesigner lhs, AccountDesigner rhs) + { + return !(lhs == rhs); + } + #endregion + + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/ActorViewModelDesigner.cs b/src/GitHub.App/SampleData/ActorViewModelDesigner.cs new file mode 100644 index 0000000000..7edc14b0e9 --- /dev/null +++ b/src/GitHub.App/SampleData/ActorViewModelDesigner.cs @@ -0,0 +1,26 @@ +using System; +using System.Windows.Media.Imaging; +using GitHub.Services; +using GitHub.ViewModels; + +namespace GitHub.SampleData +{ + public class ActorViewModelDesigner : ViewModelBase, IActorViewModel + { + public ActorViewModelDesigner() + { + AvatarUrl = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"; + Avatar = AvatarProvider.CreateBitmapImage(AvatarUrl); + } + + public ActorViewModelDesigner(string login) + : this() + { + Login = login; + } + + public BitmapSource Avatar { get; } + public string AvatarUrl { get; } + public string Login { get; set; } + } +} diff --git a/src/GitHub.App/SampleData/ForkRepositoryExecuteViewModelDesigner.cs b/src/GitHub.App/SampleData/ForkRepositoryExecuteViewModelDesigner.cs new file mode 100644 index 0000000000..87059607e1 --- /dev/null +++ b/src/GitHub.App/SampleData/ForkRepositoryExecuteViewModelDesigner.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; +using Octokit; +using ReactiveUI; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.SampleData +{ + public class ForkRepositoryExecuteViewModelDesigner : ViewModelBase, IForkRepositoryExecuteViewModel + { + public ForkRepositoryExecuteViewModelDesigner() + { + SourceRepository = new RemoteRepositoryModelDesigner + { + Owner = "github", + Name = "VisualStudio", + CloneUrl = "https://github.com/github/VisualStudio", + }; + DestinationRepository = new RemoteRepositoryModelDesigner + { + Owner = "user", + Name = "VisualStudio", + CloneUrl = "https://github.com/user/VisualStudio", + }; + DestinationAccount = new AccountDesigner(); + } + + public IObservable<object> Done => null; + + public IObservable<object> Back => null; + + public string Title => null; + + public IRepositoryModel SourceRepository { get; set; } + + public IRepositoryModel DestinationRepository { get; set; } + + public IAccount DestinationAccount { get; } + + public IReactiveCommand<Repository> CreateFork => null; + + public IReactiveCommand<object> BackCommand => null; + + public bool ResetMasterTracking { get; set; } = true; + + public bool AddUpstream { get; set; } = true; + + public bool UpdateOrigin { get; set; } = true; + + public bool CanAddUpstream => UpdateOrigin; + + public bool CanResetMasterTracking => UpdateOrigin && AddUpstream; + + public string Error { get; } = "I AM ERROR"; + + public Task InitializeAsync(ILocalRepositoryModel sourceRepository, IAccount destinationAccount, IConnection connection) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/ForkRepositorySelectViewModelDesigner.cs b/src/GitHub.App/SampleData/ForkRepositorySelectViewModelDesigner.cs new file mode 100644 index 0000000000..b0d9543be5 --- /dev/null +++ b/src/GitHub.App/SampleData/ForkRepositorySelectViewModelDesigner.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class ForkRepositorySelectViewModelDesigner : ViewModelBase, IForkRepositorySelectViewModel + { + public ForkRepositorySelectViewModelDesigner() + { + Accounts = new[] + { + new AccountDesigner { Login = "Myself", AvatarUrl = "https://identicons.github.com/myself.png" }, + new AccountDesigner { Login = "MyOrg1", AvatarUrl = "https://identicons.github.com/myorg1.png" }, + new AccountDesigner { Login = "MyOrg2", AvatarUrl = "https://identicons.github.com/myorg2.png" }, + new AccountDesigner { Login = "MyOrg3", AvatarUrl = "https://identicons.github.com/myorg3.png" }, + new AccountDesigner { Login = "a-long-org-name", AvatarUrl = "https://identicons.github.com/a-long-org-name.png" }, + }; + + ExistingForks = new[] + { + new RemoteRepositoryModelDesigner { Owner = "MyOrg5", Name = "MyRepo" }, + new RemoteRepositoryModelDesigner { Owner = "MyOrg6", Name = "MyRepo" }, + }; + } + + public IReadOnlyList<IAccount> Accounts { get; set; } + + public IObservable<object> Done => null; + + public IReadOnlyList<IRemoteRepositoryModel> ExistingForks { get; set; } + + public bool IsLoading { get; set; } + + public string Title => null; + + public ReactiveCommand<object> SelectedAccount => null; + + public ReactiveCommand<object> SwitchOrigin => null; + + public Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/ForkRepositorySwitchViewModelDesigner.cs b/src/GitHub.App/SampleData/ForkRepositorySwitchViewModelDesigner.cs new file mode 100644 index 0000000000..b655795a05 --- /dev/null +++ b/src/GitHub.App/SampleData/ForkRepositorySwitchViewModelDesigner.cs @@ -0,0 +1,47 @@ +using System; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class ForkRepositorySwitchViewModelDesigner : ViewModelBase, IForkRepositorySwitchViewModel + { + public ForkRepositorySwitchViewModelDesigner() + { + SourceRepository = new RemoteRepositoryModelDesigner + { + Owner = "github", + Name = "VisualStudio", + CloneUrl = "https://github.com/github/VisualStudio", + }; + DestinationRepository = new RemoteRepositoryModelDesigner + { + Owner = "user", + Name = "VisualStudio", + CloneUrl = "https://github.com/user/VisualStudio", + }; + } + + public string Title => null; + + public IObservable<object> Done => null; + + public IRepositoryModel SourceRepository { get; } + + public IRepositoryModel DestinationRepository { get; } + + public IReactiveCommand<object> SwitchFork => null; + + public bool ResetMasterTracking { get; set; } = true; + + public bool AddUpstream { get; set; } = true; + + public bool UpdateOrigin { get; set; } = true; + + public void Initialize(ILocalRepositoryModel sourceRepository, IRemoteRepositoryModel remoteRepository) + { + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/GitServiceDesigner.cs b/src/GitHub.App/SampleData/GitServiceDesigner.cs new file mode 100644 index 0000000000..4ce298c874 --- /dev/null +++ b/src/GitHub.App/SampleData/GitServiceDesigner.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using GitHub.Primitives; +using GitHub.Services; +using LibGit2Sharp; + +namespace GitHub.SampleData +{ + class GitServiceDesigner : IGitService + { + public Task<string> GetLatestPushedSha(string path) => Task.FromResult<string>(null); + public UriString GetRemoteUri(IRepository repo, string remote = "origin") => null; + public IRepository GetRepository(string path) => null; + public UriString GetUri(string path, string remote = "origin") => null; + public UriString GetUri(IRepository repository, string remote = "origin") => null; + } +} diff --git a/src/GitHub.App/SampleData/LocalRepositoryModelDesigner.cs b/src/GitHub.App/SampleData/LocalRepositoryModelDesigner.cs new file mode 100644 index 0000000000..dbcac88908 --- /dev/null +++ b/src/GitHub.App/SampleData/LocalRepositoryModelDesigner.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.UI; +using GitHub.Exports; + +namespace GitHub.App.SampleData +{ + public class LocalRepositoryModelDesigner : ILocalRepositoryModel + { + public UriString CloneUrl { get; set; } + public IBranch CurrentBranch { get; set; } + public Octicon Icon { get; set; } + public string LocalPath { get; set; } + public string Name { get; set; } + public string Owner { get; set; } + +#pragma warning disable CS0067 + public event PropertyChangedEventHandler PropertyChanged; +#pragma warning restore CS0067 + + public Task<UriString> GenerateUrl(LinkType linkType, string path = null, int startLine = -1, int endLine = -1) + { + throw new NotImplementedException(); + } + + public void Refresh() + { + throw new NotImplementedException(); + } + + public void SetIcon(bool isPrivate, bool isFork) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs new file mode 100644 index 0000000000..95a36e35c5 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Media.Imaging; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public sealed class PullRequestCheckViewModelDesigner : ViewModelBase, IPullRequestCheckViewModel + { + public string Title { get; set; } = "continuous-integration/appveyor/pr"; + + public string Description { get; set; } = "AppVeyor build failed"; + + public PullRequestCheckStatus Status { get; set; } = PullRequestCheckStatus.Failure; + + public Uri DetailsUrl { get; set; } = new Uri("http://github.com"); + + public ReactiveCommand<object> OpenDetailsUrl { get; set; } = null; + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs new file mode 100644 index 0000000000..dcf01d2858 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Validation; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public class PullRequestCreationViewModelDesigner : PanePageViewModelBase, IPullRequestCreationViewModel + { + public PullRequestCreationViewModelDesigner() + { + Branches = new List<IBranch> + { + new BranchModel("master", new LocalRepositoryModel("http://github.com/user/repo", new GitServiceDesigner())), + new BranchModel("don/stub-ui", new LocalRepositoryModel("http://github.com/user/repo", new GitServiceDesigner())), + new BranchModel("feature/pr/views", new LocalRepositoryModel("http://github.com/user/repo", new GitServiceDesigner())), + new BranchModel("release-1.0.17.0", new LocalRepositoryModel("http://github.com/user/repo", new GitServiceDesigner())), + }.AsReadOnly(); + + TargetBranch = new BranchModel("master", new LocalRepositoryModel("http://github.com/user/repo", new GitServiceDesigner())); + SourceBranch = Branches[2]; + + SelectedAssignee = "Haacked (Phil Haack)"; + Users = new List<string>() + { + "Haacked (Phil Haack)", + "shana (Andreia Gaita)" + }; + } + + public IBranch SourceBranch { get; set; } + public IBranch TargetBranch { get; set; } + public IReadOnlyList<IBranch> Branches { get; set; } + + public string SelectedAssignee { get; set; } + public List<string> Users { get; set; } + + public IReactiveCommand<IPullRequestModel> CreatePullRequest { get; } + public IReactiveCommand<object> Cancel { get; } + + public string PRTitle { get; set; } + + public ReactivePropertyValidator TitleValidator { get; } + + public ReactivePropertyValidator BranchValidator { get; } + + public Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) => Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs new file mode 100644 index 0000000000..bb1ac8a3c5 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -0,0 +1,137 @@ +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.SampleData; + +namespace GitHub.SampleData +{ + public class PullRequestCheckoutStateDesigner : IPullRequestCheckoutState + { + public string Caption { get; set; } + public bool IsEnabled { get; set; } + public string ToolTip { get; set; } + } + + public class PullRequestUpdateStateDesigner : IPullRequestUpdateState + { + public int CommitsAhead { get; set; } + public int CommitsBehind { get; set; } + public bool UpToDate { get; set; } + public string PullToolTip { get; set; } + public string PushToolTip { get; set; } + } + + [ExcludeFromCodeCoverage] + public class PullRequestDetailViewModelDesigner : PanePageViewModelBase, IPullRequestDetailViewModel + { + public PullRequestDetailViewModelDesigner() + { + var repoPath = @"C:\Repo"; + + Model = new PullRequestDetailModel + { + Number = 419, + Title = "Error handling/bubbling from viewmodels to views to viewhosts", + Author = new ActorModel { Login = "shana" }, + UpdatedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3)), + }; + + SourceBranchDisplayName = "shana/error-handling"; + TargetBranchDisplayName = "master"; + Body = @"Adds a way to surface errors from the view model to the view so that view hosts can get to them. + +ViewModels are responsible for handling the UI on the view they control, but they shouldn't be handling UI for things outside of the view. In this case, we're showing errors in VS outside the view, and that should be handled by the section that is hosting the view. + +This requires that errors be propagated from the viewmodel to the view and from there to the host via the IView interface, since hosts don't usually know what they're hosting. + +"; + + var gitHubDir = new PullRequestDirectoryNode("GitHub"); + var modelsDir = new PullRequestDirectoryNode("Models"); + var repositoriesDir = new PullRequestDirectoryNode("Repositories"); + var itrackingBranch = new PullRequestFileNode(repoPath, @"GitHub\Models\ITrackingBranch.cs", "abc", PullRequestFileStatus.Modified, null); + var oldBranchModel = new PullRequestFileNode(repoPath, @"GitHub\Models\OldBranchModel.cs", "abc", PullRequestFileStatus.Removed, null); + var concurrentRepositoryConnection = new PullRequestFileNode(repoPath, @"GitHub\Repositories\ConcurrentRepositoryConnection.cs", "abc", PullRequestFileStatus.Added, null); + + repositoriesDir.Files.Add(concurrentRepositoryConnection); + modelsDir.Directories.Add(repositoriesDir); + modelsDir.Files.Add(itrackingBranch); + modelsDir.Files.Add(oldBranchModel); + gitHubDir.Directories.Add(modelsDir); + + Reviews = new[] + { + new PullRequestReviewSummaryViewModel + { + Id = "id1", + User = new ActorViewModel { Login = "grokys" }, + State = PullRequestReviewState.Pending, + FileCommentCount = 0, + }, + new PullRequestReviewSummaryViewModel + { + Id = "id", + User = new ActorViewModel { Login = "jcansdale" }, + State = PullRequestReviewState.Approved, + FileCommentCount = 5, + }, + new PullRequestReviewSummaryViewModel + { + Id = "id3", + User = new ActorViewModel { Login = "shana" }, + State = PullRequestReviewState.ChangesRequested, + FileCommentCount = 5, + }, + new PullRequestReviewSummaryViewModel + { + }, + }; + + Files = new PullRequestFilesViewModelDesigner(); + + Checks = new PullRequestCheckViewModelDesigner[0]; + } + + public PullRequestDetailModel Model { get; } + public IPullRequestSession Session { get; } + public ILocalRepositoryModel LocalRepository { get; } + public string RemoteRepositoryOwner { get; } + public int Number { get; set; } + public IActorViewModel Author { get; set; } + public string SourceBranchDisplayName { get; set; } + public string TargetBranchDisplayName { get; set; } + public int CommentCount { get; set; } + public bool IsCheckedOut { get; } + public bool IsFromFork { get; } + public string Body { get; } + public IReadOnlyList<IPullRequestReviewSummaryViewModel> Reviews { get; } + public IPullRequestFilesViewModel Files { get; set; } + public IPullRequestCheckoutState CheckoutState { get; set; } + public IPullRequestUpdateState UpdateState { get; set; } + public string OperationError { get; set; } + public string ErrorMessage { get; set; } + public Uri WebUrl { get; set; } + + public ReactiveCommand<Unit> Checkout { get; } + public ReactiveCommand<Unit> Pull { get; } + public ReactiveCommand<Unit> Push { get; } + public ReactiveCommand<object> OpenOnGitHub { get; } + public ReactiveCommand<object> ShowReview { get; } + + public IReadOnlyList<IPullRequestCheckViewModel> Checks { get; } + + public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int number) => Task.CompletedTask; + + public string GetLocalFilePath(IPullRequestFileNode file) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs new file mode 100644 index 0000000000..c37398067a --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestFilesViewModelDesigner : PanePageViewModelBase, IPullRequestFilesViewModel + { + public PullRequestFilesViewModelDesigner() + { + Items = new[] + { + new PullRequestDirectoryNode("src") + { + Files = + { + new PullRequestFileNode("x", "src/File1.cs", "x", PullRequestFileStatus.Added, null), + new PullRequestFileNode("x", "src/File2.cs", "x", PullRequestFileStatus.Modified, null), + new PullRequestFileNode("x", "src/File3.cs", "x", PullRequestFileStatus.Removed, null), + new PullRequestFileNode("x", "src/File4.cs", "x", PullRequestFileStatus.Renamed, "src/Old.cs"), + } + } + }; + ChangedFilesCount = 4; + } + + public int ChangedFilesCount { get; set; } + public IReadOnlyList<IPullRequestChangeNode> Items { get; } + public ReactiveCommand<Unit> DiffFile { get; } + public ReactiveCommand<Unit> ViewFile { get; } + public ReactiveCommand<Unit> DiffFileWithWorkingDirectory { get; } + public ReactiveCommand<Unit> OpenFileInWorkingDirectory { get; } + public ReactiveCommand<Unit> OpenFirstComment { get; } + + public Task InitializeAsync( + IPullRequestSession session, + Func<IInlineCommentThreadModel, bool> commentFilter = null) + { + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs new file mode 100644 index 0000000000..500089fb84 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public class PullRequestListItemViewModelDesigner : ViewModelBase, IPullRequestListItemViewModel + { + public string Id { get; set; } + public IActorViewModel Author { get; set; } + public int CommentCount { get; set; } + public bool IsCurrent { get; set; } + public int Number { get; set; } + public string Title { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + public PullRequestChecksState Checks { get; set; } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs new file mode 100644 index 0000000000..831cf352e3 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using System.Threading.Tasks; +using System.Windows.Data; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public class PullRequestListViewModelDesigner : PanePageViewModelBase, IPullRequestListViewModel + { + public PullRequestListViewModelDesigner() + { + Items = new[] + { + new PullRequestListItemViewModelDesigner + { + Number = 399, + IsCurrent = true, + Title = "Let's try doing this differently", + Author = new ActorViewModelDesigner("shana"), + UpdatedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + }, + new PullRequestListItemViewModelDesigner + { + Number = 389, + Title = "Build system upgrade", + Author = new ActorViewModelDesigner("haacked"), + CommentCount = 4, + UpdatedAt = DateTimeOffset.Now - TimeSpan.FromMinutes(2), + }, + new PullRequestListItemViewModelDesigner + { + Number = 409, + Title = "Fix publish button style and a really, really long name for this thing... OMG look how long this name is yusssss", + Author = new ActorViewModelDesigner("shana"), + CommentCount = 27, + UpdatedAt = DateTimeOffset.Now - TimeSpan.FromHours(5), + }, + }; + + ItemsView = CollectionViewSource.GetDefaultView(Items); + States = new[] { "Open", "Closed", "All" }; + SelectedState = "Open"; + } + + public IUserFilterViewModel AuthorFilter { get; set; } + public IReadOnlyList<IIssueListItemViewModelBase> Items { get; } + public ICollectionView ItemsView { get; } + public ILocalRepositoryModel LocalRepository { get; set; } + public IssueListMessage Message { get; set; } + public IRepositoryModel RemoteRepository { get; set; } + public IReadOnlyList<IRepositoryModel> Forks { get; } + public string SearchQuery { get; set; } + public string SelectedState { get; set; } + public string StateCaption { get; set; } + public IReadOnlyList<string> States { get; } + public Uri WebUrl => null; + public ReactiveCommand<object> CreatePullRequest { get; } + public ReactiveCommand<Unit> OpenItem { get; } + public ReactiveCommand<object> OpenItemInBrowser { get; } + + public Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) => Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs new file mode 100644 index 0000000000..15c6e68f57 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestReviewAuthoringViewModelDesigner : PanePageViewModelBase, IPullRequestReviewAuthoringViewModel + { + public PullRequestReviewAuthoringViewModelDesigner() + { + PullRequestModel = new PullRequestDetailModel + { + Number = 419, + Title = "Fix a ton of potential crashers, odd code and redundant calls in ModelService", + Author = new ActorModel { Login = "Haacked" }, + UpdatedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + }; + + Files = new PullRequestFilesViewModelDesigner(); + + FileComments = new[] + { + new PullRequestReviewFileCommentViewModelDesigner + { + Body = @"These should probably be properties. Most likely they should be readonly properties. I know that makes creating instances of these not look as nice as using property initializers when constructing an instance, but if these properties should never be mutated after construction, then it guides future consumers to the right behavior. + +However, if you're two-way binding these properties to a UI, then ignore the readonly part and make them properties. But in that case they should probably be reactive properties (or implement INPC).", + RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs", + }, + new PullRequestReviewFileCommentViewModelDesigner + { + Body = "While I have no problems with naming a variable ass I think we should probably avoid swear words in case Microsoft runs their Policheck tool against this code.", + RelativePath = "src/GitHub.App/ViewModels/PullRequestListViewModel.cs", + }, + }; + } + + public string Body { get; set; } + public bool CanApproveRequestChanges { get; set; } + public IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments { get; } + public IPullRequestFilesViewModel Files { get; } + public ILocalRepositoryModel LocalRepository { get; set; } + public PullRequestReviewModel Model { get; set; } + public ReactiveCommand<object> NavigateToPullRequest { get; } + public string OperationError { get; set; } + public PullRequestDetailModel PullRequestModel { get; set; } + public string RemoteRepositoryOwner { get; set; } + public ReactiveCommand<Unit> Approve { get; } + public ReactiveCommand<Unit> Comment { get; } + public ReactiveCommand<Unit> RequestChanges { get; } + public ReactiveCommand<Unit> Cancel { get; } + + public Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestReviewFileCommentViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewFileCommentViewModelDesigner.cs new file mode 100644 index 0000000000..634cea4abc --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestReviewFileCommentViewModelDesigner.cs @@ -0,0 +1,13 @@ +using System.Reactive; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + public class PullRequestReviewFileCommentViewModelDesigner : IPullRequestReviewFileCommentViewModel + { + public string Body { get; set; } + public string RelativePath { get; set; } + public ReactiveCommand<Unit> Open { get; } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs new file mode 100644 index 0000000000..dfa5963707 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestReviewViewModelDesigner.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using GitHub.Models; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public class PullRequestReviewViewModelDesigner : PanePageViewModelBase, IPullRequestReviewViewModel + { + public PullRequestReviewViewModelDesigner() + { + PullRequestModel = new PullRequestDetailModel + { + Number = 419, + Title = "Fix a ton of potential crashers, odd code and redundant calls in ModelService", + Author = new ActorModel { Login = "Haacked" }, + UpdatedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + }; + + Model = new PullRequestReviewModel + { + + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + Author = new ActorModel { Login = "Haacked" }, + }; + + Body = @"Just a few comments. I don't feel too strongly about them though. + +Otherwise, very nice work here! ✨"; + + StateDisplay = "approved"; + + FileComments = new[] + { + new PullRequestReviewFileCommentViewModelDesigner + { + Body = @"These should probably be properties. Most likely they should be readonly properties. I know that makes creating instances of these not look as nice as using property initializers when constructing an instance, but if these properties should never be mutated after construction, then it guides future consumers to the right behavior. + +However, if you're two-way binding these properties to a UI, then ignore the readonly part and make them properties. But in that case they should probably be reactive properties (or implement INPC).", + RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs", + }, + new PullRequestReviewFileCommentViewModelDesigner + { + Body = "While I have no problems with naming a variable ass I think we should probably avoid swear words in case Microsoft runs their Policheck tool against this code.", + RelativePath = "src/GitHub.App/ViewModels/PullRequestListViewModel.cs", + }, + }; + + OutdatedFileComments = new[] + { + new PullRequestReviewFileCommentViewModelDesigner + { + Body = @"So this is just casting a mutable list to an IReadOnlyList which can be cast back to List. I know we probably won't do that, but I'm thinking of the next person to come along. The safe thing to do is to wrap List with a ReadOnlyList. We have an extension method ToReadOnlyList for observables. Wouldn't be hard to write one for IEnumerable.", + RelativePath = "src/GitHub.Exports.Reactive/ViewModels/IPullRequestListViewModel.cs", + }, + }; + } + + public string Body { get; } + public IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments { get; set; } + public bool IsExpanded { get; set; } + public bool HasDetails { get; set; } + public ILocalRepositoryModel LocalRepository { get; set; } + public PullRequestReviewModel Model { get; set; } + public ReactiveCommand<object> NavigateToPullRequest { get; } + public IReadOnlyList<IPullRequestReviewFileCommentViewModel> OutdatedFileComments { get; set; } + public PullRequestDetailModel PullRequestModel { get; set; } + public string RemoteRepositoryOwner { get; set; } + public string StateDisplay { get; set; } + } +} diff --git a/src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs new file mode 100644 index 0000000000..b8a9a4d10b --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestUserReviewsViewModelDesigner.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public class PullRequestUserReviewsViewModelDesigner : PanePageViewModelBase, IPullRequestUserReviewsViewModel + { + public PullRequestUserReviewsViewModelDesigner() + { + var userModel = new ActorModel { Login = "Haacked" }; + + User = new ActorViewModel(userModel); + PullRequestNumber = 123; + PullRequestTitle = "Error handling/bubbling from viewmodels to views to viewhosts"; + + Reviews = new[] + { + new PullRequestReviewViewModelDesigner() + { + IsExpanded = true, + HasDetails = true, + FileComments = new IPullRequestReviewFileCommentViewModel[0], + StateDisplay = "approved", + Model = new PullRequestReviewModel + { + State = PullRequestReviewState.Approved, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + Author = userModel, + }, + }, + new PullRequestReviewViewModelDesigner() + { + IsExpanded = true, + HasDetails = true, + StateDisplay = "requested changes", + Model = new PullRequestReviewModel + { + State = PullRequestReviewState.ChangesRequested, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + Author = userModel, + }, + }, + new PullRequestReviewViewModelDesigner() + { + IsExpanded = false, + HasDetails = false, + StateDisplay = "commented", + Model = new PullRequestReviewModel + { + State = PullRequestReviewState.Commented, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + Author = userModel, + }, + } + }; + } + + public ILocalRepositoryModel LocalRepository { get; set; } + public string RemoteRepositoryOwner { get; set; } + public int PullRequestNumber { get; set; } + public IActorViewModel User { get; set; } + public IReadOnlyList<IPullRequestReviewViewModel> Reviews { get; set; } + public string PullRequestTitle { get; set; } + public ReactiveCommand<object> NavigateToPullRequest { get; } + + public Task InitializeAsync(ILocalRepositoryModel localRepository, IConnection connection, string owner, string repo, int pullRequestNumber, string login) + { + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.App/SampleData/RemoteRepositoryModelDesigner.cs b/src/GitHub.App/SampleData/RemoteRepositoryModelDesigner.cs new file mode 100644 index 0000000000..d9179f1cb5 --- /dev/null +++ b/src/GitHub.App/SampleData/RemoteRepositoryModelDesigner.cs @@ -0,0 +1,42 @@ +using System; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.UI; + +namespace GitHub.SampleData +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes")] + public class RemoteRepositoryModelDesigner : IRemoteRepositoryModel + { + public UriString CloneUrl { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public IBranch DefaultBranch { get; set; } + public Octicon Icon { get; set; } + public long Id { get; set; } + public bool IsFork { get; set; } + public string Name { get; set; } + public string Owner { get; set; } + public IAccount OwnerAccount { get; set; } + public IRemoteRepositoryModel Parent { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + + public int CompareTo(IRemoteRepositoryModel other) + { + return 0; + } + + public void CopyFrom(IRemoteRepositoryModel other) + { + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + public bool Equals(IRemoteRepositoryModel other) + { + return false; + } + + public void SetIcon(bool isPrivate, bool isFork) + { + } + } +} diff --git a/src/GitHub.App/SampleData/RepositoryRecloneViewModelDesigner.cs b/src/GitHub.App/SampleData/RepositoryRecloneViewModelDesigner.cs new file mode 100644 index 0000000000..2cc1b9eee8 --- /dev/null +++ b/src/GitHub.App/SampleData/RepositoryRecloneViewModelDesigner.cs @@ -0,0 +1,24 @@ +using System; +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<string> BaseRepositoryPathValidator { get; } + public ICommand BrowseForDirectory { get; } + public IReactiveCommand<object> CloneCommand { get; } + public IRepositoryModel SelectedRepository { get; set; } + public IObservable<object> 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 57746efe6c..351ed7091b 100644 --- a/src/GitHub.App/SampleData/SampleViewModels.cs +++ b/src/GitHub.App/SampleData/SampleViewModels.cs @@ -1,53 +1,47 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reactive; +using System.Threading.Tasks; using System.Windows.Input; -using System.Windows.Media.Imaging; -using GitHub.Api; -using GitHub.Authentication; using GitHub.Extensions; using GitHub.Models; using GitHub.Primitives; -using GitHub.Services; using GitHub.UI; using GitHub.Validation; using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; +using GitHub.ViewModels.TeamExplorer; +using GitHub.VisualStudio.TeamExplorer.Connect; using GitHub.VisualStudio.TeamExplorer.Home; using ReactiveUI; -using GitHub.VisualStudio.TeamExplorer.Connect; -using System.Collections.ObjectModel; namespace GitHub.SampleData { [ExcludeFromCodeCoverage] - public class BaseViewModelDesigner : ReactiveObject, IViewModel - { - public ICommand Cancel { get; set; } - public bool IsShowing { get; set; } - public string Title { get; set; } - } - - [ExcludeFromCodeCoverage] - public class RepositoryCreationViewModelDesigner : BaseViewModelDesigner, IRepositoryCreationViewModel + public class RepositoryCreationViewModelDesigner : ViewModelBase, IRepositoryCreationViewModel { public RepositoryCreationViewModelDesigner() { RepositoryName = "Hello-World"; Description = "A description"; KeepPrivate = true; + CanKeepPrivate = true; Accounts = new ReactiveList<IAccount> { new AccountDesigner { Login = "shana" }, new AccountDesigner { Login = "GitHub", IsUser = false } }; + SelectedAccount = Accounts[0]; GitIgnoreTemplates = new ReactiveList<GitIgnoreItem> { GitIgnoreItem.Create("VisualStudio"), GitIgnoreItem.Create("Wap"), GitIgnoreItem.Create("WordPress") }; - + SelectedGitIgnoreTemplate = GitIgnoreTemplates[0]; Licenses = new ReactiveList<LicenseItem> { new LicenseItem("agpl-3.0", "GNU Affero GPL v3.0"), @@ -56,16 +50,15 @@ public RepositoryCreationViewModelDesigner() new LicenseItem("mit", "MIT License") }; - SelectedLicense = LicenseItem.None; - SelectedGitIgnoreTemplate = null; + SelectedLicense = Licenses[0]; } - public new string Title { get { return "Create a GitHub Repository"; } } // TODO: this needs to be contextual + public string Title { get { return "Create a GitHub Repository"; } } // TODO: this needs to be contextual public IReadOnlyList<IAccount> Accounts { get; - private set; + set; } public string BaseRepositoryPath @@ -191,6 +184,10 @@ public LicenseItem SelectedLicense get; set; } + + public IObservable<object> Done { get; } + + public Task InitializeAsync(IConnection connection) => Task.CompletedTask; } [ExcludeFromCodeCoverage] @@ -201,25 +198,24 @@ class Conn : IConnection public HostAddress HostAddress { get; set; } public string Username { get; set; } - public ObservableCollection<ISimpleRepositoryModel> Repositories { get; set; } + public ObservableCollection<ILocalRepositoryModel> Repositories { get; set; } - public IObservable<IConnection> Login() - { - return null; - } + public Octokit.User User => null; + public bool IsLoggedIn => true; + public bool IsLoggingIn => false; - public void Logout() - { - } + public Exception ConnectionError => null; - public void Dispose() + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { + add { } + remove { } } } public RepositoryPublishViewModelDesigner() { - Connections = new ReactiveList<IConnection> + Connections = new ObservableCollectionEx<IConnection> { new Conn() { HostAddress = new HostAddress() }, new Conn() { HostAddress = HostAddress.Create("ghe.io") } @@ -227,13 +223,7 @@ public RepositoryPublishViewModelDesigner() SelectedConnection = Connections[0]; } - public string DefaultRepositoryName - { - get - { - return "whatever"; - } - } + public bool IsBusy { get; set; } public bool IsHostComboBoxVisible { @@ -243,19 +233,13 @@ public bool IsHostComboBoxVisible } } - public bool IsPublishing - { - get; - private set; - } - public IReactiveCommand<ProgressState> PublishRepository { get; private set; } - public ReactiveList<IConnection> Connections + public IReadOnlyObservableCollection<IConnection> Connections { get; private set; @@ -268,184 +252,42 @@ public IConnection SelectedConnection } [ExcludeFromCodeCoverage] - public sealed class RepositoryHostDesigner : ReactiveObject, IRepositoryHost + public static class RepositoryModelDesigner { - public RepositoryHostDesigner(string title) - { - this.Title = title; - } - - public HostAddress Address - { - get; - private set; - } - - public IApiClient ApiClient - { - get; - private set; - } - - public bool IsLoggedIn - { - get; - private set; - } - - public IModelService ModelService - { - get; - private set; - } - - public string Title - { - get; - private set; - } - - public void Dispose() - { - } - - public IObservable<AuthenticationResult> LogIn(string usernameOrEmail, string password) - { - throw new NotImplementedException(); - } - - public IObservable<AuthenticationResult> LogInFromCache() - { - throw new NotImplementedException(); - } - - public IObservable<Unit> LogOut() + public static IRemoteRepositoryModel Create(string name = null, string owner = null) { - throw new NotImplementedException(); + name = name ?? "octocat"; + owner = owner ?? "github"; + return new RemoteRepositoryModel(0, name, new UriString("http://github.com/" + name + "/" + owner), false, false, new AccountDesigner() { Login = owner }, null); } } - [ExcludeFromCodeCoverage] - public sealed class AccountDesigner : IAccount - { - public AccountDesigner() - { - Login = "octocat"; - IsUser = true; - } - - public BitmapSource Avatar - { - get - { - return IsUser - ? AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png") - : AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_org_avatar.png"); - } - } - - public bool HasMaximumPrivateRepositories - { - get; - set; - } - - public bool IsEnterprise - { - get; - set; - } - - public bool IsOnFreePlan - { - get; - set; - } - - public bool IsUser - { - get; - set; - } - - public string Login - { - get; - set; - } - - public int OwnedPrivateRepos - { - get; - set; - } - - public long PrivateReposInPlan - { - get; - set; - } - } - - [ExcludeFromCodeCoverage] - public class RepositoryModelDesigner : NotificationAwareObject, IRepositoryModel - { - public RepositoryModelDesigner() : this("repo") - { - } - - public RepositoryModelDesigner(string name) : this("repo", "github") - { - Name = name; - } - - public RepositoryModelDesigner(string name, string owner) - { - Name = name; - Owner = new AccountDesigner { Login = owner }; - } - - public void SetIcon(bool isPrivate, bool isFork) - { - } - - public string Name { get; set; } - public UriString CloneUrl { get; set; } - public string LocalPath { get; set; } - - public Octicon Icon { get; set; } - - public IAccount Owner { get; set; } - - public void Refresh() { } - } - - public class RepositoryCloneViewModelDesigner : BaseViewModelDesigner, IRepositoryCloneViewModel + public class RepositoryCloneViewModelDesigner : ViewModelBase, IRepositoryCloneViewModel { public RepositoryCloneViewModelDesigner() { - var repositories = new ReactiveList<IRepositoryModel> + Repositories = new ObservableCollection<IRemoteRepositoryModel> { - new RepositoryModelDesigner("encourage", "haacked"), - new RepositoryModelDesigner("haacked.com", "haacked"), - new RepositoryModelDesigner("octokit.net", "octokit"), - new RepositoryModelDesigner("octokit.rb", "octokit"), - new RepositoryModelDesigner("octokit.objc", "octokit"), - new RepositoryModelDesigner("windows", "github"), - new RepositoryModelDesigner("mac", "github"), - new RepositoryModelDesigner("github", "github") + RepositoryModelDesigner.Create("encourage", "haacked"), + RepositoryModelDesigner.Create("haacked.com", "haacked"), + RepositoryModelDesigner.Create("octokit.net", "octokit"), + RepositoryModelDesigner.Create("octokit.rb", "octokit"), + RepositoryModelDesigner.Create("octokit.objc", "octokit"), + RepositoryModelDesigner.Create("windows", "github"), + RepositoryModelDesigner.Create("mac", "github"), + RepositoryModelDesigner.Create("github", "github") }; BrowseForDirectory = ReactiveCommand.Create(); - FilteredRepositories = repositories.CreateDerivedCollection( - x => x - ); - - BaseRepositoryPathValidator = this.CreateBaseRepositoryPathValidator(); + BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(this.WhenAny(x => x.BaseRepositoryPath, x => x.Value)) + .IfNullOrEmpty("Please enter a repository path") + .IfTrue(x => x.Length > 200, "Path too long") + .IfContainsInvalidPathChars("Path contains invalid characters") + .IfPathNotRooted("Please enter a valid path"); } - public IReactiveCommand<Unit> CloneCommand + public IReactiveCommand<object> CloneCommand { get; private set; @@ -453,7 +295,7 @@ public IReactiveCommand<Unit> CloneCommand public IRepositoryModel SelectedRepository { get; set; } - public IReactiveDerivedList<IRepositoryModel> FilteredRepositories + public ObservableCollection<IRemoteRepositoryModel> Repositories { get; private set; @@ -467,14 +309,9 @@ public bool FilterTextIsEnabled public string FilterText { get; set; } - public new string Title { get { return "Clone a GitHub Repository"; } } - - public bool IsLoading - { - get { return false; } - } + public string Title { get { return "Clone a GitHub Repository"; } } - public IReactiveCommand<IReadOnlyList<IRepositoryModel>> LoadRepositoriesCommand + public IReactiveCommand<IReadOnlyList<IRemoteRepositoryModel>> LoadRepositoriesCommand { get; private set; @@ -514,15 +351,20 @@ public ReactivePropertyValidator<string> BaseRepositoryPathValidator get; private set; } + + public IObservable<object> Done { get; } + + public Task InitializeAsync(IConnection connection) => Task.CompletedTask; } public class GitHubHomeSectionDesigner : IGitHubHomeSection { public GitHubHomeSectionDesigner() { - Icon = Octicon.@lock; + Icon = Octicon.repo; RepoName = "octokit"; - RepoUrl = "https://github.com/octokit/octokit.net"; + RepoUrl = "https://github.com/octokit/something-really-long-here-to-check-for-trimming"; + IsLoggedIn = false; } public Octicon Icon @@ -531,6 +373,12 @@ public Octicon Icon private set; } + public bool IsLoggedIn + { + get; + private set; + } + public string RepoName { get; @@ -542,30 +390,33 @@ public string RepoUrl get; set; } + + public void Login() + { + + } + + public ICommand OpenOnGitHub { get; } } public class GitHubConnectSectionDesigner : IGitHubConnectSection { public GitHubConnectSectionDesigner() { - Repositories = new ObservableCollection<ISimpleRepositoryModel>(); - Repositories.Add(new SimpleRepositoryModel("octokit", new UriString("https://github.com/octokit/octokit.net"), @"C:\Users\user\Source\Repos\octokit.net")); - Repositories.Add(new SimpleRepositoryModel("cefsharp", new UriString("https://github.com/cefsharp/cefsharp"), @"C:\Users\user\Source\Repos\cefsharp")); - Repositories.Add(new SimpleRepositoryModel("git-lfs", new UriString("https://github.com/github/git-lfs"), @"C:\Users\user\Source\Repos\git-lfs")); - Repositories.Add(new SimpleRepositoryModel("another octokit", new UriString("https://github.com/octokit/octokit.net"), @"C:\Users\user\Source\Repos\another-octokit.net")); - Repositories.Add(new SimpleRepositoryModel("some cefsharp", new UriString("https://github.com/cefsharp/cefsharp"), @"C:\Users\user\Source\Repos\something-else")); - Repositories.Add(new SimpleRepositoryModel("even more git-lfs", new UriString("https://github.com/github/git-lfs"), @"C:\Users\user\Source\Repos\A different path")); + Repositories = new ObservableCollection<ILocalRepositoryModel>(); + Repositories.Add(new LocalRepositoryModel("octokit", new UriString("https://github.com/octokit/octokit.net"), @"C:\Users\user\Source\Repos\octokit.net", new GitServiceDesigner())); + Repositories.Add(new LocalRepositoryModel("cefsharp", new UriString("https://github.com/cefsharp/cefsharp"), @"C:\Users\user\Source\Repos\cefsharp", new GitServiceDesigner())); + Repositories.Add(new LocalRepositoryModel("git-lfs", new UriString("https://github.com/github/git-lfs"), @"C:\Users\user\Source\Repos\git-lfs", new GitServiceDesigner())); + Repositories.Add(new LocalRepositoryModel("another octokit", new UriString("https://github.com/octokit/octokit.net"), @"C:\Users\user\Source\Repos\another-octokit.net", new GitServiceDesigner())); + Repositories.Add(new LocalRepositoryModel("some cefsharp", new UriString("https://github.com/cefsharp/cefsharp"), @"C:\Users\user\Source\Repos\something-else", new GitServiceDesigner())); + Repositories.Add(new LocalRepositoryModel("even more git-lfs", new UriString("https://github.com/github/git-lfs"), @"C:\Users\user\Source\Repos\A different path", new GitServiceDesigner())); } - public ObservableCollection<ISimpleRepositoryModel> Repositories + public ObservableCollection<ILocalRepositoryModel> Repositories { get; set; } - public void DoClone() - { - } - public void DoCreate() { } @@ -578,11 +429,27 @@ public void Login() { } + public void Retry() + { + } + public bool OpenRepository() { return true; } + public string ErrorMessage { get; set; } public IConnection SectionConnection { get; } + public bool IsLoggingIn { get; set; } + public bool ShowLogin { get; set; } + public bool ShowLogout { get; set; } + public bool ShowRetry { get; set; } + public ICommand Clone { get; } + } + + public class InfoPanelDesigner + { + public string Message => "This is an informational message for the [info panel](link) to test things in design mode."; + public MessageType MessageType => MessageType.Information; } } diff --git a/src/GitHub.App/SampleData/UserFilterViewModelDesigner.cs b/src/GitHub.App/SampleData/UserFilterViewModelDesigner.cs new file mode 100644 index 0000000000..54c0db56b8 --- /dev/null +++ b/src/GitHub.App/SampleData/UserFilterViewModelDesigner.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Data; +using GitHub.ViewModels; + +namespace GitHub.SampleData +{ + public class UserFilterViewModelDesigner : ViewModelBase, IUserFilterViewModel + { + public UserFilterViewModelDesigner() + { + Users = new[] + { + new ActorViewModelDesigner("grokys"), + new ActorViewModelDesigner("jcansdale"), + new ActorViewModelDesigner("meaghanlewis"), + new ActorViewModelDesigner("sguthals"), + new ActorViewModelDesigner("shana"), + new ActorViewModelDesigner("StanleyGoldman"), + }; + + UsersView = CollectionViewSource.GetDefaultView(Users); + } + + public string Filter { get; set; } + public IActorViewModel Selected { get; set; } + public IReadOnlyList<IActorViewModel> Users { get; } + public ICollectionView UsersView { get; } + } +} diff --git a/src/GitHub.App/Services/AvatarProvider.cs b/src/GitHub.App/Services/AvatarProvider.cs index 9b9901c47a..1a99e815f6 100644 --- a/src/GitHub.App/Services/AvatarProvider.cs +++ b/src/GitHub.App/Services/AvatarProvider.cs @@ -1,14 +1,16 @@ using System; +using System.IO.Packaging; using System.ComponentModel.Composition; using System.Diagnostics; using System.Reactive; using System.Reactive.Linq; -using System.Windows; using System.Windows.Media.Imaging; using Akavache; using GitHub.Caches; +using GitHub.Logging; +using GitHub.Extensions; using GitHub.Models; -using NullGuard; +using System.Windows; namespace GitHub.Services { @@ -24,18 +26,17 @@ public class AvatarProvider : IAvatarProvider static AvatarProvider() { - // NB: If this isn't explicitly set, WPF will try to guess it via - // GetEntryAssembly, which in a unit test runner will be null - // This is needed for the pack:// URL format to be understood. - if (Application.ResourceAssembly == null) - { - Application.ResourceAssembly = typeof(AvatarProvider).Assembly; - } + // Calling `Application.Current` will install pack URI scheme via Application.cctor. + // This is needed when unit testing for the pack:// URL format to be understood. + if (Application.Current != null) { } } [ImportingConstructor] public AvatarProvider(ISharedCache sharedCache, IImageCache imageCache) { + Guard.ArgumentNotNull(sharedCache, nameof(sharedCache)); + Guard.ArgumentNotNull(imageCache, nameof(imageCache)); + cache = sharedCache.LocalMachine; this.imageCache = imageCache; @@ -45,6 +46,8 @@ public AvatarProvider(ISharedCache sharedCache, IImageCache imageCache) public static BitmapImage CreateBitmapImage(string packUrl) { + Guard.ArgumentNotEmptyString(packUrl, nameof(packUrl)); + var bitmap = new BitmapImage(new Uri(packUrl)); bitmap.Freeze(); return bitmap; @@ -52,6 +55,8 @@ public static BitmapImage CreateBitmapImage(string packUrl) public IObservable<BitmapSource> GetAvatar(IAvatarContainer apiAccount) { + Guard.ArgumentNotNull(apiAccount, nameof(apiAccount)); + if (apiAccount.AvatarUrl == null) { return Observable.Return(DefaultAvatar(apiAccount)); @@ -59,13 +64,28 @@ public IObservable<BitmapSource> GetAvatar(IAvatarContainer apiAccount) Uri avatarUrl; Uri.TryCreate(apiAccount.AvatarUrl, UriKind.Absolute, out avatarUrl); - Debug.Assert(avatarUrl != null, "Cannot have a null avatar url"); + Log.Assert(avatarUrl != null, "Cannot have a null avatar url"); return imageCache.GetImage(avatarUrl) .Catch<BitmapSource, Exception>(_ => Observable.Return(DefaultAvatar(apiAccount))); } - - public IObservable<Unit> InvalidateAvatar([AllowNull] IAvatarContainer apiAccount) + + public IObservable<BitmapSource> GetAvatar(string url) + { + if (url == null) + { + return Observable.Return(DefaultUserBitmapImage); + } + + Uri avatarUrl; + Uri.TryCreate(url, UriKind.Absolute, out avatarUrl); + Log.Assert(avatarUrl != null, "Cannot have a null avatar url"); + + return imageCache.GetImage(avatarUrl) + .Catch<BitmapSource, Exception>(_ => Observable.Return(DefaultUserBitmapImage)); + } + + public IObservable<Unit> InvalidateAvatar(IAvatarContainer apiAccount) { return String.IsNullOrWhiteSpace(apiAccount?.Login) ? Observable.Return(Unit.Default) @@ -74,19 +94,13 @@ public IObservable<Unit> InvalidateAvatar([AllowNull] IAvatarContainer apiAccoun BitmapImage DefaultAvatar(IAvatarContainer apiAccount) { + Guard.ArgumentNotNull(apiAccount, nameof(apiAccount)); + return apiAccount.IsUser ? DefaultUserBitmapImage : DefaultOrgBitmapImage; } - bool disposed; protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - imageCache.Dispose(); - disposed = true; - } - } + { } public void Dispose() { diff --git a/src/GitHub.App/Services/Browser.cs b/src/GitHub.App/Services/Browser.cs deleted file mode 100644 index 14fbcb760a..0000000000 --- a/src/GitHub.App/Services/Browser.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.IO; -using GitHub.Services; -using Rothko; -using NLog; - -namespace GitHub -{ - [Export(typeof(IVisualStudioBrowser))] - public class Browser : IVisualStudioBrowser - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - - readonly IProcessStarter processManager; - readonly IEnvironment environment; - - [ImportingConstructor] - public Browser(IProcessStarter processManager, IEnvironment environment) - { - this.processManager = processManager; - this.environment = environment; - } - - public void OpenUrl(Uri url) - { - if (url == null) - { - log.Warn("Attempted to open a null URL"); - return; - } - - try - { - processManager.Start(url.ToString(), string.Empty); - return; - } - catch (Exception ex) - { - log.Warn("Opening URL in default browser failed", ex); - } - - try - { - processManager.Start( - Path.Combine(environment.GetProgramFilesPath(), @"Internet Explorer", "iexplore.exe"), - url.ToString()); - } - catch (Exception ex) - { - log.Error("Really can't open the URL, even in IE", ex); - } - } - } -} diff --git a/src/GitHub.App/Services/DialogService.cs b/src/GitHub.App/Services/DialogService.cs new file mode 100644 index 0000000000..aa1abe53e6 --- /dev/null +++ b/src/GitHub.App/Services/DialogService.cs @@ -0,0 +1,94 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.ViewModels.Dialog; + +namespace GitHub.Services +{ + [Export(typeof(IDialogService))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class DialogService : IDialogService + { + readonly IViewViewModelFactory factory; + readonly IShowDialogService showDialog; + + [ImportingConstructor] + public DialogService( + IViewViewModelFactory factory, + IShowDialogService showDialog) + { + Guard.ArgumentNotNull(factory, nameof(factory)); + Guard.ArgumentNotNull(showDialog, nameof(showDialog)); + + this.factory = factory; + this.showDialog = showDialog; + } + + public async Task<CloneDialogResult> ShowCloneDialog(IConnection connection) + { + var viewModel = factory.CreateViewModel<IRepositoryCloneViewModel>(); + + if (connection != null) + { + await viewModel.InitializeAsync(connection); + return (CloneDialogResult)await showDialog.Show(viewModel); + } + else + { + return (CloneDialogResult)await showDialog.ShowWithFirstConnection(viewModel); + } + } + + public async Task<string> ShowReCloneDialog(IRepositoryModel repository) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + + var viewModel = factory.CreateViewModel<IRepositoryRecloneViewModel>(); + viewModel.SelectedRepository = repository; + return (string)await showDialog.ShowWithFirstConnection(viewModel); + } + + public async Task ShowCreateGist(IConnection connection) + { + var viewModel = factory.CreateViewModel<IGistCreationViewModel>(); + + if (connection != null) + { + await viewModel.InitializeAsync(connection); + await showDialog.Show(viewModel); + } + else + { + await showDialog.ShowWithFirstConnection(viewModel); + } + } + + public async Task ShowCreateRepositoryDialog(IConnection connection) + { + Guard.ArgumentNotNull(connection, nameof(connection)); + + var viewModel = factory.CreateViewModel<IRepositoryCreationViewModel>(); + await viewModel.InitializeAsync(connection); + await showDialog.Show(viewModel); + } + + public async Task<IConnection> ShowLoginDialog() + { + var viewModel = factory.CreateViewModel<ILoginViewModel>(); + return (IConnection)await showDialog.Show(viewModel); + } + + public async Task ShowForkDialog(ILocalRepositoryModel repository, IConnection connection) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotNull(connection, nameof(connection)); + + var viewModel = factory.CreateViewModel<IForkRepositoryViewModel>(); + await viewModel.InitializeAsync(repository, connection); + await showDialog.Show(viewModel); + } + } +} diff --git a/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs b/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs new file mode 100644 index 0000000000..072ed90228 --- /dev/null +++ b/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs @@ -0,0 +1,72 @@ +using System; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Primitives; +using Octokit; +using IProgram = GitHub.Models.IProgram; + +namespace GitHub.Services +{ + [Export(typeof(IEnterpriseCapabilitiesService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class EnterpriseCapabilitiesService : IEnterpriseCapabilitiesService + { + static readonly Version MinimumOAuthVersion = new Version("2.12.2"); + readonly IEnterpriseProbe probe; + readonly IProgram program; + + [ImportingConstructor] + public EnterpriseCapabilitiesService( + IEnterpriseProbe probe, + IProgram program) + { + this.probe = probe; + this.program = program; + } + + public Task<EnterpriseProbeResult> Probe(Uri enterpriseBaseUrl) => probe.Probe(enterpriseBaseUrl); + + public async Task<EnterpriseLoginMethods> ProbeLoginMethods(Uri enterpriseBaseUrl) + { + try + { + // It's important that we don't use our cached credentials on this connection, as they + // may be wrong - we're trying to log in after all. + var hostAddress = HostAddress.Create(enterpriseBaseUrl); + var connection = new Octokit.Connection(program.ProductHeader, hostAddress.ApiUri); + var meta = await GetMetadata(connection).ConfigureAwait(false); + var result = EnterpriseLoginMethods.Token; + + if (meta.VerifiablePasswordAuthentication != false) result |= EnterpriseLoginMethods.UsernameAndPassword; + + if (meta.InstalledVersion != null) + { + var version = new Version(meta.InstalledVersion); + if (version >= MinimumOAuthVersion) result |= EnterpriseLoginMethods.OAuth; + } + + return result; + } + catch + { + return EnterpriseLoginMethods.Token | EnterpriseLoginMethods.UsernameAndPassword; + } + } + + private async Task<EnterpriseMeta> GetMetadata(IConnection connection) + { + var endpoint = new Uri("meta", UriKind.Relative); + var response = await connection.Get<EnterpriseMeta>(endpoint, null, null).ConfigureAwait(false); + return response.Body; + } + + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Created via Octokit reflection")] + class EnterpriseMeta + { + public string InstalledVersion { get; private set; } + public bool? VerifiablePasswordAuthentication { get; private set; } + } + } +} diff --git a/src/GitHub.App/Services/EnterpriseProbe.cs b/src/GitHub.App/Services/EnterpriseProbe.cs deleted file mode 100644 index a478e9ea0c..0000000000 --- a/src/GitHub.App/Services/EnterpriseProbe.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Reactive.Threading.Tasks; -using GitHub.Models; -using Octokit.Internal; - -namespace GitHub.Services -{ - [Export(typeof(IEnterpriseProbe))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class EnterpriseProbe : EnterpriseProbeTask, IEnterpriseProbe - { - [ImportingConstructor] - public EnterpriseProbe(IProgram program, IHttpClient httpClient) - : base(program, httpClient) - { - } - - public IObservable<EnterpriseProbeResult> Probe(Uri enterpriseBaseUrl) - { - return ProbeAsync(enterpriseBaseUrl).ToObservable(); - } - } -} diff --git a/src/GitHub.App/Services/ErrorMap.cs b/src/GitHub.App/Services/ErrorMap.cs index a60cd53ae8..19faffd4b9 100644 --- a/src/GitHub.App/Services/ErrorMap.cs +++ b/src/GitHub.App/Services/ErrorMap.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using NullGuard; using ReactiveUI; namespace GitHub.Services @@ -12,25 +11,22 @@ public class ErrorMap readonly IEnumerable<Translation> translations; public ErrorMap( - [AllowNull] ErrorMessage defaultMessage, - [AllowNull] IEnumerable<Translation> translations, - [AllowNull] IEnumerable<IRecoveryCommand> recoveryCommands) + ErrorMessage defaultMessage, + IEnumerable<Translation> translations, + IEnumerable<IRecoveryCommand> recoveryCommands) { this.defaultMessage = defaultMessage; this.translations = translations; RecoveryCommands = recoveryCommands; } - [AllowNull] public IEnumerable<IRecoveryCommand> RecoveryCommands { - [return: AllowNull] get; private set; } - [return: AllowNull] - public ErrorMessage GetErrorInfo([AllowNull] Exception exception) + public ErrorMessage GetErrorInfo(Exception exception) { if (exception != null && translations != null) { diff --git a/src/GitHub.App/Services/ErrorMessageTranslator.cs b/src/GitHub.App/Services/ErrorMessageTranslator.cs index 85303c0066..3d7646ca49 100644 --- a/src/GitHub.App/Services/ErrorMessageTranslator.cs +++ b/src/GitHub.App/Services/ErrorMessageTranslator.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using NullGuard; +using GitHub.Extensions; using ReactiveUI; namespace GitHub.Services @@ -13,10 +13,12 @@ public class ErrorMessageTranslator public ErrorMessageTranslator(IDictionary<ErrorType, ErrorMap> userErrors) { + Guard.ArgumentNotNull(userErrors, nameof(userErrors)); + userErrorMappings = userErrors; } - public UserError GetUserError(ErrorType errorType, [AllowNull] Exception exception, params object[] context) + public UserError GetUserError(ErrorType errorType, Exception exception, params object[] context) { var translation = GetUserErrorTranslation(errorType, exception, context); return new UserError(translation.ErrorMessage, translation.CauseOrResolution, translation.RecoveryCommands, null, exception) @@ -27,7 +29,7 @@ public UserError GetUserError(ErrorType errorType, [AllowNull] Exception excepti public UserErrorTranslation GetUserErrorTranslation( ErrorType errorType, - [AllowNull] Exception exception, + Exception exception, params object[] context) { ErrorMessage errorMessage = null; @@ -58,35 +60,29 @@ public UserErrorTranslation GetUserErrorTranslation( public class UserErrorTranslation { public UserErrorTranslation( - [AllowNull] string errorMessage, - [AllowNull] string causeOrResolution, - [AllowNull] IEnumerable<IRecoveryCommand> recoveryCommands) + string errorMessage, + string causeOrResolution, + IEnumerable<IRecoveryCommand> recoveryCommands) { ErrorMessage = errorMessage; CauseOrResolution = causeOrResolution; RecoveryCommands = recoveryCommands; } - [AllowNull] public string ErrorMessage { - [return: AllowNull] get; private set; } - [AllowNull] public string CauseOrResolution { - [return: AllowNull] get; private set; } - [AllowNull] public IEnumerable<IRecoveryCommand> RecoveryCommands { - [return: AllowNull] get; private set; } diff --git a/src/GitHub.App/Services/FromGraphQlExtensions.cs b/src/GitHub.App/Services/FromGraphQlExtensions.cs new file mode 100644 index 0000000000..d56a63ae93 --- /dev/null +++ b/src/GitHub.App/Services/FromGraphQlExtensions.cs @@ -0,0 +1,105 @@ +using System; +using GitHub.Models; +using Octokit.GraphQL.Model; +using CheckConclusionState = GitHub.Models.CheckConclusionState; +using CheckStatusState = GitHub.Models.CheckStatusState; +using PullRequestReviewState = GitHub.Models.PullRequestReviewState; +using StatusState = GitHub.Models.StatusState; + +namespace GitHub.Services +{ + public static class FromGraphQlExtensions + { + public static CheckConclusionState? FromGraphQl(this Octokit.GraphQL.Model.CheckConclusionState? value) + { + switch (value) + { + case null: + return null; + case Octokit.GraphQL.Model.CheckConclusionState.ActionRequired: + return CheckConclusionState.ActionRequired; + case Octokit.GraphQL.Model.CheckConclusionState.TimedOut: + return CheckConclusionState.TimedOut; + case Octokit.GraphQL.Model.CheckConclusionState.Cancelled: + return CheckConclusionState.Cancelled; + case Octokit.GraphQL.Model.CheckConclusionState.Failure: + return CheckConclusionState.Failure; + case Octokit.GraphQL.Model.CheckConclusionState.Success: + return CheckConclusionState.Success; + case Octokit.GraphQL.Model.CheckConclusionState.Neutral: + return CheckConclusionState.Neutral; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + + public static PullRequestStateEnum FromGraphQl(this PullRequestState value) + { + switch (value) + { + case PullRequestState.Open: + return PullRequestStateEnum.Open; + case PullRequestState.Closed: + return PullRequestStateEnum.Closed; + case PullRequestState.Merged: + return PullRequestStateEnum.Merged; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + + public static StatusState FromGraphQl(this Octokit.GraphQL.Model.StatusState value) + { + switch (value) + { + case Octokit.GraphQL.Model.StatusState.Expected: + return StatusState.Expected; + case Octokit.GraphQL.Model.StatusState.Error: + return StatusState.Error; + case Octokit.GraphQL.Model.StatusState.Failure: + return StatusState.Failure; + case Octokit.GraphQL.Model.StatusState.Pending: + return StatusState.Pending; + case Octokit.GraphQL.Model.StatusState.Success: + return StatusState.Success; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + + public static CheckStatusState FromGraphQl(this Octokit.GraphQL.Model.CheckStatusState value) + { + switch (value) + { + case Octokit.GraphQL.Model.CheckStatusState.Queued: + return CheckStatusState.Queued; + case Octokit.GraphQL.Model.CheckStatusState.InProgress: + return CheckStatusState.InProgress; + case Octokit.GraphQL.Model.CheckStatusState.Completed: + return CheckStatusState.Completed; + case Octokit.GraphQL.Model.CheckStatusState.Requested: + return CheckStatusState.Requested; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + + public static GitHub.Models.PullRequestReviewState FromGraphQl(this Octokit.GraphQL.Model.PullRequestReviewState value) + { + switch (value) { + case Octokit.GraphQL.Model.PullRequestReviewState.Pending: + return PullRequestReviewState.Pending; + case Octokit.GraphQL.Model.PullRequestReviewState.Commented: + return PullRequestReviewState.Commented; + case Octokit.GraphQL.Model.PullRequestReviewState.Approved: + return PullRequestReviewState.Approved; + case Octokit.GraphQL.Model.PullRequestReviewState.ChangesRequested: + return PullRequestReviewState.ChangesRequested; + case Octokit.GraphQL.Model.PullRequestReviewState.Dismissed: + return PullRequestReviewState.Dismissed; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/Services/GistPublishService.cs b/src/GitHub.App/Services/GistPublishService.cs new file mode 100644 index 0000000000..fac83f7e20 --- /dev/null +++ b/src/GitHub.App/Services/GistPublishService.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Reactive.Linq; +using GitHub.Api; +using GitHub.Models; +using LibGit2Sharp; +using Octokit; + +namespace GitHub.Services +{ + [Export(typeof(IGistPublishService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class GistPublishService : IGistPublishService + { + /// <summary> + /// Publishes a gist to GitHub. + /// </summary> + /// <param name="apiClient">The client to use to post to GitHub.</param> + /// <param name="gist">The new gist to post.</param> + /// <returns>The created gist.</returns> + public IObservable<Gist> PublishGist(IApiClient apiClient, NewGist gist) + { + return Observable.Defer(() => apiClient.CreateGist(gist)); + } + } +} diff --git a/src/GitHub.App/Services/GitClient.cs b/src/GitHub.App/Services/GitClient.cs index d4098bd8a0..6114d82169 100644 --- a/src/GitHub.App/Services/GitClient.cs +++ b/src/GitHub.App/Services/GitClient.cs @@ -1,9 +1,15 @@ using System; +using System.Collections.Generic; using System.ComponentModel.Composition; +using System.IO; using System.Linq; -using System.Reactive; -using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; using LibGit2Sharp; +using GitHub.Logging; +using Serilog; namespace GitHub.Services { @@ -11,75 +17,519 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.Shared)] public class GitClient : IGitClient { + const string defaultOriginName = "origin"; + static readonly ILogger log = LogManager.ForContext<GitClient>(); + readonly IGitService gitService; + readonly PullOptions pullOptions; readonly PushOptions pushOptions; readonly FetchOptions fetchOptions; [ImportingConstructor] - public GitClient(IGitHubCredentialProvider credentialProvider) + public GitClient(IGitHubCredentialProvider credentialProvider, IGitService gitService) { + Guard.ArgumentNotNull(credentialProvider, nameof(credentialProvider)); + Guard.ArgumentNotNull(gitService, nameof(gitService)); + + this.gitService = gitService; + pushOptions = new PushOptions { CredentialsProvider = credentialProvider.HandleCredentials }; fetchOptions = new FetchOptions { CredentialsProvider = credentialProvider.HandleCredentials }; + pullOptions = new PullOptions + { + FetchOptions = fetchOptions, + MergeOptions = new MergeOptions(), + }; + } + + public Task Pull(IRepository repository) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + return Task.Factory.StartNew(() => + { + 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 + }); } - public IObservable<Unit> Push(IRepository repository, string branchName, string remoteName) + public Task Push(IRepository repository, string branchName, string remoteName) { + Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Observable.Defer(() => + return Task.Factory.StartNew(() => { if (repository.Head?.Commits != null && repository.Head.Commits.Any()) { var remote = repository.Network.Remotes[remoteName]; - repository.Network.Push(remote, "HEAD", @"refs/heads/" + branchName, pushOptions); + var remoteRef = IsCanonical(branchName) ? branchName : @"refs/heads/" + branchName; + repository.Network.Push(remote, "HEAD", remoteRef, pushOptions); } - return Observable.Return(Unit.Default); }); } - public IObservable<Unit> Fetch(IRepository repository, string remoteName) + public Task Fetch(IRepository repository, string remoteName) { + Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Observable.Defer(() => + return Task.Factory.StartNew(() => { - var remote = repository.Network.Remotes[remoteName]; - repository.Network.Fetch(remote, fetchOptions); - return Observable.Return(Unit.Default); + 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) + { + if (UriString.RepositoryUrlsAreEqual(new UriString(remote.Url), cloneUrl)) + { + return Fetch(repo, remote.Name, refspecs); + } + } + + return Task.Factory.StartNew(() => + { + try + { + var remoteName = cloneUrl.Owner; + var remoteUri = cloneUrl.ToRepositoryUrl(); + + 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 + remoteName = cloneUrl.Owner + "-" + Guid.NewGuid(); + removeRemote = true; + } + + var remote = 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 + } + finally + { + if (removeRemote) + { + repo.Network.Remotes.Remove(remoteName); + } + } + } + catch (Exception ex) + { + log.Error(ex, "Failed to fetch"); +#if DEBUG + throw; +#endif + } }); } - public IObservable<Unit> SetRemote(IRepository repository, string remoteName, Uri url) + public Task Fetch(IRepository repository, string remoteName, params string[] refspecs) { + Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Observable.Defer(() => + 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, refspecs, fetchOptions); +#pragma warning restore 0618 + } + catch (Exception ex) + { + log.Error(ex, "Failed to fetch"); +#if DEBUG + throw; +#endif + } + }); + } + + public Task Checkout(IRepository repository, string branchName) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); + + return Task.Factory.StartNew(() => + { +#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<TreeChanges> 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<Commit>(sha1); + var commit2 = repository.Lookup<Commit>(sha2); + + if (commit1 != null && commit2 != null) + { + return repository.Diff.Compare<TreeChanges>(commit1.Tree, commit2.Tree, options); + } + else + { + return null; + } + }); + } + + public Task<Patch> Compare( + IRepository repository, + string sha1, + string sha2, + string path) + { + 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<Commit>(sha1); + var commit2 = repository.Lookup<Commit>(sha2); + + if (commit1 != null && commit2 != null) + { + return repository.Diff.Compare<Patch>( + commit1.Tree, + commit2.Tree, + new[] { path }); + } + else + { + return null; + } + }); + } + + public Task<ContentChanges> CompareWith(IRepository repository, string sha1, string sha2, string path, byte[] contents) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); + Guard.ArgumentNotEmptyString(sha2, nameof(sha1)); + Guard.ArgumentNotEmptyString(path, nameof(path)); + + return Task.Factory.StartNew(() => + { + var commit1 = repository.Lookup<Commit>(sha1); + var commit2 = repository.Lookup<Commit>(sha2); + + var treeChanges = repository.Diff.Compare<TreeChanges>(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; + }); + } + + public Task<T> GetConfig<T>(IRepository repository, string key) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(key, nameof(key)); + + return Task.Factory.StartNew(() => + { + var result = repository.Config.Get<T>(key); + return result != null ? result.Value : default(T); + }); + } + + public Task SetConfig(IRepository repository, string key, string value) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(key, nameof(key)); + Guard.ArgumentNotEmptyString(value, nameof(value)); + + return Task.Factory.StartNew(() => + { + repository.Config.Set(key, value); + }); + } + + public Task SetRemote(IRepository repository, string remoteName, Uri url) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); + + return Task.Factory.StartNew(() => { repository.Config.Set("remote." + remoteName + ".url", url.ToString()); repository.Config.Set("remote." + remoteName + ".fetch", "+refs/heads/*:refs/remotes/" + remoteName + "/*"); - - return Observable.Return(Unit.Default); }); } - public IObservable<Unit> SetTrackingBranch(IRepository repository, string branchName, string remoteName) + public Task SetTrackingBranch(IRepository repository, string branchName, string remoteName) { + Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Observable.Defer(() => + return Task.Factory.StartNew(() => { - var remoteBranchName = "refs/remotes/" + remoteName + "/" + branchName; + var remoteBranchName = IsCanonical(remoteName) ? remoteName : "refs/remotes/" + remoteName + "/" + branchName; var remoteBranch = repository.Branches[remoteBranchName]; // if it's null, it's because nothing was pushed if (remoteBranch != null) { - var localBranchName = "refs/heads/" + branchName; + var localBranchName = IsCanonical(branchName) ? branchName : "refs/heads/" + branchName; var localBranch = repository.Branches[localBranchName]; repository.Branches.Update(localBranch, b => b.TrackedBranch = remoteBranch.CanonicalName); } - return Observable.Return(Unit.Default); }); } + + public Task UnsetConfig(IRepository repository, string key) + { + Guard.ArgumentNotEmptyString(key, nameof(key)); + + return Task.Factory.StartNew(() => + { + repository.Config.Unset(key); + }); + } + + public Task<Remote> GetHttpRemote(IRepository repo, string remote) + { + Guard.ArgumentNotNull(repo, nameof(repo)); + Guard.ArgumentNotEmptyString(remote, nameof(remote)); + + return Task.Factory.StartNew(() => + { + var uri = gitService.GetRemoteUri(repo, remote); + var remoteName = uri.IsHypertextTransferProtocol ? remote : remote + "-http"; + var ret = repo.Network.Remotes[remoteName]; + if (ret == null) + ret = repo.Network.Remotes.Add(remoteName, UriString.ToUriString(uri.ToRepositoryUrl())); + return ret; + }); + } + + public Task<string> ExtractFile(IRepository repository, string commitSha, string fileName) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(commitSha, nameof(commitSha)); + Guard.ArgumentNotEmptyString(fileName, nameof(fileName)); + + return Task.Factory.StartNew(() => + { + var commit = repository.Lookup<Commit>(commitSha); + if (commit == null) + { + throw new FileNotFoundException("Couldn't find '" + fileName + "' at commit " + commitSha + "."); + } + + var blob = commit[fileName]?.Target as Blob; + return blob?.GetContentText(); + }); + } + + public Task<byte[]> ExtractFileBinary(IRepository repository, string commitSha, string fileName) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(commitSha, nameof(commitSha)); + Guard.ArgumentNotEmptyString(fileName, nameof(fileName)); + + return Task.Factory.StartNew(() => + { + var commit = repository.Lookup<Commit>(commitSha); + if (commit == null) + { + throw new FileNotFoundException("Couldn't find '" + fileName + "' at commit " + commitSha + "."); + } + + var blob = commit[fileName]?.Target as Blob; + + if (blob != null) + { + using (var m = new MemoryStream()) + { + var content = blob.GetContentStream(); + content.CopyTo(m); + return m.ToArray(); + } + } + + return null; + }); + } + + public Task<bool> IsModified(IRepository repository, string path, byte[] contents) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(path, nameof(path)); + + return Task.Factory.StartNew(() => + { + if (repository.RetrieveStatus(path) == FileStatus.Unaltered) + { + var treeEntry = repository.Head[path]; + if (treeEntry?.TargetType != TreeEntryTargetType.Blob) + { + return false; + } + + var blob1 = (Blob)treeEntry.Target; + using (var s = contents != null ? new MemoryStream(contents) : new MemoryStream()) + { + var blob2 = repository.ObjectDatabase.CreateBlob(s, path); + var diff = repository.Diff.Compare(blob1, blob2); + return diff.LinesAdded != 0 || diff.LinesDeleted != 0; + } + } + + return true; + }); + } + + public async Task<string> GetPullRequestMergeBase(IRepository repo, + UriString targetCloneUrl, string baseSha, string headSha, string baseRef, int pullNumber) + { + Guard.ArgumentNotNull(repo, nameof(repo)); + Guard.ArgumentNotNull(targetCloneUrl, nameof(targetCloneUrl)); + Guard.ArgumentNotEmptyString(baseRef, nameof(baseRef)); + + var headCommit = repo.Lookup<Commit>(headSha); + if (headCommit == null) + { + // The PR base branch might no longer exist, so we fetch using `refs/pull/<PR>/head` first. + // This will often fetch the base commits, even when the base branch no longer exists. + var headRef = $"refs/pull/{pullNumber}/head"; + await Fetch(repo, targetCloneUrl, headRef); + headCommit = repo.Lookup<Commit>(headSha); + if (headCommit == null) + { + throw new NotFoundException($"Couldn't find {headSha} after fetching from {targetCloneUrl}:{headRef}."); + } + } + + var baseCommit = repo.Lookup<Commit>(baseSha); + if (baseCommit == null) + { + await Fetch(repo, targetCloneUrl, baseRef); + baseCommit = repo.Lookup<Commit>(baseSha); + if (baseCommit == null) + { + throw new NotFoundException($"Couldn't find {baseSha} after fetching from {targetCloneUrl}:{baseRef}."); + } + } + + var mergeBaseCommit = repo.ObjectDatabase.FindMergeBase(baseCommit, headCommit); + if (mergeBaseCommit == null) + { + throw new NotFoundException($"Couldn't find merge base between {baseCommit} and {headCommit}."); + } + + return mergeBaseCommit.Sha; + } + + public Task<bool> IsHeadPushed(IRepository repo) + { + Guard.ArgumentNotNull(repo, nameof(repo)); + + return Task.Factory.StartNew(() => + { + return repo.Head.TrackingDetails.AheadBy == 0; + }); + } + + public Task<IReadOnlyList<CommitMessage>> GetMessagesForUniqueCommits( + IRepository repo, + string baseBranch, + string compareBranch, + int maxCommits) + { + return Task.Factory.StartNew(() => + { + var baseCommit = repo.Lookup<Commit>(baseBranch); + var compareCommit = repo.Lookup<Commit>(compareBranch); + if (baseCommit == null || compareCommit == null) + { + var missingBranch = baseCommit == null ? baseBranch : compareBranch; + throw new NotFoundException(missingBranch); + } + + var mergeCommit = repo.ObjectDatabase.FindMergeBase(baseCommit, compareCommit); + var commitFilter = new CommitFilter + { + IncludeReachableFrom = baseCommit, + ExcludeReachableFrom = mergeCommit, + }; + + var commits = repo.Commits + .QueryBy(commitFilter) + .Take(maxCommits) + .Select(c => new CommitMessage(c.Message)) + .ToList(); + + return (IReadOnlyList<CommitMessage>)commits; + }); + } + + static bool IsCanonical(string s) + { + Guard.ArgumentNotEmptyString(s, nameof(s)); + + return s.StartsWith("refs/", StringComparison.Ordinal); + } } } diff --git a/src/GitHub.App/Services/GitHubContextService.cs b/src/GitHub.App/Services/GitHubContextService.cs new file mode 100644 index 0000000000..640e85a659 --- /dev/null +++ b/src/GitHub.App/Services/GitHubContextService.cs @@ -0,0 +1,560 @@ +using System; +using System.IO; +using System.Text; +using System.Linq; +using System.Windows; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Text.RegularExpressions; +using System.Runtime.InteropServices; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Primitives; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using LibGit2Sharp; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services +{ + [Export(typeof(IGitHubContextService))] + public class GitHubContextService : IGitHubContextService + { + readonly IGitHubServiceProvider serviceProvider; + readonly IGitService gitService; + readonly Lazy<IVsTextManager2> textManager; + + // USERID_REGEX = /[a-z0-9][a-z0-9\-\_]*/i + const string owner = "(?<owner>[a-zA-Z0-9][a-zA-Z0-9-_]*)"; + + // REPO_REGEX = /(?:\w|\.|\-)+/i + // This supports "_" for legacy superfans with logins that still contain "_". + const string repo = @"(?<repo>(?:\w|\.|\-)+)"; + + //BRANCH_REGEX = /[^\/]+(\/[^\/]+)?/ + const string branch = @"(?<branch>[^./ ~^:?*\[\\][^/ ~^:?*\[\\]*(/[^./ ~^:?*\[\\][^/ ~^:?*\[\\]*)*)"; + + const string pull = "(?<pull>[0-9]+)"; + + const string issue = "(?<issue>[0-9]+)"; + + static readonly string tree = $"^{repo}/(?<tree>[^ ]+)"; + static readonly string blobName = $"^{repo}/(?<blobName>[^ /]+)"; + + static readonly Regex windowTitleRepositoryRegex = new Regex($"^(GitHub - )?{owner}/{repo}(: .*)? - ", RegexOptions.Compiled); + static readonly Regex windowTitleBranchRegex = new Regex($"^(GitHub - )?{owner}/{repo} at {branch} ", RegexOptions.Compiled); + static readonly Regex windowTitlePullRequestRegex = new Regex($" · Pull Request #{pull} · {owner}/{repo}( · GitHub)? - ", RegexOptions.Compiled); + static readonly Regex windowTitleIssueRegex = new Regex($" · Issue #{issue} · {owner}/{repo}( · GitHub)? - ", RegexOptions.Compiled); + static readonly Regex windowTitleBlobRegex = new Regex($"{blobName} at {branch} · {owner}/{repo}( · GitHub)? - ", RegexOptions.Compiled); + static readonly Regex windowTitleTreeRegex = new Regex($"{tree} at {branch} · {owner}/{repo}( · GitHub)? - ", RegexOptions.Compiled); + static readonly Regex windowTitleBranchesRegex = new Regex($"Branches · {owner}/{repo}( · GitHub)? - ", RegexOptions.Compiled); + + static readonly Regex urlLineRegex = new Regex($"#L(?<line>[0-9]+)(-L(?<lineEnd>[0-9]+))?$", RegexOptions.Compiled); + static readonly Regex urlBlobRegex = new Regex($"blob/(?<treeish>[^/]+(/[^/]+)*)/(?<blobName>[^/#]+)", RegexOptions.Compiled); + + static readonly Regex treeishCommitRegex = new Regex($"(?<commit>[a-z0-9]{{40}})(/(?<tree>.+))?", RegexOptions.Compiled); + static readonly Regex treeishBranchRegex = new Regex($"(?<branch>master)(/(?<tree>.+))?", RegexOptions.Compiled); + + static readonly Regex tempFileObjectishRegex = new Regex(@"\\TFSTemp\\[^\\]*[.](?<objectish>[a-z0-9]{8})[.][^.\\]*$", RegexOptions.Compiled); + + [ImportingConstructor] + public GitHubContextService(IGitHubServiceProvider serviceProvider, IGitService gitService) + { + this.serviceProvider = serviceProvider; + this.gitService = gitService; + textManager = new Lazy<IVsTextManager2>(() => serviceProvider.GetService<SVsTextManager, IVsTextManager2>()); + } + + /// <inheritdoc/> + public GitHubContext FindContextFromClipboard() + { + var text = Clipboard.GetText(TextDataFormat.Text); + return FindContextFromUrl(text); + } + + /// <inheritdoc/> + public GitHubContext FindContextFromUrl(string url) + { + var uri = new UriString(url); + if (!uri.IsValidUri) + { + return null; + } + + if (!uri.IsHypertextTransferProtocol) + { + return null; + } + + var context = new GitHubContext + { + Host = uri.Host, + Owner = uri.Owner, + RepositoryName = uri.RepositoryName, + Url = uri + }; + + var repositoryPrefix = uri.ToRepositoryUrl().ToString() + "/"; + if (!url.StartsWith(repositoryPrefix, StringComparison.OrdinalIgnoreCase)) + { + return context; + } + + var subpath = url.Substring(repositoryPrefix.Length); + + (context.Line, context.LineEnd) = FindLine(subpath); + + context.PullRequest = FindPullRequest(url); + + var match = urlBlobRegex.Match(subpath); + if (match.Success) + { + context.TreeishPath = match.Groups["treeish"].Value; + context.BlobName = match.Groups["blobName"].Value; + context.LinkType = LinkType.Blob; + return context; + } + + return context; + } + + /// <inheritdoc/> + public GitHubContext FindContextFromBrowser() + { + return + FindWindowTitlesForClass("Chrome_WidgetWin_1") // Chrome + .Concat(FindWindowTitlesForClass("MozillaWindowClass")) // Firefox + .Select(FindContextFromWindowTitle).Where(x => x != null) + .FirstOrDefault(); + } + + /// <inheritdoc/> + public Uri ToRepositoryUrl(GitHubContext context) + { + var builder = new UriBuilder("https", context.Host ?? "github.com"); + builder.Path = $"{context.Owner}/{context.RepositoryName}"; + return builder.Uri; + } + + /// <inheritdoc/> + public GitHubContext FindContextFromWindowTitle(string windowTitle) + { + var match = windowTitleBlobRegex.Match(windowTitle); + if (match.Success) + { + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value, + BranchName = match.Groups["branch"].Value, + BlobName = match.Groups["blobName"].Value + }; + } + + match = windowTitleTreeRegex.Match(windowTitle); + if (match.Success) + { + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value, + BranchName = match.Groups["branch"].Value, + TreeishPath = $"{match.Groups["branch"].Value}/{match.Groups["tree"].Value}" + }; + } + + match = windowTitleRepositoryRegex.Match(windowTitle); + if (match.Success) + { + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value, + }; + } + + match = windowTitleBranchRegex.Match(windowTitle); + if (match.Success) + { + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value, + BranchName = match.Groups["branch"].Value, + }; + } + + match = windowTitleBranchesRegex.Match(windowTitle); + if (match.Success) + { + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value + }; + } + + match = windowTitlePullRequestRegex.Match(windowTitle); + if (match.Success) + { + int.TryParse(match.Groups["pull"].Value, out int pullRequest); + + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value, + PullRequest = pullRequest + }; + } + + match = windowTitleIssueRegex.Match(windowTitle); + if (match.Success) + { + int.TryParse(match.Groups["issue"].Value, out int issue); + + return new GitHubContext + { + Owner = match.Groups["owner"].Value, + RepositoryName = match.Groups["repo"].Value, + Issue = issue + }; + } + + return null; + } + + /// <inheritdoc/> + public bool TryOpenFile(string repositoryDir, GitHubContext context) + { + var (commitish, path, isSha) = ResolveBlob(repositoryDir, context); + if (path == null) + { + return false; + } + + var fullPath = Path.Combine(repositoryDir, path.Replace('/', '\\')); + var textView = OpenDocument(fullPath); + SetSelection(textView, context); + return true; + } + + /// <inheritdoc/> + public (string commitish, string path, string commitSha) ResolveBlob(string repositoryDir, GitHubContext context, string remoteName = "origin") + { + Guard.ArgumentNotNull(repositoryDir, nameof(repositoryDir)); + Guard.ArgumentNotNull(context, nameof(context)); + + using (var repository = gitService.GetRepository(repositoryDir)) + { + if (context.TreeishPath == null) + { + // Blobs without a TreeishPath aren't currently supported + return (null, null, null); + } + + if (context.BlobName == null) + { + // Not a blob + return (null, null, null); + } + + var objectishPath = $"{context.TreeishPath}/{context.BlobName}"; + var objectish = ToObjectish(objectishPath); + var (commitSha, pathSha) = objectish.First(); + if (ObjectId.TryParse(commitSha, out ObjectId objectId) && repository.Lookup(objectId) != null) + { + if (repository.Lookup($"{commitSha}:{pathSha}") != null) + { + return (commitSha, pathSha, commitSha); + } + } + + foreach (var (commitish, path) in objectish) + { + var resolveRefs = new[] { $"refs/remotes/{remoteName}/{commitish}", $"refs/tags/{commitish}" }; + foreach (var resolveRef in resolveRefs) + { + var commit = repository.Lookup(resolveRef); + if (commit != null) + { + var blob = repository.Lookup($"{resolveRef}:{path}"); + if (blob != null) + { + return (resolveRef, path, commit.Sha); + } + + // Resolved commitish but not path + return (resolveRef, null, commit.Sha); + } + } + } + + return (null, null, null); + } + + IEnumerable<(string commitish, string path)> ToObjectish(string treeishPath) + { + var index = 0; + while ((index = treeishPath.IndexOf('/', index + 1)) != -1) + { + var commitish = treeishPath.Substring(0, index); + var path = treeishPath.Substring(index + 1); + yield return (commitish, path); + } + } + } + + /// <inheritdoc/> + public string FindObjectishForTFSTempFile(string tempFile) + { + var match = tempFileObjectishRegex.Match(tempFile); + if (match.Success) + { + return match.Groups["objectish"].Value; + } + + return null; + } + + /// <inheritdoc/> + public (string commitSha, string blobPath) ResolveBlobFromHistory(string repositoryDir, string objectish) + { + using (var repo = gitService.GetRepository(repositoryDir)) + { + var blob = repo.Lookup<Blob>(objectish); + if (blob == null) + { + return (null, null); + } + + foreach (var commit in repo.Commits) + { + var trees = new Stack<Tree>(); + trees.Push(commit.Tree); + + while (trees.Count > 0) + { + foreach (var treeEntry in trees.Pop()) + { + if (treeEntry.Target == blob) + { + return (commit.Sha, treeEntry.Path); + } + + if (treeEntry.TargetType == TreeEntryTargetType.Tree) + { + trees.Push((Tree)treeEntry.Target); + } + } + } + } + + return (null, null); + } + } + + /// <inheritdoc/> + public bool HasChangesInWorkingDirectory(string repositoryDir, string commitish, string path) + { + using (var repo = gitService.GetRepository(repositoryDir)) + { + var commit = repo.Lookup<Commit>(commitish); + var paths = new[] { path }; + + return repo.Diff.Compare<Patch>(commit.Tree, DiffTargets.WorkingDirectory, paths).Count() > 0; + } + } + + /// <inheritdoc/> + public async Task<bool> TryAnnotateFile(string repositoryDir, string currentBranch, GitHubContext context) + { + var (commitish, path, commitSha) = ResolveBlob(repositoryDir, context); + if (path == null) + { + return false; + } + + if (!AnnotateFile(repositoryDir, currentBranch, path, commitSha)) + { + return false; + } + + if (context.Line != null) + { + await Task.Delay(1000); + var activeView = FindActiveView(); + SetSelection(activeView, context); + } + + return true; + } + + IVsTextView FindActiveView() + { + if (!ErrorHandler.Succeeded(textManager.Value.GetActiveView2(1, null, (uint)_VIEWFRAMETYPE.vftToolWindow, out IVsTextView textView))) + { + return null; + } + + return textView; + } + + /// <summary> + /// Call AnnotateFile of the IGitExt2 service if it can be found. + /// </summary> + /// <remarks> + /// The IGitExt2 interface was introduced in an update of Visual Studio 2017. + /// The <see cref="branchName"/> must exist but doesn't appear to be used in the UI. + /// </remarks> + /// <param name="repositoryPath">Path of the target repository</param> + /// <param name="branchName">A branch of the target repository</param> + /// <param name="relativePath">A path the the target blob</param> + /// <param name="versionSha">The commit version of the blob</param> + /// <returns>True if AnnotateFile functionality is available.</returns> + bool AnnotateFile(string repositoryPath, string branchName, string relativePath, string versionSha) + { + var serviceType = Type.GetType("Microsoft.VisualStudio.TeamFoundation.Git.Extensibility.IGitExt2, Microsoft.TeamFoundation.Git.Provider", false); + if (serviceType == null) + { + return false; + } + + var service = serviceProvider.GetService(serviceType); + if (service == null) + { + return false; + } + + try + { + Invoke(service, "AnnotateFile", repositoryPath, branchName, relativePath, versionSha); + return true; + } + catch (Exception) + { + return false; + } + + void Invoke<T1, T2, T3, T4>(object target, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + var action = (Action<T1, T2, T3, T4>)Delegate.CreateDelegate(typeof(Action<T1, T2, T3, T4>), target, method); + action.Invoke(arg1, arg2, arg3, arg4); + } + } + + static void SetSelection(IVsTextView textView, GitHubContext context) + { + var line = context.Line; + var lineEnd = context.LineEnd ?? line; + + if (line != null) + { + ErrorHandler.ThrowOnFailure(textView.GetBuffer(out IVsTextLines buffer)); + buffer.GetLengthOfLine(lineEnd.Value - 1, out int lineEndLength); + ErrorHandler.ThrowOnFailure(textView.SetSelection(line.Value - 1, 0, lineEnd.Value - 1, lineEndLength)); + ErrorHandler.ThrowOnFailure(textView.CenterLines(line.Value - 1, lineEnd.Value - line.Value + 1)); + } + } + + IVsTextView OpenDocument(string fullPath) + { + var logicalView = VSConstants.LOGVIEWID.TextView_guid; + IVsUIHierarchy hierarchy; + uint itemID; + IVsWindowFrame windowFrame; + IVsTextView view; + VsShellUtilities.OpenDocument(serviceProvider, fullPath, logicalView, out hierarchy, out itemID, out windowFrame, out view); + return view; + } + + static (int? lineStart, int? lineEnd) FindLine(UriString gitHubUrl) + { + var url = gitHubUrl.ToString(); + + var match = urlLineRegex.Match(url); + if (match.Success) + { + int.TryParse(match.Groups["line"].Value, out int line); + + var lineEndGroup = match.Groups["lineEnd"]; + if (string.IsNullOrEmpty(lineEndGroup.Value)) + { + return (line, null); + } + + int.TryParse(lineEndGroup.Value, out int lineEnd); + return (line, lineEnd); + } + + return (null, null); + } + + static int? FindPullRequest(UriString gitHubUrl) + { + var pullRequest = FindSubPath(gitHubUrl, "/pull/")?.Split('/').First(); + if (pullRequest == null) + { + return null; + } + + if (!int.TryParse(pullRequest, out int number)) + { + return null; + } + + return number; + } + + static string FindSubPath(UriString gitHubUrl, string matchPath) + { + var url = gitHubUrl.ToString(); + var prefix = gitHubUrl.ToRepositoryUrl() + matchPath; + if (!url.StartsWith(prefix)) + { + return null; + } + + var endIndex = url.IndexOf('#'); + if (endIndex == -1) + { + endIndex = gitHubUrl.Length; + } + + var path = url.Substring(prefix.Length, endIndex - prefix.Length); + return path; + } + + static IEnumerable<string> FindWindowTitlesForClass(string className) + { + IntPtr handleWin = IntPtr.Zero; + while (IntPtr.Zero != (handleWin = User32.FindWindowEx(IntPtr.Zero, handleWin, className, IntPtr.Zero))) + { + // Allocate correct string length first + int length = User32.GetWindowTextLength(handleWin); + if (length == 0) + { + continue; + } + + var titleBuilder = new StringBuilder(length + 1); + User32.GetWindowText(handleWin, titleBuilder, titleBuilder.Capacity); + yield return titleBuilder.ToString(); + } + } + + static class User32 + { + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern int GetWindowTextLength(IntPtr hWnd); + } + } +} diff --git a/src/GitHub.App/Services/GitHubCredentialProvider.cs b/src/GitHub.App/Services/GitHubCredentialProvider.cs index 1300d4346b..edec581c30 100644 --- a/src/GitHub.App/Services/GitHubCredentialProvider.cs +++ b/src/GitHub.App/Services/GitHubCredentialProvider.cs @@ -1,12 +1,12 @@ using System; using System.ComponentModel.Composition; -using System.Reactive.Linq; -using System.Security; -using Akavache; -using GitHub.Caches; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Logging; using GitHub.Primitives; using LibGit2Sharp; -using NullGuard; +using Microsoft.VisualStudio.Shell; +using Serilog; namespace GitHub.Services { @@ -14,37 +14,42 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.Shared)] class GitHubCredentialProvider : IGitHubCredentialProvider { - readonly ISecureBlobCache secureCache = null; + static readonly ILogger log = LogManager.ForContext<GitHubCredentialProvider>(); + readonly IKeychain keychain; [ImportingConstructor] - public GitHubCredentialProvider(ISharedCache sharedCache) + public GitHubCredentialProvider(IKeychain keychain) { - secureCache = sharedCache.Secure; + Guard.ArgumentNotNull(keychain, nameof(keychain)); + + this.keychain = keychain; } /// <summary> /// This is a callback from libgit2 /// </summary> /// <returns></returns> - public Credentials HandleCredentials([AllowNull]string url, [AllowNull]string username, SupportedCredentialTypes types) + public Credentials HandleCredentials(string url, string username, SupportedCredentialTypes types) { if (url == null) return null; // wondering if we should return DefaultCredentials instead var host = HostAddress.Create(url); - return secureCache.GetObject<Tuple<string, SecureString>>("login:" + host.CredentialCacheKeyHost) - .Select(CreateCredentials) - .Catch(Observable.Return<Credentials>(null)) - .Wait(); - } - static Credentials CreateCredentials(Tuple<string, SecureString> data) - { - return new SecureUsernamePasswordCredentials + try + { + var credentials = ThreadHelper.JoinableTaskFactory.Run(async () => await keychain.Load(host)); + return new UsernamePasswordCredentials + { + Username = credentials.Item1, + Password = credentials.Item2, + }; + } + catch (Exception e) { - Username = data.Item1, - Password = data.Item2 - }; + log.Error(e, "Error loading credentials in GitHubCredentialProvider"); + return null; + } } } } \ No newline at end of file diff --git a/src/GitHub.App/Services/GitHubCredentialStore.cs b/src/GitHub.App/Services/GitHubCredentialStore.cs deleted file mode 100644 index 4bee84389f..0000000000 --- a/src/GitHub.App/Services/GitHubCredentialStore.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Globalization; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Akavache; -using GitHub.Caches; -using GitHub.Extensions.Reactive; -using GitHub.Primitives; -using NLog; -using Octokit; - -namespace GitHub.Services -{ - public class GitHubCredentialStore : ICredentialStore - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - readonly HostAddress hostAddress; - readonly ILoginCache loginCache; - - public GitHubCredentialStore(HostAddress hostAddress, ILoginCache loginCache) - { - this.hostAddress = hostAddress; - this.loginCache = loginCache; - } - - public async Task<Credentials> GetCredentials() - { - return await loginCache.GetLoginAsync(hostAddress) - .CatchNonCritical(Observable.Return(LoginCache.EmptyLoginInfo)) - .Select(CreateFromLoginInfo); - } - - Credentials CreateFromLoginInfo(LoginInfo loginInfo) - { - if (loginInfo == LoginCache.EmptyLoginInfo) - { - log.Debug(CultureInfo.InvariantCulture, "Could not retrieve login info from the secure cache '{0}'", - hostAddress.CredentialCacheKeyHost); - return Credentials.Anonymous; - } - - return new Credentials(loginInfo.UserName, loginInfo.Password); - } - } -} diff --git a/src/GitHub.App/Services/GlobalConnection.cs b/src/GitHub.App/Services/GlobalConnection.cs new file mode 100644 index 0000000000..01fb68fd75 --- /dev/null +++ b/src/GitHub.App/Services/GlobalConnection.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Models; +using GitHub.Services; + +namespace GitHub.App.Services +{ + [Export(typeof(IGlobalConnection))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class GlobalConnection : IGlobalConnection + { + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + public GlobalConnection(IGitHubServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public IConnection Get() => serviceProvider.TryGetService<IConnection>(); + } +} diff --git a/src/GitHub.App/Services/IEnterpriseProbe.cs b/src/GitHub.App/Services/IEnterpriseProbe.cs deleted file mode 100644 index 5bffde4fa5..0000000000 --- a/src/GitHub.App/Services/IEnterpriseProbe.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace GitHub.Services -{ - public interface IEnterpriseProbe - { - IObservable<EnterpriseProbeResult> Probe(Uri enterpriseBaseUrl); - } -} diff --git a/src/GitHub.App/Services/ImageDownloader.cs b/src/GitHub.App/Services/ImageDownloader.cs index 47751c9a77..1c911bc37c 100644 --- a/src/GitHub.App/Services/ImageDownloader.cs +++ b/src/GitHub.App/Services/ImageDownloader.cs @@ -1,11 +1,15 @@ using System; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Globalization; using System.Net; using System.Net.Http; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; +using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Logging; using Octokit; using Octokit.Internal; @@ -16,14 +20,61 @@ namespace GitHub.Services public class ImageDownloader : IImageDownloader { readonly Lazy<IHttpClient> httpClient; + readonly IDictionary<string, NonImageContentException> exceptionCache; [ImportingConstructor] public ImageDownloader(Lazy<IHttpClient> httpClient) { this.httpClient = httpClient; + exceptionCache = new Dictionary<string, NonImageContentException>(); } + public static string CachedExceptionMessage(string host) => + string.Format(CultureInfo.InvariantCulture, "Host '{0}' previously returned a non-image content type", host); + public static string CouldNotDownloadExceptionMessage(Uri imageUri) => + string.Format(CultureInfo.InvariantCulture, "Could not download image from '{0}'", imageUri); + public static string NonImageContentExceptionMessage(string contentType) => + string.Format(CultureInfo.InvariantCulture, "Server responded with a non-image content type '{0}'", contentType); + + /// <summary> + /// Get the bytes for a given image URI. + /// </summary> + /// <remarks> + /// If a host returns a non-image content type, this will be remembered and subsequent download requests + /// to the same host will automatically throw a <see cref="NonImageContentException"/>. This prevents a + /// barrage of download requests when authentication is required (but not currently supported). + /// </remarks> + /// <param name="imageUri">The URI of an image.</param> + /// <returns>The bytes for a given image URI.</returns> + /// <exception cref="HttpRequestException">When the URI returns a status code that isn't OK/200.</exception> + /// <exception cref="NonImageContentException">When the URI returns a non-image content type.</exception> public IObservable<byte[]> DownloadImageBytes(Uri imageUri) + { + return ExceptionCachingDownloadImageBytesAsync(imageUri).ToObservable(); + } + + async Task<byte[]> ExceptionCachingDownloadImageBytesAsync(Uri imageUri) + { + var host = imageUri.Host; + + NonImageContentException exception; + if (exceptionCache.TryGetValue(host, out exception)) + { + throw new NonImageContentException(CachedExceptionMessage(host), exception); + } + + try + { + return await DownloadImageBytesAsync(imageUri); + } + catch (NonImageContentException e) + { + exceptionCache[host] = e; + throw; + } + } + + async Task<byte[]> DownloadImageBytesAsync(Uri imageUri) { var request = new Request { @@ -32,26 +83,23 @@ public IObservable<byte[]> DownloadImageBytes(Uri imageUri) Method = HttpMethod.Get, }; - return HttpClient.Send(request) - .ToObservable() - .Select(response => GetSuccessfulBytes(imageUri, response)); + var response = await HttpClient.Send(request); + return GetSuccessfulBytes(imageUri, response); } static byte[] GetSuccessfulBytes(Uri imageUri, IResponse response) { - Debug.Assert(imageUri != null, "The imageUri cannot be null"); - Debug.Assert(response != null, "The response cannot be null"); + Log.Assert(imageUri != null, "The imageUri cannot be null"); + Log.Assert(response != null, "The response cannot be null"); if (response.StatusCode != HttpStatusCode.OK) { - throw new HttpRequestException(string.Format(CultureInfo.InvariantCulture, "Could not download image from {0}", imageUri)); + throw new HttpRequestException(CouldNotDownloadExceptionMessage(imageUri)); } if (response.ContentType == null || !response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { - throw new HttpRequestException( - string.Format(CultureInfo.InvariantCulture, - "Server responded with a non-image content type: {0}", response.ContentType)); + throw new NonImageContentException(NonImageContentExceptionMessage(response.ContentType)); } return response.Body as byte[]; @@ -59,4 +107,16 @@ static byte[] GetSuccessfulBytes(Uri imageUri, IResponse response) IHttpClient HttpClient { get { return httpClient.Value; } } } -} \ No newline at end of file + + [Serializable] + public class NonImageContentException : HttpRequestException + { + public NonImageContentException() { } + public NonImageContentException(string message) : base(message) { } + public NonImageContentException(string message, Exception inner) : base(message, inner) { } + + [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", + Justification = "HttpRequestException doesn't have required constructor")] + protected NonImageContentException(SerializationInfo info, StreamingContext context) { } + } +} diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index e0044764b1..3c165df248 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -2,20 +2,25 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Globalization; +using System.IO; using System.Linq; using System.Reactive; using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; using Akavache; using GitHub.Api; using GitHub.Caches; using GitHub.Collections; using GitHub.Extensions; using GitHub.Extensions.Reactive; +using GitHub.Logging; using GitHub.Models; using GitHub.Primitives; -using NLog; -using NullGuard; using Octokit; +using Octokit.GraphQL; +using Serilog; +using static Octokit.GraphQL.Variable; namespace GitHub.Services { @@ -23,48 +28,70 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.NonShared)] public class ModelService : IModelService { - static readonly Logger log = LogManager.GetCurrentClassLogger(); + static readonly ILogger log = LogManager.ForContext<ModelService>(); + + public const string PRPrefix = "pr"; + const string TempFilesDirectory = Info.ApplicationInfo.ApplicationName; + const string CachedFilesDirectory = "CachedFiles"; - readonly IApiClient apiClient; readonly IBlobCache hostCache; readonly IAvatarProvider avatarProvider; - public ModelService(IApiClient apiClient, IBlobCache hostCache, IAvatarProvider avatarProvider) + public ModelService( + IApiClient apiClient, + IBlobCache hostCache, + IAvatarProvider avatarProvider) { - this.apiClient = apiClient; + this.ApiClient = apiClient; this.hostCache = hostCache; this.avatarProvider = avatarProvider; } - public IObservable<IReadOnlyList<GitIgnoreItem>> GetGitIgnoreTemplates() + public IApiClient ApiClient { get; } + + public IObservable<IAccount> GetCurrentUser() + { + return GetUserFromCache().Select(Create); + } + + public IObservable<IAccount> GetUser(string login) + { + return hostCache.GetAndRefreshObject("user|" + login, + () => ApiClient.GetUser(login).Select(AccountCacheItem.Create), TimeSpan.FromMinutes(5), TimeSpan.FromDays(7)) + .Select(Create); + } + + public IObservable<GitIgnoreItem> GetGitIgnoreTemplates() { return Observable.Defer(() => - hostCache.GetAndRefreshObject( - "gitignores", - GetOrderedGitIgnoreTemplatesFromApi, - TimeSpan.FromDays(1), - TimeSpan.FromDays(7)) - .ToReadOnlyList(GitIgnoreItem.Create, GitIgnoreItem.None)) - .Catch<IReadOnlyList<GitIgnoreItem>, Exception>(e => + hostCache.GetAndFetchLatestFromIndex(CacheIndex.GitIgnoresPrefix, () => + GetGitIgnoreTemplatesFromApi(), + item => { }, + TimeSpan.FromMinutes(1), + TimeSpan.FromDays(7)) + ) + .Select(Create) + .Catch<GitIgnoreItem, Exception>(e => { - log.Info("Failed to retrieve GitIgnoreTemplates", e); - return Observable.Return(new[] { GitIgnoreItem.None }); + log.Error(e, "Failed to retrieve GitIgnoreTemplates"); + return Observable.Empty<GitIgnoreItem>(); }); } - public IObservable<IReadOnlyList<LicenseItem>> GetLicenses() + public IObservable<LicenseItem> GetLicenses() { return Observable.Defer(() => - hostCache.GetAndRefreshObject( - "licenses", - GetOrderedLicensesFromApi, - TimeSpan.FromDays(1), - TimeSpan.FromDays(7)) - .ToReadOnlyList(Create, LicenseItem.None)) - .Catch<IReadOnlyList<LicenseItem>, Exception>(e => + hostCache.GetAndFetchLatestFromIndex(CacheIndex.LicensesPrefix, () => + GetLicensesFromApi(), + item => { }, + TimeSpan.FromMinutes(1), + TimeSpan.FromDays(7)) + ) + .Select(Create) + .Catch<LicenseItem, Exception>(e => { - log.Info("Failed to retrieve GitIgnoreTemplates", e); - return Observable.Return(new[] { LicenseItem.None }); + log.Error(e, "Failed to retrieve licenses"); + return Observable.Empty<LicenseItem>(); }); } @@ -77,28 +104,31 @@ public IObservable<IReadOnlyList<IAccount>> GetAccounts() .ToReadOnlyList(Create); } - IObservable<IEnumerable<LicenseCacheItem>> GetOrderedLicensesFromApi() + public IObservable<IRemoteRepositoryModel> GetForks(IRepositoryModel repository) + { + return ApiClient.GetForks(repository.Owner, repository.Name) + .Select(x => new RemoteRepositoryModel(x)); + } + + IObservable<LicenseCacheItem> GetLicensesFromApi() { - return apiClient.GetLicenses() + return ApiClient.GetLicenses() .WhereNotNull() - .Select(LicenseCacheItem.Create) - .ToList() - .Select(licenses => licenses.OrderByDescending(lic => LicenseItem.IsRecommended(lic.Key))); + .Select(LicenseCacheItem.Create); } - IObservable<IEnumerable<string>> GetOrderedGitIgnoreTemplatesFromApi() + IObservable<GitIgnoreCacheItem> GetGitIgnoreTemplatesFromApi() { - return apiClient.GetGitIgnoreTemplates() + return ApiClient.GetGitIgnoreTemplates() .WhereNotNull() - .ToList() - .Select(templates => templates.OrderByDescending(GitIgnoreItem.IsRecommended)); + .Select(GitIgnoreCacheItem.Create); } IObservable<IEnumerable<AccountCacheItem>> GetUser() { return hostCache.GetAndRefreshObject("user", - () => apiClient.GetUser().Select(AccountCacheItem.Create), TimeSpan.FromMinutes(5), TimeSpan.FromDays(7)) - .Take(1) + () => ApiClient.GetUser().Select(AccountCacheItem.Create), TimeSpan.FromMinutes(5), TimeSpan.FromDays(7)) + .TakeLast(1) .ToList(); } @@ -106,36 +136,47 @@ IObservable<IEnumerable<AccountCacheItem>> GetUserOrganizations() { return GetUserFromCache().SelectMany(user => hostCache.GetAndRefreshObject(user.Login + "|orgs", - () => apiClient.GetOrganizations().Select(AccountCacheItem.Create).ToList(), - TimeSpan.FromMinutes(5), TimeSpan.FromDays(7))) + () => ApiClient.GetOrganizations().Select(AccountCacheItem.Create).ToList(), + TimeSpan.FromMinutes(2), TimeSpan.FromDays(7))) + // TODO: Akavache returns the cached version followed by the fresh version if > 2 + // minutes have expired from the last request. Here we make sure the latest value is + // returned but it's a hack. We really need a better way to cache this stuff. + .TakeLast(1) .Catch<IEnumerable<AccountCacheItem>, KeyNotFoundException>( // This could in theory happen if we try to call this before the user is logged in. e => { - log.Error("Retrieve user organizations failed because user is not stored in the cache.", (Exception)e); + log.Error(e, "Retrieve user organizations failed because user is not stored in the cache"); return Observable.Return(Enumerable.Empty<AccountCacheItem>()); }) .Catch<IEnumerable<AccountCacheItem>, Exception>(e => { - log.Error("Retrieve user organizations failed.", e); + log.Error(e, "Retrieve user organizations failed"); return Observable.Return(Enumerable.Empty<AccountCacheItem>()); }); } - public IObservable<IReadOnlyList<IRepositoryModel>> GetRepositories() + public IObservable<IReadOnlyList<IRemoteRepositoryModel>> GetRepositories() { return GetUserRepositories(RepositoryType.Owner) - .Take(1) - .Concat(GetUserRepositories(RepositoryType.Member).Take(1)) + .TakeLast(1) + .Concat(GetUserRepositories(RepositoryType.Member).TakeLast(1)) .Concat(GetAllRepositoriesForAllOrganizations()); } - public IObservable<AccountCacheItem> GetUserFromCache() + IObservable<AccountCacheItem> GetUserFromCache() { return Observable.Defer(() => hostCache.GetObject<AccountCacheItem>("user")); } - public ITrackingCollection<IPullRequestModel> GetPullRequests(ISimpleRepositoryModel repo) + /// <summary> + /// Gets a collection of Pull Requests. If you want to refresh existing data, pass a collection in + /// </summary> + /// <param name="repo"></param> + /// <param name="collection"></param> + /// <returns></returns> + public ITrackingCollection<IPullRequestModel> GetPullRequests(IRepositoryModel repo, + ITrackingCollection<IPullRequestModel> collection) { // Since the api to list pull requests returns all the data for each pr, cache each pr in its own entry // and also cache an index that contains all the keys for each pr. This way we can fetch prs in bulk @@ -144,24 +185,100 @@ public ITrackingCollection<IPullRequestModel> GetPullRequests(ISimpleRepositoryM // and replaces it instead of appending, so items get refreshed in-place as they come in. var keyobs = GetUserFromCache() - .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name)); - - var col = new TrackingCollection<IPullRequestModel>(); + .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, repo.Owner, repo.Name)); var source = Observable.Defer(() => keyobs .SelectMany(key => hostCache.GetAndFetchLatestFromIndex(key, () => - apiClient.GetPullRequestsForRepository(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName) + ApiClient.GetPullRequestsForRepository(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName) .Select(PullRequestCacheItem.Create), - item => col.RemoveItem(Create(item)), + item => + { + if (collection.Disposed) return; + + // this could blow up due to the collection being disposed somewhere else + try { collection.RemoveItem(Create(item)); } + catch (ObjectDisposedException) { } + }, + TimeSpan.Zero, + TimeSpan.FromDays(7)) + ) + .Select(Create) + ); + + collection.Listen(source); + return collection; + } + + public IObservable<IPullRequestModel> GetPullRequest(string owner, string name, int number) + { + throw new NotImplementedException(); + } + + public IObservable<IRemoteRepositoryModel> GetRepository(string owner, string repo) + { + var keyobs = GetUserFromCache() + .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2}/{3}", CacheIndex.RepoPrefix, user.Login, owner, repo)); + + return Observable.Defer(() => keyobs + .SelectMany(key => + hostCache.GetAndFetchLatest( + key, + () => ApiClient.GetRepository(owner, repo).Select(RepositoryCacheItem.Create)) + .Select(Create))); + } + + public ITrackingCollection<IRemoteRepositoryModel> GetRepositories(ITrackingCollection<IRemoteRepositoryModel> collection) + { + var keyobs = GetUserFromCache() + .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}", CacheIndex.RepoPrefix, user.Login)); + + var source = Observable.Defer(() => keyobs + .SelectMany(key => + hostCache.GetAndFetchLatestFromIndex(key, () => + ApiClient.GetRepositories() + .Select(RepositoryCacheItem.Create), + item => + { + if (collection.Disposed) return; + + // this could blow up due to the collection being disposed somewhere else + try { collection.RemoveItem(Create(item)); } + catch (ObjectDisposedException) { } + }, TimeSpan.FromMinutes(5), TimeSpan.FromDays(1)) ) .Select(Create) ); - col.Listen(source); - return col; + collection.Listen(source); + return collection; + } + + public IObservable<IPullRequestModel> CreatePullRequest(ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, + IBranch sourceBranch, IBranch targetBranch, + string title, string body) + { + var keyobs = GetUserFromCache() + .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, targetRepository.Owner, targetRepository.Name)); + + return Observable.Defer(() => keyobs + .SelectMany(key => + hostCache.PutAndUpdateIndex(key, () => + ApiClient.CreatePullRequest( + new NewPullRequest(title, + string.Format(CultureInfo.InvariantCulture, "{0}:{1}", sourceRepository.Owner, sourceBranch.Name), + targetBranch.Name) + { Body = body }, + targetRepository.Owner, + targetRepository.Name) + .Select(PullRequestCacheItem.Create) + , + TimeSpan.FromMinutes(30)) + ) + .Select(Create) + ); } public IObservable<Unit> InvalidateAll() @@ -169,70 +286,100 @@ public IObservable<Unit> InvalidateAll() return hostCache.InvalidateAll().ContinueAfter(() => hostCache.Vacuum()); } - IObservable<IReadOnlyList<IRepositoryModel>> GetUserRepositories(RepositoryType repositoryType) + public IObservable<string> GetFileContents(IRepositoryModel repo, string commitSha, string path, string fileSha) + { + return Observable.Defer(() => Task.Run(async () => + { + // Store cached file contents a the temp directory so they can be deleted by disk cleanup etc. + var tempDir = Path.Combine(Path.GetTempPath(), TempFilesDirectory, CachedFilesDirectory, fileSha.Substring(0, 2)); + var tempFile = Path.Combine(tempDir, Path.GetFileNameWithoutExtension(path) + '@' + fileSha + Path.GetExtension(path)); + + if (!File.Exists(tempFile)) + { + var contents = await ApiClient.GetFileContents(repo.Owner, repo.Name, commitSha, path); + Directory.CreateDirectory(tempDir); + File.WriteAllBytes(tempFile, Convert.FromBase64String(contents.EncodedContent)); + } + + return Observable.Return(tempFile); + })); + } + + IObservable<IReadOnlyList<IRemoteRepositoryModel>> GetUserRepositories(RepositoryType repositoryType) { return Observable.Defer(() => GetUserFromCache().SelectMany(user => hostCache.GetAndRefreshObject(string.Format(CultureInfo.InvariantCulture, "{0}|{1}:repos", user.Login, repositoryType), () => GetUserRepositoriesFromApi(repositoryType), - TimeSpan.FromMinutes(5), + TimeSpan.FromMinutes(2), TimeSpan.FromDays(7))) .ToReadOnlyList(Create)) - .Catch<IReadOnlyList<IRepositoryModel>, KeyNotFoundException>( + .Catch<IReadOnlyList<IRemoteRepositoryModel>, KeyNotFoundException>( // This could in theory happen if we try to call this before the user is logged in. e => { - string message = string.Format(CultureInfo.InvariantCulture, - "Retrieving '{0}' user repositories failed because user is not stored in the cache.", + log.Error(e, + "Retrieving {RepositoryType} user repositories failed because user is not stored in the cache", repositoryType); - log.Error(message, e); - return Observable.Return(new IRepositoryModel[] {}); + return Observable.Return(new IRemoteRepositoryModel[] {}); }); } IObservable<IEnumerable<RepositoryCacheItem>> GetUserRepositoriesFromApi(RepositoryType repositoryType) { - return apiClient.GetUserRepositories(repositoryType) + return ApiClient.GetUserRepositories(repositoryType) .WhereNotNull() .Select(RepositoryCacheItem.Create) .ToList() .Catch<IEnumerable<RepositoryCacheItem>, Exception>(_ => Observable.Return(Enumerable.Empty<RepositoryCacheItem>())); } - IObservable<IReadOnlyList<IRepositoryModel>> GetAllRepositoriesForAllOrganizations() + IObservable<IReadOnlyList<IRemoteRepositoryModel>> GetAllRepositoriesForAllOrganizations() { return GetUserOrganizations() .SelectMany(org => org.ToObservable()) - .SelectMany(org => GetOrganizationRepositories(org.Login).Take(1)); + .SelectMany(org => GetOrganizationRepositories(org.Login).TakeLast(1)); } - IObservable<IReadOnlyList<IRepositoryModel>> GetOrganizationRepositories(string organization) + IObservable<IReadOnlyList<IRemoteRepositoryModel>> GetOrganizationRepositories(string organization) { return Observable.Defer(() => GetUserFromCache().SelectMany(user => hostCache.GetAndRefreshObject(string.Format(CultureInfo.InvariantCulture, "{0}|{1}|repos", user.Login, organization), - () => apiClient.GetRepositoriesForOrganization(organization).Select( + () => ApiClient.GetRepositoriesForOrganization(organization).Select( RepositoryCacheItem.Create).ToList(), - TimeSpan.FromMinutes(5), + TimeSpan.FromMinutes(2), TimeSpan.FromDays(7))) .ToReadOnlyList(Create)) - .Catch<IReadOnlyList<IRepositoryModel>, KeyNotFoundException>( + .Catch<IReadOnlyList<IRemoteRepositoryModel>, KeyNotFoundException>( // This could in theory happen if we try to call this before the user is logged in. e => { - string message = string.Format( - CultureInfo.InvariantCulture, - "Retrieveing '{0}' org repositories failed because user is not stored in the cache.", + log.Error(e, "Retrieveing {Organization} org repositories failed because user is not stored in the cache", organization); - log.Error(message, e); - return Observable.Return(new IRepositoryModel[] {}); + return Observable.Return(new IRemoteRepositoryModel[] {}); }); } + public IObservable<IBranch> GetBranches(IRepositoryModel repo) + { + var keyobs = GetUserFromCache() + .Select(user => string.Format(CultureInfo.InvariantCulture, "{0}|{1}|branch", user.Login, repo.Name)); + + return Observable.Defer(() => keyobs + .SelectMany(key => ApiClient.GetBranches(repo.CloneUrl.Owner, repo.CloneUrl.RepositoryName))) + .Select(x => new BranchModel(x, repo)); + } + + static GitIgnoreItem Create(GitIgnoreCacheItem item) + { + return GitIgnoreItem.Create(item.Name); + } + static LicenseItem Create(LicenseCacheItem licenseCacheItem) { return new LicenseItem(licenseCacheItem.Key, licenseCacheItem.Name); } - Models.Account Create(AccountCacheItem accountCacheItem) + IAccount Create(AccountCacheItem accountCacheItem) { return new Models.Account( accountCacheItem.Login, @@ -240,17 +387,41 @@ Models.Account Create(AccountCacheItem accountCacheItem) accountCacheItem.IsEnterprise, accountCacheItem.OwnedPrivateRepositoriesCount, accountCacheItem.PrivateRepositoriesInPlanCount, + accountCacheItem.AvatarUrl, avatarProvider.GetAvatar(accountCacheItem)); } - IRepositoryModel Create(RepositoryCacheItem repositoryCacheItem) + IAccount Create(string login, string avatarUrl) { - return new RepositoryModel( - repositoryCacheItem.Name, - new UriString(repositoryCacheItem.CloneUrl), - repositoryCacheItem.Private, - repositoryCacheItem.Fork, - Create(repositoryCacheItem.Owner)); + return new Models.Account( + login, + true, + false, + 0, + 0, + avatarUrl, + avatarProvider.GetAvatar(avatarUrl)); + } + + IRemoteRepositoryModel Create(RepositoryCacheItem item) + { + return new RemoteRepositoryModel( + item.Id, + item.Name, + new UriString(item.CloneUrl), + item.Private, + item.Fork, + Create(item.Owner), + item.Parent != null ? Create(item.Parent) : null) + { + CreatedAt = item.CreatedAt, + UpdatedAt = item.UpdatedAt + }; + } + + GitReferenceModel Create(GitReferenceCacheItem item) + { + return new GitReferenceModel(item.Ref, item.Label, item.Sha, item.RepositoryCloneUrl); } IPullRequestModel Create(PullRequestCacheItem prCacheItem) @@ -262,7 +433,16 @@ IPullRequestModel Create(PullRequestCacheItem prCacheItem) prCacheItem.CreatedAt, prCacheItem.UpdatedAt) { - CommentCount = prCacheItem.CommentCount + Assignee = prCacheItem.Assignee != null ? Create(prCacheItem.Assignee) : null, + Base = Create(prCacheItem.Base), + Body = prCacheItem.Body ?? string.Empty, + CommentCount = prCacheItem.CommentCount, + CommitCount = prCacheItem.CommitCount, + CreatedAt = prCacheItem.CreatedAt, + Head = Create(prCacheItem.Head), + State = prCacheItem.State.HasValue ? + prCacheItem.State.Value : + prCacheItem.IsOpen.Value ? PullRequestStateEnum.Open : PullRequestStateEnum.Closed, }; } @@ -271,24 +451,8 @@ public IObservable<Unit> InsertUser(AccountCacheItem user) return hostCache.InsertObject("user", user); } - bool disposed; protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - try - { - hostCache.Dispose(); - } - catch (Exception e) - { - log.Warn("Exception occured while disposing host cache", e); - } - disposed = true; - } - } + {} public void Dispose() { @@ -296,18 +460,33 @@ public void Dispose() GC.SuppressFinalize(this); } - public class LicenseCacheItem + static GitHub.Models.PullRequestReviewState FromGraphQL(Octokit.GraphQL.Model.PullRequestReviewState s) + { + return (GitHub.Models.PullRequestReviewState)s; + } + + public class GitIgnoreCacheItem : CacheItem + { + public static GitIgnoreCacheItem Create(string ignore) + { + return new GitIgnoreCacheItem { Key = ignore, Name = ignore, Timestamp = DateTime.Now }; + } + + public string Name { get; set; } + } + + + public class LicenseCacheItem : CacheItem { public static LicenseCacheItem Create(LicenseMetadata licenseMetadata) { - return new LicenseCacheItem { Key = licenseMetadata.Key, Name = licenseMetadata.Name }; + return new LicenseCacheItem { Key = licenseMetadata.Key, Name = licenseMetadata.Name, Timestamp = DateTime.Now }; } - public string Key { get; set; } public string Name { get; set; } } - public class RepositoryCacheItem + public class RepositoryCacheItem : CacheItem { public static RepositoryCacheItem Create(Repository apiRepository) { @@ -318,23 +497,29 @@ public RepositoryCacheItem() {} public RepositoryCacheItem(Repository apiRepository) { + Id = apiRepository.Id; Name = apiRepository.Name; Owner = AccountCacheItem.Create(apiRepository.Owner); CloneUrl = apiRepository.CloneUrl; Private = apiRepository.Private; Fork = apiRepository.Fork; + Key = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Owner.Login, Name); + CreatedAt = apiRepository.CreatedAt; + UpdatedAt = apiRepository.UpdatedAt; + Timestamp = apiRepository.UpdatedAt; + Parent = apiRepository.Parent != null ? new RepositoryCacheItem(apiRepository.Parent) : null; } + public long Id { get; set; } + public string Name { get; set; } - [AllowNull] - public AccountCacheItem Owner - { - [return: AllowNull] - get; set; - } + public AccountCacheItem Owner { get; set; } public string CloneUrl { get; set; } public bool Private { get; set; } public bool Fork { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + public RepositoryCacheItem Parent { get; set; } } public class PullRequestCacheItem : CacheItem @@ -345,27 +530,81 @@ public static PullRequestCacheItem Create(PullRequest pr) } public PullRequestCacheItem() {} + public PullRequestCacheItem(PullRequest pr) { Title = pr.Title; Number = pr.Number; + Base = new GitReferenceCacheItem + { + Label = pr.Base.Label, + Ref = pr.Base.Ref, + Sha = pr.Base.Sha, + RepositoryCloneUrl = pr.Base.Repository.CloneUrl, + }; + Head = new GitReferenceCacheItem + { + Label = pr.Head.Label, + Ref = pr.Head.Ref, + Sha = pr.Head.Sha, + RepositoryCloneUrl = pr.Head.Repository?.CloneUrl + }; CommentCount = pr.Comments; + CommitCount = pr.Commits; Author = new AccountCacheItem(pr.User); + Assignee = pr.Assignee != null ? new AccountCacheItem(pr.Assignee) : null; CreatedAt = pr.CreatedAt; UpdatedAt = pr.UpdatedAt; - + Body = pr.Body; + State = GetState(pr); + IsOpen = pr.State == ItemState.Open; + Merged = pr.Merged; Key = Number.ToString(CultureInfo.InvariantCulture); Timestamp = UpdatedAt; } - [AllowNull] - public string Title {[return: AllowNull] get; set; } + public string Title {get; set; } public int Number { get; set; } + public GitReferenceCacheItem Base { get; set; } + public GitReferenceCacheItem Head { get; set; } public int CommentCount { get; set; } - [AllowNull] - public AccountCacheItem Author {[return: AllowNull] get; set; } + public int CommitCount { get; set; } + public AccountCacheItem Author { get; set; } + public AccountCacheItem Assignee { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; } + public string Body { get; set; } + + // Nullable for compatibility with old caches. + public PullRequestStateEnum? 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) + { + if (pullRequest.State == ItemState.Open) + { + return PullRequestStateEnum.Open; + } + else if (pullRequest.Merged) + { + return PullRequestStateEnum.Merged; + } + else + { + return PullRequestStateEnum.Closed; + } + } + } + + public class GitReferenceCacheItem + { + public string Ref { get; set; } + public string Label { get; set; } + public string Sha { get; set; } + public string RepositoryCloneUrl { get; set; } } } } diff --git a/src/GitHub.App/Services/OAuthCallbackListener.cs b/src/GitHub.App/Services/OAuthCallbackListener.cs new file mode 100644 index 0000000000..e1a48307e4 --- /dev/null +++ b/src/GitHub.App/Services/OAuthCallbackListener.cs @@ -0,0 +1,72 @@ +using System.ComponentModel.Composition; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using GitHub.Api; +using GitHub.Extensions; +using Rothko; +using static System.FormattableString; + +namespace GitHub.Services +{ + /// <summary> + /// Listens for a callback from the OAuth endpoint on "http://localhost:42549". + /// </summary> + /// <remarks> + /// The GitHub for Visual Studio OAUTH application on GitHub is configured to call back to + /// http://localhost:42549 on successful login. This class listens on that port and returns + /// the temporary code received from the callback to the caller. + /// + /// Note that as implemented this class can only listen for one OAUTH callback at a time. If + /// <see cref="Listen"/> is called when already listening for a callback, the original listen + /// operation will throw an <see cref="OperationCanceledException"/>. + /// </remarks> + [Export(typeof(IOAuthCallbackListener))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class OAuthCallbackListener : IOAuthCallbackListener + { + const int CallbackPort = 42549; + readonly IHttpListener httpListener; + + [ImportingConstructor] + public OAuthCallbackListener(IHttpListener httpListener) + { + Guard.ArgumentNotNull(httpListener, nameof(httpListener)); + + this.httpListener = httpListener; + httpListener.Prefixes.Add(CallbackUrl); + } + + public readonly static string CallbackUrl = Invariant($"http://localhost:{CallbackPort}/"); + + public async Task<string> Listen(string id, CancellationToken cancel) + { + if (httpListener.IsListening) httpListener.Stop(); + httpListener.Start(); + + try + { + using (cancel.Register(httpListener.Stop)) + { + while (true) + { + var context = await httpListener.GetContextAsync().ConfigureAwait(false); + var foo = context.Request; + var queryParts = HttpUtility.ParseQueryString(context.Request.Url.Query); + + if (queryParts["state"] == id) + { + context.Response.Close(); + return queryParts["code"]; + } + } + } + } + finally + { + httpListener.Stop(); + } + } + } +} diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs new file mode 100644 index 0000000000..9cf1b97222 --- /dev/null +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -0,0 +1,654 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.VisualStudio; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Projection; +using Microsoft.VisualStudio.TextManager.Interop; +using EnvDTE; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services +{ + /// <summary> + /// Services for opening views of pull request files in Visual Studio. + /// </summary> + [Export(typeof(IPullRequestEditorService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class PullRequestEditorService : IPullRequestEditorService + { + // If the target line doesn't have a unique match, search this number of lines above looking for a match. + public const int MatchLinesAboveTarget = 4; + + readonly IGitHubServiceProvider serviceProvider; + readonly IPullRequestService pullRequestService; + readonly IVsEditorAdaptersFactoryService vsEditorAdaptersFactory; + readonly IStatusBarNotificationService statusBar; + readonly IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand; + readonly IEditorOptionsFactoryService editorOptionsFactoryService; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public PullRequestEditorService( + IGitHubServiceProvider serviceProvider, + IPullRequestService pullRequestService, + IVsEditorAdaptersFactoryService vsEditorAdaptersFactory, + IStatusBarNotificationService statusBar, + IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand, + IEditorOptionsFactoryService editorOptionsFactoryService, + IUsageTracker usageTracker) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(pullRequestService, nameof(pullRequestService)); + Guard.ArgumentNotNull(vsEditorAdaptersFactory, nameof(vsEditorAdaptersFactory)); + Guard.ArgumentNotNull(statusBar, nameof(statusBar)); + Guard.ArgumentNotNull(goToSolutionOrPullRequestFileCommand, nameof(goToSolutionOrPullRequestFileCommand)); + Guard.ArgumentNotNull(goToSolutionOrPullRequestFileCommand, nameof(editorOptionsFactoryService)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + + this.serviceProvider = serviceProvider; + this.pullRequestService = pullRequestService; + this.vsEditorAdaptersFactory = vsEditorAdaptersFactory; + this.statusBar = statusBar; + this.goToSolutionOrPullRequestFileCommand = goToSolutionOrPullRequestFileCommand; + this.editorOptionsFactoryService = editorOptionsFactoryService; + this.usageTracker = usageTracker; + } + + /// <inheritdoc/> + public async Task<ITextView> OpenFile( + IPullRequestSession session, + string relativePath, + bool workingDirectory) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + + try + { + var fullPath = GetAbsolutePath(session.LocalRepository, relativePath); + string fileName; + string commitSha; + + if (workingDirectory) + { + fileName = fullPath; + commitSha = null; + } + else + { + var file = await session.GetFile(relativePath); + fileName = await pullRequestService.ExtractToTempFile( + session.LocalRepository, + session.PullRequest, + file.RelativePath, + file.CommitSha, + pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath)); + commitSha = file.CommitSha; + } + + IVsTextView textView; + IWpfTextView wpfTextView; + using (workingDirectory ? null : OpenInProvisionalTab()) + { + var readOnly = !workingDirectory; + textView = OpenDocument(fileName, readOnly, out wpfTextView); + + if (!workingDirectory) + { + AddBufferTag(wpfTextView.TextBuffer, session, fullPath, commitSha, null); + EnableNavigateToEditor(textView, session); + } + } + + if (workingDirectory) + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); + else + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); + + return wpfTextView; + } + catch (Exception e) + { + ShowErrorInStatusBar("Error opening file", e); + return null; + } + } + + /// <inheritdoc/> + public async Task<IDifferenceViewer> OpenDiff(IPullRequestSession session, string relativePath, string headSha, bool scrollToFirstDiff) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + + try + { + var workingDirectory = headSha == null; + var file = await session.GetFile(relativePath, headSha ?? "HEAD"); + var mergeBase = await pullRequestService.GetMergeBase(session.LocalRepository, session.PullRequest); + var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); + var rightFile = workingDirectory ? + Path.Combine(session.LocalRepository.LocalPath, relativePath) : + await pullRequestService.ExtractToTempFile( + session.LocalRepository, + session.PullRequest, + relativePath, + file.CommitSha, + encoding); + + var diffViewer = FocusExistingDiffViewer(session, mergeBase, rightFile); + if (diffViewer != null) + { + return diffViewer; + } + + var leftFile = await pullRequestService.ExtractToTempFile( + session.LocalRepository, + session.PullRequest, + relativePath, + mergeBase, + encoding); + var leftPath = await GetBaseFileName(session, file); + var rightPath = file.RelativePath; + var leftLabel = $"{leftPath};{session.GetBaseBranchDisplay()}"; + var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {session.PullRequest.Number}"; + var caption = $"Diff - {Path.GetFileName(file.RelativePath)}"; + var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | + __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; + + if (!workingDirectory) + { + options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; + } + + IVsWindowFrame frame; + using (OpenWithOption(DifferenceViewerOptions.ScrollToFirstDiffName, scrollToFirstDiff)) + using (OpenInProvisionalTab()) + { + var tooltip = $"{leftLabel}\nvs.\n{rightLabel}"; + + // Diff window will open in provisional (right hand) tab until document is touched. + frame = VisualStudio.Services.DifferenceService.OpenComparisonWindow2( + leftFile, + rightFile, + caption, + tooltip, + leftLabel, + rightLabel, + string.Empty, + string.Empty, + (uint)options); + } + + diffViewer = GetDiffViewer(frame); + + var leftText = diffViewer.LeftView.TextBuffer.CurrentSnapshot.GetText(); + var rightText = diffViewer.RightView.TextBuffer.CurrentSnapshot.GetText(); + if (leftText == string.Empty) + { + // Don't show LeftView when empty. + diffViewer.ViewMode = DifferenceViewMode.RightViewOnly; + } + else if (rightText == string.Empty) + { + // Don't show RightView when empty. + diffViewer.ViewMode = DifferenceViewMode.LeftViewOnly; + } + else if (leftText == rightText) + { + // Don't show LeftView when no changes. + diffViewer.ViewMode = DifferenceViewMode.RightViewOnly; + } + + AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, mergeBase, DiffSide.Left); + + if (!workingDirectory) + { + AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, file.CommitSha, DiffSide.Right); + EnableNavigateToEditor(diffViewer.LeftView, session); + EnableNavigateToEditor(diffViewer.RightView, session); + EnableNavigateToEditor(diffViewer.InlineView, session); + } + + if (workingDirectory) + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsCompareWithSolution); + else + await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewChanges); + + return diffViewer; + } + catch (Exception e) + { + ShowErrorInStatusBar("Error opening file", e); + return null; + } + } + + /// <inheritdoc/> + public async Task<IDifferenceViewer> OpenDiff( + IPullRequestSession session, + string relativePath, + IInlineCommentThreadModel thread) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + Guard.ArgumentNotNull(thread, nameof(thread)); + + var diffViewer = await OpenDiff(session, relativePath, thread.CommitSha, scrollToFirstDiff: false); + + var param = (object)new InlineCommentNavigationParams + { + FromLine = thread.LineNumber - 1, + }; + + // HACK: We need to wait here for the inline comment tags to initialize so we can find the next inline comment. + // There must be a better way of doing this. + await Task.Delay(1500); + RaiseWhenAvailable(Guids.CommandSetString, PkgCmdIDList.NextInlineCommentId, param); + + return diffViewer; + } + + static bool RaiseWhenAvailable(string guid, int id, object param) + { + var commands = VisualStudio.Services.Dte.Commands; + var command = commands.Item(guid, id); + + if (command.IsAvailable) + { + commands.Raise(command.Guid, command.ID, ref param, null); + return true; + } + + return false; + } + + public void OpenActiveDocumentInCodeView(IVsTextView sourceView) + { + var dte = serviceProvider.GetService<DTE>(); + // Not sure how to get a file name directly from IVsTextView. Using DTE.ActiveDocument.FullName. + var fullPath = dte.ActiveDocument.FullName; + // VsShellUtilities.OpenDocument with VSConstants.LOGVIEWID.Code_guid always open a new Code view. + // Using DTE.ItemOperations.OpenFile with Constants.vsViewKindCode instead, + dte.ItemOperations.OpenFile(fullPath, EnvDTE.Constants.vsViewKindCode); + var codeView = FindActiveView(); + NavigateToEquivalentPosition(sourceView, codeView); + } + + public bool IsEditableDiff(ITextView textView) + { + var readOnly = textView.Options.GetOptionValue(DefaultTextViewOptions.ViewProhibitUserInputId); + var isDiff = IsDiff(textView); + return !readOnly && isDiff; + } + + public void NavigateToEquivalentPosition(IVsTextView sourceView, IVsTextView targetView) + { + int line; + int column; + ErrorHandler.ThrowOnFailure(sourceView.GetCaretPos(out line, out column)); + var text1 = GetText(sourceView); + var text2 = GetText(targetView); + + var fromLines = ReadLines(text1); + var toLines = ReadLines(text2); + var matchingLine = FindMatchingLine(fromLines, toLines, line, matchLinesAbove: MatchLinesAboveTarget); + if (matchingLine == -1) + { + // If we can't match line use orignal as best guess. + matchingLine = line < toLines.Count ? line : toLines.Count - 1; + column = 0; + } + + ErrorHandler.ThrowOnFailure(targetView.SetCaretPos(matchingLine, column)); + ErrorHandler.ThrowOnFailure(targetView.CenterLines(matchingLine, 1)); + } + + public IVsTextView FindActiveView() + { + var textManager = serviceProvider.GetService<SVsTextManager, IVsTextManager2>(); + IVsTextView view; + var hresult = textManager.GetActiveView2(1, null, (uint)_VIEWFRAMETYPE.vftCodeWindow, out view); + return hresult == VSConstants.S_OK ? view : null; + } + + /// <summary> + /// Find the closest matching line in <see cref="toLines"/>. + /// </summary> + /// <remarks> + /// When matching we prioritize unique matching lines in <see cref="toLines"/>. If the target line isn't + /// unique, continue searching the lines above for a better match and use this as anchor with an offset. + /// The closest match to <see cref="line"/> with the fewest duplicate matches will be used for the matching line. + /// </remarks> + /// <param name="fromLines">The document we're navigating from.</param> + /// <param name="toLines">The document we're navigating to.</param> + /// <param name="line">The 0-based line we're navigating from.</param> + /// <param name="matchLinesAbove"></param> + /// <returns>The best matching line in <see cref="toLines"/></returns> + public int FindMatchingLine(IList<string> fromLines, IList<string> toLines, int line, int matchLinesAbove = 0) + { + var matchingLine = -1; + var minMatchedLines = -1; + for (var offset = 0; offset <= matchLinesAbove; offset++) + { + var targetLine = line - offset; + if (targetLine < 0) + { + break; + } + + int matchedLines; + var nearestLine = FindNearestMatchingLine(fromLines, toLines, targetLine, out matchedLines); + if (nearestLine != -1) + { + if (matchingLine == -1 || minMatchedLines >= matchedLines) + { + matchingLine = nearestLine + offset; + minMatchedLines = matchedLines; + } + + if (minMatchedLines == 1) + { + break; // We've found a unique matching line! + } + } + } + + if (matchingLine >= toLines.Count) + { + matchingLine = toLines.Count - 1; + } + + return matchingLine; + } + + /// <summary> + /// Find the nearest matching line to <see cref="line"/> and the number of similar matched lines in the text. + /// </summary> + /// <param name="fromLines">The document we're navigating from.</param> + /// <param name="toLines">The document we're navigating to.</param> + /// <param name="line">The 0-based line we're navigating from.</param> + /// <param name="matchedLines">The number of similar matched lines in <see cref="toLines"/></param> + /// <returns>Find the nearest matching line in <see cref="toLines"/>.</returns> + public int FindNearestMatchingLine(IList<string> fromLines, IList<string> 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]; + + matchedLines = 0; + var matchingLine = -1; + for (var offset = 0; true; offset++) + { + var lineAbove = line + offset; + var checkAbove = lineAbove < toLines.Count; + if (checkAbove && toLines[lineAbove] == fromLine) + { + if (matchedLines == 0) + { + matchingLine = lineAbove; + } + + matchedLines++; + } + + var lineBelow = line - offset; + var checkBelow = lineBelow >= 0; + if (checkBelow && offset > 0 && lineBelow < toLines.Count && toLines[lineBelow] == fromLine) + { + if (matchedLines == 0) + { + matchingLine = lineBelow; + } + + matchedLines++; + } + + if (!checkAbove && !checkBelow) + { + break; + } + } + + return matchingLine; + } + + static string GetAbsolutePath(ILocalRepositoryModel localRepository, string relativePath) + { + var localPath = localRepository.LocalPath; + relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar); + return Path.Combine(localPath, relativePath); + } + + string GetText(IVsTextView textView) + { + IVsTextLines buffer; + ErrorHandler.ThrowOnFailure(textView.GetBuffer(out buffer)); + + int line; + int index; + ErrorHandler.ThrowOnFailure(buffer.GetLastLineIndex(out line, out index)); + + string text; + ErrorHandler.ThrowOnFailure(buffer.GetLineText(0, 0, line, index, out text)); + return text; + } + + IVsTextView OpenDocument(string fullPath, bool readOnly, out IWpfTextView wpfTextView) + { + var logicalView = VSConstants.LOGVIEWID.TextView_guid; + IVsUIHierarchy hierarchy; + uint itemID; + IVsWindowFrame windowFrame; + IVsTextView view; + VsShellUtilities.OpenDocument(serviceProvider, fullPath, logicalView, out hierarchy, out itemID, out windowFrame, out view); + + wpfTextView = vsEditorAdaptersFactory.GetWpfTextView(view); + wpfTextView?.Options?.SetOptionValue(DefaultTextViewOptions.ViewProhibitUserInputId, readOnly); + + return view; + } + + IDifferenceViewer FocusExistingDiffViewer( + IPullRequestSession session, + string mergeBase, + string rightPath) + { + IVsUIHierarchy uiHierarchy; + uint itemID; + IVsWindowFrame windowFrame; + + // Diff documents are indexed by the path on the right hand side of the comparison. + if (VsShellUtilities.IsDocumentOpen( + serviceProvider, + rightPath, + Guid.Empty, + out uiHierarchy, + out itemID, + out windowFrame)) + { + var diffViewer = GetDiffViewer(windowFrame); + + if (diffViewer != null) + { + PullRequestTextBufferInfo leftBufferInfo; + + if (diffViewer.LeftView.TextBuffer.Properties.TryGetProperty( + typeof(PullRequestTextBufferInfo), + out leftBufferInfo) && + leftBufferInfo.Session.PullRequest.Number == session.PullRequest.Number && + leftBufferInfo.CommitSha == mergeBase) + { + ErrorHandler.ThrowOnFailure(windowFrame.Show()); + return diffViewer; + } + } + } + + return null; + } + + void ShowErrorInStatusBar(string message, Exception e) + { + statusBar.ShowMessage(message + ": " + e.Message); + } + + void AddBufferTag( + ITextBuffer buffer, + IPullRequestSession session, + string path, + string commitSha, + DiffSide? side) + { + buffer.Properties.GetOrCreateSingletonProperty( + typeof(PullRequestTextBufferInfo), + () => new PullRequestTextBufferInfo(session, path, commitSha, side)); + + var projection = buffer as IProjectionBuffer; + + if (projection != null) + { + foreach (var source in projection.SourceBuffers) + { + AddBufferTag(source, session, path, commitSha, side); + } + } + } + + void EnableNavigateToEditor(ITextView textView, IPullRequestSession session) + { + var vsTextView = vsEditorAdaptersFactory.GetViewAdapter(textView); + EnableNavigateToEditor(vsTextView, session); + } + + void EnableNavigateToEditor(IVsTextView vsTextView, IPullRequestSession session) + { + var commandGroup = VSConstants.CMDSETID.StandardCommandSet2K_guid; + var commandId = (int)VSConstants.VSStd2KCmdID.RETURN; + TextViewCommandDispatcher.AddCommandFilter(vsTextView, commandGroup, commandId, goToSolutionOrPullRequestFileCommand); + + EnableNavigateStatusBarMessage(vsTextView, session); + } + + void EnableNavigateStatusBarMessage(IVsTextView vsTextView, IPullRequestSession session) + { + var textView = vsEditorAdaptersFactory.GetWpfTextView(vsTextView); + + var statusMessage = session.IsCheckedOut ? + App.Resources.NavigateToEditorStatusMessage : App.Resources.NavigateToEditorNotCheckedOutStatusMessage; + + textView.GotAggregateFocus += (s, e) => + statusBar.ShowMessage(statusMessage); + + textView.LostAggregateFocus += (s, e) => + statusBar.ShowMessage(string.Empty); + } + + ITextBuffer GetBufferAt(string filePath) + { + IVsUIHierarchy uiHierarchy; + uint itemID; + IVsWindowFrame windowFrame; + + if (VsShellUtilities.IsDocumentOpen( + serviceProvider, + filePath, + Guid.Empty, + out uiHierarchy, + out itemID, + out windowFrame)) + { + IVsTextView view = VsShellUtilities.GetTextView(windowFrame); + IVsTextLines lines; + if (view.GetBuffer(out lines) == 0) + { + var buffer = lines as IVsTextBuffer; + if (buffer != null) + return vsEditorAdaptersFactory.GetDataBuffer(buffer); + } + } + + return null; + } + + async Task<string> GetBaseFileName(IPullRequestSession session, IPullRequestSessionFile file) + { + using (var changes = await pullRequestService.GetTreeChanges( + session.LocalRepository, + session.PullRequest)) + { + var fileChange = changes.FirstOrDefault(x => x.Path == file.RelativePath); + return fileChange?.Status == LibGit2Sharp.ChangeKind.Renamed ? + fileChange.OldPath : file.RelativePath; + } + } + + static bool IsDiff(ITextView textView) => textView.Roles.Contains("DIFF"); + + static IDifferenceViewer GetDiffViewer(IVsWindowFrame frame) + { + object docView; + + if (ErrorHandler.Succeeded(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView))) + { + return (docView as IVsDifferenceCodeWindow)?.DifferenceViewer; + } + + return null; + } + + static IDisposable OpenInProvisionalTab() + { + return new NewDocumentStateScope( + __VSNEWDOCUMENTSTATE.NDS_Provisional, + VSConstants.NewDocumentStateReason.SolutionExplorer); + } + + IDisposable OpenWithOption(string optionId, object value) => new OpenWithOptionScope(editorOptionsFactoryService, optionId, value); + + class OpenWithOptionScope : IDisposable + { + readonly IEditorOptionsFactoryService editorOptionsFactoryService; + readonly string optionId; + readonly object savedValue; + + internal OpenWithOptionScope(IEditorOptionsFactoryService editorOptionsFactoryService, string optionId, object value) + { + this.editorOptionsFactoryService = editorOptionsFactoryService; + this.optionId = optionId; + savedValue = editorOptionsFactoryService.GlobalOptions.GetOptionValue(optionId); + editorOptionsFactoryService.GlobalOptions.SetOptionValue(optionId, value); + } + + public void Dispose() + { + editorOptionsFactoryService.GlobalOptions.SetOptionValue(optionId, savedValue); + } + } + + static IList<string> ReadLines(string text) + { + var lines = new List<string>(); + var reader = new DiffUtilities.LineReader(text); + string line; + while ((line = reader.ReadLine()) != null) + { + lines.Add(line); + } + + return lines; + } + } +} diff --git a/src/GitHub.App/Services/PullRequestService.cs b/src/GitHub.App/Services/PullRequestService.cs new file mode 100644 index 0000000000..ffca06e083 --- /dev/null +++ b/src/GitHub.App/Services/PullRequestService.cs @@ -0,0 +1,1036 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Forms; +using GitHub.Api; +using GitHub.App.Services; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using LibGit2Sharp; +using Octokit.GraphQL; +using Octokit.GraphQL.Model; +using Rothko; +using static System.FormattableString; +using static Octokit.GraphQL.Variable; +using CheckConclusionState = GitHub.Models.CheckConclusionState; +using CheckStatusState = GitHub.Models.CheckStatusState; +using StatusState = GitHub.Models.StatusState; + +namespace GitHub.Services +{ + [Export(typeof(IPullRequestService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class PullRequestService : IPullRequestService + { + const string SettingCreatedByGHfVS = "created-by-ghfvs"; + const string SettingGHfVSPullRequest = "ghfvs-pr-owner-number"; + + static readonly Regex InvalidBranchCharsRegex = new Regex(@"[^0-9A-Za-z\-]", RegexOptions.ECMAScript); + static readonly Regex BranchCapture = new Regex(@"branch\.(?<branch>.+)\.ghfvs-pr", RegexOptions.ECMAScript); + static ICompiledQuery<Page<ActorModel>> readAssignableUsers; + static ICompiledQuery<Page<PullRequestListItemModel>> readPullRequests; + static ICompiledQuery<Page<PullRequestListItemModel>> readPullRequestsEnterprise; + + static readonly string[] TemplatePaths = new[] + { + "PULL_REQUEST_TEMPLATE.md", + "PULL_REQUEST_TEMPLATE", + ".github\\PULL_REQUEST_TEMPLATE.md", + ".github\\PULL_REQUEST_TEMPLATE", + }; + + readonly IGitClient gitClient; + readonly IGitService gitService; + readonly IVSGitExt gitExt; + readonly IGraphQLClientFactory graphqlFactory; + readonly IOperatingSystem os; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public PullRequestService( + IGitClient gitClient, + IGitService gitService, + IVSGitExt gitExt, + IGraphQLClientFactory graphqlFactory, + IOperatingSystem os, + IUsageTracker usageTracker) + { + this.gitClient = gitClient; + this.gitService = gitService; + this.gitExt = gitExt; + this.graphqlFactory = graphqlFactory; + this.os = os; + this.usageTracker = usageTracker; + } + + public async Task<Page<PullRequestListItemModel>> ReadPullRequests( + HostAddress address, + string owner, + string name, + string after, + PullRequestStateEnum[] states) + { + + ICompiledQuery<Page<PullRequestListItemModel>> query; + + if (address.IsGitHubDotCom()) + { + if (readPullRequests == null) + { + readPullRequests = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .PullRequests( + first: 100, + after: Var(nameof(after)), + orderBy: new IssueOrder { Direction = OrderDirection.Desc, Field = IssueOrderField.CreatedAt }, + states: Var(nameof(states))) + .Select(page => new Page<PullRequestListItemModel> + { + EndCursor = page.PageInfo.EndCursor, + HasNextPage = page.PageInfo.HasNextPage, + TotalCount = page.TotalCount, + Items = page.Nodes.Select(pr => new ListItemAdapter + { + Id = pr.Id.Value, + LastCommit = pr.Commits(null, null, 1, null).Nodes.Select(commit => + new LastCommitSummaryAdapter + { + CheckSuites = commit.Commit.CheckSuites(null, null, null, null, null).AllPages(10) + .Select(suite => new CheckSuiteSummaryModel + { + CheckRuns = suite.CheckRuns(null, null, null, null, null).AllPages(10) + .Select(run => new CheckRunSummaryModel + { + Conclusion = run.Conclusion.FromGraphQl(), + Status = run.Status.FromGraphQl() + }).ToList() + }).ToList(), + Statuses = commit.Commit.Status + .Select(context => + context.Contexts.Select(statusContext => new StatusSummaryModel + { + State = statusContext.State.FromGraphQl(), + }).ToList() + ).SingleOrDefault() + }).ToList().FirstOrDefault(), + Author = new ActorModel + { + Login = pr.Author.Login, + AvatarUrl = pr.Author.AvatarUrl(null), + }, + CommentCount = pr.Comments(0, null, null, null).TotalCount, + Number = pr.Number, + Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new ReviewAdapter + { + Body = review.Body, + CommentCount = review.Comments(null, null, null, null).TotalCount, + }).ToList(), + State = pr.State.FromGraphQl(), + Title = pr.Title, + UpdatedAt = pr.UpdatedAt, + }).ToList(), + }).Compile(); + } + + query = readPullRequests; + } + else + { + if (readPullRequestsEnterprise == null) + { + readPullRequestsEnterprise = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .PullRequests( + first: 100, + after: Var(nameof(after)), + orderBy: new IssueOrder { Direction = OrderDirection.Desc, Field = IssueOrderField.CreatedAt }, + states: Var(nameof(states))) + .Select(page => new Page<PullRequestListItemModel> + { + EndCursor = page.PageInfo.EndCursor, + HasNextPage = page.PageInfo.HasNextPage, + TotalCount = page.TotalCount, + Items = page.Nodes.Select(pr => new ListItemAdapter + { + Id = pr.Id.Value, + 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() + ).SingleOrDefault() + }).ToList().FirstOrDefault(), + Author = new ActorModel + { + Login = pr.Author.Login, + AvatarUrl = pr.Author.AvatarUrl(null), + }, + CommentCount = pr.Comments(0, null, null, null).TotalCount, + Number = pr.Number, + Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new ReviewAdapter + { + Body = review.Body, + CommentCount = review.Comments(null, null, null, null).TotalCount, + }).ToList(), + State = pr.State.FromGraphQl(), + Title = pr.Title, + UpdatedAt = pr.UpdatedAt, + }).ToList(), + }).Compile(); + } + + query = readPullRequestsEnterprise; + } + + var graphql = await graphqlFactory.CreateConnection(address); + var vars = new Dictionary<string, object> + { + { nameof(owner), owner }, + { nameof(name), name }, + { nameof(after), after }, + { nameof(states), states.Select(x => (PullRequestState)x).ToList() }, + }; + + var result = await graphql.Run(query, vars); + + foreach (var item in result.Items.Cast<ListItemAdapter>()) + { + item.CommentCount += item.Reviews.Sum(x => x.Count); + item.Reviews = null; + + var checkRuns = item.LastCommit?.CheckSuites?.SelectMany(model => model.CheckRuns).ToArray(); + + var hasCheckRuns = checkRuns?.Any() ?? false; + var hasStatuses = item.LastCommit?.Statuses?.Any() ?? false; + + if (!hasCheckRuns && !hasStatuses) + { + item.Checks = PullRequestChecksState.None; + } + else + { + var checksHasFailure = false; + var checksHasCompleteSuccess = true; + + if (hasCheckRuns) + { + checksHasFailure = checkRuns + .Any(model => model.Conclusion.HasValue + && (model.Conclusion.Value == CheckConclusionState.Failure + || model.Conclusion.Value == CheckConclusionState.ActionRequired)); + + if (!checksHasFailure) + { + checksHasCompleteSuccess = checkRuns + .All(model => model.Conclusion.HasValue + && (model.Conclusion.Value == CheckConclusionState.Success + || model.Conclusion.Value == CheckConclusionState.Neutral)); + } + } + + var statusHasFailure = false; + var statusHasCompleteSuccess = true; + + if (!checksHasFailure && hasStatuses) + { + statusHasFailure = item.LastCommit + .Statuses + .Any(status => status.State == StatusState.Failure + || status.State == StatusState.Error); + + 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; + } + } + + item.LastCommit = null; + } + + return result; + } + + public async Task<Page<ActorModel>> ReadAssignableUsers( + HostAddress address, + string owner, + string name, + string after) + { + if (readAssignableUsers == null) + { + readAssignableUsers = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .AssignableUsers(first: 100, after: Var(nameof(after))) + .Select(connection => new Page<ActorModel> + { + EndCursor = connection.PageInfo.EndCursor, + HasNextPage = connection.PageInfo.HasNextPage, + TotalCount = connection.TotalCount, + Items = connection.Nodes.Select(user => new ActorModel + { + AvatarUrl = user.AvatarUrl(30), + Login = user.Login, + }).ToList(), + }).Compile(); + } + + var graphql = await graphqlFactory.CreateConnection(address); + var vars = new Dictionary<string, object> + { + { nameof(owner), owner }, + { nameof(name), name }, + { nameof(after), after }, + }; + + return await graphql.Run(readAssignableUsers, vars); + } + + public IObservable<IPullRequestModel> CreatePullRequest(IModelService modelService, + ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, + IBranch sourceBranch, IBranch targetBranch, + string title, string body + ) + { + Extensions.Guard.ArgumentNotNull(modelService, nameof(modelService)); + Extensions.Guard.ArgumentNotNull(sourceRepository, nameof(sourceRepository)); + Extensions.Guard.ArgumentNotNull(targetRepository, nameof(targetRepository)); + Extensions.Guard.ArgumentNotNull(sourceBranch, nameof(sourceBranch)); + Extensions.Guard.ArgumentNotNull(targetBranch, nameof(targetBranch)); + Extensions.Guard.ArgumentNotNull(title, nameof(title)); + Extensions.Guard.ArgumentNotNull(body, nameof(body)); + + return PushAndCreatePR(modelService, sourceRepository, targetRepository, sourceBranch, targetBranch, title, body).ToObservable(); + } + + public IObservable<string> GetPullRequestTemplate(ILocalRepositoryModel repository) + { + Extensions.Guard.ArgumentNotNull(repository, nameof(repository)); + + return Observable.Defer(() => + { + var paths = TemplatePaths.Select(x => Path.Combine(repository.LocalPath, x)); + + foreach (var path in paths) + { + if (os.File.Exists(path)) + { + try { return Observable.Return(os.File.ReadAllText(path, Encoding.UTF8)); } catch { } + } + } + return Observable.Empty<string>(); + }); + } + + public IObservable<IReadOnlyList<CommitMessage>> GetMessagesForUniqueCommits( + ILocalRepositoryModel repository, + string baseBranch, + string compareBranch, + int maxCommits) + { + return Observable.Defer(async () => + { + // CommitMessage doesn't keep a reference to Repository + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var messages = await gitClient.GetMessagesForUniqueCommits(repo, baseBranch, compareBranch, maxCommits); + return Observable.Return(messages); + } + }); + } + + public IObservable<int> CountSubmodulesToSync(ILocalRepositoryModel repository) + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var count = 0; + foreach (var submodule in repo.Submodules) + { + var status = submodule.RetrieveStatus(); + if ((status & SubmoduleStatus.WorkDirAdded) != 0) + { + count++; + } + else if ((status & SubmoduleStatus.WorkDirDeleted) != 0) + { + count++; + } + else if ((status & SubmoduleStatus.WorkDirModified) != 0) + { + count++; + } + else if ((status & SubmoduleStatus.WorkDirUninitialized) != 0) + { + count++; + } + } + + return Observable.Return(count); + } + } + + public IObservable<bool> IsWorkingDirectoryClean(ILocalRepositoryModel repository) + { + // The `using` appears to resolve this issue: + // https://github.com/github/VisualStudio/issues/1306 + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var statusOptions = new StatusOptions { ExcludeSubmodules = true }; + var status = repo.RetrieveStatus(statusOptions); + var isClean = !IsCheckoutBlockingDirty(status); + return Observable.Return(isClean); + } + } + + static bool IsCheckoutBlockingDirty(RepositoryStatus status) + { + if (status.IsDirty) + { + return status.Any(entry => IsCheckoutBlockingChange(entry)); + } + + return false; + } + + // This is similar to IsDirty, but also allows NewInWorkdir and DeletedFromWorkdir files + static bool IsCheckoutBlockingChange(StatusEntry entry) + { + switch (entry.State) + { + case FileStatus.Ignored: + return false; + case FileStatus.Unaltered: + return false; + case FileStatus.NewInWorkdir: + return false; + case FileStatus.DeletedFromWorkdir: + return false; + default: + return true; + } + } + + public IObservable<Unit> Pull(ILocalRepositoryModel repository) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + await gitClient.Pull(repo); + return Observable.Return(Unit.Default); + } + }); + } + + public IObservable<Unit> Push(ILocalRepositoryModel repository) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var remoteName = repo.Head.RemoteName; + var remote = await gitClient.GetHttpRemote(repo, remoteName); + await gitClient.Push(repo, repo.Head.TrackedBranch.UpstreamBranchCanonicalName, remote.Name); + return Observable.Return(Unit.Default); + } + }); + } + + public async Task<bool> SyncSubmodules(ILocalRepositoryModel repository, Action<string> progress) + { + var exitCode = await Where("git"); + if (exitCode != 0) + { + progress(App.Resources.CouldntFindGitOnPath); + return false; + } + + return await SyncSubmodules(repository.LocalPath, progress) == 0; + } + + // LibGit2Sharp has limited submodule support so shelling out Git.exe for submodule commands. + async Task<int> SyncSubmodules(string workingDir, Action<string> progress) + { + var cmdArguments = "/C git submodule init & git submodule sync --recursive & git submodule update --recursive"; + var startInfo = new ProcessStartInfo("cmd", cmdArguments) + { + WorkingDirectory = workingDir, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var process = Process.Start(startInfo)) + { + await Task.WhenAll( + ReadLinesAsync(process.StandardOutput, progress), + ReadLinesAsync(process.StandardError, progress), + Task.Run(() => process.WaitForExit())); + return process.ExitCode; + } + } + + static Task<int> Where(string fileName) + { + return Task.Run(() => + { + var cmdArguments = "/C WHERE /Q " + fileName; + var startInfo = new ProcessStartInfo("cmd", cmdArguments) + { + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(); + return process.ExitCode; + } + }); + } + + static async Task ReadLinesAsync(TextReader reader, Action<string> progress) + { + string line; + while ((line = await reader.ReadLineAsync()) != null) + { + progress(line); + } + } + + public IObservable<Unit> Checkout(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest, string localBranchName) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var existing = repo.Branches[localBranchName]; + + if (existing != null) + { + await gitClient.Checkout(repo, localBranchName); + } + else if (string.Equals(repository.CloneUrl.Owner, pullRequest.HeadRepositoryOwner, StringComparison.OrdinalIgnoreCase)) + { + var remote = await gitClient.GetHttpRemote(repo, "origin"); + await gitClient.Fetch(repo, remote.Name); + await gitClient.Checkout(repo, localBranchName); + } + else + { + var refSpec = $"{pullRequest.HeadRefName}:{localBranchName}"; + var remoteName = await CreateRemote(repo, repository.CloneUrl.WithOwner(pullRequest.HeadRepositoryOwner)); + + await gitClient.Fetch(repo, remoteName); + await gitClient.Fetch(repo, remoteName, new[] { refSpec }); + await gitClient.Checkout(repo, localBranchName); + await gitClient.SetTrackingBranch(repo, localBranchName, $"refs/remotes/{remoteName}/{pullRequest.HeadRefName}"); + } + + // Store the PR number in the branch config with the key "ghfvs-pr". + var prConfigKey = $"branch.{localBranchName}.{SettingGHfVSPullRequest}"; + await gitClient.SetConfig(repo, prConfigKey, BuildGHfVSConfigKeyValue(pullRequest.BaseRepositoryOwner, pullRequest.Number)); + + return Observable.Return(Unit.Default); + } + }); + } + + public IObservable<string> GetDefaultLocalBranchName(ILocalRepositoryModel repository, int pullRequestNumber, string pullRequestTitle) + { + return Observable.Defer(() => + { + var initial = "pr/" + pullRequestNumber + "-" + GetSafeBranchName(pullRequestTitle); + var current = initial; + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var index = 2; + + while (repo.Branches[current] != null) + { + current = initial + '-' + index++; + } + } + + return Observable.Return(current.TrimEnd('-')); + }); + } + + public IObservable<BranchTrackingDetails> CalculateHistoryDivergence(ILocalRepositoryModel repository, int pullRequestNumber) + { + return Observable.Defer(async () => + { + // BranchTrackingDetails doesn't keep a reference to Repository + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var remoteName = repo.Head.RemoteName; + if (remoteName != null) + { + var remote = await gitClient.GetHttpRemote(repo, remoteName); + await gitClient.Fetch(repo, remote.Name); + } + + return Observable.Return(repo.Head.TrackingDetails); + } + }); + } + + public async Task<string> GetMergeBase(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + return await gitClient.GetPullRequestMergeBase( + repo, + repository.CloneUrl.WithOwner(pullRequest.BaseRepositoryOwner), + pullRequest.BaseRefSha, + pullRequest.HeadRefSha, + pullRequest.BaseRefName, + pullRequest.Number); + } + } + + public IObservable<TreeChanges> GetTreeChanges(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + return Observable.Defer(async () => + { + // TreeChanges doesn't keep a reference to Repository + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + 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); + return Observable.Return(changes); + } + }); + } + + public IObservable<IBranch> GetLocalBranches(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + return Observable.Defer(() => + { + // BranchModel doesn't keep a reference to rep + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var result = GetLocalBranchesInternal(repository, repo, pullRequest).Select(x => new BranchModel(x, repository)); + return result.ToList().ToObservable(); + } + }); + } + + public IObservable<bool> EnsureLocalBranchesAreMarkedAsPullRequests(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var branches = GetLocalBranchesInternal(repository, repo, pullRequest).Select(x => new BranchModel(x, repository)); + var result = false; + + foreach (var branch in branches) + { + if (!await IsBranchMarkedAsPullRequest(repo, branch.Name, pullRequest)) + { + await MarkBranchAsPullRequest(repo, branch.Name, pullRequest.BaseRepositoryOwner, pullRequest.Number); + result = true; + } + } + + return Observable.Return(result); + } + }); + } + + public bool IsPullRequestFromRepository(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + return string.Equals(repository.CloneUrl?.Owner, pullRequest.HeadRepositoryOwner, StringComparison.OrdinalIgnoreCase); + } + + public IObservable<Unit> SwitchToBranch(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var branchName = GetLocalBranchesInternal(repository, repo, pullRequest).FirstOrDefault(); + + Log.Assert(branchName != null, "PullRequestService.SwitchToBranch called but no local branch found"); + + if (branchName != null) + { + var remote = await gitClient.GetHttpRemote(repo, "origin"); + await gitClient.Fetch(repo, remote.Name); + + var branch = repo.Branches[branchName]; + + if (branch == null) + { + var trackedBranchName = $"refs/remotes/{remote.Name}/" + branchName; + var trackedBranch = repo.Branches[trackedBranchName]; + + if (trackedBranch != null) + { + branch = repo.CreateBranch(branchName, trackedBranch.Tip); + await gitClient.SetTrackingBranch(repo, branchName, trackedBranchName); + } + else + { + throw new InvalidOperationException($"Could not find branch '{trackedBranchName}'."); + } + } + + await gitClient.Checkout(repo, branchName); + await MarkBranchAsPullRequest(repo, branchName, pullRequest.BaseRepositoryOwner, pullRequest.Number); + } + } + + return Observable.Return(Unit.Default); + }); + } + + public IObservable<Tuple<string, int>> GetPullRequestForCurrentBranch(ILocalRepositoryModel repository) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var configKey = string.Format( + CultureInfo.InvariantCulture, + "branch.{0}.{1}", + repo.Head.FriendlyName, + SettingGHfVSPullRequest); + var value = await gitClient.GetConfig<string>(repo, configKey); + return Observable.Return(ParseGHfVSConfigKeyValue(value)); + } + }); + } + + public async Task<string> ExtractToTempFile( + ILocalRepositoryModel repository, + PullRequestDetailModel pullRequest, + string relativePath, + string commitSha, + Encoding encoding) + { + var tempFilePath = CalculateTempFileName(relativePath, commitSha, encoding); + + 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); + } + } + + return tempFilePath; + } + + public Encoding GetEncoding(ILocalRepositoryModel repository, string relativePath) + { + var fullPath = Path.Combine(repository.LocalPath, relativePath); + + if (File.Exists(fullPath)) + { + var encoding = Encoding.UTF8; + if (HasPreamble(fullPath, encoding)) + { + return encoding; + } + } + + return Encoding.Default; + } + + static bool HasPreamble(string file, Encoding encoding) + { + using (var stream = File.OpenRead(file)) + { + foreach (var b in encoding.GetPreamble()) + { + if (b != stream.ReadByte()) + { + return false; + } + } + } + + return true; + } + + public IObservable<Unit> RemoveUnusedRemotes(ILocalRepositoryModel repository) + { + return Observable.Defer(async () => + { + using (var repo = gitService.GetRepository(repository.LocalPath)) + { + var usedRemotes = new HashSet<string>( + repo.Branches + .Where(x => !x.IsRemote && x.RemoteName != null) + .Select(x => x.RemoteName)); + + foreach (var remote in repo.Network.Remotes) + { + var key = $"remote.{remote.Name}.{SettingCreatedByGHfVS}"; + var createdByUs = await gitClient.GetConfig<bool>(repo, key); + + if (createdByUs && !usedRemotes.Contains(remote.Name)) + { + repo.Network.Remotes.Remove(remote.Name); + } + } + + return Observable.Return(Unit.Default); + } + }); + } + + /// <inheritdoc /> + public bool ConfirmCancelPendingReview() + { + return MessageBox.Show( + GitHub.App.Resources.CancelPendingReviewConfirmation, + GitHub.App.Resources.CancelPendingReviewConfirmationCaption, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question) == DialogResult.Yes; + } + + async Task<string> CreateRemote(IRepository repo, UriString cloneUri) + { + foreach (var remote in repo.Network.Remotes) + { + if (UriString.RepositoryUrlsAreEqual(new UriString(remote.Url), cloneUri)) + { + return remote.Name; + } + } + + var remoteName = CreateUniqueRemoteName(repo, cloneUri.Owner); + await gitClient.SetRemote(repo, remoteName, new Uri(cloneUri)); + await gitClient.SetConfig(repo, $"remote.{remoteName}.{SettingCreatedByGHfVS}", "true"); + return remoteName; + } + + string CreateUniqueRemoteName(IRepository repo, string name) + { + var uniqueName = name; + var number = 1; + + while (repo.Network.Remotes[uniqueName] != null) + { + uniqueName = name + number++; + } + + return uniqueName; + } + + async Task ExtractToTempFile( + IRepository repo, + int pullRequestNumber, + string commitSha, + string relativePath, + Encoding encoding, + string tempFilePath) + { + string contents; + + try + { + contents = await gitClient.ExtractFile(repo, commitSha, relativePath) ?? string.Empty; + } + catch (FileNotFoundException) + { + var pullHeadRef = $"refs/pull/{pullRequestNumber}/head"; + var remote = await gitClient.GetHttpRemote(repo, "origin"); + await gitClient.Fetch(repo, remote.Name, commitSha, pullHeadRef); + contents = await gitClient.ExtractFile(repo, commitSha, relativePath) ?? string.Empty; + } + + Directory.CreateDirectory(Path.GetDirectoryName(tempFilePath)); + File.WriteAllText(tempFilePath, contents, encoding); + } + + IEnumerable<string> GetLocalBranchesInternal( + ILocalRepositoryModel localRepository, + IRepository repository, + PullRequestDetailModel pullRequest) + { + if (IsPullRequestFromRepository(localRepository, pullRequest)) + { + return new[] { pullRequest.HeadRefName }; + } + else + { + var key = BuildGHfVSConfigKeyValue(pullRequest.BaseRepositoryOwner, pullRequest.Number); + + return repository.Config + .Select(x => new { Branch = BranchCapture.Match(x.Key).Groups["branch"].Value, Value = x.Value }) + .Where(x => !string.IsNullOrWhiteSpace(x.Branch) && x.Value == key) + .Select(x => x.Branch); + } + } + + async Task<bool> IsBranchMarkedAsPullRequest(IRepository repo, string branchName, PullRequestDetailModel pullRequest) + { + var prConfigKey = $"branch.{branchName}.{SettingGHfVSPullRequest}"; + var value = ParseGHfVSConfigKeyValue(await gitClient.GetConfig<string>(repo, prConfigKey)); + return value != null && + value.Item1 == pullRequest.BaseRepositoryOwner && + value.Item2 == pullRequest.Number; + } + + async Task MarkBranchAsPullRequest(IRepository repo, string branchName, string owner, int number) + { + // Store the PR number in the branch config with the key "ghfvs-pr". + var prConfigKey = $"branch.{branchName}.{SettingGHfVSPullRequest}"; + await gitClient.SetConfig(repo, prConfigKey, BuildGHfVSConfigKeyValue(owner, number)); + } + + async Task<IPullRequestModel> PushAndCreatePR(IModelService modelService, + ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, + IBranch sourceBranch, IBranch targetBranch, + string title, string body) + { + // PullRequestModel doesn't keep a reference to repo + using (var repo = await Task.Run(() => gitService.GetRepository(sourceRepository.LocalPath))) + { + var remote = await gitClient.GetHttpRemote(repo, "origin"); + await gitClient.Push(repo, sourceBranch.Name, remote.Name); + + if (!repo.Branches[sourceBranch.Name].IsTracking) + await gitClient.SetTrackingBranch(repo, sourceBranch.Name, remote.Name); + + // delay things a bit to avoid a race between pushing a new branch and creating a PR on it + if (!Splat.ModeDetector.Current.InUnitTestRunner().GetValueOrDefault()) + await Task.Delay(TimeSpan.FromSeconds(5)); + + var ret = await modelService.CreatePullRequest(sourceRepository, targetRepository, sourceBranch, targetBranch, title, body); + await MarkBranchAsPullRequest(repo, sourceBranch.Name, targetRepository.CloneUrl.Owner, ret.Number); + gitExt.RefreshActiveRepositories(); + await usageTracker.IncrementCounter(x => x.NumberOfUpstreamPullRequests); + return ret; + } + } + + static string GetSafeBranchName(string name) + { + var before = InvalidBranchCharsRegex.Replace(name, "-").TrimEnd('-'); + + for (; ; ) + { + string after = before.Replace("--", "-"); + + if (after == before) + { + return before.ToLower(CultureInfo.CurrentCulture); + } + + before = after; + } + } + + static string CalculateTempFileName(string relativePath, string commitSha, Encoding encoding) + { + // The combination of relative path, commit SHA and encoding should be sufficient to uniquely identify a file. + var relativeDir = Path.GetDirectoryName(relativePath) ?? string.Empty; + var key = relativeDir + '|' + encoding.WebName; + var relativePathHash = key.GetSha256Hash(); + var tempDir = Path.Combine(Path.GetTempPath(), "GitHubVisualStudio", "FileContents", relativePathHash); + var tempFileName = Invariant($"{Path.GetFileNameWithoutExtension(relativePath)}@{commitSha}{Path.GetExtension(relativePath)}"); + return Path.Combine(tempDir, tempFileName); + } + + static string BuildGHfVSConfigKeyValue(string owner, int number) + { + return owner + '#' + number.ToString(CultureInfo.InvariantCulture); + } + + static Tuple<string, int> ParseGHfVSConfigKeyValue(string value) + { + if (value != null) + { + var separator = value.IndexOf('#'); + + if (separator != -1) + { + var owner = value.Substring(0, separator); + int number; + + if (int.TryParse(value.Substring(separator + 1), NumberStyles.None, CultureInfo.InvariantCulture, out number)) + { + return Tuple.Create(owner, number); + } + } + } + + return null; + } + + class ListItemAdapter : PullRequestListItemModel + { + public IList<ReviewAdapter> Reviews { get; set; } + + public LastCommitSummaryAdapter LastCommit { get; set; } + } + + class ReviewAdapter + { + public string Body { get; set; } + public int CommentCount { get; set; } + public int Count => CommentCount + (!string.IsNullOrWhiteSpace(Body) ? 1 : 0); + } + + class LastCommitSummaryAdapter + { + public List<CheckSuiteSummaryModel> CheckSuites { get; set; } + + public List<StatusSummaryModel> Statuses { get; set; } + } + + class CheckSuiteSummaryModel + { + public List<CheckRunSummaryModel> CheckRuns { get; set; } + } + + class CheckRunSummaryModel + { + public CheckConclusionState? Conclusion { get; set; } + public CheckStatusState Status { get; set; } + } + + class StatusSummaryModel + { + public StatusState State { get; set; } + } + } +} diff --git a/src/GitHub.App/Services/RepositoryCloneService.cs b/src/GitHub.App/Services/RepositoryCloneService.cs index cb052240a8..8952585263 100644 --- a/src/GitHub.App/Services/RepositoryCloneService.cs +++ b/src/GitHub.App/Services/RepositoryCloneService.cs @@ -3,9 +3,13 @@ using System.IO; using System.Reactive; using System.Reactive.Linq; -using Rothko; using GitHub.Extensions; -using NLog; +using GitHub.Logging; +using Microsoft.VisualStudio.Shell; +using Serilog; +using Rothko; +using GitHub.Helpers; +using Task = System.Threading.Tasks.Task; namespace GitHub.Services { @@ -18,51 +22,60 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.NonShared)] public class RepositoryCloneService : IRepositoryCloneService { - static readonly Logger log = LogManager.GetCurrentClassLogger(); + static readonly ILogger log = LogManager.ForContext<RepositoryCloneService>(); readonly IOperatingSystem operatingSystem; readonly string defaultClonePath; - readonly IVSServices vsservices; + readonly IVSGitServices vsGitServices; + readonly IUsageTracker usageTracker; [ImportingConstructor] - public RepositoryCloneService(IOperatingSystem operatingSystem, IVSServices vsservices) + public RepositoryCloneService( + IOperatingSystem operatingSystem, + IVSGitServices vsGitServices, + IUsageTracker usageTracker) { this.operatingSystem = operatingSystem; - this.vsservices = vsservices; + this.vsGitServices = vsGitServices; + this.usageTracker = usageTracker; defaultClonePath = GetLocalClonePathFromGitProvider(operatingSystem.Environment.GetUserRepositoriesPath()); } - public IObservable<Unit> CloneRepository(string cloneUrl, string repositoryName, string repositoryPath) + /// <inheritdoc/> + public async Task CloneRepository( + string cloneUrl, + string repositoryName, + string repositoryPath, + object progress = null) { Guard.ArgumentNotEmptyString(cloneUrl, nameof(cloneUrl)); Guard.ArgumentNotEmptyString(repositoryName, nameof(repositoryName)); Guard.ArgumentNotEmptyString(repositoryPath, nameof(repositoryPath)); - return Observable.Start(() => - { - string path = Path.Combine(repositoryPath, repositoryName); + string path = Path.Combine(repositoryPath, repositoryName); - operatingSystem.Directory.CreateDirectory(path); + // 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(path); + await ThreadingHelper.SwitchToMainThreadAsync(); - try - { - // this will throw if it can't find it - vsservices.Clone(cloneUrl, path, true); - } - catch (Exception ex) - { - log.Error("Could not clone {0} to {1}. {2}", cloneUrl, path, ex); - throw; - } - - return Unit.Default; - }); + try + { + await vsGitServices.Clone(cloneUrl, path, true, progress); + await usageTracker.IncrementCounter(x => x.NumberOfClones); + } + catch (Exception ex) + { + log.Error(ex, "Could not clone {CloneUrl} to {Path}", cloneUrl, path); + throw; + } } string GetLocalClonePathFromGitProvider(string fallbackPath) { - var ret = vsservices.GetLocalClonePathFromGitProvider(); + var ret = vsGitServices.GetLocalClonePathFromGitProvider(); return !string.IsNullOrEmpty(ret) ? operatingSystem.Environment.ExpandEnvironmentVariables(ret) : fallbackPath; diff --git a/src/GitHub.App/Services/RepositoryCreationService.cs b/src/GitHub.App/Services/RepositoryCreationService.cs index 5823cc6c9c..64dba3729f 100644 --- a/src/GitHub.App/Services/RepositoryCreationService.cs +++ b/src/GitHub.App/Services/RepositoryCreationService.cs @@ -3,6 +3,7 @@ using System.Reactive; using System.Reactive.Linq; using GitHub.Api; +using GitHub.Extensions; using GitHub.Extensions.Reactive; using GitHub.Models; using Octokit; diff --git a/src/GitHub.App/Services/RepositoryForkService.cs b/src/GitHub.App/Services/RepositoryForkService.cs new file mode 100644 index 0000000000..10519fb736 --- /dev/null +++ b/src/GitHub.App/Services/RepositoryForkService.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.ViewModels.Dialog; +using LibGit2Sharp; +using Octokit; +using ReactiveUI; +using Serilog; +using Repository = Octokit.Repository; + +namespace GitHub.Services +{ + [Export(typeof(IRepositoryForkService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class RepositoryForkService : IRepositoryForkService + { + static readonly ILogger log = LogManager.ForContext<RepositoryForkService>(); + + readonly IGitClient gitClient; + readonly IVSGitServices vsGitServices; + readonly IVSGitExt vsGitExt; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public RepositoryForkService(IGitClient gitClient, IVSGitServices vsGitServices, IVSGitExt vsGitExt, IUsageTracker usageTracker) + { + this.gitClient = gitClient; + this.vsGitServices = vsGitServices; + this.vsGitExt = vsGitExt; + this.usageTracker = usageTracker; + } + + public IObservable<Repository> ForkRepository(IApiClient apiClient, IRepositoryModel sourceRepository, NewRepositoryFork repositoryFork, bool updateOrigin, bool addUpstream, bool trackMasterUpstream) + { + log.Verbose("ForkRepository Source:{SourceOwner}/{SourceName} To:{DestinationOwner}", sourceRepository.Owner, sourceRepository.Name, repositoryFork.Organization ?? "[Current User]"); + log.Verbose("ForkRepository updateOrigin:{UpdateOrigin} addUpstream:{AddUpstream} trackMasterUpstream:{TrackMasterUpstream}", updateOrigin, addUpstream, trackMasterUpstream); + + usageTracker.IncrementCounter(model => model.NumberOfReposForked).Forget(); + + return Observable.Defer(() => apiClient.ForkRepository(sourceRepository.Owner, sourceRepository.Name, repositoryFork) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(remoteRepo => new { RemoteRepo = remoteRepo, ActiveRepo = updateOrigin ? vsGitServices.GetActiveRepo() : null })) + .SelectMany(async repo => + { + if (repo.ActiveRepo != null) + { + using (repo.ActiveRepo) + { + var originUri = repo.RemoteRepo != null ? new Uri(repo.RemoteRepo.CloneUrl) : null; + var upstreamUri = addUpstream ? sourceRepository.CloneUrl.ToUri() : null; + + await SwitchRemotes(repo.ActiveRepo, originUri, upstreamUri, trackMasterUpstream); + } + } + + return repo.RemoteRepo; + }); + } + + public IObservable<object> SwitchRemotes(IRepositoryModel destinationRepository, bool updateOrigin, bool addUpstream, bool trackMasterUpstream) + { + return Observable.Defer(() => Observable.Return(new object()) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(_ => vsGitServices.GetActiveRepo())) + .SelectMany(async activeRepo => + { + using (activeRepo) + { + Uri currentOrigin = null; + + if (addUpstream) + { + var remote = await gitClient.GetHttpRemote(activeRepo, "origin"); + currentOrigin = new Uri(remote.Url); + } + + await SwitchRemotes(activeRepo, updateOrigin ? destinationRepository.CloneUrl.ToUri() : null, + currentOrigin, trackMasterUpstream); + } + + if (updateOrigin) + { + vsGitExt.RefreshActiveRepositories(); + + var updatedRepository = vsGitExt.ActiveRepositories.FirstOrDefault(); + log.Assert(updatedRepository?.CloneUrl == destinationRepository.CloneUrl, + "CloneUrl is {UpdatedRepository} not {DestinationRepository}", updatedRepository?.CloneUrl ?? "[NULL]", destinationRepository.CloneUrl); + } + + return new object(); + }); + } + + private async Task SwitchRemotes(IRepository repository, Uri originUri, Uri upstreamUri = null, bool trackMasterUpstream = false) + { + Guard.ArgumentNotNull(originUri, nameof(originUri)); + + log.Verbose("Set remote origin to {OriginUri}", originUri); + + await gitClient.SetRemote(repository, "origin", originUri); + + if (upstreamUri != null) + { + log.Verbose("Set remote upstream to {UpstreamUri}", upstreamUri); + + await gitClient.SetRemote(repository, "upstream", upstreamUri); + + await gitClient.Fetch(repository, "upstream"); + + if (trackMasterUpstream) + { + log.Verbose("set master tracking to upstream"); + + await gitClient.SetTrackingBranch(repository, "master", "upstream"); + } + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/Services/RepositoryPublishService.cs b/src/GitHub.App/Services/RepositoryPublishService.cs index 2f7edcefe6..48d4a21fc4 100644 --- a/src/GitHub.App/Services/RepositoryPublishService.cs +++ b/src/GitHub.App/Services/RepositoryPublishService.cs @@ -4,7 +4,7 @@ using System.Reactive.Linq; using GitHub.Api; using GitHub.Models; -using LibGit2Sharp; +using ReactiveUI; namespace GitHub.Services { @@ -13,22 +13,25 @@ namespace GitHub.Services public class RepositoryPublishService : IRepositoryPublishService { readonly IGitClient gitClient; - readonly IRepository activeRepository; + readonly IVSGitServices vsGitServices; [ImportingConstructor] - public RepositoryPublishService(IGitClient gitClient, IVSServices services) + public RepositoryPublishService(IGitClient gitClient, IVSGitServices vsGitServices) { this.gitClient = gitClient; - this.activeRepository = services.GetActiveRepo(); + this.vsGitServices = vsGitServices; } public string LocalRepositoryName { get { - if (!string.IsNullOrEmpty(activeRepository?.Info?.WorkingDirectory)) - return new DirectoryInfo(activeRepository.Info.WorkingDirectory).Name ?? ""; - return string.Empty; + using (var activeRepo = vsGitServices.GetActiveRepo()) + { + if (!string.IsNullOrEmpty(activeRepo?.Info?.WorkingDirectory)) + return new DirectoryInfo(activeRepo.Info.WorkingDirectory).Name ?? ""; + return string.Empty; + } } } @@ -37,13 +40,20 @@ public string LocalRepositoryName IAccount account, IApiClient apiClient) { - return Observable.Defer(() => Observable.Return(activeRepository)) - .SelectMany(r => apiClient.CreateRepository(newRepository, account.Login, account.IsUser) - .Select(gitHubRepo => Tuple.Create(gitHubRepo, r))) - .SelectMany(repo => gitClient.SetRemote(repo.Item2, "origin", new Uri(repo.Item1.CloneUrl)).Select(_ => repo)) - .SelectMany(repo => gitClient.Push(repo.Item2, "master", "origin").Select(_ => repo)) - .SelectMany(repo => gitClient.Fetch(repo.Item2, "origin").Select(_ => repo)) - .SelectMany(repo => gitClient.SetTrackingBranch(repo.Item2, "master", "origin").Select(_ => repo.Item1)); + return Observable.Defer(() => apiClient.CreateRepository(newRepository, account.Login, account.IsUser) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(remoteRepo => new { RemoteRepo = remoteRepo, LocalRepo = vsGitServices.GetActiveRepo() })) + .SelectMany(async repo => + { + using (repo.LocalRepo) + { + await gitClient.SetRemote(repo.LocalRepo, "origin", new Uri(repo.RemoteRepo.CloneUrl)); + await gitClient.Push(repo.LocalRepo, "master", "origin"); + await gitClient.Fetch(repo.LocalRepo, "origin"); + await gitClient.SetTrackingBranch(repo.LocalRepo, "master", "origin"); + return repo.RemoteRepo; + } + }); } } } diff --git a/src/GitHub.App/Services/RepositoryService.cs b/src/GitHub.App/Services/RepositoryService.cs new file mode 100644 index 0000000000..36b729fe3e --- /dev/null +++ b/src/GitHub.App/Services/RepositoryService.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Primitives; +using Octokit.GraphQL; +using static Octokit.GraphQL.Variable; + +namespace GitHub.Services +{ + [Export(typeof(IRepositoryService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class RepositoryService : IRepositoryService + { + static ICompiledQuery<Tuple<string, string>> readParentOwnerLogin; + readonly IGraphQLClientFactory graphqlFactory; + + [ImportingConstructor] + public RepositoryService(IGraphQLClientFactory graphqlFactory) + { + Guard.ArgumentNotNull(graphqlFactory, nameof(graphqlFactory)); + + this.graphqlFactory = graphqlFactory; + } + + public async Task<(string owner, string name)?> FindParent(HostAddress address, string owner, string name) + { + Guard.ArgumentNotNull(address, nameof(address)); + Guard.ArgumentNotEmptyString(owner, nameof(owner)); + Guard.ArgumentNotEmptyString(name, nameof(name)); + + if (readParentOwnerLogin == null) + { + readParentOwnerLogin = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .Select(r => r.Parent != null ? Tuple.Create(r.Parent.Owner.Login, r.Parent.Name) : null) + .Compile(); + } + + var vars = new Dictionary<string, object> + { + { nameof(owner), owner }, + { nameof(name), name }, + }; + + var graphql = await graphqlFactory.CreateConnection(address); + var result = await graphql.Run(readParentOwnerLogin, vars); + return result != null ? (result.Item1, result.Item2) : ((string, string)?)null; + } + } +} diff --git a/src/GitHub.App/Services/StandardUserErrors.cs b/src/GitHub.App/Services/StandardUserErrors.cs index 51ef7dfe7b..571bee379f 100644 --- a/src/GitHub.App/Services/StandardUserErrors.cs +++ b/src/GitHub.App/Services/StandardUserErrors.cs @@ -40,6 +40,7 @@ public enum ErrorType GettingHeadFailed, LaunchEnterpriseConnectionFailed, LoginFailed, + LogoutFailed, LogFileError, RepoCorrupted, RepoDirectoryAlreadyExists, @@ -62,7 +63,9 @@ public enum ErrorType SaveRepositorySettingsFailed, MenuActionFailed, Global, - RefreshFailed + RefreshFailed, + GistCreateFailed, + RepoForkFailed } public static class StandardUserErrors @@ -135,7 +138,8 @@ public static class StandardUserErrors { ErrorType.EnterpriseConnectFailed, Map(Defaults("Connecting to GitHub Enterprise instance failed", "Could not find a GitHub Enterprise instance at '{0}'. Double check the URL and your internet/intranet connection.")) }, { ErrorType.LaunchEnterpriseConnectionFailed, Map(Defaults("Failed to launch the enterprise connection.")) }, { ErrorType.LogFileError, Map(Defaults("Could not open the log file", "Could not find or open the log file.")) }, - { ErrorType.LoginFailed, Map(Defaults("login failed", "Unable to retrieve your user info from the server. A proxy server might be interfering with the request.")) }, + { ErrorType.LoginFailed, Map(Defaults("Login failed", "Unable to retrieve your user info from the server. A proxy server might be interfering with the request.")) }, + { ErrorType.LogoutFailed, Map(Defaults("Logout failed", "Logout failed. A proxy server might be interfering with the request.")) }, { ErrorType.RepoCreationAsPrivateNotAvailableForFreePlan, Map(Defaults("Failed to create private repository", "You are currently on a free plan and unable to create private repositories. Either make the repository public or upgrade your account on the website to a plan that allows for private repositories.")) }, { ErrorType.RepoCreationFailed, Map(Defaults("Failed to create repository", "An error occurred while creating the repository. You might need to open a shell and debug the state of this repo.")) }, { ErrorType.RepoExistsOnDisk, Map(Defaults("Failed to create repository", "A repository named '{0}' exists in the directory\n'{1}'.")) }, @@ -146,6 +150,8 @@ public static class StandardUserErrors { ErrorType.RefreshFailed, Map(Defaults("Refresh failed", "Refresh failed unexpectedly. Please email support@github.com if this error persists."), new Translation<HttpRequestException>("Refresh failed", "Could not connect to the remote server. The server or your internect connection could be down")) }, + { ErrorType.GistCreateFailed, Map(Defaults("Failed to create gist", "Creating a gist failed unexpectedly. Try logging back in.")) }, + { ErrorType.RepoForkFailed, Map(Defaults("Failed to create fork")) }, })); public static string GetUserFriendlyErrorMessage(this Exception exception, ErrorType errorType, params object[] messageArgs) @@ -170,14 +176,6 @@ public static IObservable<RecoveryOptionResult> ShowUserThatRepoAlreadyExists(st return DisplayErrorMessage(ErrorType.RepoExistsOnDisk, new object[] { repositoryName, fullPath }, new[] { OpenPathInExplorer(fullPath), Cancel }); } - public static IObservable<RecoveryOptionResult> ShowCloneError(this Exception exception, - ErrorType errorType, - string displayName, - string repositoryLocalWorkingDirectory) - { - return exception.DisplayErrorMessage(errorType, new object[] { displayName }, null); - } - public static IObservable<RecoveryOptionResult> ShowUserErrorThatRequiresNavigatingToBilling( this Exception exception, IAccount account) @@ -211,7 +209,7 @@ static IObservable<RecoveryOptionResult> DisplayErrorMessage(this Exception exce return userError.Throw(); } - static UserError GetUserFriendlyError(this Exception exception, ErrorType errorType, params object[] messageArgs) + public static UserError GetUserFriendlyError(this Exception exception, ErrorType errorType, params object[] messageArgs) { return Translator.Value.GetUserError(errorType, exception, messageArgs); } diff --git a/src/GitHub.App/Services/TeamExplorerContext.cs b/src/GitHub.App/Services/TeamExplorerContext.cs new file mode 100644 index 0000000000..6d443e4dfc --- /dev/null +++ b/src/GitHub.App/Services/TeamExplorerContext.cs @@ -0,0 +1,207 @@ +using System; +using System.Linq; +using System.ComponentModel; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Logging; +using GitHub.Primitives; +using GitHub.Extensions; +using Serilog; +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services +{ + /// <summary> + /// This implementation listenes to IGitExt for ActiveRepositories property change events and fires + /// <see cref="PropertyChanged"/> and <see cref="StatusChanged"/> events when appropriate. + /// </summary> + /// <remarks> + /// A <see cref="PropertyChanged"/> is fired when a solution is opened in a new repository (or not in a repository). + /// A <see cref="StatusChanged"/> event is only fired when the current branch, head SHA or tracked SHA changes (not + /// on every IGitExt property change event). <see cref="ActiveRepository"/> contains the active repository or null + /// if a solution is opened that isn't in a repository. No events are fired when the same solution is unloaded then + /// reloaded (e.g. when a .sln file is touched). + /// </remarks> + [Export(typeof(ITeamExplorerContext))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class TeamExplorerContext : ITeamExplorerContext + { + static ILogger log = LogManager.ForContext<TeamExplorerContext>(); + + readonly AsyncLazy<DTE> dteAsync; + readonly IVSGitExt gitExt; + readonly IPullRequestService pullRequestService; + + string solutionPath; + string repositoryPath; + UriString cloneUrl; + string branchName; + string headSha; + string trackedSha; + Tuple<string, int> pullRequest; + + ILocalRepositoryModel repositoryModel; + JoinableTask refreshJoinableTask; + + [ImportingConstructor] + TeamExplorerContext( + IVSGitExt gitExt, + [Import(typeof(SVsServiceProvider))] IServiceProvider sp, + IPullRequestService pullRequestService) : this( + gitExt, + new AsyncLazy<DTE>(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return (DTE)sp.GetService(typeof(DTE)); + }), + pullRequestService, + ThreadHelper.JoinableTaskContext) + { + } + + public TeamExplorerContext( + IVSGitExt gitExt, + AsyncLazy<DTE> dteAsync, + IPullRequestService pullRequestService, + JoinableTaskContext joinableTaskContext) + { + JoinableTaskCollection = joinableTaskContext.CreateCollection(); + JoinableTaskCollection.DisplayName = nameof(TeamExplorerContext); + JoinableTaskFactory = joinableTaskContext.CreateFactory(JoinableTaskCollection); + + this.gitExt = gitExt; + this.dteAsync = dteAsync; + this.pullRequestService = pullRequestService; + + StartRefresh(); + gitExt.ActiveRepositoriesChanged += Refresh; + } + + void StartRefresh() => JoinableTaskFactory.RunAsync(QueueRefreshAsync).Task.Forget(log); + void Refresh() => JoinableTaskFactory.Run(QueueRefreshAsync); + + async Task QueueRefreshAsync() + { + if (refreshJoinableTask != null) + { + await refreshJoinableTask.JoinAsync(); // make sure StartRefresh has completed + } + + await (refreshJoinableTask = JoinableTaskFactory.RunAsync(RefreshAsync)); + } + + async Task RefreshAsync() + { + try + { + await TaskScheduler.Default; // switch to threadpool + + var repo = gitExt.ActiveRepositories?.FirstOrDefault(); + string newSolutionPath = await GetSolutionPath(); + if (repo == null && newSolutionPath == solutionPath) + { + // Ignore when ActiveRepositories is empty and solution hasn't changed. + // https://github.com/github/VisualStudio/issues/1421 + log.Debug("Ignoring no ActiveRepository when solution hasn't changed"); + } + else + { + var newRepositoryPath = repo?.LocalPath; + var newCloneUrl = repo?.CloneUrl; + var newBranchName = repo?.CurrentBranch?.Name; + var newHeadSha = repo?.CurrentBranch?.Sha; + var newTrackedSha = repo?.CurrentBranch?.TrackedSha; + var newPullRequest = repo != null ? await pullRequestService.GetPullRequestForCurrentBranch(repo) : null; + + if (newRepositoryPath != repositoryPath) + { + log.Debug("ActiveRepository changed to {CloneUrl} @ {Path}", repo?.CloneUrl, newRepositoryPath); + ActiveRepository = repo; + } + else if (newCloneUrl != cloneUrl) + { + log.Debug("ActiveRepository changed to {CloneUrl} @ {Path}", repo?.CloneUrl, newRepositoryPath); + ActiveRepository = repo; + } + else if (newBranchName != branchName) + { + log.Debug("Fire StatusChanged event when BranchName changes for ActiveRepository"); + StatusChanged?.Invoke(this, EventArgs.Empty); + } + else if (newHeadSha != headSha) + { + log.Debug("Fire StatusChanged event when HeadSha changes for ActiveRepository"); + StatusChanged?.Invoke(this, EventArgs.Empty); + } + else if (newTrackedSha != trackedSha) + { + log.Debug("Fire StatusChanged event when TrackedSha changes for ActiveRepository"); + StatusChanged?.Invoke(this, EventArgs.Empty); + } + else if (newPullRequest != pullRequest) + { + log.Debug("Fire StatusChanged event when PullRequest changes for ActiveRepository"); + StatusChanged?.Invoke(this, EventArgs.Empty); + } + + repositoryPath = newRepositoryPath; + cloneUrl = newCloneUrl; + branchName = newBranchName; + headSha = newHeadSha; + solutionPath = newSolutionPath; + trackedSha = newTrackedSha; + pullRequest = newPullRequest; + } + } + catch (Exception e) + { + log.Error(e, "Refreshing active repository"); + } + } + + async Task<string> GetSolutionPath() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = await dteAsync.GetValueAsync(); + return dte.Solution?.FullName; + } + + /// <summary> + /// The active repository or null if not in a repository. + /// </summary> + public ILocalRepositoryModel ActiveRepository + { + get + { + return repositoryModel; + } + + private set + { + if (value != repositoryModel) + { + repositoryModel = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ActiveRepository))); + } + } + } + + /// <summary> + /// Fired when a solution is opened in a new repository (or that isn't in a repository). + /// </summary> + public event PropertyChangedEventHandler PropertyChanged; + + /// <summary> + /// Fired when the current branch, head SHA or tracked SHA changes. + /// </summary> + public event EventHandler StatusChanged; + + public JoinableTaskCollection JoinableTaskCollection { get; } + JoinableTaskFactory JoinableTaskFactory { get; } + } +} diff --git a/src/GitHub.App/Services/TextViewCommandDispatcher.cs b/src/GitHub.App/Services/TextViewCommandDispatcher.cs new file mode 100644 index 0000000000..f8e8290824 --- /dev/null +++ b/src/GitHub.App/Services/TextViewCommandDispatcher.cs @@ -0,0 +1,82 @@ +using System; +using System.Windows.Input; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace GitHub.Services +{ + /// <summary> + /// Intercepts all commands sent to a <see cref="IVsTextView"/> and fires <see href="Exec"/> when a specified command is encountered. + /// </summary> + /// <remarks> + /// Intercepting commands like this is necessary if we want to capture commands in context of a <see cref="IVsTextView"/>. + /// Well known commands like Copy, Paste and Enter can be captured as well as custom commands. + /// </remarks> + class TextViewCommandDispatcher : IOleCommandTarget, IDisposable + { + readonly IVsTextView textView; + readonly Guid commandGroup; + readonly int commandId; + readonly IOleCommandTarget next; + readonly ICommand targetCommand; + + public static IDisposable AddCommandFilter(IVsTextView textView, Guid commandGroup, int commandId, ICommand targetCommand) + { + return new TextViewCommandDispatcher(textView, commandGroup, commandId, targetCommand); + } + + /// <summary> + /// Add a command filter to <see cref="IVsTextView"/>. + /// </summary> + /// <param name="textView">The text view to filter commands from.</param> + /// <param name="commandGroup">The group of the command to listen for.</param> + /// <param name="commandId">The ID of the command to listen for.</param> + /// <param name="targetCommand">The command to dispatch to.</param> + TextViewCommandDispatcher(IVsTextView textView, Guid commandGroup, int commandId, ICommand targetCommand) + { + this.textView = textView; + this.commandGroup = commandGroup; + this.commandId = commandId; + this.targetCommand = targetCommand; + + ErrorHandler.ThrowOnFailure(textView.AddCommandFilter(this, out next)); + } + + /// <summary> + /// Remove the command filter. + /// </summary> + public void Dispose() + { + ErrorHandler.ThrowOnFailure(textView.RemoveCommandFilter(this)); + } + + int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + if (pguidCmdGroup == commandGroup && nCmdID == commandId) + { + targetCommand.Execute(null); + return VSConstants.S_OK; + } + + return next?.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut) ?? 0; + } + + int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + if (pguidCmdGroup == commandGroup) + { + if (prgCmds != null && cCmds == 1) + { + if (prgCmds[0].cmdID == commandId) + { + prgCmds[0].cmdf = (uint)OLECMDF.OLECMDF_SUPPORTED | (uint)OLECMDF.OLECMDF_ENABLED; + return VSConstants.S_OK; + } + } + } + + return next?.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText) ?? 0; + } + } +} diff --git a/src/GitHub.App/Services/Translation.cs b/src/GitHub.App/Services/Translation.cs index 9f08a6ec6b..8235ffc8a1 100644 --- a/src/GitHub.App/Services/Translation.cs +++ b/src/GitHub.App/Services/Translation.cs @@ -4,7 +4,6 @@ using System.Text.RegularExpressions; using GitHub.Extensions; using Newtonsoft.Json; -using NullGuard; using Octokit; namespace GitHub.Services @@ -14,29 +13,39 @@ public class Translation readonly ErrorMessage defaultMessage; readonly Func<Exception, ErrorMessage> translator; - public Translation([AllowNull]string original, string heading, string message) + public Translation(string original, string heading, string message) { + Guard.ArgumentNotNull(heading, nameof(heading)); + Guard.ArgumentNotNull(message, nameof(message)); + Original = original; defaultMessage = new ErrorMessage(heading, message); } - public Translation([AllowNull]string original, Func<Exception, ErrorMessage> translator) + public Translation(string original, Func<Exception, ErrorMessage> translator) { + Guard.ArgumentNotNull(translator, nameof(translator)); + Original = original; this.translator = translator; } - public Translation([AllowNull]string original, string heading, string messageFormatString, Func<Exception, string> translator) + public Translation(string original, string heading, string messageFormatString, Func<Exception, string> translator) : this(original, heading, messageFormatString) { + Guard.ArgumentNotNull(heading, nameof(heading)); + Guard.ArgumentNotNull(messageFormatString, nameof(messageFormatString)); + Guard.ArgumentNotNull(translator, nameof(translator)); + this.translator = e => new ErrorMessage(heading, String.Format(CultureInfo.InvariantCulture, messageFormatString, translator(e))); } public string Original { get; private set; } - [return: AllowNull] public ErrorMessage Translate(Exception exception) { + Guard.ArgumentNotNull(exception, nameof(exception)); + var match = Match(exception); if (match == null) return null; @@ -64,6 +73,8 @@ public ErrorMessage Translate(Exception exception) // Returns a tuple indicating whether this translation is a match for the exception and the regex line if existing. protected virtual Tuple<Translation, string> Match(Exception exception) { + Guard.ArgumentNotNull(exception, nameof(exception)); + string exceptionMessage = exception.Message; var apiException = exception as ApiValidationException; @@ -105,14 +116,19 @@ public class Translation<TException> : Translation where TException : Exception { public Translation(string heading, string message) : base(null, heading, message) { + Guard.ArgumentNotNull(heading, nameof(heading)); + Guard.ArgumentNotNull(message, nameof(message)); } public Translation(string heading) : base(null, e => new ErrorMessage(heading, e.Message)) { + Guard.ArgumentNotNull(heading, nameof(heading)); } protected override Tuple<Translation, string> Match(Exception exception) { + Guard.ArgumentNotNull(exception, nameof(exception)); + if (exception is TException) return new Tuple<Translation, string>(null, null); return null; } diff --git a/src/GitHub.App/ViewModels/ActorViewModel.cs b/src/GitHub.App/ViewModels/ActorViewModel.cs new file mode 100644 index 0000000000..a266b43e84 --- /dev/null +++ b/src/GitHub.App/ViewModels/ActorViewModel.cs @@ -0,0 +1,56 @@ +using System; +using System.Windows.Media.Imaging; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Serilog; + +namespace GitHub.ViewModels +{ + public class ActorViewModel : ViewModelBase, IActorViewModel + { + const string DefaultAvatar = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"; + static readonly ILogger log = LogManager.ForContext<ActorViewModel>(); + + public ActorViewModel() + { + } + + public ActorViewModel(ActorModel model) + { + Login = model?.Login ?? "[unknown]"; + + if (model?.AvatarUrl != null) + { + try + { + var uri = new Uri(model.AvatarUrl); + + // Image requests to enterprise hosts over https always fail currently, + // so just display the default avatar. See #1547. + if (uri.Scheme != "https" || + uri.Host.EndsWith("githubusercontent.com", StringComparison.OrdinalIgnoreCase)) + { + AvatarUrl = model.AvatarUrl; + Avatar = new BitmapImage(uri); + } + } + catch (Exception ex) + { + log.Error(ex, "Invalid avatar URL"); + } + } + + if (AvatarUrl == null) + { + Avatar = AvatarProvider.CreateBitmapImage(DefaultAvatar); + AvatarUrl = DefaultAvatar; + } + } + + public BitmapSource Avatar { get; set; } + public string AvatarUrl { get; set; } + public string Login { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/BaseViewModel.cs b/src/GitHub.App/ViewModels/BaseViewModel.cs deleted file mode 100644 index 0e816401a8..0000000000 --- a/src/GitHub.App/ViewModels/BaseViewModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Windows.Input; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - public class BaseViewModel : ReactiveObject, IViewModel - { - protected ObservableAsPropertyHelper<bool> isShowing; - - public ReactiveCommand<object> CancelCommand { get; protected set; } - public ICommand Cancel { get { return CancelCommand; } } - - public string Title { get; protected set; } - public bool IsShowing { get { return isShowing.Value; } } - } -} diff --git a/src/GitHub.App/ViewModels/Dialog/ForkRepositoryExecuteViewModel.cs b/src/GitHub.App/ViewModels/Dialog/ForkRepositoryExecuteViewModel.cs new file mode 100644 index 0000000000..460800c460 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/ForkRepositoryExecuteViewModel.cs @@ -0,0 +1,155 @@ +using System; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Octokit; +using ReactiveUI; +using Serilog; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IForkRepositoryExecuteViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ForkRepositoryExecuteViewModel : ViewModelBase, IForkRepositoryExecuteViewModel + { + static readonly ILogger log = LogManager.ForContext<ForkRepositoryExecuteViewModel>(); + + readonly IModelServiceFactory modelServiceFactory; + readonly IRepositoryForkService repositoryForkService; + + IApiClient apiClient; + + [ImportingConstructor] + public ForkRepositoryExecuteViewModel( + IModelServiceFactory modelServiceFactory, + IRepositoryForkService repositoryForkService + ) + { + this.modelServiceFactory = modelServiceFactory; + this.repositoryForkService = repositoryForkService; + + this.WhenAnyValue(model => model.UpdateOrigin) + .Subscribe(value => CanAddUpstream = value); + + this.WhenAnyValue(model => model.UpdateOrigin, model => model.AddUpstream) + .Subscribe(tuple => CanResetMasterTracking = tuple.Item1 && tuple.Item2); + + CreateFork = ReactiveCommand.CreateAsyncObservable(OnCreateFork); + BackCommand = ReactiveCommand.Create(); + } + + public IRepositoryModel SourceRepository { get; private set; } + + public IAccount DestinationAccount { get; private set; } + + public IRepositoryModel DestinationRepository { get; private set; } + + public IReactiveCommand<Repository> CreateFork { get; } + + public IReactiveCommand<object> BackCommand { get; } + + public string Title => Resources.ForkRepositoryTitle; + + public IObservable<object> Done => CreateFork.Where(repository => repository != null); + + public IObservable<object> Back => BackCommand.AsObservable(); + + public async Task InitializeAsync(ILocalRepositoryModel sourceRepository, IAccount destinationAccount, IConnection connection) + { + var modelService = await modelServiceFactory.CreateAsync(connection); + apiClient = modelService.ApiClient; + + DestinationAccount = destinationAccount; + + SourceRepository = sourceRepository; + DestinationRepository = new RemoteRepositoryModel( + 0, + sourceRepository.Name, + CreateForkUri(sourceRepository.CloneUrl, destinationAccount.Login), + false, + true, + destinationAccount, + null); + } + + UriString CreateForkUri(UriString url, string login) + { + var original = url.ToRepositoryUrl(); + var forkUri = string.Format(CultureInfo.CurrentCulture, "{0}://{1}/{2}/{3}", original.Scheme, original.Authority, login, url.RepositoryName); + return new UriString(forkUri); + } + + IObservable<Repository> OnCreateFork(object o) + { + var newRepositoryFork = new NewRepositoryFork + { + Organization = !DestinationAccount.IsUser ? DestinationAccount.Login : null + }; + + return repositoryForkService + .ForkRepository(apiClient, SourceRepository, newRepositoryFork, UpdateOrigin, CanAddUpstream && AddUpstream, CanResetMasterTracking && ResetMasterTracking) + .Catch<Repository, Exception>(ex => + { + log.Error(ex, "Error Creating Fork"); + + var apiEx = ex as ApiException; + Error = apiEx != null ? apiEx.Message : "An unexpected error occurred."; + + return Observable.Return<Repository>(null); + }); + } + + bool updateOrigin = true; + public bool UpdateOrigin + { + get { return updateOrigin; } + set { this.RaiseAndSetIfChanged(ref updateOrigin, value); } + } + + bool canAddUpstream = true; + public bool CanAddUpstream + { + get { return canAddUpstream; } + private set { this.RaiseAndSetIfChanged(ref canAddUpstream, value); } + } + + bool addUpstream = true; + public bool AddUpstream + { + get { return addUpstream; } + set { this.RaiseAndSetIfChanged(ref addUpstream, value); } + } + + bool canResetMasterTracking; + public bool CanResetMasterTracking + { + get { return canResetMasterTracking; } + private set { this.RaiseAndSetIfChanged(ref canResetMasterTracking, value); } + } + + bool resetMasterTracking; + public bool ResetMasterTracking + { + get { return resetMasterTracking; } + set { this.RaiseAndSetIfChanged(ref resetMasterTracking, value); } + } + + string error = null; + + public string Error + { + get { return error; } + private set { this.RaiseAndSetIfChanged(ref error, value); } + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/ForkRepositorySelectViewModel.cs b/src/GitHub.App/ViewModels/Dialog/ForkRepositorySelectViewModel.cs new file mode 100644 index 0000000000..5d412c4f86 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/ForkRepositorySelectViewModel.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.App; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IForkRepositorySelectViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ForkRepositorySelectViewModel : ViewModelBase, IForkRepositorySelectViewModel + { + static readonly ILogger log = LogManager.ForContext<ForkRepositorySelectViewModel>(); + + readonly IModelServiceFactory modelServiceFactory; + IReadOnlyList<IAccount> accounts; + IReadOnlyList<IRemoteRepositoryModel> existingForks; + bool isLoading; + + [ImportingConstructor] + public ForkRepositorySelectViewModel(IModelServiceFactory modelServiceFactory) + { + this.modelServiceFactory = modelServiceFactory; + SelectedAccount = ReactiveCommand.Create(); + SwitchOrigin = ReactiveCommand.Create(); + } + + public string Title => Resources.ForkRepositoryTitle; + + public IReadOnlyList<IAccount> Accounts + { + get { return accounts; } + private set { this.RaiseAndSetIfChanged(ref accounts, value); } + } + + public IReadOnlyList<IRemoteRepositoryModel> ExistingForks + { + get { return existingForks; } + private set { this.RaiseAndSetIfChanged(ref existingForks, value); } + } + + public bool IsLoading + { + get { return isLoading; } + private set { this.RaiseAndSetIfChanged(ref isLoading, value); } + } + + public ReactiveCommand<object> SelectedAccount { get; } + + public ReactiveCommand<object> SwitchOrigin { get; } + + public IObservable<object> Done => SelectedAccount; + + public async Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) + { + IsLoading = true; + + try + { + var modelService = await modelServiceFactory.CreateAsync(connection); + + Observable.CombineLatest( + modelService.GetAccounts(), + modelService.GetRepository(repository.Owner, repository.Name), + modelService.GetForks(repository).ToList(), + (a, r, f) => new { Accounts = a, Respoitory = r, Forks = f }) + .Finally(() => IsLoading = false) + .Subscribe(x => + { + var forks = x.Forks; + + var parents = new List<IRemoteRepositoryModel>(); + var current = x.Respoitory; + while (current.Parent != null) + { + parents.Add(current.Parent); + current = current.Parent; + } + + BuildAccounts(x.Accounts, repository, forks, parents); + }); + + } + catch (Exception ex) + { + log.Error(ex, "Error initializing ForkRepositoryViewModel"); + IsLoading = false; + } + } + + void BuildAccounts(IReadOnlyList<IAccount> accessibleAccounts, ILocalRepositoryModel currentRepository, IList<IRemoteRepositoryModel> forks, List<IRemoteRepositoryModel> parents) + { + log.Verbose("BuildAccounts: {AccessibleAccounts} accessibleAccounts, {Forks} forks, {Parents} parents", accessibleAccounts.Count, forks.Count, parents.Count); + + var existingForksAndParents = forks.Union(parents).ToDictionary(model => model.Owner); + + var readOnlyList = accessibleAccounts + .Where(account => account.Login != currentRepository.Owner) + .Select(account => new {Account = account, Fork = existingForksAndParents.ContainsKey(account.Login) ? existingForksAndParents[account.Login] : null }) + .ToArray(); + + Accounts = readOnlyList.Where(arg => arg.Fork == null).Select(arg => arg.Account).ToList(); + ExistingForks = readOnlyList.Where(arg => arg.Fork != null).Select(arg => arg.Fork).ToList(); + + // HACK: Our avatar cache only provides avatars in a very small size, but we want to + // display them 100x100 in the Fork view. For now, wse the AvatarUrl directly to get + // the avatar, appending "s=100" to the URL to get the correct size. + foreach (Account account in Accounts) + { + account.AvatarUrl += "&s=100"; + } + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/ForkRepositorySwitchViewModel.cs b/src/GitHub.App/ViewModels/Dialog/ForkRepositorySwitchViewModel.cs new file mode 100644 index 0000000000..f8ad35e335 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/ForkRepositorySwitchViewModel.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Octokit; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IForkRepositorySwitchViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ForkRepositorySwitchViewModel : ViewModelBase, IForkRepositorySwitchViewModel + { + readonly IRepositoryForkService repositoryForkService; + + [ImportingConstructor] + public ForkRepositorySwitchViewModel(IRepositoryForkService repositoryForkService) + { + this.repositoryForkService = repositoryForkService; + + SwitchFork = ReactiveCommand.CreateAsyncObservable(OnSwitchFork); + } + + public IRepositoryModel SourceRepository { get; private set; } + + public IRepositoryModel DestinationRepository { get; private set; } + + public IReactiveCommand<object> SwitchFork { get; } + + public string Title => Resources.SwitchOriginTitle; + + public IObservable<object> Done => SwitchFork.Where(value => value != null); + + public void Initialize(ILocalRepositoryModel sourceRepository, IRemoteRepositoryModel remoteRepository) + { + SourceRepository = sourceRepository; + DestinationRepository = remoteRepository; + } + + IObservable<object> OnSwitchFork(object o) + { + return repositoryForkService.SwitchRemotes(DestinationRepository, UpdateOrigin, AddUpstream, ResetMasterTracking); + } + + bool resetMasterTracking = true; + public bool ResetMasterTracking + { + get { return resetMasterTracking; } + set { this.RaiseAndSetIfChanged(ref resetMasterTracking, value); } + } + + bool addUpstream = true; + public bool AddUpstream + { + get { return addUpstream; } + set { this.RaiseAndSetIfChanged(ref addUpstream, value); } + } + + bool updateOrigin = true; + public bool UpdateOrigin + { + get { return updateOrigin; } + set { this.RaiseAndSetIfChanged(ref updateOrigin, value); } + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/ForkRepositoryViewModel.cs b/src/GitHub.App/ViewModels/Dialog/ForkRepositoryViewModel.cs new file mode 100644 index 0000000000..3dc1b932f8 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/ForkRepositoryViewModel.cs @@ -0,0 +1,72 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IForkRepositoryViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ForkRepositoryViewModel : PagedDialogViewModelBase, IForkRepositoryViewModel + { + readonly IForkRepositorySelectViewModel selectPage; + readonly IForkRepositorySwitchViewModel switchPage; + readonly IForkRepositoryExecuteViewModel executePage; + + [ImportingConstructor] + public ForkRepositoryViewModel( + IForkRepositorySelectViewModel selectPage, + IForkRepositorySwitchViewModel switchPage, + IForkRepositoryExecuteViewModel executePage) + { + this.selectPage = selectPage; + this.executePage = executePage; + this.switchPage = switchPage; + + Completed = ReactiveCommand.Create(); + + selectPage.SwitchOrigin.Subscribe(x => ShowSwitchRepositoryPath((IRemoteRepositoryModel)x)); + selectPage.Done.Subscribe(x => ShowExecutePage((IAccount)x).Forget()); + executePage.Back.Subscribe(x => ShowSelectPage().Forget()); + } + + public ILocalRepositoryModel Repository { get; private set; } + + public IConnection Connection { get; private set; } + + private ReactiveCommand<object> Completed { get; } + + public override IObservable<object> Done => executePage.Done; + + public async Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) + { + Repository = repository; + Connection = connection; + await ShowSelectPage(); + } + + async Task ShowSelectPage() + { + await selectPage.InitializeAsync(Repository, Connection); + Content = selectPage; + } + + async Task ShowExecutePage(IAccount account) + { + await executePage.InitializeAsync(Repository, account, Connection); + Content = executePage; + } + + void ShowSwitchRepositoryPath(IRemoteRepositoryModel remoteRepository) + { + switchPage.Initialize(Repository, remoteRepository); + Content = switchPage; + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/GistCreationViewModel.cs b/src/GitHub.App/ViewModels/Dialog/GistCreationViewModel.cs new file mode 100644 index 0000000000..a32531dc35 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/GistCreationViewModel.cs @@ -0,0 +1,136 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using Octokit; +using ReactiveUI; +using Serilog; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IGistCreationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GistCreationViewModel : ViewModelBase, IGistCreationViewModel + { + static readonly ILogger log = LogManager.ForContext<GistCreationViewModel>(); + + readonly IModelServiceFactory modelServiceFactory; + readonly IGistPublishService gistPublishService; + readonly INotificationService notificationService; + readonly IUsageTracker usageTracker; + IApiClient apiClient; + ObservableAsPropertyHelper<IAccount> account; + + [ImportingConstructor] + public GistCreationViewModel( + IModelServiceFactory modelServiceFactory, + ISelectedTextProvider selectedTextProvider, + IGistPublishService gistPublishService, + INotificationService notificationService, + IUsageTracker usageTracker) + { + Guard.ArgumentNotNull(selectedTextProvider, nameof(selectedTextProvider)); + Guard.ArgumentNotNull(gistPublishService, nameof(gistPublishService)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + + this.modelServiceFactory = modelServiceFactory; + this.gistPublishService = gistPublishService; + this.notificationService = notificationService; + this.usageTracker = usageTracker; + + FileName = VisualStudio.Services.GetFileNameFromActiveDocument() ?? Resources.DefaultGistFileName; + SelectedText = selectedTextProvider.GetSelectedText(); + + var canCreateGist = this.WhenAny( + x => x.FileName, + fileName => !String.IsNullOrEmpty(fileName.Value)); + + CreateGist = ReactiveCommand.CreateAsyncObservable(canCreateGist, OnCreateGist); + } + + public async Task InitializeAsync(IConnection connection) + { + var modelService = await modelServiceFactory.CreateAsync(connection); + apiClient = modelService.ApiClient; + + // This class is only instantiated after we are logged into to a github account, so we should be safe to grab the first one here as the defaut. + account = modelService.GetAccounts() + .FirstAsync() + .Select(a => a.First()) + .ObserveOn(RxApp.MainThreadScheduler) + .ToProperty(this, vm => vm.Account); + } + + IObservable<Gist> OnCreateGist(object unused) + { + var newGist = new NewGist + { + Description = Description, + Public = !IsPrivate + }; + + newGist.Files.Add(FileName, SelectedText); + + return gistPublishService.PublishGist(apiClient, newGist) + .Do(_ => usageTracker.IncrementCounter(x => x.NumberOfGists).Forget()) + .Catch<Gist, Exception>(ex => + { + if (!ex.IsCriticalException()) + { + log.Error(ex, "Error Creating Gist"); + var error = StandardUserErrors.GetUserFriendlyErrorMessage(ex, ErrorType.GistCreateFailed); + notificationService.ShowError(error); + } + return Observable.Return<Gist>(null); + }); + } + + public string Title => Resources.CreateGistTitle; + + public IReactiveCommand<Gist> CreateGist { get; } + + public IAccount Account + { + get { return account.Value; } + } + + bool isPrivate; + public bool IsPrivate + { + get { return isPrivate; } + set { this.RaiseAndSetIfChanged(ref isPrivate, value); } + } + + string description; + public string Description + { + get { return description; } + set { this.RaiseAndSetIfChanged(ref description, value); } + } + + string selectedText; + public string SelectedText + { + get { return selectedText; } + set { this.RaiseAndSetIfChanged(ref selectedText, value); } + } + + string fileName; + public string FileName + { + get { return fileName; } + set { this.RaiseAndSetIfChanged(ref fileName, value); } + } + + public IObservable<object> Done => CreateGist.Where(x => x != null); + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs b/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs new file mode 100644 index 0000000000..28086211c1 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs @@ -0,0 +1,101 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Represents the top-level view model for the GitHub dialog. + /// </summary> + [Export(typeof(IGitHubDialogWindowViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class GitHubDialogWindowViewModel : ViewModelBase, IGitHubDialogWindowViewModel + { + readonly IViewViewModelFactory factory; + readonly Lazy<IConnectionManager> connectionManager; + IDialogContentViewModel content; + Subject<object> done = new Subject<object>(); + IDisposable subscription; + + [ImportingConstructor] + public GitHubDialogWindowViewModel( + IViewViewModelFactory factory, + Lazy<IConnectionManager> connectionManager) + { + this.factory = factory; + this.connectionManager = connectionManager; + } + + /// <inheritdoc/> + public IDialogContentViewModel Content + { + get { return content; } + private set { this.RaiseAndSetIfChanged(ref content, value); } + } + + /// <inheritdoc/> + public IObservable<object> Done => done; + + /// <inheritdoc/> + public void Dispose() + { + subscription?.Dispose(); + subscription = null; + } + + /// <inheritdoc/> + public void Start(IDialogContentViewModel viewModel) + { + subscription?.Dispose(); + Content = viewModel; + subscription = viewModel.Done?.Subscribe(done); + } + + /// <inheritdoc/> + public async Task StartWithConnection<T>(T viewModel) + where T : IDialogContentViewModel, IConnectionInitializedViewModel + { + var connections = await connectionManager.Value.GetLoadedConnections(); + var connection = connections.FirstOrDefault(x => x.IsLoggedIn); + + if (connection == null) + { + var login = CreateLoginViewModel(); + + subscription = login.Done.Take(1).Subscribe(async x => + { + var newConnection = (IConnection)x; + + if (newConnection != null) + { + await viewModel.InitializeAsync(newConnection); + Start(viewModel); + } + else + { + done.OnNext(null); + } + }); + + Content = login; + } + else + { + await viewModel.InitializeAsync(connection); + Start(viewModel); + } + } + + ILoginViewModel CreateLoginViewModel() + { + return factory.CreateViewModel<ILoginViewModel>(); + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/Login2FaViewModel.cs b/src/GitHub.App/ViewModels/Dialog/Login2FaViewModel.cs new file mode 100644 index 0000000000..5d5492769a --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/Login2FaViewModel.cs @@ -0,0 +1,160 @@ +using System; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Reactive.Linq; +using GitHub.App; +using GitHub.Authentication; +using GitHub.Extensions; +using GitHub.Info; +using GitHub.Services; +using GitHub.Validation; +using Octokit; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(ILogin2FaViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class Login2FaViewModel : ViewModelBase, ILogin2FaViewModel + { + bool isAuthenticationCodeSent; + bool invalidAuthenticationCode; + string authenticationCode; + TwoFactorType twoFactorType; + bool isBusy; + readonly ObservableAsPropertyHelper<string> description; + readonly ObservableAsPropertyHelper<bool> isSms; + readonly ObservableAsPropertyHelper<bool> showErrorMessage; + + [ImportingConstructor] + public Login2FaViewModel(IVisualStudioBrowser browser) + { + Guard.ArgumentNotNull(browser, nameof(browser)); + + var canVerify = this.WhenAny( + x => x.AuthenticationCode, + x => x.IsBusy, + (code, busy) => !string.IsNullOrEmpty(code.Value) && code.Value.Length == 6 && !busy.Value); + + OkCommand = ReactiveCommand.Create(canVerify); + NavigateLearnMore = ReactiveCommand.Create(); + NavigateLearnMore.Subscribe(x => browser.OpenUrl(GitHubUrls.TwoFactorLearnMore)); + //TODO: ShowHelpCommand.Subscribe(x => browser.OpenUrl(twoFactorHelpUri)); + ResendCodeCommand = ReactiveCommand.Create(); + + showErrorMessage = this.WhenAny( + x => x.IsAuthenticationCodeSent, + x => x.InvalidAuthenticationCode, + (authSent, invalid) => invalid.Value && !authSent.Value) + .ToProperty(this, x => x.ShowErrorMessage); + + description = this.WhenAny(x => x.TwoFactorType, x => x.Value) + .Select(type => + { + switch (type) + { + case TwoFactorType.Sms: + return Resources.TwoFactorSms; + case TwoFactorType.AuthenticatorApp: + return Resources.TwoFactorApp; + case TwoFactorType.Unknown: + return Resources.TwoFactorUnknown; + + default: + return null; + } + }) + .ToProperty(this, x => x.Description); + + isSms = this.WhenAny(x => x.TwoFactorType, x => x.Value) + .Select(factorType => factorType == TwoFactorType.Sms) + .ToProperty(this, x => x.IsSms); + } + + public IObservable<TwoFactorChallengeResult> Show(UserError userError) + { + Guard.ArgumentNotNull(userError, nameof(userError)); + + IsBusy = false; + var error = userError as TwoFactorRequiredUserError; + + if (error == null) + { + throw new GitHubLogicException( + String.Format( + CultureInfo.InvariantCulture, + "The user error is '{0}' not a TwoFactorRequiredUserError", + userError)); + } + + InvalidAuthenticationCode = error.RetryFailed; + IsAuthenticationCodeSent = false; + TwoFactorType = error.TwoFactorType; + var ok = OkCommand + .Do(_ => IsBusy = true) + .Select(_ => AuthenticationCode == null + ? null + : new TwoFactorChallengeResult(AuthenticationCode)); + var resend = ResendCodeCommand.Select(_ => RecoveryOptionResult.RetryOperation) + .Select(_ => TwoFactorChallengeResult.RequestResendCode) + .Do(_ => IsAuthenticationCodeSent = true); + var cancel = this.WhenAnyValue(x => x.TwoFactorType) + .Skip(1) + .Where(x => x == TwoFactorType.None) + .Select(_ => default(TwoFactorChallengeResult)); + return Observable.Merge(ok, cancel, resend).Take(1); + } + + public string Title => Resources.TwoFactorTitle; + + public TwoFactorType TwoFactorType + { + get { return twoFactorType; } + private set { this.RaiseAndSetIfChanged(ref twoFactorType, value); } + } + + public bool IsBusy + { + get { return isBusy; } + private set { this.RaiseAndSetIfChanged(ref isBusy, value); } + } + + public bool IsSms { get { return isSms.Value; } } + + public bool IsAuthenticationCodeSent + { + get { return isAuthenticationCodeSent; } + private set { this.RaiseAndSetIfChanged(ref isAuthenticationCodeSent, value); } + } + + public string Description + { + get { return description.Value; } + } + + public string AuthenticationCode + { + get { return authenticationCode; } + set { this.RaiseAndSetIfChanged(ref authenticationCode, value); } + } + + public IObservable<object> Done => null; + public ReactiveCommand<object> OkCommand { get; private set; } + public ReactiveCommand<object> NavigateLearnMore { get; private set; } + public ReactiveCommand<object> ResendCodeCommand { get; private set; } + public ReactivePropertyValidator AuthenticationCodeValidator { get; private set; } + + public bool InvalidAuthenticationCode + { + get { return invalidAuthenticationCode; } + private set { this.RaiseAndSetIfChanged(ref invalidAuthenticationCode, value); } + } + + public bool ShowErrorMessage + { + get { return showErrorMessage.Value; } + } + + public void Cancel() => TwoFactorType = TwoFactorType.None; + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs new file mode 100644 index 0000000000..2b071a9f32 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using GitHub.App; +using GitHub.Primitives; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(ILoginCredentialsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class LoginCredentialsViewModel : ViewModelBase, ILoginCredentialsViewModel + { + [ImportingConstructor] + public LoginCredentialsViewModel( + IConnectionManager connectionManager, + ILoginToGitHubViewModel loginToGitHubViewModel, + ILoginToGitHubForEnterpriseViewModel loginToGitHubEnterpriseViewModel) + { + ConnectionManager = connectionManager; + GitHubLogin = loginToGitHubViewModel; + EnterpriseLogin = loginToGitHubEnterpriseViewModel; + + isLoginInProgress = this.WhenAny( + x => x.GitHubLogin.IsLoggingIn, + x => x.EnterpriseLogin.IsLoggingIn, + (x, y) => x.Value || y.Value + ).ToProperty(this, vm => vm.IsLoginInProgress); + + UpdateLoginMode(); + connectionManager.Connections.CollectionChanged += (_, __) => UpdateLoginMode(); + + Done = Observable.Merge( + loginToGitHubViewModel.Login, + loginToGitHubViewModel.LoginViaOAuth, + loginToGitHubEnterpriseViewModel.Login, + loginToGitHubEnterpriseViewModel.LoginViaOAuth) + .Where(x => x != null); + } + + public string Title => Resources.LoginTitle; + public ILoginToGitHubViewModel GitHubLogin { get; } + public ILoginToGitHubForEnterpriseViewModel EnterpriseLogin { get; } + public IConnectionManager ConnectionManager { get; } + + LoginMode loginMode; + public LoginMode LoginMode + { + get { return loginMode; } + private set { this.RaiseAndSetIfChanged(ref loginMode, value); } + } + + readonly ObservableAsPropertyHelper<bool> isLoginInProgress; + public bool IsLoginInProgress { get { return isLoginInProgress.Value; } } + + public IObservable<object> 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/LoginTabViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs new file mode 100644 index 0000000000..b0948176c4 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs @@ -0,0 +1,214 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using GitHub.App; +using GitHub.Authentication; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.Info; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Validation; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels.Dialog +{ + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + public abstract class LoginTabViewModel : ReactiveObject + { + static readonly ILogger log = LogManager.ForContext<LoginTabViewModel>(); + CancellationTokenSource oauthCancel; + + protected LoginTabViewModel( + IConnectionManager connectionManager, + IVisualStudioBrowser browser) + { + Guard.ArgumentNotNull(connectionManager, nameof(connectionManager)); + Guard.ArgumentNotNull(browser, nameof(browser)); + + ConnectionManager = connectionManager; + + UsernameOrEmailValidator = ReactivePropertyValidator.For(this, x => x.UsernameOrEmail) + .IfNullOrEmpty(Resources.UsernameOrEmailValidatorEmpty) + .IfMatch(@"\s", Resources.UsernameOrEmailValidatorSpaces); + + PasswordValidator = ReactivePropertyValidator.For(this, x => x.Password) + .IfNullOrEmpty(Resources.PasswordValidatorEmpty); + + canLogin = this.WhenAny( + x => x.UsernameOrEmailValidator.ValidationResult.IsValid, + x => x.PasswordValidator.ValidationResult.IsValid, + (x, y) => x.Value && y.Value).ToProperty(this, x => x.CanLogin); + + Login = ReactiveCommand.CreateAsyncTask(this.WhenAny(x => x.CanLogin, x => x.Value), LogIn); + Login.ThrownExceptions.Subscribe(HandleError); + isLoggingIn = Login.IsExecuting.ToProperty(this, x => x.IsLoggingIn); + + LoginViaOAuth = ReactiveCommand.CreateAsyncTask( + this.WhenAnyValue(x => x.IsLoggingIn, x => !x), + LogInViaOAuth); + LoginViaOAuth.ThrownExceptions.Subscribe(HandleError); + + Reset = ReactiveCommand.CreateAsyncTask(_ => Clear()); + + NavigateForgotPassword = new RecoveryCommand(Resources.ForgotPasswordLink, _ => + { + browser.OpenUrl(new Uri(BaseUri, GitHubUrls.ForgotPasswordPath)); + return RecoveryOptionResult.RetryOperation; + }); + + SignUp = ReactiveCommand.CreateAsyncObservable(_ => + { + browser.OpenUrl(GitHubUrls.Plans); + return Observable.Return(Unit.Default); + }); + } + protected IConnectionManager ConnectionManager { get; } + protected abstract Uri BaseUri { get; } + public IReactiveCommand<Unit> SignUp { get; } + + public IReactiveCommand<IConnection> Login { get; } + public IReactiveCommand<IConnection> LoginViaOAuth { get; } + public IReactiveCommand<Unit> Reset { get; } + public IRecoveryCommand NavigateForgotPassword { get; } + + string usernameOrEmail; + public string UsernameOrEmail + { + get { return usernameOrEmail; } + set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); } + } + + ReactivePropertyValidator usernameOrEmailValidator; + public ReactivePropertyValidator UsernameOrEmailValidator + { + get { return usernameOrEmailValidator; } + private set { this.RaiseAndSetIfChanged(ref usernameOrEmailValidator, value); } + } + + string password; + public string Password + { + get { return password; } + set { this.RaiseAndSetIfChanged(ref password, value); } + } + + ReactivePropertyValidator passwordValidator; + public ReactivePropertyValidator PasswordValidator + { + get { return passwordValidator; } + private set { this.RaiseAndSetIfChanged(ref passwordValidator, value); } + } + + readonly ObservableAsPropertyHelper<bool> isLoggingIn; + public bool IsLoggingIn + { + get { return isLoggingIn.Value; } + } + + protected ObservableAsPropertyHelper<bool> canLogin; + public bool CanLogin + { + get { return canLogin.Value; } + } + + protected ObservableAsPropertyHelper<bool> canSsoLogin; + public bool CanSsoLogin + { + get { return canSsoLogin.Value; } + } + + UserError error; + public UserError Error + { + get { return error; } + set { this.RaiseAndSetIfChanged(ref error, value); } + } + + public void Deactivated() => oauthCancel?.Cancel(); + + protected abstract Task<IConnection> LogIn(object args); + protected abstract Task<IConnection> LogInViaOAuth(object args); + + protected async Task<IConnection> LogInToHost(HostAddress hostAddress) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + + if (await ConnectionManager.GetConnection(hostAddress) != null) + { + await ConnectionManager.LogOut(hostAddress); + } + + return await ConnectionManager.LogIn(hostAddress, UsernameOrEmail, Password); + } + + protected async Task<IConnection> LoginToHostViaOAuth(HostAddress address) + { + oauthCancel = new CancellationTokenSource(); + + if (await ConnectionManager.GetConnection(address) != null) + { + await ConnectionManager.LogOut(address); + } + + try + { + return await ConnectionManager.LogInViaOAuth(address, oauthCancel.Token); + } + finally + { + oauthCancel.Dispose(); + oauthCancel = null; + } + } + + async Task Clear() + { + UsernameOrEmail = null; + Password = null; + await UsernameOrEmailValidator.ResetAsync(); + await PasswordValidator.ResetAsync(); + await ResetValidation(); + } + + protected virtual Task ResetValidation() + { + // noop + return Task.FromResult(0); + } + + void HandleError(Exception ex) + { + // The Windows ERROR_OPERATION_ABORTED error code. + const int operationAborted = 995; + + if (ex is HttpListenerException && + ((HttpListenerException)ex).ErrorCode == operationAborted) + { + // An Oauth listener was aborted, probably because the user closed the login + // dialog or switched between the GitHub and Enterprise tabs while listening + // for an Oauth callbacl. + return; + } + + if (ex.IsCriticalException()) return; + + log.Error(ex, "Error logging into '{BaseUri}' as '{UsernameOrEmail}'", BaseUri, UsernameOrEmail); + if (ex is Octokit.ForbiddenException) + { + Error = new UserError(Resources.LoginFailedForbiddenMessage, ex.Message); + } + else + { + Error = new UserError(ex.Message); + } + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModel.cs new file mode 100644 index 0000000000..acba78ca12 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModel.cs @@ -0,0 +1,184 @@ +using System; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Info; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Validation; +using Octokit; +using ReactiveUI; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(ILoginToGitHubForEnterpriseViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class LoginToGitHubForEnterpriseViewModel : LoginTabViewModel, ILoginToGitHubForEnterpriseViewModel + { + readonly IEnterpriseCapabilitiesService enterpriseCapabilities; + + [ImportingConstructor] + public LoginToGitHubForEnterpriseViewModel( + IConnectionManager connectionManager, + IEnterpriseCapabilitiesService enterpriseCapabilities, + IVisualStudioBrowser browser) + : this(connectionManager, enterpriseCapabilities, browser, Scheduler.Default) + { + } + + public LoginToGitHubForEnterpriseViewModel( + IConnectionManager connectionManager, + IEnterpriseCapabilitiesService enterpriseCapabilities, + IVisualStudioBrowser browser, + IScheduler scheduler) + : base(connectionManager, browser) + { + Guard.ArgumentNotNull(connectionManager, nameof(connectionManager)); + Guard.ArgumentNotNull(enterpriseCapabilities, nameof(enterpriseCapabilities)); + Guard.ArgumentNotNull(browser, nameof(browser)); + + this.enterpriseCapabilities = enterpriseCapabilities; + + EnterpriseUrlValidator = ReactivePropertyValidator.For(this, x => x.EnterpriseUrl) + .IfNullOrEmpty(Resources.EnterpriseUrlValidatorEmpty) + .IfNotUri(Resources.EnterpriseUrlValidatorInvalid) + .IfGitHubDotComHost(Resources.EnterpriseUrlValidatorNotAGitHubHost); + + canLogin = this.WhenAnyValue( + x => x.UsernameOrEmailValidator.ValidationResult.IsValid, + x => x.PasswordValidator.ValidationResult.IsValid, + x => x.SupportedLoginMethods, + (x, y, z) => (x || (z & EnterpriseLoginMethods.Token) != 0) && y) + .ToProperty(this, x => x.CanLogin); + + canSsoLogin = this.WhenAnyValue( + x => x.EnterpriseUrlValidator.ValidationResult.IsValid) + .ToProperty(this, x => x.CanLogin); + + this.WhenAnyValue(x => x.EnterpriseUrl, x => x.EnterpriseUrlValidator.ValidationResult) + .Throttle(TimeSpan.FromMilliseconds(500), scheduler) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => UpdatingProbeStatus = EnterpriseUrlChanged(x.Item1, x.Item2?.IsValid ?? false)); + + NavigateLearnMore = ReactiveCommand.CreateAsyncObservable(_ => + { + browser.OpenUrl(GitHubUrls.LearnMore); + return Observable.Return(Unit.Default); + }); + } + + protected override Task<IConnection> LogIn(object args) + { + if (string.IsNullOrWhiteSpace(UsernameOrEmail)) + { + return LogInWithToken(HostAddress.Create(EnterpriseUrl), Password); + } + else + { + return LogInToHost(HostAddress.Create(EnterpriseUrl)); + } + } + + protected override Task<IConnection> LogInViaOAuth(object args) + { + return LoginToHostViaOAuth(HostAddress.Create(EnterpriseUrl)); + } + + string enterpriseUrl; + public string EnterpriseUrl + { + get { return enterpriseUrl; } + set { this.RaiseAndSetIfChanged(ref enterpriseUrl, value); } + } + + EnterpriseProbeStatus probeStatus; + public EnterpriseProbeStatus ProbeStatus + { + get { return probeStatus; } + private set { this.RaiseAndSetIfChanged(ref probeStatus, value); } + } + + EnterpriseLoginMethods? supportedLoginMethods; + public EnterpriseLoginMethods? SupportedLoginMethods + { + get { return supportedLoginMethods; } + private set { this.RaiseAndSetIfChanged(ref supportedLoginMethods, value); } + } + + public ReactivePropertyValidator EnterpriseUrlValidator { get; } + + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")] + protected override Uri BaseUri => new UriBuilder(EnterpriseUrl).Uri; + + public IReactiveCommand<Unit> NavigateLearnMore + { + get; + } + + public Task UpdatingProbeStatus + { + get; + private set; + } + + protected override async Task ResetValidation() + { + EnterpriseUrl = null; + await EnterpriseUrlValidator.ResetAsync(); + } + + async Task EnterpriseUrlChanged(string url, bool valid) + { + if (!valid) + { + // The EnterpriseUrlValidator will display an adorner in this case so don't show anything. + ProbeStatus = EnterpriseProbeStatus.None; + SupportedLoginMethods = null; + return; + } + + var enterpriseInstance = false; + var loginMethods = (EnterpriseLoginMethods?)null; + var uri = new UriBuilder(url).Uri; + + ProbeStatus = EnterpriseProbeStatus.Checking; + + if (await enterpriseCapabilities.Probe(uri) == EnterpriseProbeResult.Ok) + { + loginMethods = await enterpriseCapabilities.ProbeLoginMethods(uri); + enterpriseInstance = true; + } + + if (url == EnterpriseUrl) + { + if ((loginMethods & EnterpriseLoginMethods.Token) != 0 && + (loginMethods & EnterpriseLoginMethods.UsernameAndPassword) != 0) + { + loginMethods &= ~EnterpriseLoginMethods.Token; + } + + ProbeStatus = enterpriseInstance ? EnterpriseProbeStatus.Valid : EnterpriseProbeStatus.Invalid; + SupportedLoginMethods = loginMethods; + } + } + + async Task<IConnection> LogInWithToken(HostAddress hostAddress, string token) + { + Guard.ArgumentNotNull(hostAddress, nameof(hostAddress)); + + if (await ConnectionManager.GetConnection(hostAddress) != null) + { + await ConnectionManager.LogOut(hostAddress); + } + + return await ConnectionManager.LogInWithToken(hostAddress, token); + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/LoginToGitHubViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginToGitHubViewModel.cs new file mode 100644 index 0000000000..4790ad04d9 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/LoginToGitHubViewModel.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Authentication; +using GitHub.Info; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(ILoginToGitHubViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class LoginToGitHubViewModel : LoginTabViewModel, ILoginToGitHubViewModel + { + [ImportingConstructor] + public LoginToGitHubViewModel( + IConnectionManager connectionManager, + IVisualStudioBrowser browser) + : base(connectionManager, browser) + { + BaseUri = HostAddress.GitHubDotComHostAddress.WebUri; + + NavigatePricing = ReactiveCommand.CreateAsyncObservable(_ => + { + browser.OpenUrl(GitHubUrls.Pricing); + return Observable.Return(Unit.Default); + + }); + } + + public IReactiveCommand<Unit> NavigatePricing { get; } + + protected override Uri BaseUri { get; } + + protected override Task<IConnection> LogIn(object args) + { + return LogInToHost(HostAddress.GitHubDotComHostAddress); + } + + protected override Task<IConnection> LogInViaOAuth(object args) + { + return LoginToHostViaOAuth(HostAddress.GitHubDotComHostAddress); + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/Dialog/LoginViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginViewModel.cs new file mode 100644 index 0000000000..2bfaf0d59b --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/LoginViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using GitHub.Authentication; +using Octokit; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Represents the Login dialog content. + /// </summary> + [Export(typeof(ILoginViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class LoginViewModel : PagedDialogViewModelBase, ILoginViewModel + { + [ImportingConstructor] + public LoginViewModel( + ILoginCredentialsViewModel credentials, + ILogin2FaViewModel twoFactor, + IDelegatingTwoFactorChallengeHandler twoFactorHandler) + { + twoFactorHandler.SetViewModel(twoFactor); + + Content = credentials; + Done = credentials.Done; + + twoFactor.WhenAnyValue(x => x.TwoFactorType) + .Subscribe(x => + { + Content = x == TwoFactorType.None ? + (IDialogContentViewModel)credentials : + twoFactor; + }); + } + + /// <inheritdoc/> + public override IObservable<object> Done { get; } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/PagedDialogViewModelBase.cs b/src/GitHub.App/ViewModels/Dialog/PagedDialogViewModelBase.cs new file mode 100644 index 0000000000..621d747eab --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/PagedDialogViewModelBase.cs @@ -0,0 +1,38 @@ +using System; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Base class for view models representing a multi-page dialog. + /// </summary> + public abstract class PagedDialogViewModelBase : ViewModelBase, IDialogContentViewModel + { + readonly ObservableAsPropertyHelper<string> title; + IDialogContentViewModel content; + + /// <summary> + /// Initializes a new instance of the <see cref="PagedDialogViewModelBase"/> class. + /// </summary> + protected PagedDialogViewModelBase() + { + title = this.WhenAny(x => x.Content, x => x.Value?.Title ?? "GitHub") + .ToProperty(this, x => x.Title); + } + + /// <summary> + /// Gets the current page being displayed in the dialog. + /// </summary> + public IDialogContentViewModel Content + { + get { return content; } + protected set { this.RaiseAndSetIfChanged(ref content, value); } + } + + /// <inheritdoc/> + public abstract IObservable<object> Done { get; } + + /// <inheritdoc/> + public string Title => title.Value; + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/RepositoryCloneViewModel.cs b/src/GitHub.App/ViewModels/Dialog/RepositoryCloneViewModel.cs new file mode 100644 index 0000000000..f959091ee0 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/RepositoryCloneViewModel.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using GitHub.App; +using GitHub.Collections; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using GitHub.Validation; +using ReactiveUI; +using Rothko; +using Serilog; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IRepositoryCloneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class RepositoryCloneViewModel : ViewModelBase, IRepositoryCloneViewModel + { + static readonly ILogger log = LogManager.ForContext<RepositoryCloneViewModel>(); + + readonly IModelServiceFactory modelServiceFactory; + readonly IOperatingSystem operatingSystem; + readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create(); + bool noRepositoriesFound; + readonly ObservableAsPropertyHelper<bool> canClone; + string baseRepositoryPath; + bool loadingFailed; + + [ImportingConstructor] + public RepositoryCloneViewModel( + IModelServiceFactory modelServiceFactory, + IRepositoryCloneService cloneService, + IOperatingSystem operatingSystem) + { + Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + Guard.ArgumentNotNull(cloneService, nameof(cloneService)); + Guard.ArgumentNotNull(operatingSystem, nameof(operatingSystem)); + + this.modelServiceFactory = modelServiceFactory; + this.operatingSystem = operatingSystem; + + Repositories = new TrackingCollection<IRemoteRepositoryModel>(); + repositories.ProcessingDelay = TimeSpan.Zero; + repositories.Comparer = OrderedComparer<IRemoteRepositoryModel>.OrderBy(x => x.Owner).ThenBy(x => x.Name).Compare; + repositories.Filter = FilterRepository; + repositories.NewerComparer = OrderedComparer<IRemoteRepositoryModel>.OrderByDescending(x => x.UpdatedAt).Compare; + + filterTextIsEnabled = this.WhenAny(x => x.IsBusy, + loading => loading.Value || repositories.UnfilteredCount > 0 && !LoadingFailed) + .ToProperty(this, x => x.FilterTextIsEnabled); + + this.WhenAny( + x => x.repositories.UnfilteredCount, + x => x.IsBusy, + x => x.LoadingFailed, + (unfilteredCount, loading, failed) => + { + if (loading.Value) + return false; + + if (failed.Value) + return false; + + return unfilteredCount.Value == 0; + }) + .Subscribe(x => + { + NoRepositoriesFound = x; + }); + + this.WhenAny(x => x.FilterText, x => x.Value) + .DistinctUntilChanged(StringComparer.OrdinalIgnoreCase) + .Throttle(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler) + .Subscribe(_ => repositories.Filter = FilterRepository); + + 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); + Done = CloneCommand.Select(_ => new CloneDialogResult(BaseRepositoryPath, SelectedRepository)); + + browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog()); + this.WhenAny(x => x.BaseRepositoryPathValidator.ValidationResult, x => x.Value) + .Subscribe(); + BaseRepositoryPath = cloneService.DefaultClonePath; + NoRepositoriesFound = true; + } + + public async Task InitializeAsync(IConnection connection) + { + var modelService = await modelServiceFactory.CreateAsync(connection); + + Title = string.Format(CultureInfo.CurrentCulture, Resources.CloneTitle, connection.HostAddress.Title); + + IsBusy = true; + modelService.GetRepositories(repositories); + repositories.OriginalCompleted + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe( + _ => { } + , ex => + { + LoadingFailed = true; + IsBusy = false; + log.Error(ex, "Error while loading repositories"); + }, + () => IsBusy = false + ); + repositories.Subscribe(); + } + + bool FilterRepository(IRemoteRepositoryModel repo, int position, IList<IRemoteRepositoryModel> list) + { + Guard.ArgumentNotNull(repo, nameof(repo)); + Guard.ArgumentNotNull(list, nameof(list)); + + if (string.IsNullOrWhiteSpace(FilterText)) + return true; + + // Not matching on NameWithOwner here since that's already been filtered on by the selected account + return repo.Name.IndexOf(FilterText ?? "", StringComparison.OrdinalIgnoreCase) != -1; + } + + bool IsAlreadyRepoAtPath(string path) + { + Guard.ArgumentNotEmptyString(path, nameof(path)); + + bool isAlreadyRepoAtPath = false; + + if (SelectedRepository != null) + { + string potentialPath = Path.Combine(path, SelectedRepository.Name); + isAlreadyRepoAtPath = operatingSystem.Directory.Exists(potentialPath); + } + + return isAlreadyRepoAtPath; + } + + IObservable<Unit> 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. {@Repository}", + new { localBaseRepositoryPath, BaseRepositoryPath, directory }); + } + }, RxApp.MainThreadScheduler); + } + + /// <summary> + /// Gets the title for the dialog. + /// </summary> + public string Title { get; private set; } + + /// <summary> + /// Path to clone repositories into + /// </summary> + public string BaseRepositoryPath + { + get { return baseRepositoryPath; } + set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, value); } + } + + /// <summary> + /// Signals that the user clicked the clone button. + /// </summary> + public IReactiveCommand<object> CloneCommand { get; private set; } + + bool isBusy; + public bool IsBusy + { + get { return isBusy; } + private set { this.RaiseAndSetIfChanged(ref isBusy, value); } + } + + TrackingCollection<IRemoteRepositoryModel> repositories; + public ObservableCollection<IRemoteRepositoryModel> Repositories + { + get { return repositories; } + private set { this.RaiseAndSetIfChanged(ref repositories, (TrackingCollection<IRemoteRepositoryModel>)value); } + } + + IRepositoryModel selectedRepository; + /// <summary> + /// Selected repository to clone + /// </summary> + public IRepositoryModel SelectedRepository + { + get { return selectedRepository; } + set { this.RaiseAndSetIfChanged(ref selectedRepository, value); } + } + + readonly ObservableAsPropertyHelper<bool> filterTextIsEnabled; + /// <summary> + /// True if there are repositories (otherwise no point in filtering) + /// </summary> + public bool FilterTextIsEnabled { get { return filterTextIsEnabled.Value; } } + + string filterText; + /// <summary> + /// User text to filter the repositories list + /// </summary> + public string FilterText + { + get { return filterText; } + set { this.RaiseAndSetIfChanged(ref filterText, value); } + } + + public bool LoadingFailed + { + get { return loadingFailed; } + private set { this.RaiseAndSetIfChanged(ref loadingFailed, value); } + } + + public bool NoRepositoriesFound + { + get { return noRepositoriesFound; } + private set { this.RaiseAndSetIfChanged(ref noRepositoriesFound, value); } + } + + public ICommand BrowseForDirectory + { + get { return browseForDirectoryCommand; } + } + + public bool CanClone + { + get { return canClone.Value; } + } + + public ReactivePropertyValidator<string> BaseRepositoryPathValidator + { + get; + private set; + } + + public IObservable<object> Done { get; } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs b/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs new file mode 100644 index 0000000000..673a1147fc --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using GitHub.App; +using GitHub.Collections; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using GitHub.UserErrors; +using GitHub.Validation; +using Octokit; +using ReactiveUI; +using Rothko; +using Serilog; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.ViewModels.Dialog +{ + [Export(typeof(IRepositoryCreationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class RepositoryCreationViewModel : RepositoryFormViewModel, IRepositoryCreationViewModel + { + static readonly ILogger log = LogManager.ForContext<RepositoryCreationViewModel>(); + + readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create(); + readonly IModelServiceFactory modelServiceFactory; + readonly IRepositoryCreationService repositoryCreationService; + readonly ObservableAsPropertyHelper<bool> isCreating; + readonly ObservableAsPropertyHelper<bool> canKeepPrivate; + readonly IOperatingSystem operatingSystem; + readonly IUsageTracker usageTracker; + ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts; + IModelService modelService; + + [ImportingConstructor] + public RepositoryCreationViewModel( + IModelServiceFactory modelServiceFactory, + IOperatingSystem operatingSystem, + IRepositoryCreationService repositoryCreationService, + IUsageTracker usageTracker) + { + Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + Guard.ArgumentNotNull(operatingSystem, nameof(operatingSystem)); + Guard.ArgumentNotNull(repositoryCreationService, nameof(repositoryCreationService)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + + this.modelServiceFactory = modelServiceFactory; + this.operatingSystem = operatingSystem; + this.repositoryCreationService = repositoryCreationService; + this.usageTracker = usageTracker; + + SelectedGitIgnoreTemplate = GitIgnoreItem.None; + SelectedLicense = LicenseItem.None; + + browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog()); + + BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(this.WhenAny(x => x.BaseRepositoryPath, x => x.Value)) + .IfNullOrEmpty(Resources.RepositoryCreationClonePathEmpty) + .IfTrue(x => x.Length > 200, Resources.RepositoryCreationClonePathTooLong) + .IfContainsInvalidPathChars(Resources.RepositoryCreationClonePathInvalidCharacters) + .IfPathNotRooted(Resources.RepositoryCreationClonePathInvalid); + + var nonNullRepositoryName = this.WhenAny( + x => x.RepositoryName, + x => x.BaseRepositoryPath, + (x, y) => x.Value) + .WhereNotNull(); + + RepositoryNameValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) + .IfNullOrEmpty(Resources.RepositoryNameValidatorEmpty) + .IfTrue(x => x.Length > 100, Resources.RepositoryNameValidatorTooLong) + .IfTrue(IsAlreadyRepoAtPath, Resources.RepositoryNameValidatorAlreadyExists); + + SafeRepositoryNameWarningValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) + .Add(repoName => + { + var parsedReference = GetSafeRepositoryName(repoName); + 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); + + BaseRepositoryPath = repositoryCreationService.DefaultClonePath; + } + + public string Title { get; private set; } + + string baseRepositoryPath; + /// <summary> + /// Path to clone repositories into + /// </summary> + public string BaseRepositoryPath + { + get { return baseRepositoryPath; } + set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, StripSurroundingQuotes(value)); } + } + + /// <summary> + /// Fires up a file dialog to select the directory to clone into + /// </summary> + public ICommand BrowseForDirectory { get { return browseForDirectoryCommand; } } + + /// <summary> + /// Is running the creation process + /// </summary> + public bool IsCreating { get { return isCreating.Value; } } + + /// <summary> + /// If the repo can be made private (depends on the user plan) + /// </summary> + public bool CanKeepPrivate { get { return canKeepPrivate.Value; } } + + IReadOnlyList<GitIgnoreItem> gitIgnoreTemplates; + public IReadOnlyList<GitIgnoreItem> GitIgnoreTemplates + { + get { return gitIgnoreTemplates; } + set { this.RaiseAndSetIfChanged(ref gitIgnoreTemplates, value); } + } + + IReadOnlyList<LicenseItem> licenses; + public IReadOnlyList<LicenseItem> Licenses + { + get { return licenses; } + set { this.RaiseAndSetIfChanged(ref licenses, value); } + } + + GitIgnoreItem selectedGitIgnoreTemplate; + public GitIgnoreItem SelectedGitIgnoreTemplate + { + get { return selectedGitIgnoreTemplate; } + set { this.RaiseAndSetIfChanged(ref selectedGitIgnoreTemplate, value ?? GitIgnoreItem.None); } + } + + LicenseItem selectedLicense; + public LicenseItem SelectedLicense + { + get { return selectedLicense; } + set { this.RaiseAndSetIfChanged(ref selectedLicense, value ?? LicenseItem.None); } + } + + /// <summary> + /// List of accounts (at least one) + /// </summary> + public IReadOnlyList<IAccount> Accounts { get { return accounts.Value; } } + + public ReactivePropertyValidator<string> BaseRepositoryPathValidator { get; private set; } + + /// <summary> + /// Fires off the process of creating the repository remotely and then cloning it locally + /// </summary> + public IReactiveCommand<Unit> CreateRepository { get; private set; } + + public IObservable<object> Done => CreateRepository.Select(_ => (object)null); + + public async Task InitializeAsync(IConnection connection) + { + modelService = await modelServiceFactory.CreateAsync(connection); + + Title = string.Format(CultureInfo.CurrentCulture, Resources.CreateTitle, connection.HostAddress.Title); + + accounts = modelService.GetAccounts() + .ObserveOn(RxApp.MainThreadScheduler) + .ToProperty(this, vm => vm.Accounts, initialValue: new ReadOnlyCollection<IAccount>(new IAccount[] { })); + + this.WhenAny(x => x.Accounts, x => x.Value) + .Select(accts => accts?.FirstOrDefault()) + .WhereNotNull() + .Subscribe(a => SelectedAccount = a); + + GitIgnoreTemplates = TrackingCollection.CreateListenerCollectionAndRun( + modelService.GetGitIgnoreTemplates(), + new[] { GitIgnoreItem.None }, + OrderedComparer<GitIgnoreItem>.OrderByDescending(item => GitIgnoreItem.IsRecommended(item.Name)).Compare, + x => + { + if (x.Name.Equals("VisualStudio", StringComparison.OrdinalIgnoreCase)) + SelectedGitIgnoreTemplate = x; + }); + + Licenses = TrackingCollection.CreateListenerCollectionAndRun( + modelService.GetLicenses(), + new[] { LicenseItem.None }, + OrderedComparer<LicenseItem>.OrderByDescending(item => LicenseItem.IsRecommended(item.Name)).Compare); + } + + protected override NewRepository GatherRepositoryInfo() + { + var gitHubRepository = base.GatherRepositoryInfo(); + + if (SelectedLicense != LicenseItem.None) + { + gitHubRepository.LicenseTemplate = SelectedLicense.Key; + gitHubRepository.AutoInit = true; + } + + if (SelectedGitIgnoreTemplate != GitIgnoreItem.None) + { + gitHubRepository.GitignoreTemplate = SelectedGitIgnoreTemplate.Name; + gitHubRepository.AutoInit = true; + } + return gitHubRepository; + } + + IObservable<Unit> 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 {@0}", + new { localBaseRepositoryPath, BaseRepositoryPath, directory }); + } + }, RxApp.MainThreadScheduler); + } + + bool IsAlreadyRepoAtPath(string potentialRepositoryName) + { + Guard.ArgumentNotNull(potentialRepositoryName, nameof(potentialRepositoryName)); + + bool isAlreadyRepoAtPath = false; + var validationResult = BaseRepositoryPathValidator.ValidationResult; + string safeRepositoryName = GetSafeRepositoryName(potentialRepositoryName); + if (validationResult != null && validationResult.IsValid) + { + if (Path.GetInvalidPathChars().Any(chr => BaseRepositoryPath.Contains(chr))) + return false; + string potentialPath = Path.Combine(BaseRepositoryPath, safeRepositoryName); + isAlreadyRepoAtPath = operatingSystem.Directory.Exists(potentialPath); + } + return isAlreadyRepoAtPath; + } + + IObservable<Unit> OnCreateRepository(object state) + { + var newRepository = GatherRepositoryInfo(); + + return repositoryCreationService.CreateRepository( + newRepository, + SelectedAccount, + BaseRepositoryPath, + modelService.ApiClient) + .Do(_ => usageTracker.IncrementCounter(x => x.NumberOfReposCreated).Forget()); + } + + ReactiveCommand<Unit> InitializeCreateRepositoryCommand() + { + var canCreate = this.WhenAny( + x => x.RepositoryNameValidator.ValidationResult.IsValid, + x => x.BaseRepositoryPathValidator.ValidationResult.IsValid, + (x, y) => x.Value && y.Value); + var createCommand = ReactiveCommand.CreateAsyncObservable(canCreate, OnCreateRepository); + createCommand.ThrownExceptions.Subscribe(ex => + { + if (!Extensions.ExceptionExtensions.IsCriticalException(ex)) + { + log.Error(ex, "Error creating repository"); + UserError.Throw(TranslateRepositoryCreateException(ex)); + } + }); + + return createCommand; + } + + static string StripSurroundingQuotes(string path) + { + Guard.ArgumentNotNull(path, nameof(path)); + + if (string.IsNullOrEmpty(path) + || path.Length < 2 + || !path.StartsWith("\"", StringComparison.Ordinal) + || !path.EndsWith("\"", StringComparison.Ordinal)) + { + return path; + } + + return path.Substring(1, path.Length - 2); + } + + PublishRepositoryUserError TranslateRepositoryCreateException(Exception ex) + { + Guard.ArgumentNotNull(ex, nameof(ex)); + + var existsException = ex as RepositoryExistsException; + if (existsException != null && SelectedAccount != null) + { + string message = string.Format( + CultureInfo.InvariantCulture, + Resources.RepositoryCreationFailedAlreadyExists, + SelectedAccount.Login, RepositoryName); + return new PublishRepositoryUserError(message, Resources.RepositoryCreationFailedAlreadyExistsMessage); + } + var quotaExceededException = ex as PrivateRepositoryQuotaExceededException; + if (quotaExceededException != null && SelectedAccount != null) + { + return new PublishRepositoryUserError(Resources.RepositoryCreationFailedQuota, quotaExceededException.Message); + } + return new PublishRepositoryUserError(ex.Message); + } + } +} diff --git a/src/GitHub.App/ViewModels/Dialog/RepositoryRecloneViewModel.cs b/src/GitHub.App/ViewModels/Dialog/RepositoryRecloneViewModel.cs new file mode 100644 index 0000000000..e74b5121b2 --- /dev/null +++ b/src/GitHub.App/ViewModels/Dialog/RepositoryRecloneViewModel.cs @@ -0,0 +1,162 @@ +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<RepositoryRecloneViewModel>(); + + readonly IOperatingSystem operatingSystem; + readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create(); + readonly ObservableAsPropertyHelper<bool> 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<Unit> 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); + } + + /// <summary> + /// Gets the dialog title. + /// </summary> + public string Title { get; private set; } + + /// <summary> + /// Path to clone repositories into + /// </summary> + public string BaseRepositoryPath + { + get { return baseRepositoryPath; } + set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, value); } + } + + /// <summary> + /// Signals that the user clicked the clone button. + /// </summary> + public IReactiveCommand<object> CloneCommand { get; private set; } + + IRepositoryModel selectedRepository; + /// <summary> + /// Selected repository to clone + /// </summary> + public IRepositoryModel 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<string> BaseRepositoryPathValidator + { + get; + private set; + } + + public IObservable<object> Done => CloneCommand.Select(_ => BaseRepositoryPath); + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs new file mode 100644 index 0000000000..5f73947db9 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs @@ -0,0 +1,496 @@ +using System; +using System.ComponentModel.Composition; +using System.ComponentModel.Design; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Info; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Services.Vssdk.Commands; +using GitHub.VisualStudio; +using ReactiveUI; +using Serilog; +using OleMenuCommand = Microsoft.VisualStudio.Shell.OleMenuCommand; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// The view model for the GitHub Pane. + /// </summary> + [Export(typeof(IGitHubPaneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, IDisposable + { + static readonly ILogger log = LogManager.ForContext<GitHubPaneViewModel>(); + 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"); + + readonly IViewViewModelFactory viewModelFactory; + readonly ISimpleApiClientFactory apiClientFactory; + readonly IConnectionManager connectionManager; + readonly ITeamExplorerContext teamExplorerContext; + readonly INavigationViewModel navigator; + readonly ILoggedOutViewModel loggedOut; + readonly INotAGitHubRepositoryViewModel notAGitHubRepository; + readonly INotAGitRepositoryViewModel notAGitRepository; + readonly ILoginFailedViewModel loginFailed; + readonly SemaphoreSlim navigating = new SemaphoreSlim(1); + readonly ObservableAsPropertyHelper<ContentOverride> contentOverride; + readonly ObservableAsPropertyHelper<bool> isSearchEnabled; + readonly ObservableAsPropertyHelper<string> title; + readonly ReactiveCommand<Unit> refresh; + readonly ReactiveCommand<Unit> showPullRequests; + readonly ReactiveCommand<object> openInBrowser; + readonly ReactiveCommand<object> help; + IDisposable connectionSubscription; + Task initializeTask; + IViewModel content; + ILocalRepositoryModel localRepository; + string searchQuery; + + [ImportingConstructor] + public GitHubPaneViewModel( + IViewViewModelFactory viewModelFactory, + ISimpleApiClientFactory apiClientFactory, + IConnectionManager connectionManager, + ITeamExplorerContext teamExplorerContext, + IVisualStudioBrowser browser, + IUsageTracker usageTracker, + INavigationViewModel navigator, + ILoggedOutViewModel loggedOut, + INotAGitHubRepositoryViewModel notAGitHubRepository, + INotAGitRepositoryViewModel notAGitRepository, + ILoginFailedViewModel loginFailed) + { + Guard.ArgumentNotNull(viewModelFactory, nameof(viewModelFactory)); + Guard.ArgumentNotNull(apiClientFactory, nameof(apiClientFactory)); + Guard.ArgumentNotNull(connectionManager, nameof(connectionManager)); + Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); + Guard.ArgumentNotNull(browser, nameof(browser)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + Guard.ArgumentNotNull(navigator, nameof(navigator)); + Guard.ArgumentNotNull(loggedOut, nameof(loggedOut)); + Guard.ArgumentNotNull(notAGitHubRepository, nameof(notAGitHubRepository)); + Guard.ArgumentNotNull(notAGitRepository, nameof(notAGitRepository)); + Guard.ArgumentNotNull(loginFailed, nameof(loginFailed)); + + this.viewModelFactory = viewModelFactory; + this.apiClientFactory = apiClientFactory; + this.connectionManager = connectionManager; + this.teamExplorerContext = teamExplorerContext; + this.navigator = navigator; + this.loggedOut = loggedOut; + this.notAGitHubRepository = notAGitHubRepository; + this.notAGitRepository = notAGitRepository; + this.loginFailed = loginFailed; + + var contentAndNavigatorContent = Observable.CombineLatest( + this.WhenAnyValue(x => x.Content), + navigator.WhenAnyValue(x => x.Content), + (c, nc) => new { Content = c, NavigatorContent = nc }); + + contentOverride = contentAndNavigatorContent + .SelectMany(x => + { + if (x.Content == null) return Observable.Return(ContentOverride.Spinner); + else if (x.Content == navigator && x.NavigatorContent != null) + { + return x.NavigatorContent.WhenAnyValue( + y => y.IsLoading, + y => y.Error, + (l, e) => + { + if (l) return ContentOverride.Spinner; + if (e != null) return ContentOverride.Error; + else return ContentOverride.None; + }); + } + else return Observable.Return(ContentOverride.None); + }) + .ToProperty(this, x => x.ContentOverride); + + // Returns navigator.Content if Content == navigator, otherwise null. + var currentPage = contentAndNavigatorContent + .Select(x => x.Content == navigator ? x.NavigatorContent : null); + + title = currentPage + .SelectMany(x => x?.WhenAnyValue(y => y.Title) ?? Observable.Return<string>(null)) + .Select(x => x ?? "GitHub") + .ToProperty(this, x => x.Title); + + isSearchEnabled = currentPage + .Select(x => x is ISearchablePageViewModel) + .ToProperty(this, x => x.IsSearchEnabled); + + refresh = ReactiveCommand.CreateAsyncTask( + currentPage.SelectMany(x => x?.WhenAnyValue( + y => y.IsLoading, + y => y.IsBusy, + (loading, busy) => !loading && !busy) + ?? Observable.Return(false)), + _ => navigator.Content.Refresh()); + refresh.ThrownExceptions.Subscribe(); + + showPullRequests = ReactiveCommand.CreateAsyncTask( + this.WhenAny(x => x.Content, x => x.Value == navigator), + _ => ShowPullRequests()); + + openInBrowser = ReactiveCommand.Create(currentPage.Select(x => x is IOpenInBrowser)); + openInBrowser.Subscribe(_ => + { + var url = ((IOpenInBrowser)navigator.Content).WebUrl; + if (url != null) browser.OpenUrl(url); + }); + + help = ReactiveCommand.Create(); + help.Subscribe(_ => + { + browser.OpenUrl(new Uri(GitHubUrls.Documentation)); + usageTracker.IncrementCounter(x => x.NumberOfGitHubPaneHelpClicks).Forget(); + }); + + navigator.WhenAnyObservable(x => x.Content.NavigationRequested) + .Subscribe(x => NavigateTo(x).Forget()); + + this.WhenAnyValue(x => x.SearchQuery) + .Where(x => navigator.Content is ISearchablePageViewModel) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => ((ISearchablePageViewModel)navigator.Content).SearchQuery = x); + } + + /// <inheritdoc/> + public IConnection Connection + { + get; + private set; + } + + /// <inheritdoc/> + public IViewModel Content + { + get { return content; } + private set { this.RaiseAndSetIfChanged(ref content, value); } + } + + /// <inheritdoc/> + public ContentOverride ContentOverride => contentOverride.Value; + + /// <inheritdoc/> + public bool IsSearchEnabled => isSearchEnabled.Value; + + /// <inheritdoc/> + public ILocalRepositoryModel LocalRepository + { + get { return localRepository; } + private set { this.RaiseAndSetIfChanged(ref localRepository, value); } + } + + /// <inheritdoc/> + public string SearchQuery + { + get { return searchQuery; } + set { this.RaiseAndSetIfChanged(ref searchQuery, value); } + } + + /// <inheritdoc/> + public string Title => title.Value; + + /// <inheritdoc/> + public void Dispose() + { + navigating.Dispose(); + } + + /// <inheritdoc/> + public Task InitializeAsync(IServiceProvider paneServiceProvider) + { + return initializeTask = initializeTask ?? CreateInitializeTask(paneServiceProvider); + } + + /// <inheritdoc/> + public async Task NavigateTo(Uri uri) + { + Guard.ArgumentNotNull(uri, nameof(uri)); + + if (uri.Scheme != "github") + { + throw new NotSupportedException("Invalid URI scheme for GitHub pane: " + uri.Scheme); + } + + if (uri.Authority != "pane") + { + throw new NotSupportedException("Invalid URI authority for GitHub pane: " + uri.Authority); + } + + Match match; + + if (uri.AbsolutePath == "/pulls") + { + await ShowPullRequests(); + } + else if (uri.AbsolutePath == "/pull/new") + { + await ShowCreatePullRequest(); + } + else if ((match = pullUri.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); + 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); + 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 login = match.Groups["login"].Value; + await ShowPullRequestReviews(owner, repo, number, login); + } + else + { + throw new NotSupportedException("Unrecognised GitHub pane URL: " + uri.AbsolutePath); + } + + var queries = HttpUtility.ParseQueryString(uri.Query); + + if (queries["refresh"] == "true") + { + await navigator.Content.Refresh(); + } + } + + /// <inheritdoc/> + public Task ShowDefaultPage() => ShowPullRequests(); + + /// <inheritdoc/> + public Task ShowCreatePullRequest() + { + return NavigateTo<IPullRequestCreationViewModel>(x => x.InitializeAsync(LocalRepository, Connection)); + } + + /// <inheritdoc/> + public Task ShowPullRequests() + { + return NavigateTo<IPullRequestListViewModel>(x => x.InitializeAsync(LocalRepository, Connection)); + } + + /// <inheritdoc/> + public Task ShowPullRequest(string owner, string repo, int number) + { + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(repo, nameof(repo)); + + return NavigateTo<IPullRequestDetailViewModel>( + x => x.InitializeAsync(LocalRepository, Connection, owner, repo, number), + x => x.RemoteRepositoryOwner == owner && x.LocalRepository.Name == repo && x.Number == number); + } + + /// <inheritdoc/> + public Task ShowPullRequestReviews(string owner, string repo, int number, string login) + { + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(repo, nameof(repo)); + + return NavigateTo<IPullRequestUserReviewsViewModel>( + x => x.InitializeAsync(LocalRepository, Connection, owner, repo, number, login), + x => x.RemoteRepositoryOwner == owner && + x.LocalRepository.Name == repo && + x.PullRequestNumber == number && + x.User.Login == login); + } + + /// <inheritdoc/> + public Task ShowPullRequestReviewAuthoring(string owner, string repo, int number) + { + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(repo, nameof(repo)); + + return NavigateTo<IPullRequestReviewAuthoringViewModel>( + x => x.InitializeAsync(LocalRepository, Connection, owner, repo, number), + x => x.RemoteRepositoryOwner == owner && + x.LocalRepository.Name == repo && + x.PullRequestModel.Number == number); + } + + async Task CreateInitializeTask(IServiceProvider paneServiceProvider) + { + await UpdateContent(teamExplorerContext.ActiveRepository); + teamExplorerContext.WhenAnyValue(x => x.ActiveRepository) + .Skip(1) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => UpdateContent(x).Forget(log)); + + connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget(log); + + var menuService = (IMenuCommandService)paneServiceProvider.GetService(typeof(IMenuCommandService)); + BindNavigatorCommand(menuService, PkgCmdIDList.pullRequestCommand, showPullRequests); + BindNavigatorCommand(menuService, PkgCmdIDList.backCommand, navigator.NavigateBack); + BindNavigatorCommand(menuService, PkgCmdIDList.forwardCommand, navigator.NavigateForward); + BindNavigatorCommand(menuService, PkgCmdIDList.refreshCommand, refresh); + BindNavigatorCommand(menuService, PkgCmdIDList.githubCommand, openInBrowser); + BindNavigatorCommand(menuService, PkgCmdIDList.helpCommand, help); + } + + OleMenuCommand BindNavigatorCommand<T>(IMenuCommandService menu, int commandId, ReactiveCommand<T> command) + { + Guard.ArgumentNotNull(menu, nameof(menu)); + Guard.ArgumentNotNull(command, nameof(command)); + + return menu.BindCommand(new CommandID(Guids.guidGitHubToolbarCmdSet, commandId), command); + } + + async Task NavigateTo<TViewModel>(Func<TViewModel, Task> initialize, Func<TViewModel, bool> match = null) + where TViewModel : class, IPanePageViewModel + { + Guard.ArgumentNotNull(initialize, nameof(initialize)); + + if (Content != navigator) return; + await navigating.WaitAsync(); + + try + { + var viewModel = navigator.History + .OfType<TViewModel>() + .FirstOrDefault(x => match?.Invoke(x) ?? true); + + if (viewModel == null) + { + viewModel = viewModelFactory.CreateViewModel<TViewModel>(); + navigator.NavigateTo(viewModel); + await initialize(viewModel); + } + else if (navigator.Content != viewModel) + { + navigator.NavigateTo(viewModel); + } + } + finally + { + navigating.Release(); + } + } + + async Task UpdateContent(ILocalRepositoryModel repository) + { + log.Debug("UpdateContent called with {CloneUrl}", repository?.CloneUrl); + + LocalRepository = repository; + connectionSubscription?.Dispose(); + connectionSubscription = null; + Connection = null; + Content = null; + navigator.Clear(); + + if (repository == null) + { + log.Debug("Not a git repository: {CloneUrl}", repository?.CloneUrl); + Content = notAGitRepository; + return; + } + else if (string.IsNullOrWhiteSpace(repository.CloneUrl)) + { + log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl); + Content = notAGitHubRepository; + return; + } + + var repositoryUrl = repository.CloneUrl.ToRepositoryUrl(); + var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl); + var client = await apiClientFactory.Create(repository.CloneUrl); + var isEnterprise = isDotCom ? false : await client.IsEnterprise(); + var notGitHubRepo = true; + + if (isDotCom || isEnterprise) + { + var hostAddress = HostAddress.Create(repository.CloneUrl); + + notGitHubRepo = false; + + Connection = await connectionManager.GetConnection(hostAddress); + Connection?.WhenAnyValue( + x => x.IsLoggedIn, + x => x.IsLoggingIn, + (_, __) => Unit.Default) + .Skip(1) + .Throttle(TimeSpan.FromMilliseconds(100)) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(_ => UpdateContent(LocalRepository).Forget()); + + if (Connection?.IsLoggedIn == true) + { + if (await IsValidRepository(client) == true) + { + log.Debug("Found a GitHub repository: {CloneUrl}", repository?.CloneUrl); + Content = navigator; + await ShowDefaultPage(); + } + else + { + notGitHubRepo = true; + } + } + else if (Connection?.IsLoggingIn == true) + { + log.Debug("Found a GitHub repository: {CloneUrl} and logging in", repository?.CloneUrl); + Content = null; + } + else if (Connection?.ConnectionError != null) + { + log.Debug("Found a GitHub repository: {CloneUrl} with login error", repository?.CloneUrl); + loginFailed.Initialize(Connection.ConnectionError.GetUserFriendlyError(ErrorType.LoginFailed)); + Content = loginFailed; + } + else + { + log.Debug("Found a a GitHub repository but not logged in: {CloneUrl}", repository?.CloneUrl); + Content = loggedOut; + } + } + + if (notGitHubRepo) + { + log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl); + Content = notAGitHubRepository; + } + } + + static async Task<bool> IsValidRepository(ISimpleApiClient client) + { + try + { + var repo = await client.GetRepository(); + return repo.Id != 0; + } + catch + { + return false; + } + } + + static Regex CreateRoute(string route) + { + // Build RegEx from route (:foo to named group (?<foo>[\w_.-]+)). + var routeFormat = "^" + new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.-]+)") + "$"; + return new Regex(routeFormat, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs b/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs new file mode 100644 index 0000000000..472029c3a3 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reactive; +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; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model which implements the functionality common to issue and pull request lists. + /// </summary> + public abstract class IssueListViewModelBase : PanePageViewModelBase, IIssueListViewModelBase + { + static readonly ILogger log = LogManager.ForContext<GitHubPaneViewModel>(); + readonly IRepositoryService repositoryService; + IReadOnlyList<IIssueListItemViewModelBase> items; + ICollectionView itemsView; + IDisposable subscription; + IssueListMessage message; + IRepositoryModel remoteRepository; + IReadOnlyList<IRepositoryModel> forks; + string searchQuery; + string selectedState; + ObservableAsPropertyHelper<string> stateCaption; + string stringFilter; + int numberFilter; + IUserFilterViewModel authorFilter; + + /// <summary> + /// Initializes a new instance of the <see cref="IssueListViewModelBase"/> class. + /// </summary> + /// <param name="repositoryService">The repository service.</param> + public IssueListViewModelBase(IRepositoryService repositoryService) + { + this.repositoryService = repositoryService; + OpenItem = ReactiveCommand.CreateAsyncTask(OpenItemImpl); + stateCaption = this.WhenAnyValue( + x => x.Items.Count, + x => x.SelectedState, + x => x.IsBusy, + x => x.IsLoading, + (count, state, busy, loading) => busy || loading ? state : count + " " + state) + .ToProperty(this, x => x.StateCaption); + } + + /// <inheritdoc/> + public IUserFilterViewModel AuthorFilter + { + get { return authorFilter; } + private set { this.RaiseAndSetIfChanged(ref authorFilter, value); } + } + + /// <inheritdoc/> + public IReadOnlyList<IRepositoryModel> Forks + { + get { return forks; } + set { this.RaiseAndSetIfChanged(ref forks, value); } + } + + /// <inheritdoc/> + public IReadOnlyList<IIssueListItemViewModelBase> Items + { + get { return items; } + private set { this.RaiseAndSetIfChanged(ref items, value); } + } + + /// <inheritdoc/> + public ICollectionView ItemsView + { + get { return itemsView; } + private set { this.RaiseAndSetIfChanged(ref itemsView, value); } + } + + /// <inheritdoc/> + public ILocalRepositoryModel LocalRepository { get; private set; } + + /// <inheritdoc/> + public IssueListMessage Message + { + get { return message; } + private set { this.RaiseAndSetIfChanged(ref message, value); } + } + + /// <inheritdoc/> + public IRepositoryModel RemoteRepository + { + get { return remoteRepository; } + set { this.RaiseAndSetIfChanged(ref remoteRepository, value); } + } + + /// <inheritdoc/> + public string SearchQuery + { + get { return searchQuery; } + set { this.RaiseAndSetIfChanged(ref searchQuery, value); } + } + + /// <inheritdoc/> + public string SelectedState + { + get { return selectedState; } + set { this.RaiseAndSetIfChanged(ref selectedState, value); } + } + + /// <inheritdoc/> + public abstract IReadOnlyList<string> States { get; } + + /// <inheritdoc/> + public string StateCaption => stateCaption.Value; + + /// <inheritdoc/> + public ReactiveCommand<Unit> OpenItem { get; } + + /// <inheritdoc/> + public async Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) + { + try + { + LocalRepository = repository; + SelectedState = States.FirstOrDefault(); + AuthorFilter = new UserFilterViewModel(LoadAuthors); + IsLoading = true; + + var parent = await repositoryService.FindParent( + HostAddress.Create(repository.CloneUrl), + repository.Owner, + repository.Name); + + if (parent == null) + { + RemoteRepository = repository; + } + else + { + // TODO: Handle forks with different names. + RemoteRepository = new RepositoryModel( + repository.Name, + UriString.ToUriString(repository.CloneUrl.ToRepositoryUrl(parent.Value.owner))); + + Forks = new IRepositoryModel[] + { + RemoteRepository, + repository, + }; + } + + this.WhenAnyValue(x => x.SelectedState, x => x.RemoteRepository) + .Skip(1) + .Subscribe(_ => Refresh().Forget()); + + Observable.Merge( + this.WhenAnyValue(x => x.SearchQuery).Skip(1).SelectUnit(), + AuthorFilter.WhenAnyValue(x => x.Selected).Skip(1).SelectUnit()) + .Subscribe(_ => FilterChanged()); + + await Refresh(); + } + catch (Exception ex) + { + Error = ex; + IsLoading = false; + log.Error(ex, "Error initializing IssueListViewModelBase"); + } + } + + /// <summary> + /// Refreshes the view model. + /// </summary> + /// <returns>A task tracking the operation.</returns> + public override Task Refresh() + { + if (RemoteRepository == null) + { + // If an exception occurred reading the parent repository, do nothing. + return Task.CompletedTask; + } + + subscription?.Dispose(); + + var dispose = new CompositeDisposable(); + var itemSource = CreateItemSource(); + var items = new VirtualizingList<IIssueListItemViewModelBase>(itemSource, null); + var view = new VirtualizingListCollectionView<IIssueListItemViewModelBase>(items); + + view.Filter = FilterItem; + Items = items; + ItemsView = view; + Error = null; + + dispose.Add(itemSource); + dispose.Add( + Observable.CombineLatest( + itemSource.WhenAnyValue(x => x.IsLoading), + view.WhenAnyValue(x => x.Count), + this.WhenAnyValue(x => x.SearchQuery), + this.WhenAnyValue(x => x.SelectedState), + this.WhenAnyValue(x => x.AuthorFilter.Selected), + (loading, count, _, __, ___) => Tuple.Create(loading, count)) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => UpdateState(x.Item1, x.Item2))); + dispose.Add( + Observable.FromEventPattern<ErrorEventArgs>( + x => items.InitializationError += x, + x => items.InitializationError -= x) + .Subscribe(x => Error = x.EventArgs.GetException())); + subscription = dispose; + + return Task.CompletedTask; + } + + /// <summary> + /// When overridden in a derived class, creates the <see cref="IVirtualizingListSource{T}"/> + /// that will act as the source for <see cref="Items"/>. + /// </summary> + protected abstract IVirtualizingListSource<IIssueListItemViewModelBase> CreateItemSource(); + + /// <summary> + /// When overridden in a derived class, navigates to the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>A task tracking the operation.</returns> + protected abstract Task DoOpenItem(IIssueListItemViewModelBase item); + + /// <summary> + /// Loads a page of authors for the <see cref="AuthorFilter"/>. + /// </summary> + /// <param name="after">The GraphQL "after" cursor.</param> + /// <returns>A task that returns a page of authors.</returns> + protected abstract Task<Page<ActorModel>> LoadAuthors(string after); + + void FilterChanged() + { + if (!string.IsNullOrWhiteSpace(SearchQuery)) + { + numberFilter = 0; + + int.TryParse(SearchQuery.Substring(SearchQuery.StartsWith('#') ? 1 : 0), out numberFilter); + + if (numberFilter == 0) + { + stringFilter = SearchQuery.ToUpper(); + } + } + else + { + stringFilter = null; + numberFilter = 0; + } + + ItemsView?.Refresh(); + } + + bool FilterItem(object o) + { + var item = o as IIssueListItemViewModelBase; + var result = true; + + if (!string.IsNullOrWhiteSpace(SearchQuery)) + { + + if (item != null) + { + if (numberFilter != 0) + { + result = item.Number == numberFilter; + } + else + { + result = item.Title.ToUpper().Contains(stringFilter); + } + } + } + + if (result && AuthorFilter.Selected != null) + { + result = item.Author.Login.Equals( + AuthorFilter.Selected.Login, + StringComparison.CurrentCultureIgnoreCase); + } + + return result; + } + + async Task OpenItemImpl(object i) + { + var item = i as IIssueListItemViewModelBase; + if (item != null) await DoOpenItem(item); + } + + void UpdateState(bool loading, int count) + { + var message = IssueListMessage.None; + + if (!loading) + { + if (count == 0) + { + if (SelectedState == States[0] && + string.IsNullOrWhiteSpace(SearchQuery) && + AuthorFilter.Selected == null) + { + message = IssueListMessage.NoOpenItems; + } + else + { + message = IssueListMessage.NoItemsMatchCriteria; + } + } + + IsLoading = false; + } + + IsBusy = loading; + Message = message; + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/LoggedOutViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/LoggedOutViewModel.cs new file mode 100644 index 0000000000..42f8b9f369 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/LoggedOutViewModel.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using GitHub.Info; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// The view model for the "Sign in to GitHub" view in the GitHub pane. + /// </summary> + [Export(typeof(ILoggedOutViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class LoggedOutViewModel : PanePageViewModelBase, ILoggedOutViewModel + { + readonly IDialogService dialogService; + readonly IVisualStudioBrowser browser; + + /// <summary> + /// Initializes a new instance of the <see cref="LoggedOutViewModel"/> class. + /// </summary> + [ImportingConstructor] + public LoggedOutViewModel(IDialogService dialogService, IVisualStudioBrowser browser) + { + this.dialogService = dialogService; + this.browser = browser; + SignIn = ReactiveCommand.Create(); + SignIn.Subscribe(_ => OnSignIn()); + Register = ReactiveCommand.Create(); + Register.Subscribe(_ => OnRegister()); + } + + /// <inheritdoc/> + public IReactiveCommand<object> SignIn { get; } + + /// <inheritdoc/> + public IReactiveCommand<object> Register { get; } + + /// <summary> + /// Called when the <see cref="SignIn"/> command is executed. + /// </summary> + void OnSignIn() + { + // Show the Sign In dialog. We don't need to listen to the outcome of this: the parent + // GitHubPaneViewModel will listen to the ConnectionManager and close this view when + // the user logs in. + dialogService.ShowLoginDialog(); + } + + /// <summary> + /// Called when the <see cref="Register"/> command is executed. + /// </summary> + void OnRegister() + { + browser.OpenUrl(GitHubUrls.Pricing); + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/LoginFailedViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/LoginFailedViewModel.cs new file mode 100644 index 0000000000..aaeded79e1 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/LoginFailedViewModel.cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// The view model for the "Login Failed" view in the GitHub pane. + /// </summary> + [Export(typeof(ILoginFailedViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class LoginFailedViewModel : PanePageViewModelBase, ILoginFailedViewModel + { + readonly ITeamExplorerServices teServices; + UserError loginError; + + /// <summary> + /// Initializes a new instance of the <see cref="LoginFailedViewModel"/> class. + /// </summary> + [ImportingConstructor] + public LoginFailedViewModel(ITeamExplorerServices teServices) + { + this.teServices = teServices; + OpenTeamExplorer = ReactiveCommand.Create().OnExecuteCompleted(_ => DoOpenTeamExplorer()); + } + + /// <inheritdoc/> + public UserError LoginError + { + get => loginError; + private set => this.RaiseAndSetIfChanged(ref loginError, value); + } + + /// <inheritdoc/> + public ReactiveCommand<object> OpenTeamExplorer { get; } + + public void Initialize(UserError error) + { + LoginError = error; + } + + void DoOpenTeamExplorer() => teServices.ShowConnectPage(); + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/NavigationViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/NavigationViewModel.cs new file mode 100644 index 0000000000..9ba7fb04f9 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/NavigationViewModel.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using GitHub.Extensions; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model that supports back/forward navigation of an inner content page. + /// </summary> + [Export(typeof(INavigationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class NavigationViewModel : ViewModelBase, INavigationViewModel + { + readonly ReactiveList<IPanePageViewModel> history; + readonly ObservableAsPropertyHelper<IPanePageViewModel> content; + int index = -1; + + /// <summary> + /// Initializes a new instance of the <see cref="NavigationViewModel"/> class. + /// </summary> + public NavigationViewModel() + { + history = new ReactiveList<IPanePageViewModel>(); + history.Changing.Subscribe(CollectionChanging); + history.Changed.Subscribe(CollectionChanged); + + var pos = this.WhenAnyValue( + x => x.Index, + x => x.History.Count, + (i, c) => new { Index = i, Count = c }); + + content = pos + .Where(x => x.Index < x.Count) + .Select(x => x.Index != -1 ? history[x.Index] : null) + .StartWith((IPanePageViewModel)null) + .ToProperty(this, x => x.Content); + + this.WhenAnyValue(x => x.Content) + .Buffer(2, 1) + .Subscribe(x => { + if (x[0] != null && history.Contains(x[0])) x[0].Deactivated(); + x[1]?.Activated(); + }); + + NavigateBack = ReactiveCommand.Create(pos.Select(x => x.Index > 0)); + NavigateBack.Subscribe(_ => Back()); + NavigateForward = ReactiveCommand.Create(pos.Select(x => x.Index < x.Count - 1)); + NavigateForward.Subscribe(_ => Forward()); + } + + /// <inheritdoc/> + public IPanePageViewModel Content => content.Value; + + /// <inheritdoc/> + public int Index + { + get { return index; } + set { this.RaiseAndSetIfChanged(ref index, value); } + } + + /// <inheritdoc/> + public IReadOnlyReactiveList<IPanePageViewModel> History => history; + + /// <inheritdoc/> + public ReactiveCommand<object> NavigateBack { get; } + + /// <inheritdoc/> + public ReactiveCommand<object> NavigateForward { get; } + + /// <inheritdoc/> + public void NavigateTo(IPanePageViewModel page) + { + Guard.ArgumentNotNull(page, nameof(page)); + + history.Insert(index + 1, page); + ++Index; + + if (index < history.Count - 1) + { + history.RemoveRange(index + 1, history.Count - (index + 1)); + } + } + + /// <inheritdoc/> + public bool Back() + { + if (index == 0) + return false; + --Index; + return true; + } + + /// <inheritdoc/> + public bool Forward() + { + if (index >= history.Count - 1) + return false; + ++Index; + return true; + } + + /// <inheritdoc/> + public void Clear() + { + Index = -1; + history.Clear(); + } + + public int RemoveAll(IPanePageViewModel page) + { + var count = 0; + while (history.Remove(page)) ++count; + return count; + } + + void BeforeItemAdded(IPanePageViewModel page) + { + if (!history.Contains(page)) + { + page.CloseRequested.Subscribe(_ => RemoveAll(page)); + } + } + + void ItemRemoved(int removedIndex, IPanePageViewModel page) + { + if (removedIndex <= Index) + { + --Index; + } + + if (!history.Contains(page)) + { + if (Content == page) page.Deactivated(); + page.Dispose(); + } + } + + void CollectionChanging(NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (IPanePageViewModel page in e.NewItems) + { + BeforeItemAdded(page); + } + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + foreach (var page in history) + { + page.Dispose(); + } + } + } + + void CollectionChanged(NotifyCollectionChangedEventArgs e) + { + using (DelayChangeNotifications()) + { + if (e.Action == NotifyCollectionChangedAction.Remove) + { + for (var i = 0; i < e.OldItems.Count; ++i) + { + ItemRemoved(e.OldStartingIndex + i, (IPanePageViewModel)e.OldItems[i]); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/NotAGitHubRepositoryViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/NotAGitHubRepositoryViewModel.cs new file mode 100644 index 0000000000..bb349b7078 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/NotAGitHubRepositoryViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// The view model for the "Not a GitHub repository" view in the GitHub pane. + /// </summary> + [Export(typeof(INotAGitHubRepositoryViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class NotAGitHubRepositoryViewModel : PanePageViewModelBase, INotAGitHubRepositoryViewModel + { + ITeamExplorerServices teamExplorerServices; + + /// <summary> + /// Initializes a new instance of the <see cref="NotAGitHubRepositoryViewModel"/> class. + /// </summary> + [ImportingConstructor] + public NotAGitHubRepositoryViewModel(ITeamExplorerServices teamExplorerServices) + { + this.teamExplorerServices = teamExplorerServices; + Publish = ReactiveCommand.Create(); + Publish.Subscribe(_ => OnPublish()); + } + + /// <summary> + /// Gets the command executed when the user clicks the "Publish to GitHub" link. + /// </summary> + public IReactiveCommand<object> Publish { get; } + + /// <summary> + /// Called when the <see cref="Publish"/> command is executed. + /// </summary> + private void OnPublish() + { + teamExplorerServices.ShowPublishSection(); + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/NotAGitRepositoryViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/NotAGitRepositoryViewModel.cs new file mode 100644 index 0000000000..ee9e046f85 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/NotAGitRepositoryViewModel.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.Composition; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// The view model for the "Not a Git repository" view in the GitHub pane. + /// </summary> + [Export(typeof(INotAGitRepositoryViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class NotAGitRepositoryViewModel : PanePageViewModelBase, INotAGitRepositoryViewModel + { + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PanePageViewModelBase.cs b/src/GitHub.App/ViewModels/GitHubPane/PanePageViewModelBase.cs new file mode 100644 index 0000000000..182506567d --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PanePageViewModelBase.cs @@ -0,0 +1,110 @@ +using System; +using System.Reactive; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Base class for view models that appear as a page in a the GitHub pane. + /// </summary> + public abstract class PanePageViewModelBase : ViewModelBase, IPanePageViewModel, IDisposable + { + static readonly Uri paneUri = new Uri("github://pane"); + readonly Subject<Uri> navigate = new Subject<Uri>(); + readonly Subject<Unit> close = new Subject<Unit>(); + Exception error; + bool isBusy; + bool isLoading; + string title; + + /// <summary> + /// Initializes a new instance of the <see cref="PanePageViewModelBase"/> class. + /// </summary> + protected PanePageViewModelBase() + { + } + + ~PanePageViewModelBase() + { + Dispose(false); + } + + /// <inheritdoc/> + public Exception Error + { + get { return error; } + protected set { this.RaiseAndSetIfChanged(ref error, value); } + } + + /// <inheritdoc/> + public bool IsBusy + { + get { return isBusy; } + protected set { this.RaiseAndSetIfChanged(ref isBusy, value); } + } + + /// <inheritdoc/> + public bool IsLoading + { + get { return isLoading; } + protected set { this.RaiseAndSetIfChanged(ref isLoading, value); } + } + + /// <inheritdoc/> + public string Title + { + get { return title; } + protected set { this.RaiseAndSetIfChanged(ref title, value); } + } + + /// <inheritdoc/> + public IObservable<Unit> CloseRequested => close; + + /// <inheritdoc/> + public IObservable<Uri> NavigationRequested => navigate; + + /// <inheritdoc/> + public virtual void Activated() + { + } + + /// <inheritdoc/> + public virtual void Deactivated() + { + } + + /// <inheritdoc/> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <inheritdoc/> + public virtual Task Refresh() => Task.CompletedTask; + + /// <summary> + /// Sends a request to close the page. + /// </summary> + protected void Close() => close.OnNext(Unit.Default); + + /// <summary> + /// Sends a request to navigate to a new page. + /// </summary> + /// <param name="uri"> + /// The path portion of the URI of the new page, e.g. "pulls". + /// </param> + protected void NavigateTo(string uri) => navigate.OnNext(new Uri(paneUri, new Uri(uri, UriKind.Relative))); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + close.Dispose(); + navigate.Dispose(); + } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckType.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckType.cs new file mode 100644 index 0000000000..f5cc9dbc20 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckType.cs @@ -0,0 +1,8 @@ +namespace GitHub.ViewModels.GitHubPane +{ + public enum PullRequestCheckType + { + StatusApi, + ChecksApi + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs new file mode 100644 index 0000000000..7572aac568 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +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.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"; + + public static IEnumerable<IPullRequestCheckViewModel> Build(IViewViewModelFactory viewViewModelFactory, PullRequestDetailModel pullRequest) + { + var statuses = pullRequest.Statuses?.Select(model => + { + PullRequestCheckStatus checkStatus; + switch (model.State) + { + case StatusState.Expected: + case StatusState.Error: + case StatusState.Failure: + checkStatus = PullRequestCheckStatus.Failure; + break; + case StatusState.Pending: + checkStatus = PullRequestCheckStatus.Pending; + break; + case StatusState.Success: + checkStatus = PullRequestCheckStatus.Success; + break; + default: + throw new InvalidOperationException("Unkown PullRequestCheckStatusEnum"); + } + + var pullRequestCheckViewModel = (PullRequestCheckViewModel) viewViewModelFactory.CreateViewModel<IPullRequestCheckViewModel>(); + pullRequestCheckViewModel.CheckType = PullRequestCheckType.StatusApi; + pullRequestCheckViewModel.Title = model.Context; + pullRequestCheckViewModel.Description = model.Description; + pullRequestCheckViewModel.Status = checkStatus; + pullRequestCheckViewModel.DetailsUrl = !string.IsNullOrEmpty(model.TargetUrl) ? new Uri(model.TargetUrl) : null; + + return pullRequestCheckViewModel; + }) ?? new PullRequestCheckViewModel[0]; + + var checks = pullRequest.CheckSuites?.SelectMany(model => model.CheckRuns) + .Select(model => + { + PullRequestCheckStatus checkStatus; + switch (model.Status) + { + case CheckStatusState.Requested: + case CheckStatusState.Queued: + case CheckStatusState.InProgress: + checkStatus = PullRequestCheckStatus.Pending; + break; + + case CheckStatusState.Completed: + switch (model.Conclusion) + { + case CheckConclusionState.Success: + checkStatus = PullRequestCheckStatus.Success; + break; + + case CheckConclusionState.ActionRequired: + case CheckConclusionState.TimedOut: + case CheckConclusionState.Cancelled: + case CheckConclusionState.Failure: + case CheckConclusionState.Neutral: + checkStatus = PullRequestCheckStatus.Failure; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var pullRequestCheckViewModel = (PullRequestCheckViewModel)viewViewModelFactory.CreateViewModel<IPullRequestCheckViewModel>(); + pullRequestCheckViewModel.CheckType = PullRequestCheckType.ChecksApi; + pullRequestCheckViewModel.Title = model.Name; + pullRequestCheckViewModel.Description = model.Summary; + pullRequestCheckViewModel.Status = checkStatus; + pullRequestCheckViewModel.DetailsUrl = new Uri(model.DetailsUrl); + + return pullRequestCheckViewModel; + }) ?? new PullRequestCheckViewModel[0]; + + return statuses.Concat(checks).OrderBy(model => model.Title); + } + + [ImportingConstructor] + public PullRequestCheckViewModel(IUsageTracker usageTracker) + { + this.usageTracker = usageTracker; + OpenDetailsUrl = ReactiveCommand.Create().OnExecuteCompleted(DoOpenDetailsUrl); + } + + private void DoOpenDetailsUrl(object obj) + { + Expression<Func<UsageModel.MeasuresModel, int>> expression; + if (CheckType == PullRequestCheckType.StatusApi) + { + expression = x => x.NumberOfPRStatusesOpenInGitHub; + } + else + { + expression = x => x.NumberOfPRChecksOpenInGitHub; + } + + usageTracker.IncrementCounter(expression).Forget(); + } + + public string Title { get; private set; } + + public string Description { get; private set; } + + public PullRequestCheckType CheckType { get; private set; } + + public PullRequestCheckStatus Status{ get; private set; } + + public Uri DetailsUrl { get; private set; } + + public ReactiveCommand<object> OpenDetailsUrl { get; } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs new file mode 100644 index 0000000000..d3dd0d2565 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using GitHub.Validation; +using Octokit; +using ReactiveUI; +using Serilog; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.ViewModels.GitHubPane +{ + [Export(typeof(IPullRequestCreationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + public class PullRequestCreationViewModel : PanePageViewModelBase, IPullRequestCreationViewModel + { + static readonly ILogger log = LogManager.ForContext<PullRequestCreationViewModel>(); + + readonly ObservableAsPropertyHelper<bool> isExecuting; + readonly IPullRequestService service; + readonly IModelServiceFactory modelServiceFactory; + readonly CompositeDisposable disposables = new CompositeDisposable(); + ILocalRepositoryModel activeLocalRepo; + ObservableAsPropertyHelper<IRemoteRepositoryModel> githubRepository; + IModelService modelService; + + [ImportingConstructor] + public PullRequestCreationViewModel( + IModelServiceFactory modelServiceFactory, + IPullRequestService service, + INotificationService notifications) + { + Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(notifications, nameof(notifications)); + + this.service = service; + this.modelServiceFactory = modelServiceFactory; + + this.WhenAnyValue(x => x.Branches) + .WhereNotNull() + .Where(_ => TargetBranch != null) + .Subscribe(x => + { + if (!x.Any(t => t.Equals(TargetBranch))) + TargetBranch = GitHubRepository.IsFork ? GitHubRepository.Parent.DefaultBranch : GitHubRepository.DefaultBranch; + }); + + SetupValidators(); + + var whenAnyValidationResultChanges = this.WhenAny( + x => x.TitleValidator.ValidationResult, + x => x.BranchValidator.ValidationResult, + x => x.IsBusy, + (x, y, z) => (x.Value?.IsValid ?? false) && (y.Value?.IsValid ?? false) && !z.Value); + + this.WhenAny(x => x.BranchValidator.ValidationResult, x => x.GetValue()) + .WhereNotNull() + .Where(x => !x.IsValid && x.DisplayValidationError) + .Subscribe(x => notifications.ShowError(BranchValidator.ValidationResult.Message)); + + CreatePullRequest = ReactiveCommand.CreateAsyncObservable(whenAnyValidationResultChanges, + _ => service + .CreatePullRequest(modelService, activeLocalRepo, TargetBranch.Repository, SourceBranch, TargetBranch, PRTitle, Description ?? String.Empty) + .Catch<IPullRequestModel, Exception>(ex => + { + log.Error(ex, "Error creating pull request"); + + //TODO:Will need a uniform solution to HTTP exception message handling + var apiException = ex as ApiValidationException; + var error = apiException?.ApiError?.Errors?.FirstOrDefault(); + notifications.ShowError(error?.Message ?? ex.Message); + return Observable.Empty<IPullRequestModel>(); + })) + .OnExecuteCompleted(pr => + { + notifications.ShowMessage(String.Format(CultureInfo.CurrentCulture, Resources.PRCreatedUpstream, SourceBranch.DisplayName, TargetBranch.Repository.Owner + "/" + TargetBranch.Repository.Name + "#" + pr.Number, + TargetBranch.Repository.CloneUrl.ToRepositoryUrl().Append("pull/" + pr.Number))); + NavigateTo("/pulls?refresh=true"); + Cancel.Execute(null); + }); + + Cancel = ReactiveCommand.Create(); + Cancel.Subscribe(_ => Close()); + + isExecuting = CreatePullRequest.IsExecuting.ToProperty(this, x => x.IsExecuting); + + this.WhenAnyValue(x => x.Initialized, x => x.GitHubRepository, x => x.IsExecuting) + .Select(x => !(x.Item1 && x.Item2 != null && !x.Item3)) + .Subscribe(x => IsBusy = x); + } + + public async Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection) + { + modelService = await modelServiceFactory.CreateAsync(connection); + activeLocalRepo = repository; + SourceBranch = repository.CurrentBranch; + + var obs = modelService.ApiClient.GetRepository(repository.Owner, repository.Name) + .Select(r => new RemoteRepositoryModel(r)) + .PublishLast(); + disposables.Add(obs.Connect()); + var githubObs = obs; + + githubRepository = githubObs.ToProperty(this, x => x.GitHubRepository); + + this.WhenAnyValue(x => x.GitHubRepository) + .WhereNotNull() + .Subscribe(r => + { + TargetBranch = r.IsFork ? r.Parent.DefaultBranch : r.DefaultBranch; + }); + + githubObs.SelectMany(r => + { + var b = Observable.Empty<IBranch>(); + if (r.IsFork) + { + b = modelService.GetBranches(r.Parent).Select(x => + { + return x; + }); + } + return b.Concat(modelService.GetBranches(r)); + }) + .ToList() + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => + { + Branches = x.ToList(); + Initialized = true; + }); + + SourceBranch = activeLocalRepo.CurrentBranch; + + var uniqueCommits = this.WhenAnyValue( + x => x.SourceBranch, + x => x.TargetBranch) + .Where(x => x.Item1 != null && x.Item2 != null) + .Select(branches => + { + var baseBranch = branches.Item1.Name; + var compareBranch = branches.Item2.Name; + + // We only need to get max two commits for what we're trying to achieve here. + // If there's no commits we want to block creation of the PR, if there's one commits + // we wan't to use its commit message as the PR title/body and finally if there's more + // than one we'll use the branch name for the title. + return service.GetMessagesForUniqueCommits(activeLocalRepo, baseBranch, compareBranch, maxCommits: 2) + .Catch<IReadOnlyList<CommitMessage>, Exception>(ex => + { + log.Warning(ex, "Could not load unique commits"); + return Observable.Empty<IReadOnlyList<CommitMessage>>(); + }); + }) + .Switch() + .ObserveOn(RxApp.MainThreadScheduler) + .Replay(1) + .RefCount(); + + Observable.CombineLatest( + this.WhenAnyValue(x => x.SourceBranch), + uniqueCommits, + service.GetPullRequestTemplate(repository).DefaultIfEmpty(string.Empty), + (compare, commits, template) => new { compare, commits, template }) + .Subscribe(x => + { + var prTitle = string.Empty; + var prDescription = string.Empty; + + if (x.commits.Count == 1) + { + prTitle = x.commits[0].Summary; + prDescription = x.commits[0].Details; + } + else + { + prTitle = x.compare.Name.Humanize(); + } + + if (!string.IsNullOrWhiteSpace(x.template)) + { + if (!string.IsNullOrEmpty(prDescription)) + prDescription += "\n\n"; + prDescription += x.template; + } + + PRTitle = prTitle; + Description = prDescription; + }); + + Initialized = true; + } + + void SetupValidators() + { + var titleObs = this.WhenAnyValue(x => x.PRTitle); + TitleValidator = ReactivePropertyValidator.ForObservable(titleObs) + .IfNullOrEmpty(Resources.PullRequestCreationTitleValidatorEmpty); + + var branchObs = this.WhenAnyValue( + x => x.Initialized, + x => x.TargetBranch, + x => x.SourceBranch, + (init, target, source) => new { Initialized = init, Source = source, Target = target }) + .Where(x => x.Initialized); + + BranchValidator = ReactivePropertyValidator.ForObservable(branchObs) + .IfTrue(x => x.Source == null, Resources.PullRequestSourceBranchDoesNotExist) + .IfTrue(x => x.Source.Equals(x.Target), Resources.PullRequestSourceAndTargetBranchTheSame); + } + + bool disposed; // To detect redundant calls + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + if (disposed) return; + disposed = true; + + disposables.Dispose(); + } + } + + public IRemoteRepositoryModel GitHubRepository { get { return githubRepository?.Value; } } + bool IsExecuting { get { return isExecuting.Value; } } + + bool initialized; + bool Initialized + { + get { return initialized; } + set { this.RaiseAndSetIfChanged(ref initialized, value); } + } + + IBranch sourceBranch; + public IBranch SourceBranch + { + get { return sourceBranch; } + set { this.RaiseAndSetIfChanged(ref sourceBranch, value); } + } + + IBranch targetBranch; + public IBranch TargetBranch + { + get { return targetBranch; } + set { this.RaiseAndSetIfChanged(ref targetBranch, value); } + } + + IReadOnlyList<IBranch> branches; + public IReadOnlyList<IBranch> Branches + { + get { return branches; } + set { this.RaiseAndSetIfChanged(ref branches, value); } + } + + public IReactiveCommand<IPullRequestModel> CreatePullRequest { get; } + public IReactiveCommand<object> Cancel { get; } + + string title; + public string PRTitle + { + get { return title; } + set { this.RaiseAndSetIfChanged(ref title, value); } + } + + string description; + public string Description + { + get { return description; } + set { this.RaiseAndSetIfChanged(ref description, value); } + } + + ReactivePropertyValidator titleValidator; + public ReactivePropertyValidator TitleValidator + { + get { return titleValidator; } + set { this.RaiseAndSetIfChanged(ref titleValidator, value); } + } + + ReactivePropertyValidator branchValidator; + ReactivePropertyValidator BranchValidator + { + get { return branchValidator; } + set { this.RaiseAndSetIfChanged(ref branchValidator, value); } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs new file mode 100644 index 0000000000..be08da1b1b --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -0,0 +1,705 @@ +using System; +using System.Globalization; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +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 ReactiveUI; +using Serilog; +using static System.FormattableString; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model which displays the details of a pull request. + /// </summary> + [Export(typeof(IPullRequestDetailViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullRequestDetailViewModel + { + static readonly ILogger log = LogManager.ForContext<PullRequestDetailViewModel>(); + + readonly IModelServiceFactory modelServiceFactory; + readonly IPullRequestService pullRequestsService; + readonly IPullRequestSessionManager sessionManager; + readonly IUsageTracker usageTracker; + readonly ITeamExplorerContext teamExplorerContext; + readonly ISyncSubmodulesCommand syncSubmodulesCommand; + readonly IViewViewModelFactory viewViewModelFactory; + IModelService modelService; + PullRequestDetailModel model; + IActorViewModel author; + string sourceBranchDisplayName; + string targetBranchDisplayName; + string body; + IReadOnlyList<IPullRequestReviewSummaryViewModel> reviews; + IPullRequestCheckoutState checkoutState; + IPullRequestUpdateState updateState; + string operationError; + bool isCheckedOut; + bool isFromFork; + bool isInCheckout; + bool active; + bool refreshOnActivate; + Uri webUrl; + IDisposable sessionSubscription; + IReadOnlyList<IPullRequestCheckViewModel> checks; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestDetailViewModel"/> class. + /// </summary> + /// <param name="pullRequestsService">The pull requests service.</param> + /// <param name="sessionManager">The pull request session manager.</param> + /// <param name="modelServiceFactory">The model service factory</param> + /// <param name="usageTracker">The usage tracker.</param> + /// <param name="teamExplorerContext">The context for tracking repo changes</param> + /// <param name="files">The view model which will display the changed files</param> + /// <param name="syncSubmodulesCommand">A command that will be run when <see cref="SyncSubmodules"/> is executed</param> + [ImportingConstructor] + public PullRequestDetailViewModel( + IPullRequestService pullRequestsService, + IPullRequestSessionManager sessionManager, + IModelServiceFactory modelServiceFactory, + IUsageTracker usageTracker, + ITeamExplorerContext teamExplorerContext, + IPullRequestFilesViewModel files, + ISyncSubmodulesCommand syncSubmodulesCommand, + IViewViewModelFactory viewViewModelFactory) + { + Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); + Guard.ArgumentNotNull(syncSubmodulesCommand, nameof(syncSubmodulesCommand)); + Guard.ArgumentNotNull(viewViewModelFactory, nameof(viewViewModelFactory)); + + this.pullRequestsService = pullRequestsService; + this.sessionManager = sessionManager; + this.modelServiceFactory = modelServiceFactory; + this.usageTracker = usageTracker; + this.teamExplorerContext = teamExplorerContext; + this.syncSubmodulesCommand = syncSubmodulesCommand; + this.viewViewModelFactory = viewViewModelFactory; + Files = files; + + Checkout = ReactiveCommand.CreateAsyncObservable( + this.WhenAnyValue(x => x.CheckoutState) + .Cast<CheckoutCommandState>() + .Select(x => x != null && x.IsEnabled), + DoCheckout); + Checkout.IsExecuting.Subscribe(x => isInCheckout = x); + SubscribeOperationError(Checkout); + + Pull = ReactiveCommand.CreateAsyncObservable( + this.WhenAnyValue(x => x.UpdateState) + .Cast<UpdateCommandState>() + .Select(x => x != null && x.PullEnabled), + DoPull); + SubscribeOperationError(Pull); + + Push = ReactiveCommand.CreateAsyncObservable( + this.WhenAnyValue(x => x.UpdateState) + .Cast<UpdateCommandState>() + .Select(x => x != null && x.PushEnabled), + DoPush); + SubscribeOperationError(Push); + + SyncSubmodules = ReactiveCommand.CreateAsyncTask( + this.WhenAnyValue(x => x.UpdateState) + .Cast<UpdateCommandState>() + .Select(x => x != null && x.SyncSubmodulesEnabled), + DoSyncSubmodules); + SyncSubmodules.Subscribe(_ => Refresh().ToObservable()); + SubscribeOperationError(SyncSubmodules); + + OpenOnGitHub = ReactiveCommand.Create().OnExecuteCompleted(DoOpenDetailsUrl); + + ShowReview = ReactiveCommand.Create().OnExecuteCompleted(DoShowReview); + } + + private void DoOpenDetailsUrl(object obj) + { + usageTracker.IncrementCounter(measuresModel => measuresModel.NumberOfPRDetailsOpenInGitHub).Forget(); + } + + /// <summary> + /// Gets the underlying pull request model. + /// </summary> + public PullRequestDetailModel Model + { + get { return model; } + private set + { + // PullRequestModel overrides Equals such that two PRs with the same number are + // considered equal. This was causing the Model not to be updated on refresh: + // we need to use ReferenceEquals. + if (!ReferenceEquals(model, value)) + { + this.RaisePropertyChanging(nameof(Model)); + model = value; + this.RaisePropertyChanged(nameof(Model)); + } + } + } + + /// <summary> + /// Gets the local repository. + /// </summary> + public ILocalRepositoryModel LocalRepository { get; private set; } + + /// <summary> + /// Gets the owner of the remote repository that contains the pull request. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + public string RemoteRepositoryOwner { get; private set; } + + /// <summary> + /// Gets the Pull Request number. + /// </summary> + public int Number { get; private set; } + + /// <summary> + /// Gets the Pull Request author. + /// </summary> + public IActorViewModel Author + { + get { return author; } + private set { this.RaiseAndSetIfChanged(ref author, value); } + } + + /// <summary> + /// Gets the session for the pull request. + /// </summary> + public IPullRequestSession Session { get; private set; } + + /// <summary> + /// Gets a string describing how to display the pull request's source branch. + /// </summary> + public string SourceBranchDisplayName + { + get { return sourceBranchDisplayName; } + private set { this.RaiseAndSetIfChanged(ref sourceBranchDisplayName, value); } + } + + /// <summary> + /// Gets a string describing how to display the pull request's target branch. + /// </summary> + public string TargetBranchDisplayName + { + get { return targetBranchDisplayName; } + private set { this.RaiseAndSetIfChanged(ref targetBranchDisplayName, value); } + } + + /// <summary> + /// Gets a value indicating whether the pull request branch is checked out. + /// </summary> + public bool IsCheckedOut + { + get { return isCheckedOut; } + private set { this.RaiseAndSetIfChanged(ref isCheckedOut, value); } + } + + /// <summary> + /// Gets a value indicating whether the pull request comes from a fork. + /// </summary> + public bool IsFromFork + { + get { return isFromFork; } + private set { this.RaiseAndSetIfChanged(ref isFromFork, value); } + } + + /// <summary> + /// Gets the pull request body. + /// </summary> + public string Body + { + get { return body; } + private set { this.RaiseAndSetIfChanged(ref body, value); } + } + + /// <summary> + /// Gets the state associated with the <see cref="Checkout"/> command. + /// </summary> + public IPullRequestCheckoutState CheckoutState + { + get { return checkoutState; } + private set { this.RaiseAndSetIfChanged(ref checkoutState, value); } + } + + /// <summary> + /// Gets the state associated with the <see cref="Pull"/> and <see cref="Push"/> commands. + /// </summary> + public IPullRequestUpdateState UpdateState + { + get { return updateState; } + private set { this.RaiseAndSetIfChanged(ref updateState, value); } + } + + /// <summary> + /// Gets the error message to be displayed in the action area as a result of an error in a + /// git operation. + /// </summary> + public string OperationError + { + get { return operationError; } + private set { this.RaiseAndSetIfChanged(ref operationError, value); } + } + + /// <summary> + /// Gets the latest pull request review for each user. + /// </summary> + public IReadOnlyList<IPullRequestReviewSummaryViewModel> Reviews + { + get { return reviews; } + private set { this.RaiseAndSetIfChanged(ref reviews, value); } + } + + /// <summary> + /// Gets the pull request's changed files. + /// </summary> + public IPullRequestFilesViewModel Files { get; } + + /// <summary> + /// Gets the web URL for the pull request. + /// </summary> + public Uri WebUrl + { + get { return webUrl; } + private set { this.RaiseAndSetIfChanged(ref webUrl, value); } + } + + /// <summary> + /// Gets a command that checks out the pull request locally. + /// </summary> + public ReactiveCommand<Unit> Checkout { get; } + + /// <summary> + /// Gets a command that pulls changes to the current branch. + /// </summary> + public ReactiveCommand<Unit> Pull { get; } + + /// <summary> + /// Gets a command that pushes changes from the current branch. + /// </summary> + public ReactiveCommand<Unit> Push { get; } + + /// <summary> + /// Sync submodules for PR branch. + /// </summary> + public ReactiveCommand<Unit> SyncSubmodules { get; } + + /// <summary> + /// Gets a command that opens the pull request on GitHub. + /// </summary> + public ReactiveCommand<object> OpenOnGitHub { get; } + + /// <summary> + /// Gets a command that navigates to a pull request review. + /// </summary> + public ReactiveCommand<object> ShowReview { get; } + + public IReadOnlyList<IPullRequestCheckViewModel> Checks + { + get { return checks; } + private set { this.RaiseAndSetIfChanged(ref checks, value); } + } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="connection">The connection to the repository host.</param> + /// <param name="owner">The pull request's repository owner.</param> + /// <param name="repo">The pull request's repository name.</param> + /// <param name="number">The pull request number.</param> + public async Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int number) + { + IsLoading = true; + + try + { + if (!string.Equals(repo, localRepository.Name, StringComparison.OrdinalIgnoreCase)) + { + throw new NotSupportedException("Showing pull requests from other repositories not yet supported."); + } + + LocalRepository = localRepository; + RemoteRepositoryOwner = owner; + Number = number; + WebUrl = localRepository.CloneUrl.ToRepositoryUrl(owner).Append("pull/" + number); + modelService = await modelServiceFactory.CreateAsync(connection); + Session = await sessionManager.GetSession(owner, repo, number); + await Load(Session.PullRequest); + teamExplorerContext.StatusChanged += RefreshIfActive; + } + catch (Exception ex) + { + Error = ex; + } + finally + { + IsLoading = false; + } + } + + void RefreshIfActive(object sender, EventArgs e) + { + if (active) + { + Refresh().Forget(); + } + else + { + refreshOnActivate = true; + } + } + + /// <summary> + /// Loads the view model from octokit models. + /// </summary> + /// <param name="pullRequest">The pull request model.</param> + public async Task Load(PullRequestDetailModel pullRequest) + { + try + { + var firstLoad = (Model == null); + Model = pullRequest; + Author = new ActorViewModel(pullRequest.Author); + Title = Resources.PullRequestNavigationItemText + " #" + pullRequest.Number; + + IsBusy = true; + IsFromFork = !pullRequestsService.IsPullRequestFromRepository(LocalRepository, pullRequest); + SourceBranchDisplayName = GetBranchDisplayName(IsFromFork, pullRequest.HeadRepositoryOwner, pullRequest.HeadRefName); + TargetBranchDisplayName = GetBranchDisplayName(IsFromFork, pullRequest.BaseRepositoryOwner, pullRequest.BaseRefName); + Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; + Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, pullRequest).ToList(); + + Checks = PullRequestCheckViewModel.Build(viewViewModelFactory, pullRequest)?.ToList(); + + await Files.InitializeAsync(Session); + + var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList(); + + IsCheckedOut = localBranches.Contains(LocalRepository.CurrentBranch); + + if (IsCheckedOut) + { + var divergence = await pullRequestsService.CalculateHistoryDivergence(LocalRepository, Model.Number); + var pullEnabled = divergence.BehindBy > 0; + var pushEnabled = divergence.AheadBy > 0 && !pullEnabled; + string pullToolTip; + string pushToolTip; + + if (pullEnabled) + { + pullToolTip = string.Format( + Resources.PullRequestDetailsPullToolTip, + IsFromFork ? Resources.Fork : Resources.Remote, + SourceBranchDisplayName); + } + else + { + pullToolTip = Resources.NoCommitsToPull; + } + + if (pushEnabled) + { + pushToolTip = string.Format( + Resources.PullRequestDetailsPushToolTip, + IsFromFork ? Resources.Fork : Resources.Remote, + SourceBranchDisplayName); + } + else if (divergence.AheadBy == 0) + { + pushToolTip = Resources.NoCommitsToPush; + } + else + { + pushToolTip = Resources.MustPullBeforePush; + } + + var submodulesToSync = await pullRequestsService.CountSubmodulesToSync(LocalRepository); + var syncSubmodulesToolTip = string.Format(Resources.SyncSubmodules, submodulesToSync); + + UpdateState = new UpdateCommandState(divergence, pullEnabled, pushEnabled, pullToolTip, pushToolTip, syncSubmodulesToolTip, submodulesToSync); + CheckoutState = null; + } + 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)); + var clean = await pullRequestsService.IsWorkingDirectoryClean(LocalRepository); + string disabled = null; + + if (pullRequest.HeadRepositoryOwner == null) + { + disabled = Resources.SourceRepositoryNoLongerAvailable; + } + else if (!clean) + { + disabled = Resources.WorkingDirectoryHasUncommittedCHanges; + } + + CheckoutState = new CheckoutCommandState(caption, disabled); + UpdateState = null; + } + + sessionSubscription?.Dispose(); + sessionSubscription = Session.WhenAnyValue(x => x.HasPendingReview) + .Skip(1) + .Subscribe(x => Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, Session.PullRequest).ToList()); + + if (firstLoad) + { + usageTracker.IncrementCounter(x => x.NumberOfPullRequestsOpened).Forget(); + } + + if (!isInCheckout) + { + pullRequestsService.RemoveUnusedRemotes(LocalRepository).Subscribe(_ => { }); + } + } + finally + { + IsBusy = false; + } + } + + /// <summary> + /// Refreshes the contents of the view model. + /// </summary> + public override async Task Refresh() + { + try + { + await ThreadingHelper.SwitchToMainThreadAsync(); + + Error = null; + OperationError = null; + IsBusy = true; + await Session.Refresh(); + await Load(Session.PullRequest); + } + catch (Exception ex) + { + log.Error( + ex, + "Error loading pull request {Owner}/{Repo}/{Number} from {Address}", + RemoteRepositoryOwner, + LocalRepository.Name, + Number, + modelService.ApiClient.HostAddress.Title); + Error = ex; + IsBusy = false; + } + } + + /// <summary> + /// Gets the full path to a file in the working directory. + /// </summary> + /// <param name="file">The file.</param> + /// <returns>The full path to the file in the working directory.</returns> + public string GetLocalFilePath(IPullRequestFileNode file) + { + return Path.Combine(LocalRepository.LocalPath, file.RelativePath); + } + + /// <inheritdoc/> + public override void Activated() + { + active = true; + + if (refreshOnActivate) + { + Refresh().Forget(); + refreshOnActivate = false; + } + } + + /// <inheritdoc/> + public override void Deactivated() => active = false; + + /// <inheritdoc/> + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + teamExplorerContext.StatusChanged -= RefreshIfActive; + } + } + + void SubscribeOperationError(ReactiveCommand<Unit> command) + { + command.ThrownExceptions.Subscribe(x => OperationError = x.Message); + command.IsExecuting.Select(x => x).Subscribe(x => OperationError = null); + } + + static string GetBranchDisplayName(bool isFromFork, string owner, string label) + { + if (owner != null) + { + return isFromFork ? owner + ':' + label : label; + } + else + { + return Resources.InvalidBranchName; + } + } + + IObservable<Unit> DoCheckout(object unused) + { + return Observable.Defer(async () => + { + var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, Model).ToList(); + + if (localBranches.Count > 0) + { + return pullRequestsService.SwitchToBranch(LocalRepository, Model); + } + else + { + return pullRequestsService + .GetDefaultLocalBranchName(LocalRepository, Model.Number, Model.Title) + .SelectMany(x => pullRequestsService.Checkout(LocalRepository, Model, x)); + } + }).Do(_ => + { + if (IsFromFork) + usageTracker.IncrementCounter(x => x.NumberOfForkPullRequestsCheckedOut).Forget(); + else + usageTracker.IncrementCounter(x => x.NumberOfLocalPullRequestsCheckedOut).Forget(); + }); + } + + IObservable<Unit> DoPull(object unused) + { + return pullRequestsService.Pull(LocalRepository) + .Do(_ => + { + if (IsFromFork) + usageTracker.IncrementCounter(x => x.NumberOfForkPullRequestPulls).Forget(); + else + usageTracker.IncrementCounter(x => x.NumberOfLocalPullRequestPulls).Forget(); + }); + } + + IObservable<Unit> DoPush(object unused) + { + return pullRequestsService.Push(LocalRepository) + .Do(_ => + { + if (IsFromFork) + usageTracker.IncrementCounter(x => x.NumberOfForkPullRequestPushes).Forget(); + else + usageTracker.IncrementCounter(x => x.NumberOfLocalPullRequestPushes).Forget(); + }); + } + + async Task DoSyncSubmodules(object unused) + { + try + { + IsBusy = true; + usageTracker.IncrementCounter(x => x.NumberOfSyncSubmodules).Forget(); + + var result = await syncSubmodulesCommand.SyncSubmodules(); + var complete = result.Item1; + var summary = result.Item2; + if (!complete) + { + throw new ApplicationException(summary); + } + } + finally + { + IsBusy = false; + } + } + + void DoShowReview(object item) + { + var review = (PullRequestReviewSummaryViewModel)item; + + if (review.State == PullRequestReviewState.Pending) + { + NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{Number}/review/new")); + } + else + { + NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{Number}/reviews/{review.User.Login}")); + } + } + + class CheckoutCommandState : IPullRequestCheckoutState + { + public CheckoutCommandState(string caption, string disabledMessage) + { + Caption = caption; + IsEnabled = disabledMessage == null; + ToolTip = disabledMessage ?? caption; + } + + public string Caption { get; } + public bool IsEnabled { get; } + public string ToolTip { get; } + } + + class UpdateCommandState : IPullRequestUpdateState + { + public UpdateCommandState( + BranchTrackingDetails divergence, + bool pullEnabled, + bool pushEnabled, + string pullToolTip, + string pushToolTip, + string syncSubmodulesToolTip, + int submodulesToSync) + { + CommitsAhead = divergence.AheadBy ?? 0; + CommitsBehind = divergence.BehindBy ?? 0; + PushEnabled = pushEnabled; + PullEnabled = pullEnabled; + PullToolTip = pullToolTip; + PushToolTip = pushToolTip; + SyncSubmodulesToolTip = syncSubmodulesToolTip; + SubmodulesToSync = submodulesToSync; + } + + public int CommitsAhead { get; } + public int CommitsBehind { get; } + public bool UpToDate => CommitsAhead == 0 && CommitsBehind == 0 && !SyncSubmodulesEnabled; + public bool PullEnabled { get; } + public bool PushEnabled { get; } + public bool SyncSubmodulesEnabled => SubmodulesToSync > 0; + public string PullToolTip { get; } + public string PushToolTip { get; } + public string SyncSubmodulesToolTip { get; } + public int SubmodulesToSync { get; } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs new file mode 100644 index 0000000000..c270780ff8 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A directory node in a pull request changes tree. + /// </summary> + public class PullRequestDirectoryNode : IPullRequestDirectoryNode + { + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestDirectoryNode"/> class. + /// </summary> + /// <param name="relativePath">The path to the directory, relative to the repository.</param> + public PullRequestDirectoryNode(string relativePath) + { + DirectoryName = System.IO.Path.GetFileName(relativePath); + RelativePath = relativePath.Replace("/", "\\"); + Directories = new List<IPullRequestDirectoryNode>(); + Files = new List<IPullRequestFileNode>(); + } + + /// <summary> + /// Gets the name of the directory without path information. + /// </summary> + public string DirectoryName { get; } + + /// <summary> + /// Gets the path to the directory, relative to the root of the repository. + /// </summary> + public string RelativePath { get; } + + /// <summary> + /// Gets the directory children of the node. + /// </summary> + public IList<IPullRequestDirectoryNode> Directories { get; } + + /// <summary> + /// Gets the file children of the node. + /// </summary> + public IList<IPullRequestFileNode> Files { get; } + + /// <summary> + /// Gets the children of the directory. + /// </summary> + public IEnumerable<IPullRequestChangeNode> Children + { + get { return Directories.Cast<IPullRequestChangeNode>().Concat(Files); } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs new file mode 100644 index 0000000000..f9e1e4e164 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A file node in a pull request changes tree. + /// </summary> + public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode + { + int commentCount; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestFileNode"/> class. + /// </summary> + /// <param name="repositoryPath">The absolute path to the repository.</param> + /// <param name="relativePath">The path to the file, relative to the repository.</param> + /// <param name="sha">The SHA of the file.</param> + /// <param name="status">The way the file was changed.</param> + /// <param name="statusDisplay">The string to display in the [message] box next to the filename.</param> + /// <param name="oldPath"> + /// The old path of a moved/renamed file, relative to the repository. Should be null if the + /// file was not moved/renamed. + /// </param> + public PullRequestFileNode( + string repositoryPath, + string relativePath, + string sha, + PullRequestFileStatus status, + string oldPath) + { + Guard.ArgumentNotEmptyString(repositoryPath, nameof(repositoryPath)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + Guard.ArgumentNotEmptyString(sha, nameof(sha)); + + FileName = Path.GetFileName(relativePath); + RelativePath = relativePath.Replace("/", "\\"); + Sha = sha; + Status = status; + OldPath = oldPath; + + if (status == PullRequestFileStatus.Added) + { + StatusDisplay = Resources.AddedFileStatus; + } + else if (status == PullRequestFileStatus.Renamed) + { + if (oldPath != null) + { + StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(relativePath) ? + Path.GetFileName(oldPath) : oldPath; + } + else + { + StatusDisplay = Resources.RenamedFileStatus; + } + } + } + + /// <summary> + /// Gets the name of the file without path information. + /// </summary> + public string FileName { get; } + + /// <summary> + /// Gets the path to the file, relative to the root of the repository. + /// </summary> + public string RelativePath { get; } + + /// <summary> + /// Gets the old path of a moved/renamed file, relative to the root of the repository. + /// </summary> + public string OldPath { get; } + + /// <summary> + /// Gets the SHA of the file. + /// </summary> + public string Sha { get; } + + /// <summary> + /// Gets the type of change that was made to the file. + /// </summary> + public PullRequestFileStatus Status { get; } + + /// <summary> + /// Gets the string to display in the [message] box next to the filename. + /// </summary> + public string StatusDisplay { get; } + + /// <summary> + /// Gets or sets the number of review comments on the file. + /// </summary> + public int CommentCount + { + get { return commentCount; } + set { this.RaiseAndSetIfChanged(ref commentCount, value); } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs new file mode 100644 index 0000000000..f512c0f57a --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using System.Windows.Input; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using ReactiveUI; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// View model displaying a tree of changed files in a pull request. + /// </summary> + [Export(typeof(IPullRequestFilesViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class PullRequestFilesViewModel : ViewModelBase, IPullRequestFilesViewModel + { + readonly IPullRequestService service; + readonly BehaviorSubject<bool> isBranchCheckedOut = new BehaviorSubject<bool>(false); + + IPullRequestSession pullRequestSession; + Func<IInlineCommentThreadModel, bool> commentFilter; + int changedFilesCount; + IReadOnlyList<IPullRequestChangeNode> items; + CompositeDisposable subscriptions; + + [ImportingConstructor] + public PullRequestFilesViewModel( + IPullRequestService service, + IPullRequestEditorService editorService) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(editorService, nameof(editorService)); + + this.service = service; + + DiffFile = ReactiveCommand.CreateAsyncTask(x => + (Task)editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, "HEAD")); + ViewFile = ReactiveCommand.CreateAsyncTask(x => + (Task)editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, false)); + DiffFileWithWorkingDirectory = ReactiveCommand.CreateAsyncTask( + isBranchCheckedOut, + x => (Task)editorService.OpenDiff(pullRequestSession, ((IPullRequestFileNode)x).RelativePath)); + OpenFileInWorkingDirectory = new NonDeletedFileCommand( + isBranchCheckedOut, + x => (Task)editorService.OpenFile(pullRequestSession, ((IPullRequestFileNode)x).RelativePath, true)); + + OpenFirstComment = ReactiveCommand.CreateAsyncTask(async x => + { + var file = (IPullRequestFileNode)x; + var thread = await GetFirstCommentThread(file); + + if (thread != null) + { + await editorService.OpenDiff(pullRequestSession, file.RelativePath, thread); + } + }); + } + + /// <inheritdoc/> + public int ChangedFilesCount + { + get { return changedFilesCount; } + private set { this.RaiseAndSetIfChanged(ref changedFilesCount, value); } + } + + /// <inheritdoc/> + public IReadOnlyList<IPullRequestChangeNode> Items + { + get { return items; } + private set { this.RaiseAndSetIfChanged(ref items, value); } + } + + /// <inheritdoc/> + public void Dispose() + { + subscriptions?.Dispose(); + subscriptions = null; + } + + /// <inheritdoc/> + public async Task InitializeAsync( + IPullRequestSession session, + Func<IInlineCommentThreadModel, bool> filter = null) + { + Guard.ArgumentNotNull(session, nameof(session)); + + subscriptions?.Dispose(); + this.pullRequestSession = session; + this.commentFilter = filter; + subscriptions = new CompositeDisposable(); + subscriptions.Add(session.WhenAnyValue(x => x.IsCheckedOut).Subscribe(isBranchCheckedOut)); + + var dirs = new Dictionary<string, PullRequestDirectoryNode> + { + { string.Empty, new PullRequestDirectoryNode(string.Empty) } + }; + + using (var changes = await service.GetTreeChanges(session.LocalRepository, session.PullRequest)) + { + foreach (var changedFile in session.PullRequest.ChangedFiles) + { + var node = new PullRequestFileNode( + session.LocalRepository.LocalPath, + changedFile.FileName, + changedFile.Sha, + changedFile.Status, + GetOldFileName(changedFile, changes)); + var file = await session.GetFile(changedFile.FileName); + + if (file != null) + { + subscriptions.Add(file.WhenAnyValue(x => x.InlineCommentThreads) + .Subscribe(x => node.CommentCount = CountComments(x, filter))); + } + + var dir = GetDirectory(Path.GetDirectoryName(node.RelativePath), dirs); + dir.Files.Add(node); + } + } + + ChangedFilesCount = session.PullRequest.ChangedFiles.Count; + Items = dirs[string.Empty].Children.ToList(); + } + + /// <inheritdoc/> + public ReactiveCommand<Unit> DiffFile { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> ViewFile { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> DiffFileWithWorkingDirectory { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> OpenFileInWorkingDirectory { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> OpenFirstComment { get; } + + static int CountComments( + IEnumerable<IInlineCommentThreadModel> thread, + Func<IInlineCommentThreadModel, bool> commentFilter) + { + return thread.Count(x => x.LineNumber != -1 && (commentFilter?.Invoke(x) ?? true)); + } + + static PullRequestDirectoryNode GetDirectory(string path, Dictionary<string, PullRequestDirectoryNode> dirs) + { + PullRequestDirectoryNode dir; + + if (!dirs.TryGetValue(path, out dir)) + { + var parentPath = Path.GetDirectoryName(path); + var parentDir = GetDirectory(parentPath, dirs); + + dir = new PullRequestDirectoryNode(path); + + if (!parentDir.Directories.Any(x => x.DirectoryName == dir.DirectoryName)) + { + parentDir.Directories.Add(dir); + dirs.Add(path, dir); + } + } + + return dir; + } + + 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; + } + + return null; + } + + async Task<IInlineCommentThreadModel> GetFirstCommentThread(IPullRequestFileNode file) + { + var sessionFile = await pullRequestSession.GetFile(file.RelativePath); + var threads = sessionFile.InlineCommentThreads.AsEnumerable(); + + if (commentFilter != null) + { + threads = threads.Where(commentFilter); + } + + return threads.FirstOrDefault(); + } + + /// <summary> + /// Implements the <see cref="OpenFileInWorkingDirectory"/> command. + /// </summary> + /// <remarks> + /// We need to "Open File in Solution" when the parameter passed to the command parameter + /// represents a deleted file. ReactiveCommand doesn't allow us to change the CanExecute + /// state depending on the parameter, so we override + /// <see cref="ICommand.CanExecute(object)"/> to do this ourselves. + /// </remarks> + class NonDeletedFileCommand : ReactiveCommand<Unit>, ICommand + { + public NonDeletedFileCommand( + IObservable<bool> canExecute, + Func<object, Task> executeAsync) + : base(canExecute, x => executeAsync(x).ToObservable()) + { + } + + bool ICommand.CanExecute(object parameter) + { + if (parameter is IPullRequestFileNode node) + { + if (node.Status == PullRequestFileStatus.Removed) + { + return false; + } + } + + return CanExecute(parameter); + } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs new file mode 100644 index 0000000000..b60cf0e70b --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs @@ -0,0 +1,57 @@ +using System; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model which displays an item in a <see cref="PullRequestListViewModel"/>. + /// </summary> + public class PullRequestListItemViewModel : ViewModelBase, IPullRequestListItemViewModel + { + bool isCurrent; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestListItemViewModel"/> class. + /// </summary> + /// <param name="model">The underlying pull request item model.</param> + public PullRequestListItemViewModel(PullRequestListItemModel model) + { + Id = model.Id; + Author = new ActorViewModel(model.Author); + Checks = model.Checks; + CommentCount = model.CommentCount; + Number = model.Number; + Title = model.Title; + UpdatedAt = model.UpdatedAt; + } + + /// <inheritdoc/> + public string Id { get; } + + /// <inheritdoc/> + public IActorViewModel Author { get; } + + /// <inheritdoc/> + public PullRequestChecksState Checks { get; } + + /// <inheritdoc/> + public int CommentCount { get; } + + /// <inheritdoc/> + public bool IsCurrent + { + get { return isCurrent; } + internal set { this.RaiseAndSetIfChanged(ref isCurrent, value); } + } + + /// <inheritdoc/> + public int Number { get; } + + /// <inheritdoc/> + public string Title { get; } + + /// <inheritdoc/> + public DateTimeOffset UpdatedAt { get; } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs new file mode 100644 index 0000000000..8e705a1757 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Collections; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using ReactiveUI; +using static System.FormattableString; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model which displays a pull request list. + /// </summary> + [Export(typeof(IPullRequestListViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestListViewModel : IssueListViewModelBase, IPullRequestListViewModel + { + static readonly IReadOnlyList<string> states = new[] { "Open", "Closed", "All" }; + readonly IPullRequestSessionManager sessionManager; + readonly IPullRequestService service; + readonly IDisposable subscription; + ObservableAsPropertyHelper<Uri> webUrl; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestListViewModel"/> class. + /// </summary> + /// <param name="sessionManager">The session manager.</param> + /// <param name="repositoryService">The repository service.</param> + /// <param name="service">The pull request service.</param> + [ImportingConstructor] + public PullRequestListViewModel( + IPullRequestSessionManager sessionManager, + IRepositoryService repositoryService, + IPullRequestService service) + : base(repositoryService) + { + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + Guard.ArgumentNotNull(service, nameof(service)); + + this.sessionManager = sessionManager; + this.service = service; + + subscription = sessionManager.WhenAnyValue(x => x.CurrentSession.PullRequest.Number).Subscribe(UpdateCurrent); + webUrl = this.WhenAnyValue(x => x.RemoteRepository) + .Select(x => x?.CloneUrl?.ToRepositoryUrl().Append("pulls")) + .ToProperty(this, x => x.WebUrl); + CreatePullRequest = ReactiveCommand.Create().OnExecuteCompleted(_ => NavigateTo("pull/new")); + OpenItemInBrowser = ReactiveCommand.Create(); + } + + /// <inheritdoc/> + public override IReadOnlyList<string> States => states; + + /// <inheritdoc/> + public Uri WebUrl => webUrl.Value; + + /// <inheritdoc/> + public ReactiveCommand<object> CreatePullRequest { get; } + + /// <inheritdoc/> + public ReactiveCommand<object> OpenItemInBrowser { get; } + + /// <inheritdoc/> + protected override IVirtualizingListSource<IIssueListItemViewModelBase> CreateItemSource() + { + return new ItemSource(this); + } + + /// <inheritdoc/> + protected override Task DoOpenItem(IIssueListItemViewModelBase item) + { + var i = (IPullRequestListItemViewModel)item; + NavigateTo(Invariant($"{RemoteRepository.Owner}/{RemoteRepository.Name}/pull/{i.Number}")); + return Task.CompletedTask; + } + + /// <inheritdoc/> + protected override Task<Page<ActorModel>> LoadAuthors(string after) + { + return service.ReadAssignableUsers( + HostAddress.Create(LocalRepository.CloneUrl), + LocalRepository.Owner, + LocalRepository.Name, + after); + } + + void UpdateCurrent(int number) + { + if (Items != null) + { + foreach (var i in Items) + { + var item = i as PullRequestListItemViewModel; + + if (item != null) + { + item.IsCurrent = item.Number == number; + } + } + } + } + + class ItemSource : SequentialListSource<PullRequestListItemModel, IIssueListItemViewModelBase> + { + readonly PullRequestListViewModel owner; + + public ItemSource(PullRequestListViewModel owner) + { + this.owner = owner; + } + + protected override IIssueListItemViewModelBase CreateViewModel(PullRequestListItemModel model) + { + var result = new PullRequestListItemViewModel(model); + result.IsCurrent = owner.sessionManager.CurrentSession?.PullRequest.Number == model.Number; + return result; + } + + protected override async Task<Page<PullRequestListItemModel>> LoadPage(string after) + { + PullRequestStateEnum[] states; + + switch (owner.SelectedState) + { + case "Open": + states = new[] { PullRequestStateEnum.Open }; + break; + case "Closed": + states = new[] { PullRequestStateEnum.Closed, PullRequestStateEnum.Merged }; + break; + default: + states = new[] { PullRequestStateEnum.Open, PullRequestStateEnum.Closed, PullRequestStateEnum.Merged }; + break; + } + + var result = await owner.service.ReadPullRequests( + HostAddress.Create(owner.RemoteRepository.CloneUrl), + owner.RemoteRepository.Owner, + owner.RemoteRepository.Name, + after, + states).ConfigureAwait(false); + return result; + } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs new file mode 100644 index 0000000000..82637c454e --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; +using Serilog; +using static System.FormattableString; + +namespace GitHub.ViewModels.GitHubPane +{ + [Export(typeof(IPullRequestReviewAuthoringViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestReviewAuthoringViewModel : PanePageViewModelBase, IPullRequestReviewAuthoringViewModel + { + static readonly ILogger log = LogManager.ForContext<PullRequestReviewAuthoringViewModel>(); + + readonly IPullRequestEditorService editorService; + readonly IPullRequestSessionManager sessionManager; + readonly IPullRequestService pullRequestService; + IPullRequestSession session; + IDisposable sessionSubscription; + PullRequestReviewModel model; + PullRequestDetailModel pullRequestModel; + string body; + ObservableAsPropertyHelper<bool> canApproveRequestChanges; + IReadOnlyList<IPullRequestReviewFileCommentViewModel> fileComments; + string operationError; + + [ImportingConstructor] + public PullRequestReviewAuthoringViewModel( + IPullRequestService pullRequestService, + IPullRequestEditorService editorService, + IPullRequestSessionManager sessionManager, + IPullRequestFilesViewModel files) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + Guard.ArgumentNotNull(files, nameof(files)); + + this.pullRequestService = pullRequestService; + this.editorService = editorService; + this.sessionManager = sessionManager; + + canApproveRequestChanges = this.WhenAnyValue( + x => x.Model, + x => x.PullRequestModel, + (review, pr) => review != null && pr != null && review.Author.Login != pr.Author.Login) + .ToProperty(this, x => x.CanApproveRequestChanges); + + Files = files; + + var hasBodyOrComments = this.WhenAnyValue( + x => x.Body, + x => x.FileComments.Count, + (body, comments) => !string.IsNullOrWhiteSpace(body) || comments > 0); + + Approve = ReactiveCommand.CreateAsyncTask(_ => DoSubmit(Octokit.PullRequestReviewEvent.Approve)); + Comment = ReactiveCommand.CreateAsyncTask( + hasBodyOrComments, + _ => DoSubmit(Octokit.PullRequestReviewEvent.Comment)); + RequestChanges = ReactiveCommand.CreateAsyncTask( + hasBodyOrComments, + _ => DoSubmit(Octokit.PullRequestReviewEvent.RequestChanges)); + Cancel = ReactiveCommand.CreateAsyncTask(DoCancel); + NavigateToPullRequest = ReactiveCommand.Create().OnExecuteCompleted(_ => + NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{PullRequestModel.Number}"))); + } + + /// <inheritdoc/> + public ILocalRepositoryModel LocalRepository { get; private set; } + + /// <inheritdoc/> + public string RemoteRepositoryOwner { get; private set; } + + /// <inheritdoc/> + public PullRequestReviewModel Model + { + get { return model; } + private set { this.RaiseAndSetIfChanged(ref model, value); } + } + + /// <inheritdoc/> + public PullRequestDetailModel PullRequestModel + { + get { return pullRequestModel; } + private set { this.RaiseAndSetIfChanged(ref pullRequestModel, value); } + } + + /// <inheritdoc/> + public IPullRequestFilesViewModel Files { get; } + + /// <inheritdoc/> + public string Body + { + get { return body; } + set { this.RaiseAndSetIfChanged(ref body, value); } + } + + /// <inheritdoc/> + public bool CanApproveRequestChanges => canApproveRequestChanges.Value; + + /// <summary> + /// Gets the error message to be displayed in the action area as a result of an error in a + /// git operation. + /// </summary> + public string OperationError + { + get { return operationError; } + private set { this.RaiseAndSetIfChanged(ref operationError, value); } + } + + /// <inheritdoc/> + public IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments + { + get { return fileComments; } + private set { this.RaiseAndSetIfChanged(ref fileComments, value); } + } + + public ReactiveCommand<object> NavigateToPullRequest { get; } + public ReactiveCommand<Unit> Approve { get; } + public ReactiveCommand<Unit> Comment { get; } + public ReactiveCommand<Unit> RequestChanges { get; } + public ReactiveCommand<Unit> Cancel { get; } + + public async Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber) + { + if (repo != localRepository.Name) + { + throw new NotSupportedException(); + } + + IsLoading = true; + + try + { + LocalRepository = localRepository; + RemoteRepositoryOwner = owner; + session = await sessionManager.GetSession(owner, repo, pullRequestNumber); + await Load(session.PullRequest); + } + finally + { + IsLoading = false; + } + } + + /// <inheritdoc/> + public override async Task Refresh() + { + try + { + Error = null; + IsBusy = true; + await session.Refresh(); + await Load(session.PullRequest); + } + catch (Exception ex) + { + log.Error( + ex, + "Error loading pull request review {Owner}/{Repo}/{Number}/{PullRequestReviewId} from {Address}", + RemoteRepositoryOwner, + LocalRepository.Name, + PullRequestModel.Number, + Model.Id, + session.LocalRepository.CloneUrl.Host); + Error = ex; + IsBusy = false; + } + } + + async Task Load(PullRequestDetailModel pullRequest) + { + try + { + PullRequestModel = pullRequest; + + Model = pullRequest.Reviews.FirstOrDefault(x => + x.State == PullRequestReviewState.Pending && x.Author.Login == session.User.Login) ?? + new PullRequestReviewModel + { + Body = string.Empty, + Author = session.User, + State = PullRequestReviewState.Pending, + }; + + Body = Model.Body; + + sessionSubscription?.Dispose(); + await UpdateFileComments(); + sessionSubscription = session.PullRequestChanged.Subscribe(_ => UpdateFileComments().Forget()); + } + finally + { + IsBusy = false; + } + } + + bool FilterComments(IInlineCommentThreadModel thread) + { + return thread.Comments.Any(x => x.Review.Id == Model.Id); + } + + async Task UpdateFileComments() + { + var result = new List<PullRequestReviewCommentViewModel>(); + + if (Model.Id == null && session.PendingReviewId != null) + { + Model.Id = session.PendingReviewId; + } + + foreach (var file in await session.GetAllFiles()) + { + foreach (var thread in file.InlineCommentThreads) + { + foreach (var comment in thread.Comments) + { + if (comment.Review.Id == Model.Id) + { + result.Add(new PullRequestReviewCommentViewModel( + editorService, + session, + thread.RelativePath, + comment.Comment)); + } + } + } + } + + FileComments = result; + await Files.InitializeAsync(session, FilterComments); + } + + async Task DoSubmit(Octokit.PullRequestReviewEvent e) + { + OperationError = null; + IsBusy = true; + + try + { + await session.PostReview(Body, e); + Close(); + } + catch (Exception ex) + { + OperationError = ex.Message; + } + finally + { + IsBusy = false; + } + } + + async Task DoCancel(object arg) + { + OperationError = null; + IsBusy = true; + + try + { + if (Model?.Id != null) + { + if (pullRequestService.ConfirmCancelPendingReview()) + { + await session.CancelReview(); + Close(); + } + } + else + { + Close(); + } + + } + catch (Exception ex) + { + OperationError = ex.Message; + } + finally + { + IsBusy = false; + } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs new file mode 100644 index 0000000000..c3bb7295d5 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs @@ -0,0 +1,70 @@ +using System; +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 +{ + /// <summary> + /// A view model for a file comment in a <see cref="PullRequestReviewViewModel"/>. + /// </summary> + public class PullRequestReviewCommentViewModel : IPullRequestReviewFileCommentViewModel + { + readonly IPullRequestEditorService editorService; + readonly IPullRequestSession session; + readonly PullRequestReviewCommentModel model; + IInlineCommentThreadModel thread; + + public PullRequestReviewCommentViewModel( + IPullRequestEditorService editorService, + IPullRequestSession session, + string relativePath, + PullRequestReviewCommentModel model) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotNull(model, nameof(model)); + + this.editorService = editorService; + this.session = session; + this.model = model; + RelativePath = relativePath; + + Open = ReactiveCommand.CreateAsyncTask(DoOpen); + } + + /// <inheritdoc/> + public string Body => model.Body; + + /// <inheritdoc/> + public string RelativePath { get; set; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> Open { get; } + + async Task DoOpen(object o) + { + try + { + 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 (thread != null && thread.LineNumber != -1) + { + await editorService.OpenDiff(session, RelativePath, thread); + } + } + catch (Exception) + { + // TODO: Show error. + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs new file mode 100644 index 0000000000..3ec2fb4e7d --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewSummaryViewModel.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GitHub.App; +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Displays a short overview of a pull request review in the <see cref="PullRequestDetailViewModel"/>. + /// </summary> + public class PullRequestReviewSummaryViewModel : IPullRequestReviewSummaryViewModel + { + /// <inheritdoc/> + public string Id { get; set; } + + /// <inheritdoc/> + public IActorViewModel User { get; set; } + + /// <inheritdoc/> + public PullRequestReviewState State { get; set; } + + /// <inheritdoc/> + public string StateDisplay => ToString(State); + + /// <inheritdoc/> + public int FileCommentCount { get; set; } + + /// <summary> + /// Builds a collection of <see cref="PullRequestReviewSummaryViewModel"/>s by user. + /// </summary> + /// <param name="currentUser">The current user.</param> + /// <param name="pullRequest">The pull request model.</param> + /// <remarks> + /// This method builds a list similar to that found in the "Reviewers" section at the top- + /// right of the Pull Request page on GitHub. + /// </remarks> + public static IEnumerable<PullRequestReviewSummaryViewModel> BuildByUser( + ActorModel currentUser, + PullRequestDetailModel pullRequest) + { + var existing = new Dictionary<string, PullRequestReviewSummaryViewModel>(); + + foreach (var review in pullRequest.Reviews.OrderBy(x => x.SubmittedAt)) + { + if (review.State == PullRequestReviewState.Pending && review.Author.Login != currentUser.Login) + continue; + + PullRequestReviewSummaryViewModel previous; + existing.TryGetValue(review.Author.Login, out previous); + + var previousPriority = ToPriority(previous); + var reviewPriority = ToPriority(review.State); + + if (reviewPriority >= previousPriority) + { + existing[review.Author.Login] = new PullRequestReviewSummaryViewModel + { + Id = review.Id, + User = new ActorViewModel(review.Author), + State = review.State, + FileCommentCount = review.Comments.Count, + }; + } + } + + var result = existing.Values.OrderBy(x => x.User.Login).AsEnumerable(); + + if (!result.Any(x => x.State == PullRequestReviewState.Pending)) + { + var newReview = new PullRequestReviewSummaryViewModel + { + State = PullRequestReviewState.Pending, + User = new ActorViewModel(currentUser), + }; + result = result.Concat(new[] { newReview }); + } + + return result; + } + + static int ToPriority(PullRequestReviewSummaryViewModel review) + { + return review != null ? ToPriority(review.State) : 0; + } + + static int ToPriority(PullRequestReviewState state) + { + switch (state) + { + case PullRequestReviewState.Approved: + case PullRequestReviewState.ChangesRequested: + case PullRequestReviewState.Dismissed: + return 1; + case PullRequestReviewState.Pending: + return 2; + default: + return 0; + } + } + + static string ToString(PullRequestReviewState state) + { + switch (state) + { + case PullRequestReviewState.Approved: + return Resources.Approved; + case PullRequestReviewState.ChangesRequested: + return Resources.ChangesRequested; + case PullRequestReviewState.Commented: + case PullRequestReviewState.Dismissed: + return Resources.Commented; + case PullRequestReviewState.Pending: + return Resources.InProgress; + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs new file mode 100644 index 0000000000..8ff45e8442 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewViewModel.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// View model for displaying details of a pull request review. + /// </summary> + public class PullRequestReviewViewModel : ViewModelBase, IPullRequestReviewViewModel + { + bool isExpanded; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestReviewViewModel"/> class. + /// </summary> + /// <param name="editorService">The pull request editor service.</param> + /// <param name="session">The pull request session.</param> + /// <param name="model">The pull request review model.</param> + public PullRequestReviewViewModel( + IPullRequestEditorService editorService, + IPullRequestSession session, + PullRequestReviewModel model) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotNull(model, nameof(model)); + + Model = model; + Body = string.IsNullOrWhiteSpace(Model.Body) ? null : Model.Body; + StateDisplay = ToString(Model.State); + + var comments = new List<IPullRequestReviewFileCommentViewModel>(); + var outdated = new List<IPullRequestReviewFileCommentViewModel>(); + + foreach (var comment in model.Comments) + { + if (comment.Thread != null) + { + var vm = new PullRequestReviewCommentViewModel( + editorService, + session, + comment.Thread.Path, + comment); + + if (comment.Thread.Position != null) + comments.Add(vm); + else + outdated.Add(vm); + } + } + + FileComments = comments; + OutdatedFileComments = outdated; + + HasDetails = Body != null || + FileComments.Count > 0 || + OutdatedFileComments.Count > 0; + } + + /// <inheritdoc/> + public PullRequestReviewModel Model { get; } + + /// <inheritdoc/> + public string Body { get; } + + /// <inheritdoc/> + public string StateDisplay { get; } + + /// <inheritdoc/> + public bool IsExpanded + { + get { return isExpanded; } + set { this.RaiseAndSetIfChanged(ref isExpanded, value); } + } + + /// <inheritdoc/> + public bool HasDetails { get; } + + /// <inheritdoc/> + public IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments { get; } + + /// <inheritdoc/> + public IReadOnlyList<IPullRequestReviewFileCommentViewModel> OutdatedFileComments { get; } + + static string ToString(PullRequestReviewState state) + { + switch (state) + { + case PullRequestReviewState.Approved: + return "approved"; + case PullRequestReviewState.ChangesRequested: + return "requested changes"; + case PullRequestReviewState.Commented: + case PullRequestReviewState.Dismissed: + return "commented"; + default: + throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs new file mode 100644 index 0000000000..6f75160792 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; +using Serilog; +using static System.FormattableString; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Displays all reviews made by a user on a pull request. + /// </summary> + [Export(typeof(IPullRequestUserReviewsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestUserReviewsViewModel : PanePageViewModelBase, IPullRequestUserReviewsViewModel + { + static readonly ILogger log = LogManager.ForContext<PullRequestReviewViewModel>(); + + readonly IPullRequestEditorService editorService; + readonly IPullRequestSessionManager sessionManager; + IPullRequestSession session; + string login; + IActorViewModel user; + string title; + IReadOnlyList<IPullRequestReviewViewModel> reviews; + + [ImportingConstructor] + public PullRequestUserReviewsViewModel( + IPullRequestEditorService editorService, + IPullRequestSessionManager sessionManager) + { + Guard.ArgumentNotNull(editorService, nameof(editorService)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + + this.editorService = editorService; + this.sessionManager = sessionManager; + + NavigateToPullRequest = ReactiveCommand.Create().OnExecuteCompleted(_ => + NavigateTo(Invariant($"{LocalRepository.Owner}/{LocalRepository.Name}/pull/{PullRequestNumber}"))); + } + + /// <inheritdoc/> + public ILocalRepositoryModel LocalRepository { get; private set; } + + /// <inheritdoc/> + public string RemoteRepositoryOwner { get; private set; } + + /// <inheritdoc/> + public int PullRequestNumber { get; private set; } + + public IActorViewModel User + { + get { return user; } + private set { this.RaiseAndSetIfChanged(ref user, value); } + } + + /// <inheritdoc/> + public IReadOnlyList<IPullRequestReviewViewModel> Reviews + { + get { return reviews; } + private set { this.RaiseAndSetIfChanged(ref reviews, value); } + } + + /// <inheritdoc/> + public string PullRequestTitle + { + get { return title; } + private set { this.RaiseAndSetIfChanged(ref title, value); } + } + + /// <inheritdoc/> + public ReactiveCommand<object> NavigateToPullRequest { get; } + + /// <inheritdoc/> + [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "login")] + public async Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber, + string login) + { + if (repo != localRepository.Name) + { + throw new NotSupportedException(); + } + + IsLoading = true; + + try + { + LocalRepository = localRepository; + RemoteRepositoryOwner = owner; + PullRequestNumber = pullRequestNumber; + this.login = login; + session = await sessionManager.GetSession(owner, repo, pullRequestNumber); + await Load(session.PullRequest); + } + finally + { + IsLoading = false; + } + } + + /// <inheritdoc/> + public override async Task Refresh() + { + try + { + Error = null; + IsBusy = true; + await session.Refresh(); + await Load(session.PullRequest); + } + catch (Exception ex) + { + log.Error( + ex, + "Error loading pull request reviews {Owner}/{Repo}/{Number} from {Address}", + RemoteRepositoryOwner, + LocalRepository.Name, + PullRequestNumber, + LocalRepository.CloneUrl.Host); + Error = ex; + IsBusy = false; + } + } + + /// <inheritdoc/> + async Task Load(PullRequestDetailModel pullRequest) + { + IsBusy = true; + + try + { + await Task.Delay(0); + PullRequestTitle = pullRequest.Title; + + var reviews = new List<IPullRequestReviewViewModel>(); + var isFirst = true; + + foreach (var review in pullRequest.Reviews.OrderByDescending(x => x.SubmittedAt)) + { + if (review.Author.Login == login) + { + if (User == null) + { + User = new ActorViewModel(review.Author); + } + + if (review.State != PullRequestReviewState.Pending) + { + var vm = new PullRequestReviewViewModel(editorService, session, review); + vm.IsExpanded = isFirst; + reviews.Add(vm); + isFirst = false; + } + } + } + + Reviews = reviews; + + if (User == null) + { + User = new ActorViewModel(new ActorModel { Login = login }); + } + } + finally + { + IsBusy = false; + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/LoginControlViewModel.cs b/src/GitHub.App/ViewModels/LoginControlViewModel.cs deleted file mode 100644 index 719f869487..0000000000 --- a/src/GitHub.App/ViewModels/LoginControlViewModel.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Reactive.Linq; -using GitHub.Authentication; -using GitHub.Exports; -using GitHub.Models; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - [ExportViewModel(ViewType = UIViewType.Login)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class LoginControlViewModel : BaseViewModel, ILoginControlViewModel - { - [ImportingConstructor] - public LoginControlViewModel( - IRepositoryHosts hosts, - ILoginToGitHubViewModel loginToGitHubViewModel, - ILoginToGitHubForEnterpriseViewModel loginToGitHubEnterpriseViewModel) - { - Title = Resources.LoginTitle; - RepositoryHosts = hosts; - GitHubLogin = loginToGitHubViewModel; - EnterpriseLogin = loginToGitHubEnterpriseViewModel; - - isLoginInProgress = this.WhenAny( - x => x.GitHubLogin.IsLoggingIn, - x => x.EnterpriseLogin.IsLoggingIn, - (x, y) => x.Value || y.Value - ).ToProperty(this, vm => vm.IsLoginInProgress); - - loginMode = this.WhenAny( - x => x.RepositoryHosts.GitHubHost.IsLoggedIn, - x => x.RepositoryHosts.EnterpriseHost.IsLoggedIn, - (x, y) => - { - var canLogInToGitHub = x.Value == false; - var canLogInToEnterprise = y.Value == false; - - return canLogInToGitHub && canLogInToEnterprise ? LoginMode.DotComOrEnterprise - : canLogInToGitHub ? LoginMode.DotComOnly - : canLogInToEnterprise ? LoginMode.EnterpriseOnly - : LoginMode.None; - - }).ToProperty(this, x => x.LoginMode); - - AuthenticationResults = Observable.Amb( - loginToGitHubViewModel.Login, - EnterpriseLogin.Login); - CancelCommand = ReactiveCommand.Create(); - } - - ILoginToGitHubViewModel github; - public ILoginToGitHubViewModel GitHubLogin - { - get { return github; } - set { this.RaiseAndSetIfChanged(ref github, value); } - } - - ILoginToGitHubForEnterpriseViewModel githubEnterprise; - public ILoginToGitHubForEnterpriseViewModel EnterpriseLogin - { - get { return githubEnterprise; } - set { this.RaiseAndSetIfChanged(ref githubEnterprise, value); } - } - - IRepositoryHosts repositoryHosts; - public IRepositoryHosts RepositoryHosts - { - get { return repositoryHosts; } - set { this.RaiseAndSetIfChanged(ref repositoryHosts, value); } - } - - readonly ObservableAsPropertyHelper<LoginMode> loginMode; - public LoginMode LoginMode { get { return loginMode.Value; } } - - readonly ObservableAsPropertyHelper<bool> isLoginInProgress; - public bool IsLoginInProgress { get { return isLoginInProgress.Value; } } - - public IObservable<AuthenticationResult> AuthenticationResults { get; private set; } - } - - public enum LoginTarget - { - None = 0, - DotCom = 1, - Enterprise = 2, - } - - public enum VisualState - { - None = 0, - DotCom = 1, - Enterprise = 2, - DotComOnly = 3, - EnterpriseOnly = 4 - } -} diff --git a/src/GitHub.App/ViewModels/LoginTabViewModel.cs b/src/GitHub.App/ViewModels/LoginTabViewModel.cs deleted file mode 100644 index 77ede2183f..0000000000 --- a/src/GitHub.App/ViewModels/LoginTabViewModel.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Globalization; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using GitHub.Authentication; -using GitHub.Extensions; -using GitHub.Extensions.Reactive; -using GitHub.Info; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.Validation; -using NLog; -using NullGuard; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - public abstract class LoginTabViewModel : ReactiveObject - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - - protected LoginTabViewModel(IRepositoryHosts repositoryHosts, IVisualStudioBrowser browser) - { - RepositoryHosts = repositoryHosts; - - UsernameOrEmailValidator = ReactivePropertyValidator.For(this, x => x.UsernameOrEmail) - .IfNullOrEmpty(Resources.UsernameOrEmailValidatorEmpty) - .IfMatch(@"\s", Resources.UsernameOrEmailValidatorSpaces); - - PasswordValidator = ReactivePropertyValidator.For(this, x => x.Password) - .IfNullOrEmpty(Resources.PasswordValidatorEmpty); - - canLogin = this.WhenAny( - x => x.UsernameOrEmailValidator.ValidationResult.IsValid, - x => x.PasswordValidator.ValidationResult.IsValid, - (x, y) => x.Value && y.Value).ToProperty(this, x => x.CanLogin); - - Login = ReactiveCommand.CreateAsyncObservable(this.WhenAny(x => x.CanLogin, x => x.Value), LogIn); - - Login.ThrownExceptions.Subscribe(ex => - { - if (ex.IsCriticalException()) return; - - log.Info(string.Format(CultureInfo.InvariantCulture, "Error logging into '{0}' as '{1}'", BaseUri, UsernameOrEmail), ex); - if (ex is Octokit.ForbiddenException) - { - ShowLogInFailedError = true; - LoginFailedMessage = Resources.LoginFailedForbiddenMessage; - } - else - { - ShowConnectingToHostFailed = true; - } - }); - - isLoggingIn = Login.IsExecuting.ToProperty(this, x => x.IsLoggingIn); - - Reset = ReactiveCommand.CreateAsyncTask(_ => Clear()); - - NavigateForgotPassword = ReactiveCommand.CreateAsyncObservable(_ => - { - browser.OpenUrl(new Uri(BaseUri, GitHubUrls.ForgotPasswordPath)); - return Observable.Return(Unit.Default); - }); - - SignUp = ReactiveCommand.CreateAsyncObservable(_ => - { - browser.OpenUrl(GitHubUrls.Plans); - return Observable.Return(Unit.Default); - }); - } - protected IRepositoryHosts RepositoryHosts { get; } - protected abstract Uri BaseUri { get; } - public IReactiveCommand<Unit> SignUp { get; } - - public IReactiveCommand<AuthenticationResult> Login { get; } - public IReactiveCommand<Unit> Reset { get; } - public IReactiveCommand<Unit> NavigateForgotPassword { get; } - - string loginFailedMessage = Resources.LoginFailedMessage; - public string LoginFailedMessage - { - get { return loginFailedMessage; } - set { this.RaiseAndSetIfChanged(ref loginFailedMessage, value); } - } - - string usernameOrEmail; - [AllowNull] - public string UsernameOrEmail - { - [return: AllowNull] - get - { return usernameOrEmail; } - set { this.RaiseAndSetIfChanged(ref usernameOrEmail, value); } - } - - ReactivePropertyValidator usernameOrEmailValidator; - public ReactivePropertyValidator UsernameOrEmailValidator - { - get { return usernameOrEmailValidator; } - private set { this.RaiseAndSetIfChanged(ref usernameOrEmailValidator, value); } - } - - string password; - [AllowNull] - public string Password - { - [return: AllowNull] - get - { return password; } - set { this.RaiseAndSetIfChanged(ref password, value); } - } - - ReactivePropertyValidator passwordValidator; - public ReactivePropertyValidator PasswordValidator - { - get { return passwordValidator; } - private set { this.RaiseAndSetIfChanged(ref passwordValidator, value); } - } - - readonly ObservableAsPropertyHelper<bool> isLoggingIn; - public bool IsLoggingIn - { - get { return isLoggingIn.Value; } - } - - protected ObservableAsPropertyHelper<bool> canLogin; - public bool CanLogin - { - get { return canLogin.Value; } - } - - bool showLogInFailedError; - public bool ShowLogInFailedError - { - get { return showLogInFailedError; } - protected set { this.RaiseAndSetIfChanged(ref showLogInFailedError, value); } - } - - bool showConnectingToHostFailed; - public bool ShowConnectingToHostFailed - { - get { return showConnectingToHostFailed; } - set { this.RaiseAndSetIfChanged(ref showConnectingToHostFailed, value); } - } - - protected abstract IObservable<AuthenticationResult> LogIn(object args); - - protected IObservable<AuthenticationResult> LogInToHost(HostAddress hostAddress) - { - return Observable.Defer(() => - { - ShowLogInFailedError = false; - ShowConnectingToHostFailed = false; - - return hostAddress != null ? - RepositoryHosts.LogIn(hostAddress, UsernameOrEmail, Password) - : Observable.Return(AuthenticationResult.CredentialFailure); - }) - .ObserveOn(RxApp.MainThreadScheduler) - .Do(authResult => { - switch (authResult) - { - case AuthenticationResult.CredentialFailure: - ShowLogInFailedError = true; - break; - case AuthenticationResult.VerificationFailure: - break; - case AuthenticationResult.EnterpriseServerNotFound: - ShowConnectingToHostFailed = true; - break; - } - }) - .SelectMany(authResult => - { - switch (authResult) - { - case AuthenticationResult.CredentialFailure: - case AuthenticationResult.EnterpriseServerNotFound: - case AuthenticationResult.VerificationFailure: - Password = ""; - return Observable.FromAsync(PasswordValidator.ResetAsync) - .Select(_ => AuthenticationResult.CredentialFailure); - case AuthenticationResult.Success: - return Reset.ExecuteAsync() - .ContinueAfter(() => Observable.Return(AuthenticationResult.Success)); - default: - return Observable.Throw<AuthenticationResult>( - new InvalidOperationException("Unknown EnterpriseLoginResult: " + authResult)); - } - }); - } - - async Task Clear() - { - UsernameOrEmail = null; - Password = null; - await UsernameOrEmailValidator.ResetAsync(); - await PasswordValidator.ResetAsync(); - await ResetValidation(); - - ShowLogInFailedError = false; - } - - protected virtual Task ResetValidation() - { - // noop - return Task.FromResult(0); - } - } -} diff --git a/src/GitHub.App/ViewModels/LoginToGitHubForEnterpriseViewModel.cs b/src/GitHub.App/ViewModels/LoginToGitHubForEnterpriseViewModel.cs deleted file mode 100644 index 4f391d7787..0000000000 --- a/src/GitHub.App/ViewModels/LoginToGitHubForEnterpriseViewModel.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using GitHub.Authentication; -using GitHub.Info; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.Validation; -using NullGuard; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - [Export(typeof(ILoginToGitHubForEnterpriseViewModel))] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class LoginToGitHubForEnterpriseViewModel : LoginTabViewModel, ILoginToGitHubForEnterpriseViewModel - { - [ImportingConstructor] - public LoginToGitHubForEnterpriseViewModel(IRepositoryHosts hosts, IVisualStudioBrowser browser) : base(hosts, browser) - { - EnterpriseUrlValidator = ReactivePropertyValidator.For(this, x => x.EnterpriseUrl) - .IfNullOrEmpty(Resources.EnterpriseUrlValidatorEmpty) - .IfNotUri(Resources.EnterpriseUrlValidatorInvalid) - .IfGitHubDotComHost(Resources.EnterpriseUrlValidatorNotAGitHubHost); - - canLogin = this.WhenAny( - x => x.UsernameOrEmailValidator.ValidationResult.IsValid, - x => x.PasswordValidator.ValidationResult.IsValid, - x => x.EnterpriseUrlValidator.ValidationResult.IsValid, - (x, y, z) => x.Value && y.Value && z.Value) - .ToProperty(this, x => x.CanLogin); - - NavigateLearnMore = ReactiveCommand.CreateAsyncObservable(_ => - { - browser.OpenUrl(GitHubUrls.LearnMore); - return Observable.Return(Unit.Default); - - }); - } - - protected override IObservable<AuthenticationResult> LogIn(object args) - { - return LogInToHost(HostAddress.Create(EnterpriseUrl)); - } - - string enterpriseUrl; - [AllowNull] - public string EnterpriseUrl - { - [return: AllowNull] - get { return enterpriseUrl; } - set { this.RaiseAndSetIfChanged(ref enterpriseUrl, value); } - } - - public ReactivePropertyValidator EnterpriseUrlValidator { get; } - - protected override Uri BaseUri => new Uri(EnterpriseUrl); - - public IReactiveCommand<Unit> NavigateLearnMore - { - get; - } - - protected override async Task ResetValidation() - { - EnterpriseUrl = null; - await EnterpriseUrlValidator.ResetAsync(); - ShowConnectingToHostFailed = false; - } - } -} diff --git a/src/GitHub.App/ViewModels/LoginToGitHubViewModel.cs b/src/GitHub.App/ViewModels/LoginToGitHubViewModel.cs deleted file mode 100644 index b5557df5e2..0000000000 --- a/src/GitHub.App/ViewModels/LoginToGitHubViewModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Reactive; -using System.Reactive.Linq; -using GitHub.Authentication; -using GitHub.Info; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - [Export(typeof(ILoginToGitHubViewModel))] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class LoginToGitHubViewModel : LoginTabViewModel, ILoginToGitHubViewModel - { - [ImportingConstructor] - public LoginToGitHubViewModel(IRepositoryHosts repositoryHosts, IVisualStudioBrowser browser) - : base(repositoryHosts, browser) - { - BaseUri = HostAddress.GitHubDotComHostAddress.WebUri; - - NavigatePricing = ReactiveCommand.CreateAsyncObservable(_ => - { - browser.OpenUrl(GitHubUrls.Pricing); - return Observable.Return(Unit.Default); - - }); - } - - public IReactiveCommand<Unit> NavigatePricing { get; } - - protected override Uri BaseUri { get; } - - protected override IObservable<AuthenticationResult> LogIn(object args) - { - return LogInToHost(HostAddress.GitHubDotComHostAddress); - } - } -} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/PullRequestFileViewModel.cs b/src/GitHub.App/ViewModels/PullRequestFileViewModel.cs new file mode 100644 index 0000000000..97e2f31582 --- /dev/null +++ b/src/GitHub.App/ViewModels/PullRequestFileViewModel.cs @@ -0,0 +1,35 @@ +namespace GitHub.ViewModels +{ + /// <summary> + /// A file node in a pull request changes tree. + /// </summary> + public class PullRequestFileViewModel : IPullRequestFileViewModel + { + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestFileViewModel"/> class. + /// </summary> + /// <param name="path">The path to the file, relative to the repository.</param> + /// <param name="changeType">The way the file was changed.</param> + public PullRequestFileViewModel(string path, FileChangeType changeType) + { + ChangeType = changeType; + FileName = System.IO.Path.GetFileName(path); + Path = path; + } + + /// <summary> + /// Gets the type of change that was made to the file. + /// </summary> + public FileChangeType ChangeType { get; } + + /// <summary> + /// Gets the name of the file without path information. + /// </summary> + public string FileName { get; } + + /// <summary> + /// Gets the path to the file, relative to the root of the repository. + /// </summary> + public string Path { get; } + } +} diff --git a/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs b/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs deleted file mode 100644 index 77d9646aad..0000000000 --- a/src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Globalization; -using System.Reactive; -using System.Reactive.Linq; -using System.Windows.Input; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Services; -using GitHub.Validation; -using NLog; -using NullGuard; -using ReactiveUI; -using Rothko; - -namespace GitHub.ViewModels -{ - [ExportViewModel(ViewType=UIViewType.Clone)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class RepositoryCloneViewModel : BaseViewModel, IRepositoryCloneViewModel - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - - readonly IRepositoryHost repositoryHost; - readonly IRepositoryCloneService cloneService; - readonly IOperatingSystem operatingSystem; - readonly IVSServices vsServices; - readonly IReactiveCommand<IReadOnlyList<IRepositoryModel>> loadRepositoriesCommand; - readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create(); - readonly ObservableAsPropertyHelper<bool> isLoading; - readonly ObservableAsPropertyHelper<bool> noRepositoriesFound; - readonly ObservableAsPropertyHelper<bool> canClone; - string baseRepositoryPath; - bool loadingFailed; - - [ImportingConstructor] - RepositoryCloneViewModel( - IConnectionRepositoryHostMap connectionRepositoryHostMap, - IRepositoryCloneService repositoryCloneService, - IOperatingSystem operatingSystem, - IVSServices vsServices) - : this(connectionRepositoryHostMap.CurrentRepositoryHost, repositoryCloneService, operatingSystem, vsServices) - { } - - public RepositoryCloneViewModel( - IRepositoryHost repositoryHost, - IRepositoryCloneService cloneService, - IOperatingSystem operatingSystem, - IVSServices vsServices) - { - this.repositoryHost = repositoryHost; - this.cloneService = cloneService; - this.operatingSystem = operatingSystem; - this.vsServices = vsServices; - - Title = string.Format(CultureInfo.CurrentCulture, Resources.CloneTitle, repositoryHost.Title); - Repositories = new ReactiveList<IRepositoryModel>(); - loadRepositoriesCommand = ReactiveCommand.CreateAsyncObservable(OnLoadRepositories); - isLoading = this.WhenAny(x => x.LoadingFailed, x => x.Value) - .CombineLatest(loadRepositoriesCommand.IsExecuting, (failed, loading) => !failed && loading) - .ToProperty(this, x => x.IsLoading); - loadRepositoriesCommand.Subscribe(Repositories.AddRange); - filterTextIsEnabled = this.WhenAny(x => x.Repositories.Count, x => x.Value > 0) - .ToProperty(this, x => x.FilterTextIsEnabled); - noRepositoriesFound = this.WhenAny(x => x.FilterTextIsEnabled, x => x.IsLoading, x => x.LoadingFailed - , (any, loading, failed) => !any.Value && !loading.Value && !failed.Value) - .ToProperty(this, x => x.NoRepositoriesFound); - - var filterResetSignal = this.WhenAny(x => x.FilterText, x => x.Value) - .DistinctUntilChanged(StringComparer.OrdinalIgnoreCase) - .Throttle(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler); - - FilteredRepositories = Repositories.CreateDerivedCollection( - x => x, - filter: FilterRepository, - signalReset: filterResetSignal - ); - - BaseRepositoryPathValidator = this.CreateBaseRepositoryPathValidator(); - - 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.CreateAsyncObservable(canCloneObservable, OnCloneRepository); - - browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog()); - this.WhenAny(x => x.BaseRepositoryPathValidator.ValidationResult, x => x.Value) - .Subscribe(); - BaseRepositoryPath = cloneService.DefaultClonePath; - } - - IObservable<IReadOnlyList<IRepositoryModel>> OnLoadRepositories(object value) - { - return repositoryHost.ModelService.GetRepositories() - .Catch<IReadOnlyList<IRepositoryModel>, Exception>(ex => - { - log.Error("Error while loading repositories", ex); - return Observable.Start(() => LoadingFailed = true, RxApp.MainThreadScheduler) - .Select(_ => new IRepositoryModel[] { }); - }); - } - - bool FilterRepository(IRepositoryModel repo) - { - if (string.IsNullOrWhiteSpace(FilterText)) - return true; - - // Not matching on NameWithOwner here since that's already been filtered on by the selected account - return repo.Name.IndexOf(FilterText ?? "", StringComparison.OrdinalIgnoreCase) != -1; - } - - IObservable<Unit> OnCloneRepository(object state) - { - return Observable.Start(() => - { - var repository = SelectedRepository; - Debug.Assert(repository != null, "Should not be able to attempt to clone a repo when it's null"); - // The following is a noop if the directory already exists. - operatingSystem.Directory.CreateDirectory(BaseRepositoryPath); - return cloneService.CloneRepository(repository.CloneUrl, repository.Name, BaseRepositoryPath); - }) - .SelectMany(_ => _) - .Catch<Unit, Exception>(e => - { - var repository = SelectedRepository; - Debug.Assert(repository != null, "Should not be able to attempt to clone a repo when it's null"); - vsServices.ShowError(e.GetUserFriendlyErrorMessage(ErrorType.ClonedFailed, repository.Name)); - return Observable.Return(Unit.Default); - }); - } - - /// <summary> - /// Path to clone repositories into - /// </summary> - public string BaseRepositoryPath - { - [return: AllowNull] - get { return baseRepositoryPath; } - set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, value); } - } - - /// <summary> - /// Fires off the cloning process - /// </summary> - public IReactiveCommand<Unit> CloneCommand { get; private set; } - - IReactiveList<IRepositoryModel> repositories; - /// <summary> - /// List of repositories as returned by the server - /// </summary> - public IReactiveList<IRepositoryModel> Repositories - { - get { return repositories; } - private set { this.RaiseAndSetIfChanged(ref repositories, value); } - } - - IReactiveDerivedList<IRepositoryModel> filteredRepositories; - /// <summary> - /// List of repositories as filtered by user - /// </summary> - public IReactiveDerivedList<IRepositoryModel> FilteredRepositories - { - get { return filteredRepositories; } - private set { this.RaiseAndSetIfChanged(ref filteredRepositories, value); } - } - - IRepositoryModel selectedRepository; - /// <summary> - /// Selected repository to clone - /// </summary> - [AllowNull] - public IRepositoryModel SelectedRepository - { - [return: AllowNull] - get { return selectedRepository; } - set { this.RaiseAndSetIfChanged(ref selectedRepository, value); } - } - - readonly ObservableAsPropertyHelper<bool> filterTextIsEnabled; - /// <summary> - /// True if there are repositories (otherwise no point in filtering) - /// </summary> - public bool FilterTextIsEnabled { get { return filterTextIsEnabled.Value; } } - - string filterText; - /// <summary> - /// User text to filter the repositories list - /// </summary> - [AllowNull] - public string FilterText - { - [return: AllowNull] - get { return filterText; } - set { this.RaiseAndSetIfChanged(ref filterText, value); } - } - - public bool IsLoading - { - get { return isLoading.Value; } - } - - public IReactiveCommand<IReadOnlyList<IRepositoryModel>> LoadRepositoriesCommand - { - get { return loadRepositoriesCommand; } - } - - public bool LoadingFailed - { - get { return loadingFailed; } - private set { this.RaiseAndSetIfChanged(ref loadingFailed, value); } - } - - public bool NoRepositoriesFound - { - get { return noRepositoriesFound.Value; } - } - - public ICommand BrowseForDirectory - { - get { return browseForDirectoryCommand; } - } - - public bool CanClone - { - get { return canClone.Value; } - } - - public ReactivePropertyValidator<string> BaseRepositoryPathValidator - { - get; - private set; - } - - IObservable<Unit> 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(string.Format(CultureInfo.InvariantCulture, - "Failed to set base repository path.{0}localBaseRepositoryPath = \"{1}\"{0}BaseRepositoryPath = \"{2}\"{0}Chosen directory = \"{3}\"", - System.Environment.NewLine, localBaseRepositoryPath ?? "(null)", BaseRepositoryPath ?? "(null)", directory ?? "(null)"), e); - } - }, RxApp.MainThreadScheduler); - } - } -} diff --git a/src/GitHub.App/ViewModels/RepositoryCreationViewModel.cs b/src/GitHub.App/ViewModels/RepositoryCreationViewModel.cs deleted file mode 100644 index 8ac90ef2bf..0000000000 --- a/src/GitHub.App/ViewModels/RepositoryCreationViewModel.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.Composition; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Windows.Input; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Extensions.Reactive; -using GitHub.Models; -using GitHub.Services; -using GitHub.UserErrors; -using GitHub.Validation; -using NLog; -using NullGuard; -using Octokit; -using ReactiveUI; -using Rothko; - -namespace GitHub.ViewModels -{ - [ExportViewModel(ViewType=UIViewType.Create)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class RepositoryCreationViewModel : RepositoryFormViewModel, IRepositoryCreationViewModel - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - - readonly ReactiveCommand<object> browseForDirectoryCommand = ReactiveCommand.Create(); - readonly ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts; - readonly ObservableAsPropertyHelper<IReadOnlyList<LicenseItem>> licenses; - readonly ObservableAsPropertyHelper<IReadOnlyList<GitIgnoreItem>> gitIgnoreTemplates; - readonly IRepositoryHost repositoryHost; - readonly IRepositoryCreationService repositoryCreationService; - readonly ObservableAsPropertyHelper<bool> isCreating; - readonly ObservableAsPropertyHelper<bool> canKeepPrivate; - readonly IOperatingSystem operatingSystem; - - [ImportingConstructor] - RepositoryCreationViewModel( - IConnectionRepositoryHostMap connectionRepositoryHostMap, - IOperatingSystem operatingSystem, - IRepositoryCreationService repositoryCreationService, - IAvatarProvider avatarProvider) - : this(connectionRepositoryHostMap.CurrentRepositoryHost, operatingSystem, repositoryCreationService, avatarProvider) - {} - - public RepositoryCreationViewModel( - IRepositoryHost repositoryHost, - IOperatingSystem operatingSystem, - IRepositoryCreationService repositoryCreationService, - IAvatarProvider avatarProvider) - { - this.repositoryHost = repositoryHost; - this.operatingSystem = operatingSystem; - this.repositoryCreationService = repositoryCreationService; - - Title = string.Format(CultureInfo.CurrentCulture, Resources.CreateTitle, repositoryHost.Title); - SelectedGitIgnoreTemplate = GitIgnoreItem.None; - SelectedLicense = LicenseItem.None; - - accounts = repositoryHost.ModelService.GetAccounts() - .ObserveOn(RxApp.MainThreadScheduler) - .ToProperty(this, vm => vm.Accounts, initialValue: new ReadOnlyCollection<IAccount>(new IAccount[] {})); - - this.WhenAny(x => x.Accounts, x => x.Value) - .WhereNotNull() - .Where(accts => accts.Any()) - .Subscribe(accts => { - var selectedAccount = accts.FirstOrDefault(); - if (selectedAccount != null) - { - SelectedAccount = accts.FirstOrDefault(); - } - }); - - browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog()); - - BaseRepositoryPathValidator = this.CreateBaseRepositoryPathValidator(); - - var nonNullRepositoryName = this.WhenAny( - x => x.RepositoryName, - x => x.BaseRepositoryPath, - (x, y) => x.Value) - .WhereNotNull(); - - RepositoryNameValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) - .IfNullOrEmpty(Resources.RepositoryNameValidatorEmpty) - .IfTrue(x => x.Length > 100, Resources.RepositoryNameValidatorTooLong) - .IfTrue(IsAlreadyRepoAtPath, Resources.RepositoryNameValidatorAlreadyExists); - - SafeRepositoryNameWarningValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) - .Add(repoName => - { - var parsedReference = GetSafeRepositoryName(repoName); - 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); - - gitIgnoreTemplates = repositoryHost.ModelService.GetGitIgnoreTemplates() - .ObserveOn(RxApp.MainThreadScheduler) - .ToProperty(this, x => x.GitIgnoreTemplates, initialValue: new GitIgnoreItem[] { }); - - this.WhenAny(x => x.GitIgnoreTemplates, x => x.Value) - .WhereNotNull() - .Where(ignores => ignores.Any()) - .Subscribe(ignores => - { - SelectedGitIgnoreTemplate = ignores.FirstOrDefault( - template => template.Name.Equals("VisualStudio", StringComparison.OrdinalIgnoreCase)); - }); - - licenses = repositoryHost.ModelService.GetLicenses() - .ObserveOn(RxApp.MainThreadScheduler) - .ToProperty(this, x => x.Licenses, initialValue: new LicenseItem[] { }); - - BaseRepositoryPath = repositoryCreationService.DefaultClonePath; - } - - string baseRepositoryPath; - /// <summary> - /// Path to clone repositories into - /// </summary> - public string BaseRepositoryPath - { - [return: AllowNull] - get { return baseRepositoryPath; } - set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, StripSurroundingQuotes(value)); } - } - - /// <summary> - /// Fires up a file dialog to select the directory to clone into - /// </summary> - public ICommand BrowseForDirectory { get { return browseForDirectoryCommand; } } - - /// <summary> - /// Is running the creation process - /// </summary> - public bool IsCreating { get { return isCreating.Value; } } - - /// <summary> - /// If the repo can be made private (depends on the user plan) - /// </summary> - public bool CanKeepPrivate { get { return canKeepPrivate.Value; } } - - public IReadOnlyList<GitIgnoreItem> GitIgnoreTemplates - { - get { return gitIgnoreTemplates.Value; } - } - - public IReadOnlyList<LicenseItem> Licenses - { - get { return licenses.Value; } - } - - GitIgnoreItem selectedGitIgnoreTemplate; - [AllowNull] - public GitIgnoreItem SelectedGitIgnoreTemplate - { - get { return selectedGitIgnoreTemplate; } - set { this.RaiseAndSetIfChanged(ref selectedGitIgnoreTemplate, value ?? GitIgnoreItem.None); } - } - - LicenseItem selectedLicense; - [AllowNull] - public LicenseItem SelectedLicense - { - get { return selectedLicense; } - set { this.RaiseAndSetIfChanged(ref selectedLicense, value ?? LicenseItem.None); } - } - - /// <summary> - /// List of accounts (at least one) - /// </summary> - public IReadOnlyList<IAccount> Accounts { get { return accounts.Value; } } - - public ReactivePropertyValidator<string> BaseRepositoryPathValidator { get; private set; } - - /// <summary> - /// Fires off the process of creating the repository remotely and then cloning it locally - /// </summary> - public IReactiveCommand<Unit> CreateRepository { get; private set; } - - protected override NewRepository GatherRepositoryInfo() - { - var gitHubRepository = base.GatherRepositoryInfo(); - - if (SelectedLicense != LicenseItem.None) - { - gitHubRepository.LicenseTemplate = SelectedLicense.Key; - gitHubRepository.AutoInit = true; - } - - if (SelectedGitIgnoreTemplate != GitIgnoreItem.None) - { - gitHubRepository.GitignoreTemplate = SelectedGitIgnoreTemplate.Name; - gitHubRepository.AutoInit = true; - } - return gitHubRepository; - } - - IObservable<Unit> 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(string.Format(CultureInfo.InvariantCulture, - "Failed to set base repository path.{0}localBaseRepositoryPath = \"{1}\"{0}BaseRepositoryPath = \"{2}\"{0}Chosen directory = \"{3}\"", - System.Environment.NewLine, localBaseRepositoryPath ?? "(null)", BaseRepositoryPath ?? "(null)", directory ?? "(null)"), e); - } - }, RxApp.MainThreadScheduler); - } - - bool IsAlreadyRepoAtPath(string potentialRepositoryName) - { - bool isAlreadyRepoAtPath = false; - var validationResult = BaseRepositoryPathValidator.ValidationResult; - string safeRepositoryName = GetSafeRepositoryName(potentialRepositoryName); - if (validationResult != null && validationResult.IsValid) - { - if (Path.GetInvalidPathChars().Any(chr => BaseRepositoryPath.Contains(chr))) - return false; - string potentialPath = Path.Combine(BaseRepositoryPath, safeRepositoryName); - isAlreadyRepoAtPath = IsGitRepo(potentialPath); - } - return isAlreadyRepoAtPath; - } - - bool IsGitRepo(string path) - { - try - { - return operatingSystem.File.Exists(Path.Combine(path, ".git", "HEAD")); - } - catch (PathTooLongException) - { - return false; - } - } - - IObservable<Unit> OnCreateRepository(object state) - { - var newRepository = GatherRepositoryInfo(); - - return repositoryCreationService.CreateRepository( - newRepository, - SelectedAccount, - BaseRepositoryPath, - repositoryHost.ApiClient); - } - - ReactiveCommand<Unit> InitializeCreateRepositoryCommand() - { - var canCreate = this.WhenAny( - x => x.RepositoryNameValidator.ValidationResult.IsValid, - x => x.BaseRepositoryPathValidator.ValidationResult.IsValid, - (x, y) => x.Value && y.Value); - var createCommand = ReactiveCommand.CreateAsyncObservable(canCreate, OnCreateRepository); - createCommand.ThrownExceptions.Subscribe(ex => - { - if (!Extensions.ExceptionExtensions.IsCriticalException(ex)) - { - log.Error("Error creating repository.", ex); - UserError.Throw(TranslateRepositoryCreateException(ex)); - } - }); - - return createCommand; - } - - static string StripSurroundingQuotes(string path) - { - if (string.IsNullOrEmpty(path) - || path.Length < 2 - || !path.StartsWith("\"", StringComparison.Ordinal) - || !path.EndsWith("\"", StringComparison.Ordinal)) - { - return path; - } - - return path.Substring(1, path.Length - 2); - } - - PublishRepositoryUserError TranslateRepositoryCreateException(Exception ex) - { - var existsException = ex as RepositoryExistsException; - if (existsException != null && SelectedAccount != null) - { - string message = string.Format( - CultureInfo.InvariantCulture, - Resources.RepositoryCreationFailedAlreadyExists, - SelectedAccount.Login, RepositoryName); - return new PublishRepositoryUserError(message, Resources.RepositoryCreationFailedAlreadyExistsMessage); - } - var quotaExceededException = ex as PrivateRepositoryQuotaExceededException; - if (quotaExceededException != null && SelectedAccount != null) - { - return new PublishRepositoryUserError(Resources.RepositoryCreationFailedQuota, quotaExceededException.Message); - } - return new PublishRepositoryUserError(ex.Message); - } - } -} diff --git a/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs b/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs index e8e926dd5f..6aa13f02b1 100644 --- a/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs +++ b/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs @@ -4,7 +4,6 @@ using System.Windows.Input; using GitHub.Models; using GitHub.Validation; -using NullGuard; using ReactiveUI; namespace GitHub.ViewModels @@ -12,7 +11,7 @@ namespace GitHub.ViewModels /// <summary> /// Base class for the Repository publish/create dialogs. It represents the details about the repository itself. /// </summary> - public abstract class RepositoryFormViewModel : BaseViewModel + public abstract class RepositoryFormViewModel : ViewModelBase { readonly ObservableAsPropertyHelper<string> safeRepositoryName; @@ -38,10 +37,8 @@ protected RepositoryFormViewModel() /// <summary> /// Description to set on the repo (optional) /// </summary> - [AllowNull] public string Description { - [return: AllowNull] get { return description; } set { this.RaiseAndSetIfChanged(ref description, value); } } @@ -60,10 +57,8 @@ public bool KeepPrivate /// <summary> /// Name of the repository as typed by user /// </summary> - [AllowNull] public string RepositoryName { - [return: AllowNull] get { return repositoryName; } set { this.RaiseAndSetIfChanged(ref repositoryName, value); } } @@ -75,7 +70,6 @@ public string RepositoryName /// </summary> public string SafeRepositoryName { - [return: AllowNull] get { return safeRepositoryName.Value; } } @@ -85,10 +79,8 @@ public string SafeRepositoryName /// <summary> /// Account where the repository is going to be created on /// </summary> - [AllowNull] public IAccount SelectedAccount { - [return: AllowNull] get { return selectedAccount; } set { this.RaiseAndSetIfChanged(ref selectedAccount, value); } } diff --git a/src/GitHub.App/ViewModels/RepositoryPublishViewModel.cs b/src/GitHub.App/ViewModels/RepositoryPublishViewModel.cs deleted file mode 100644 index cc6aed5ec0..0000000000 --- a/src/GitHub.App/ViewModels/RepositoryPublishViewModel.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.Composition; -using System.Globalization; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Extensions.Reactive; -using GitHub.Models; -using GitHub.Services; -using GitHub.UserErrors; -using GitHub.Validation; -using NLog; -using NullGuard; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - [ExportViewModel(ViewType = UIViewType.Publish)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class RepositoryPublishViewModel : RepositoryFormViewModel, IRepositoryPublishViewModel - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - - readonly IRepositoryHosts hosts; - readonly IRepositoryPublishService repositoryPublishService; - readonly IVSServices vsServices; - readonly ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts; - readonly ObservableAsPropertyHelper<bool> isHostComboBoxVisible; - readonly ObservableAsPropertyHelper<bool> canKeepPrivate; - readonly ObservableAsPropertyHelper<bool> isPublishing; - readonly ObservableAsPropertyHelper<string> title; - - [ImportingConstructor] - public RepositoryPublishViewModel( - IRepositoryHosts hosts, - IRepositoryPublishService repositoryPublishService, - IVSServices vsServices, - IConnectionManager connectionManager) - { - this.vsServices = vsServices; - this.hosts = hosts; - - title = this.WhenAny( - x => x.SelectedHost, - x => x.Value != null ? - string.Format(CultureInfo.CurrentCulture, Resources.PublishToTitle, x.Value.Title) : - Resources.PublishTitle - ) - .ToProperty(this, x => x.Title); - - Connections = new ReactiveList<IConnection>(connectionManager.Connections); - this.repositoryPublishService = repositoryPublishService; - - if (Connections.Any()) - { - SelectedConnection = Connections.FirstOrDefault(x => x.HostAddress.IsGitHubDotCom()) ?? Connections[0]; - } - - accounts = this.WhenAny(x => x.SelectedConnection, x => x.Value != null ? hosts.LookupHost(x.Value.HostAddress) : RepositoryHosts.DisconnectedRepositoryHost) - .Where(x => !(x is DisconnectedRepositoryHost)) - .SelectMany(host => host.ModelService.GetAccounts()) - .ObserveOn(RxApp.MainThreadScheduler) - .ToProperty(this, x => x.Accounts, initialValue: new ReadOnlyCollection<IAccount>(new IAccount[] {})); - - this.WhenAny(x => x.Accounts, x => x.Value) - .WhereNotNull() - .Where(accts => accts.Any()) - .Subscribe(accts => { - var selectedAccount = accts.FirstOrDefault(); - if (selectedAccount != null) - { - SelectedAccount = accts.FirstOrDefault(); - } - }); - - isHostComboBoxVisible = this.WhenAny(x => x.Connections, x => x.Value) - .WhereNotNull() - .Select(h => h.Count > 1) - .ToProperty(this, x => x.IsHostComboBoxVisible); - - InitializeValidation(); - - PublishRepository = InitializePublishRepositoryCommand(); - - canKeepPrivate = CanKeepPrivateObservable.CombineLatest(PublishRepository.IsExecuting, - (canKeep, publishing) => canKeep && !publishing) - .ToProperty(this, x => x.CanKeepPrivate); - - isPublishing = PublishRepository.IsExecuting - .ToProperty(this, x => x.IsPublishing); - - var defaultRepositoryName = repositoryPublishService.LocalRepositoryName; - if (!string.IsNullOrEmpty(defaultRepositoryName)) - { - DefaultRepositoryName = defaultRepositoryName; - } - - this.WhenAny(x => x.SelectedConnection, x => x.SelectedAccount, - (a,b) => true) - .Where(x => RepositoryNameValidator.ValidationResult != null && SafeRepositoryNameWarningValidator.ValidationResult != null) - .Subscribe(async _ => - { - var name = RepositoryName; - RepositoryName = null; - await RepositoryNameValidator.ResetAsync(); - await SafeRepositoryNameWarningValidator.ResetAsync(); - RepositoryName = name; - }); - } - - public string DefaultRepositoryName { get; private set; } - public new string Title { get { return title.Value; } } - public bool CanKeepPrivate { get { return canKeepPrivate.Value; } } - public bool IsPublishing { get { return isPublishing.Value; } } - - public IReactiveCommand<ProgressState> PublishRepository { get; private set; } - public ReactiveList<IConnection> Connections { get; private set; } - - IConnection selectedConnection; - [AllowNull] - public IConnection SelectedConnection - { - [return: AllowNull] - get { return selectedConnection; } - set { this.RaiseAndSetIfChanged(ref selectedConnection, value); } - } - - IRepositoryHost SelectedHost - { - [return:AllowNull] - get { return selectedConnection != null ? hosts.LookupHost(selectedConnection.HostAddress) : null; } - } - - public IReadOnlyList<IAccount> Accounts - { - get { return accounts.Value; } - } - - public bool IsHostComboBoxVisible - { - get { return isHostComboBoxVisible.Value; } - } - - ReactiveCommand<ProgressState> InitializePublishRepositoryCommand() - { - var canCreate = this.WhenAny(x => x.RepositoryNameValidator.ValidationResult.IsValid, x => x.Value); - return ReactiveCommand.CreateAsyncObservable(canCreate, OnPublishRepository); - } - - IObservable<ProgressState> OnPublishRepository(object arg) - { - var newRepository = GatherRepositoryInfo(); - var account = SelectedAccount; - - return repositoryPublishService.PublishRepository(newRepository, account, SelectedHost.ApiClient) - .Select(_ => - { - vsServices.ShowMessage("Repository published successfully."); - return ProgressState.Success; - }) - .Catch<ProgressState, Exception>(ex => - { - if (!ex.IsCriticalException()) - { - log.Error(ex); - var error = new PublishRepositoryUserError(ex.Message); - vsServices.ShowError((error.ErrorMessage + Environment.NewLine + error.ErrorCauseOrResolution).TrimEnd()); - } - return Observable.Return(ProgressState.Fail); - }); - } - - void InitializeValidation() - { - var nonNullRepositoryName = this.WhenAny( - x => x.RepositoryName, - x => x.Value) - .WhereNotNull(); - - RepositoryNameValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) - .IfNullOrEmpty(Resources.RepositoryNameValidatorEmpty) - .IfTrue(x => x.Length > 100, Resources.RepositoryNameValidatorTooLong); - - SafeRepositoryNameWarningValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) - .Add(repoName => - { - var parsedReference = GetSafeRepositoryName(repoName); - return parsedReference != repoName ? String.Format(CultureInfo.CurrentCulture, Resources.SafeRepositoryNameWarning, parsedReference) : null; - }); - - this.WhenAny(x => x.SafeRepositoryNameWarningValidator.ValidationResult, x => x.Value) - .WhereNotNull() // When this is instantiated, it sends a null result. - .Select(result => result?.Message) - .Subscribe(message => - { - if (!string.IsNullOrEmpty(message)) - { - vsServices.ShowWarning(message); - } - else - { - vsServices.ClearNotifications(); - } - }); - } - } -} diff --git a/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs b/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs new file mode 100644 index 0000000000..048ccfa77a --- /dev/null +++ b/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Linq; +using System.Reactive.Linq; +using GitHub.App; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.Factories; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using GitHub.UserErrors; +using GitHub.Validation; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels.TeamExplorer +{ + [Export(typeof(IRepositoryPublishViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class RepositoryPublishViewModel : RepositoryFormViewModel, IRepositoryPublishViewModel + { + static readonly ILogger log = LogManager.ForContext<RepositoryPublishViewModel>(); + + readonly IRepositoryPublishService repositoryPublishService; + readonly INotificationService notificationService; + readonly IModelServiceFactory modelServiceFactory; + readonly ObservableAsPropertyHelper<IReadOnlyList<IAccount>> accounts; + readonly ObservableAsPropertyHelper<bool> isHostComboBoxVisible; + readonly ObservableAsPropertyHelper<bool> canKeepPrivate; + readonly ObservableAsPropertyHelper<string> title; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public RepositoryPublishViewModel( + IRepositoryPublishService repositoryPublishService, + INotificationService notificationService, + IConnectionManager connectionManager, + IModelServiceFactory modelServiceFactory, + IUsageTracker usageTracker) + { + Guard.ArgumentNotNull(repositoryPublishService, nameof(repositoryPublishService)); + Guard.ArgumentNotNull(notificationService, nameof(notificationService)); + Guard.ArgumentNotNull(connectionManager, nameof(connectionManager)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + + 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); + + Connections = connectionManager.Connections; + this.repositoryPublishService = repositoryPublishService; + + if (Connections.Any()) + SelectedConnection = Connections.FirstOrDefault(x => x.HostAddress.IsGitHubDotCom()) ?? Connections[0]; + + accounts = this.WhenAnyValue(x => x.SelectedConnection) + .Where(x => x != null) + .SelectMany(async c => (await modelServiceFactory.CreateAsync(c)).GetAccounts()) + .Switch() + .ObserveOn(RxApp.MainThreadScheduler) + .ToProperty(this, x => x.Accounts, initialValue: new ReadOnlyCollection<IAccount>(new IAccount[] {})); + + this.WhenAny(x => x.Accounts, x => x.Value) + .WhereNotNull() + .Where(accts => accts.Any()) + .Subscribe(accts => { + var selectedAccount = accts.FirstOrDefault(); + if (selectedAccount != null) + SelectedAccount = accts.FirstOrDefault(); + }); + + isHostComboBoxVisible = this.WhenAny(x => x.Connections, x => x.Value) + .WhereNotNull() + .Select(h => h.Count > 1) + .ToProperty(this, x => x.IsHostComboBoxVisible); + + InitializeValidation(); + + PublishRepository = InitializePublishRepositoryCommand(); + + canKeepPrivate = CanKeepPrivateObservable.CombineLatest(PublishRepository.IsExecuting, + (canKeep, publishing) => canKeep && !publishing) + .ToProperty(this, x => x.CanKeepPrivate); + + PublishRepository.IsExecuting.Subscribe(x => IsBusy = x); + + var defaultRepositoryName = repositoryPublishService.LocalRepositoryName; + if (!string.IsNullOrEmpty(defaultRepositoryName)) + RepositoryName = defaultRepositoryName; + + this.WhenAny(x => x.SelectedConnection, x => x.SelectedAccount, + (a,b) => true) + .Where(x => RepositoryNameValidator.ValidationResult != null && SafeRepositoryNameWarningValidator.ValidationResult != null) + .Subscribe(async _ => + { + var name = RepositoryName; + RepositoryName = null; + await RepositoryNameValidator.ResetAsync(); + await SafeRepositoryNameWarningValidator.ResetAsync(); + RepositoryName = name; + }); + } + + public string Title { get { return title.Value; } } + public bool CanKeepPrivate { get { return canKeepPrivate.Value; } } + + public IReactiveCommand<ProgressState> PublishRepository { get; private set; } + public IReadOnlyObservableCollection<IConnection> Connections { get; private set; } + + bool isBusy; + public bool IsBusy + { + get { return isBusy; } + private set { this.RaiseAndSetIfChanged(ref isBusy, value); } + } + + IConnection selectedConnection; + public IConnection SelectedConnection + { + get { return selectedConnection; } + set { this.RaiseAndSetIfChanged(ref selectedConnection, value); } + } + + public IReadOnlyList<IAccount> Accounts + { + get { return accounts.Value; } + } + + public bool IsHostComboBoxVisible + { + get { return isHostComboBoxVisible.Value; } + } + + ReactiveCommand<ProgressState> InitializePublishRepositoryCommand() + { + var canCreate = this.WhenAny(x => x.RepositoryNameValidator.ValidationResult.IsValid, x => x.Value); + return ReactiveCommand.CreateAsyncObservable(canCreate, OnPublishRepository); + } + + IObservable<ProgressState> OnPublishRepository(object arg) + { + var newRepository = GatherRepositoryInfo(); + var account = SelectedAccount; + var modelService = modelServiceFactory.CreateBlocking(SelectedConnection); + + return repositoryPublishService.PublishRepository(newRepository, account, modelService.ApiClient) + .Do(_ => usageTracker.IncrementCounter(x => x.NumberOfReposPublished).Forget()) + .Select(_ => ProgressState.Success) + .Catch<ProgressState, Exception>(ex => + { + if (!ex.IsCriticalException()) + { + log.Error(ex, "Error Publishing Repository"); + var error = new PublishRepositoryUserError(ex.Message); + notificationService.ShowError((error.ErrorMessage + Environment.NewLine + error.ErrorCauseOrResolution).TrimEnd()); + } + return Observable.Return(ProgressState.Fail); + }); + } + + void InitializeValidation() + { + var nonNullRepositoryName = this.WhenAny( + x => x.RepositoryName, + x => x.Value) + .WhereNotNull(); + + RepositoryNameValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) + .IfNullOrEmpty(Resources.RepositoryNameValidatorEmpty) + .IfTrue(x => x.Length > 100, Resources.RepositoryNameValidatorTooLong); + + SafeRepositoryNameWarningValidator = ReactivePropertyValidator.ForObservable(nonNullRepositoryName) + .Add(repoName => + { + var parsedReference = GetSafeRepositoryName(repoName); + return parsedReference != repoName ? String.Format(CultureInfo.CurrentCulture, Resources.SafeRepositoryNameWarning, parsedReference) : null; + }); + } + } +} diff --git a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs b/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs deleted file mode 100644 index d5c489f06a..0000000000 --- a/src/GitHub.App/ViewModels/TwoFactorDialogViewModel.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Globalization; -using System.Reactive.Linq; -using GitHub.Authentication; -using GitHub.Exports; -using GitHub.Info; -using GitHub.Services; -using GitHub.Validation; -using NullGuard; -using Octokit; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - [ExportViewModel(ViewType = UIViewType.TwoFactor)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class TwoFactorDialogViewModel : BaseViewModel, ITwoFactorDialogViewModel - { - bool isAuthenticationCodeSent; - bool isBusy; - bool invalidAuthenticationCode; - string authenticationCode; - TwoFactorType twoFactorType; - readonly ObservableAsPropertyHelper<string> description; - readonly ObservableAsPropertyHelper<bool> isSms; - readonly ObservableAsPropertyHelper<bool> showErrorMessage; - - [ImportingConstructor] - public TwoFactorDialogViewModel( - IVisualStudioBrowser browser, - ITwoFactorChallengeHandler twoFactorChallengeHandler) - { - Title = Resources.TwoFactorTitle; - twoFactorChallengeHandler.SetViewModel(this); - - var canVerify = this.WhenAny( - x => x.AuthenticationCode, - x => x.IsBusy, - (code, busy) => !string.IsNullOrEmpty(code.Value) && code.Value.Length == 6 && !busy.Value); - - OkCommand = ReactiveCommand.Create(canVerify); - CancelCommand = ReactiveCommand.Create(); - NavigateLearnMore = ReactiveCommand.Create(); - NavigateLearnMore.Subscribe(x => browser.OpenUrl(GitHubUrls.TwoFactorLearnMore)); - //TODO: ShowHelpCommand.Subscribe(x => browser.OpenUrl(twoFactorHelpUri)); - ResendCodeCommand = ReactiveCommand.Create(); - - showErrorMessage = this.WhenAny( - x => x.IsAuthenticationCodeSent, - x => x.InvalidAuthenticationCode, - (authSent, invalid) => invalid.Value && !authSent.Value) - .ToProperty(this, x => x.ShowErrorMessage); - - description = this.WhenAny(x => x.TwoFactorType, x => x.Value) - .Select(type => - { - switch (type) - { - case TwoFactorType.Sms: - return Resources.TwoFactorSms; - case TwoFactorType.AuthenticatorApp: - return Resources.TwoFactorApp; - case TwoFactorType.Unknown: - return Resources.TwoFactorUnknown; - - default: - return null; - } - }) - .ToProperty(this, x => x.Description); - - isShowing = this.WhenAny(x => x.TwoFactorType, x => x.Value) - .Select(factorType => factorType != TwoFactorType.None) - .ToProperty(this, x => x.IsShowing); - - isSms = this.WhenAny(x => x.TwoFactorType, x => x.Value) - .Select(factorType => factorType == TwoFactorType.Sms) - .ToProperty(this, x => x.IsSms); - } - - public IObservable<RecoveryOptionResult> Show(UserError userError) - { - IsBusy = false; - var error = userError as TwoFactorRequiredUserError; - Debug.Assert(error != null, - String.Format(CultureInfo.InvariantCulture, "The user error is '{0}' not a TwoFactorRequiredUserError", userError)); - InvalidAuthenticationCode = error.RetryFailed; - TwoFactorType = error.TwoFactorType; - var ok = OkCommand - .Do(_ => IsBusy = true) - .Select(_ => AuthenticationCode == null - ? RecoveryOptionResult.CancelOperation - : RecoveryOptionResult.RetryOperation) - .Do(_ => error.ChallengeResult = AuthenticationCode != null - ? new TwoFactorChallengeResult(AuthenticationCode) - : null); - var resend = ResendCodeCommand.Select(_ => RecoveryOptionResult.RetryOperation) - .Do(_ => error.ChallengeResult = TwoFactorChallengeResult.RequestResendCode); - var cancel = CancelCommand.Select(_ => RecoveryOptionResult.CancelOperation); - return Observable.Merge(ok, cancel, resend) - .Take(1) - .Do(_ => IsAuthenticationCodeSent = error.ChallengeResult == TwoFactorChallengeResult.RequestResendCode); - } - - public TwoFactorType TwoFactorType - { - get { return twoFactorType; } - private set { this.RaiseAndSetIfChanged(ref twoFactorType, value); } - } - - public bool IsSms { get { return isSms.Value; } } - - public bool IsAuthenticationCodeSent - { - get { return isAuthenticationCodeSent; } - private set { this.RaiseAndSetIfChanged(ref isAuthenticationCodeSent, value); } - } - - public string Description - { - [return: AllowNull] - get { return description.Value; } - } - - [AllowNull] - public string AuthenticationCode - { - [return: AllowNull] - get { return authenticationCode; } - set { this.RaiseAndSetIfChanged(ref authenticationCode, value); } - } - - public ReactiveCommand<object> OkCommand { get; private set; } - public ReactiveCommand<object> NavigateLearnMore { get; private set; } - public ReactiveCommand<object> ResendCodeCommand { get; private set; } - public ReactivePropertyValidator AuthenticationCodeValidator { get; private set; } - - public bool InvalidAuthenticationCode - { - get { return invalidAuthenticationCode; } - private set { this.RaiseAndSetIfChanged(ref invalidAuthenticationCode, value); } - } - - public bool ShowErrorMessage - { - get { return showErrorMessage.Value; } - } - - public bool IsBusy - { - get { return isBusy; } - set { this.RaiseAndSetIfChanged(ref isBusy, value); } - } - } -} diff --git a/src/GitHub.App/ViewModels/UserFilterViewModel.cs b/src/GitHub.App/ViewModels/UserFilterViewModel.cs new file mode 100644 index 0000000000..f56b54700c --- /dev/null +++ b/src/GitHub.App/ViewModels/UserFilterViewModel.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Windows.Data; +using GitHub.Extensions; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels +{ + public class UserFilterViewModel : ViewModelBase, IUserFilterViewModel + { + readonly LoadPageDelegate load; + ReactiveList<IActorViewModel> users; + ListCollectionView usersView; + string filter; + IActorViewModel selected; + IActorViewModel ersatzUser; + + public delegate Task<Page<ActorModel>> LoadPageDelegate(string after); + + public UserFilterViewModel(LoadPageDelegate load) + { + Guard.ArgumentNotNull(load, nameof(load)); + + this.load = load; + this.WhenAnyValue(x => x.Filter).Subscribe(FilterChanged); + this.WhenAnyValue(x => x.Selected).Subscribe(_ => Filter = null); + ClearSelection = ReactiveCommand.Create( + this.WhenAnyValue(x => x.Selected).Select(x => x != null)) + .OnExecuteCompleted(_ => Selected = null); + } + + public IReadOnlyList<IActorViewModel> Users + { + get + { + if (users == null) + { + users = new ReactiveList<IActorViewModel>(); + Load().Forget(); + } + + return users; + } + } + + public ICollectionView UsersView + { + get + { + if (usersView == null) + { + usersView = new ListCollectionView((IList)Users); + usersView.CustomSort = new UserComparer(this); + usersView.Filter = FilterUsers; + } + + return usersView; + } + } + + public string Filter + { + get { return filter; } + set { this.RaiseAndSetIfChanged(ref filter, value); } + } + + public IActorViewModel Selected + { + get { return selected; } + set { this.RaiseAndSetIfChanged(ref selected, value); } + } + + public ReactiveCommand<object> ClearSelection { get; } + + void FilterChanged(string filter) + { + if (users == null) return; + + if (ersatzUser != null) + { + users.Remove(ersatzUser); + ersatzUser = null; + } + + if (!string.IsNullOrWhiteSpace(filter)) + { + var existing = users.FirstOrDefault(x => x.Login.Equals(filter, StringComparison.CurrentCultureIgnoreCase)); + + if (existing == null) + { + ersatzUser = new ActorViewModel(new ActorModel { Login = filter }); + users.Add(ersatzUser); + } + } + + UsersView.Refresh(); + } + + bool FilterUsers(object obj) + { + if (Filter != null) + { + var user = obj as IActorViewModel; + return user?.Login.IndexOf(Filter, StringComparison.CurrentCultureIgnoreCase) != -1; + } + + return true; + } + + async Task Load() + { + string after = null; + + while (true) + { + var page = await load(after); + + foreach (var item in page.Items) + { + var vm = new ActorViewModel(item); + users.Add(vm); + } + + after = page.EndCursor; + if (!page.HasNextPage) break; + } + } + + class UserComparer : IComparer + { + readonly UserFilterViewModel owner; + + public UserComparer(UserFilterViewModel owner) + { + this.owner = owner; + } + + 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); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/app.config b/src/GitHub.App/app.config deleted file mode 100644 index 06e6567666..0000000000 --- a/src/GitHub.App/app.config +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<configuration> - <runtime> - <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> - <dependentAssembly> - <assemblyIdentity name="System.Reactive.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" /> - </dependentAssembly> - <dependentAssembly> - <assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="31bf3856ad364e35" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" /> - </dependentAssembly> - <dependentAssembly> - <assemblyIdentity name="System.Reactive.Linq" publicKeyToken="31bf3856ad364e35" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" /> - </dependentAssembly> - </assemblyBinding> - </runtime> -</configuration> \ No newline at end of file diff --git a/src/GitHub.App/packages.config b/src/GitHub.App/packages.config index a9595ff7ec..f531b14594 100644 --- a/src/GitHub.App/packages.config +++ b/src/GitHub.App/packages.config @@ -1,15 +1,37 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Fody" version="1.28.0" targetFramework="net45" developmentDependency="true" /> - <package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" /> - <package id="NLog" version="3.1.0.0" targetFramework="net45" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net45" developmentDependency="true" /> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6072" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30320" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61031" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30111" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50728" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50728" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net461" /> + <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> + <package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" /> + <package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" /> <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> <package id="SQLitePCL.raw_basic" version="0.7.3.0-vs2012" targetFramework="net45" /> - <package id="Stateless" version="2.5.11.0" targetFramework="net45" /> + <package id="Stateless" version="2.5.56.0" targetFramework="net45" /> + <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" /> </packages> \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Api/IApiClient.cs b/src/GitHub.Exports.Reactive/Api/IApiClient.cs index 5f3cd879a3..28bb4044ce 100644 --- a/src/GitHub.Exports.Reactive/Api/IApiClient.cs +++ b/src/GitHub.Exports.Reactive/Api/IApiClient.cs @@ -1,5 +1,6 @@ using System; using System.Reactive; +using GitHub.Models; using GitHub.Primitives; using Octokit; @@ -8,8 +9,15 @@ namespace GitHub.Api public interface IApiClient { HostAddress HostAddress { get; } + + // HACK: This is temporary. Should be removed in login refactor timeframe. + IGitHubClient GitHubClient { get; } + IObservable<Repository> CreateRepository(NewRepository repository, string login, bool isUser); + IObservable<Repository> ForkRepository(string owner, string name, NewRepositoryFork repository); + IObservable<Gist> CreateGist(NewGist newGist); IObservable<User> GetUser(); + IObservable<User> GetUser(string login); IObservable<Organization> GetOrganizations(); /// <summary> /// Retrieves all repositories that belong to this user. @@ -21,15 +29,94 @@ public interface IApiClient /// </summary> /// <returns></returns> IObservable<Repository> GetRepositoriesForOrganization(string organization); - IObservable<ApplicationAuthorization> GetOrCreateApplicationAuthenticationCode( - Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHander, - string authenticationCode = null, - bool useOldScopes = false, - bool useFingerprint = true); + IObservable<Repository> GetForks(string owner, string name); IObservable<string> GetGitIgnoreTemplates(); IObservable<LicenseMetadata> GetLicenses(); IObservable<Unit> DeleteApplicationAuthorization(int id, string twoFactorAuthorizationCode); + IObservable<IssueComment> GetIssueComments(string owner, string name, int number); + IObservable<PullRequest> GetPullRequest(string owner, string name, int number); + IObservable<PullRequestFile> GetPullRequestFiles(string owner, string name, int number); + IObservable<PullRequestReviewComment> GetPullRequestReviewComments(string owner, string name, int number); IObservable<PullRequest> GetPullRequestsForRepository(string owner, string name); + IObservable<PullRequest> CreatePullRequest(NewPullRequest pullRequest, string owner, string repo); + + /// <summary> + /// Posts a new PR review. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request number.</param> + /// <param name="commitId">The SHA of the commit being reviewed.</param> + /// <param name="body">The review body.</param> + /// <param name="e">The review event.</param> + /// <returns></returns> + IObservable<PullRequestReview> PostPullRequestReview( + string owner, + string name, + int number, + string commitId, + string body, + PullRequestReviewEvent e); + + /// <summary> + /// Creates a new PR review comment. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request number.</param> + /// <param name="body">The comment body.</param> + /// <param name="commitId">THe SHA of the commit to comment on.</param> + /// <param name="path">The relative path of the file to comment on.</param> + /// <param name="position">The line index in the diff to comment on.</param> + /// <returns></returns> + IObservable<PullRequestReviewComment> CreatePullRequestReviewComment( + string owner, + string name, + int number, + string body, + string commitId, + string path, + int position); + + /// <summary> + /// Creates a new PR review comment reply. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request number.</param> + /// <param name="body">The comment body.</param> + /// <param name="inReplyTo">The comment ID to reply to.</param> + /// <returns></returns> + IObservable<PullRequestReviewComment> CreatePullRequestReviewComment(string owner, string name, int number, string body, int inReplyTo); + + /// <summary> + /// Delete a PR review comment. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request comment number.</param> + IObservable<Unit> DeletePullRequestReviewComment( + string owner, + string name, + int number); + + /// <summary> + /// Edits a PR review comment. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request comment number.</param> + /// <param name="body">The replacement comment body.</param> + IObservable<PullRequestReviewComment> EditPullRequestReviewComment( + string owner, + string name, + int number, + string body); + + IObservable<Branch> GetBranches(string owner, string repo); + IObservable<Repository> GetRepositories(); + IObservable<Repository> GetRepository(string owner, string repo); + IObservable<RepositoryContent> GetFileContents(string owner, string name, string reference, string path); } } diff --git a/src/GitHub.Exports.Reactive/Authentication/IDelegatingTwoFactorChallengeHandler.cs b/src/GitHub.Exports.Reactive/Authentication/IDelegatingTwoFactorChallengeHandler.cs new file mode 100644 index 0000000000..2590479433 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Authentication/IDelegatingTwoFactorChallengeHandler.cs @@ -0,0 +1,13 @@ +using System; +using Octokit; +using GitHub.ViewModels; +using GitHub.Api; + +namespace GitHub.Authentication +{ + public interface IDelegatingTwoFactorChallengeHandler : ITwoFactorChallengeHandler + { + void SetViewModel(IViewModel vm); + IViewModel CurrentViewModel { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs b/src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs deleted file mode 100644 index cbc5e37ac4..0000000000 --- a/src/GitHub.Exports.Reactive/Authentication/ITwoFactorChallengeHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Octokit; -using GitHub.ViewModels; - -namespace GitHub.Authentication -{ - public interface ITwoFactorChallengeHandler - { - void SetViewModel(IViewModel vm); - IViewModel CurrentViewModel { get; } - IObservable<TwoFactorChallengeResult> HandleTwoFactorException(TwoFactorAuthorizationException exception); - } -} diff --git a/src/GitHub.Exports.Reactive/Caches/AccountCacheItem.cs b/src/GitHub.Exports.Reactive/Caches/AccountCacheItem.cs index a90f559251..36ad661261 100644 --- a/src/GitHub.Exports.Reactive/Caches/AccountCacheItem.cs +++ b/src/GitHub.Exports.Reactive/Caches/AccountCacheItem.cs @@ -18,8 +18,6 @@ public AccountCacheItem() public AccountCacheItem(Account account) { Login = account.Login; - OwnedPrivateRepositoriesCount = account.OwnedPrivateRepos; - PrivateRepositoriesInPlanCount = (account.Plan == null ? 0 : account.Plan.PrivateRepos); IsUser = (account as User) != null; Uri htmlUrl; IsEnterprise = Uri.TryCreate(account.HtmlUrl, UriKind.Absolute, out htmlUrl) diff --git a/src/GitHub.Exports.Reactive/Caches/ILoginCache.cs b/src/GitHub.Exports.Reactive/Caches/ILoginCache.cs deleted file mode 100644 index aecaa03a3b..0000000000 --- a/src/GitHub.Exports.Reactive/Caches/ILoginCache.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Reactive; -using Akavache; -using GitHub.Primitives; - -namespace GitHub.Caches -{ - public interface ILoginCache : IDisposable - { - IObservable<LoginInfo> GetLoginAsync(HostAddress hostAddress); - IObservable<Unit> SaveLogin(string user, string password, HostAddress hostAddress); - IObservable<Unit> EraseLogin(HostAddress hostAddress); - IObservable<Unit> Flush(); - } -} diff --git a/src/GitHub.Exports.Reactive/Caches/ISharedCache.cs b/src/GitHub.Exports.Reactive/Caches/ISharedCache.cs index 06ed9b386c..1df074ca27 100644 --- a/src/GitHub.Exports.Reactive/Caches/ISharedCache.cs +++ b/src/GitHub.Exports.Reactive/Caches/ISharedCache.cs @@ -12,7 +12,6 @@ public interface ISharedCache : IDisposable { IBlobCache UserAccount { get; } IBlobCache LocalMachine { get; } - ISecureBlobCache Secure { get; } /// <summary> /// Retrieves the Enterpise Host Uri from cache if present. diff --git a/src/GitHub.Exports.Reactive/Collections/ISelectable.cs b/src/GitHub.Exports.Reactive/Collections/ISelectable.cs deleted file mode 100644 index f26de2b41a..0000000000 --- a/src/GitHub.Exports.Reactive/Collections/ISelectable.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ReactiveUI; - -namespace GitHub.Exports -{ - public interface ISelectable : IReactiveNotifyPropertyChanged<IReactiveObject> - { - bool IsSelected { get; set; } - } -} diff --git a/src/GitHub.Exports.Reactive/Collections/ITrackingCollection.cs b/src/GitHub.Exports.Reactive/Collections/ITrackingCollection.cs index 58d1fe2da0..23892fdaa2 100644 --- a/src/GitHub.Exports.Reactive/Collections/ITrackingCollection.cs +++ b/src/GitHub.Exports.Reactive/Collections/ITrackingCollection.cs @@ -1,6 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; +using System.Reactive; namespace GitHub.Collections { @@ -14,7 +17,10 @@ namespace GitHub.Collections /// for T /// </summary> /// <typeparam name="T"></typeparam> - public interface ITrackingCollection<T> : IDisposable, IList<T> where T : ICopyable<T> + public interface ITrackingCollection<T> : IDisposable, + INotifyCollectionChanged, INotifyPropertyChanged, + IList<T>, ICollection<T>, IEnumerable<T> + where T : class, ICopyable<T> { /// <summary> /// Sets up an observable as source for the collection. @@ -31,18 +37,37 @@ public interface ITrackingCollection<T> : IDisposable, IList<T> where T : ICopya /// collection to be resorted and refiltered. /// </summary> /// <param name="theComparer">The comparer method for sorting, or null if not sorting</param> - void SetComparer(Func<T, T, int> comparer); + Func<T, T, int> Comparer { get; set; } + /// <summary> /// Set a new filter. This will cause the collection to be filtered /// </summary> /// <param name="theFilter">The new filter, or null to not have any filtering</param> - void SetFilter(Func<T, int, IList<T>, bool> filter); + Func<T, int, IList<T>, bool> Filter { get; set; } + + /// <summary> + /// Set a comparer that determines whether the item being processed is newer than the same + /// item seen before. This is to prevent stale items from overriding newer items when data + /// is coming simultaneously from cache and from live data. Use a timestamp-like comparison + /// for best results + /// </summary> + /// <param name="theComparer">The comparer method for sorting, or null if not sorting</param> + Func<T, T, int> NewerComparer { get; set; } + void AddItem(T item); void RemoveItem(T item); /// <summary> /// How long to delay between processing incoming items /// </summary> TimeSpan ProcessingDelay { get; set; } - event NotifyCollectionChangedEventHandler CollectionChanged; + IObservable<Unit> OriginalCompleted { get; } + + /// <summary> + /// Returns the number of elements that the collection contains + /// regardless of filtering + /// </summary> + int UnfilteredCount { get; } + + bool Disposed { get; } } } \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Collections/TrackingCollection.cs b/src/GitHub.Exports.Reactive/Collections/TrackingCollection.cs index 6d335f8dba..fc557d1a44 100644 --- a/src/GitHub.Exports.Reactive/Collections/TrackingCollection.cs +++ b/src/GitHub.Exports.Reactive/Collections/TrackingCollection.cs @@ -1,8 +1,8 @@ #if !DISABLE_REACTIVEUI +using GitHub.VisualStudio.Helpers; using ReactiveUI; #else using System.Windows.Threading; -using System.Threading; #endif using System; using System.Collections.Concurrent; @@ -13,9 +13,147 @@ using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Linq; +using System.Collections.Specialized; +using System.ComponentModel; +using GitHub.Extensions; namespace GitHub.Collections { + public static class TrackingCollection + { + public static TrackingCollection<T> Create<T>(IObservable<T> source, + Func<T, T, int> comparer = null, + Func<T, int, IList<T>, bool> filter = null, + Func<T, T, int> newer = null, + IScheduler scheduler = null) + where T : class, ICopyable<T> + { + return new TrackingCollection<T>(source, comparer, filter, newer, scheduler); + } + + public static ObservableCollection<T> CreateListenerCollectionAndRun<T>(IObservable<T> source, + IList<T> stickieItemsOnTop = null, + Func<T, T, int> comparer = null, + Action<T> onNext = null) + where T : class, ICopyable<T> + { + var col = Create(source, comparer); + var ret = col.CreateListenerCollection(stickieItemsOnTop); + col.Subscribe(onNext ?? (_ => {}), () => {}); + return ret; + } + + public static ObservableCollection<T> CreateListenerCollection<T>(this ITrackingCollection<T> tcol, + IList<T> stickieItemsOnTop = null) + where T : class, ICopyable<T> + { + if (stickieItemsOnTop == null) + { + stickieItemsOnTop = new T[0]; + } + + var col = new ObservableCollection<T>(stickieItemsOnTop.Concat(tcol)); + tcol.CollectionChanged += (_, e) => UpdateStickieItems(col, e, stickieItemsOnTop); + return col; + } + + /// <summary> + /// Creates an observable collection that tracks an <see cref="ITrackingCollection{T}"/> + /// and adds a sticky item to the top of the collection when a related selection is null. + /// </summary> + /// <typeparam name="T">The type of items in the collection.</typeparam> + /// <param name="tcol">The source tracking collection</param> + /// <param name="stickieItemOnTop">The sticky item to add to the top of the collection.</param> + /// <param name="selection"> + /// The current selection. If null or equal to the sticky item then the sticky item will be + /// added to the collection. + /// </param> + /// <returns>An <see cref="ObservableCollection{T}"/>.</returns> + public static ObservableCollection<T> CreateListenerCollection<T>(this ITrackingCollection<T> tcol, + T stickieItemOnTop, + IObservable<T> selection) + where T : class, ICopyable<T> + { + Guard.ArgumentNotNull(stickieItemOnTop, nameof(stickieItemOnTop)); + Guard.ArgumentNotNull(selection, nameof(selection)); + + var stickieItems = new[] { stickieItemOnTop }; + var result = new ObservableCollection<T>(tcol); + var hasSelection = false; + + tcol.CollectionChanged += (_, e) => + { + UpdateStickieItems(result, e, hasSelection ? stickieItems : null); + }; + + selection.Subscribe(x => + { + hasSelection = x != null && !object.Equals(x, stickieItemOnTop); + var hasStickie = result.FirstOrDefault() == stickieItemOnTop; + + if (hasSelection && !hasStickie) + { + result.Insert(0, stickieItemOnTop); + } + else if (!hasSelection && hasStickie) + { + result.Remove(stickieItemOnTop); + } + }); + + return result; + } + + static void UpdateStickieItems<T>( + ObservableCollection<T> col, + NotifyCollectionChangedEventArgs e, + IList<T> stickieItemsOnTop) + { + var offset = 0; + if (stickieItemsOnTop != null) + { + if (object.Equals(col.FirstOrDefault(), stickieItemsOnTop.FirstOrDefault())) + offset = stickieItemsOnTop.Count; + } + + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move) + { + for (int i = 0, oldIdx = e.OldStartingIndex, newIdx = e.NewStartingIndex; + i < e.OldItems.Count; i++, oldIdx++, newIdx++) + { + col.Move(oldIdx + offset, newIdx + offset); + } + } + else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) + { + foreach (T item in e.NewItems) + col.Add(item); + } + else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) + { + foreach (T item in e.OldItems) + col.Remove(item); + } + else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) + { + for (int i = 0, idx = e.OldStartingIndex; i < e.OldItems.Count; i++, idx++) + col[idx + offset] = (T)e.NewItems[i]; + } + else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) + { + col.Clear(); + if (stickieItemsOnTop != null) + { + foreach (var item in stickieItemsOnTop) + col.Add(item); + } + } + } + } + /// <summary> /// TrackingCollection is a specialization of ObservableCollection that gets items from /// an observable sequence and updates its contents in such a way that two updates to @@ -26,8 +164,8 @@ namespace GitHub.Collections /// for T /// </summary> /// <typeparam name="T"></typeparam> - public class TrackingCollection<T> : ObservableCollection<T>, ITrackingCollection<T>, IDisposable - where T : class, ICopyable<T>, IComparable<T> + public class TrackingCollection<T> : ObservableCollection<T>, ITrackingCollection<T>, IReadOnlyObservableCollection<T>, IDisposable + where T : class, ICopyable<T> { enum TheAction { @@ -35,19 +173,32 @@ enum TheAction Move, Add, Insert, - Remove + Remove, + Ignore, + End } bool isChanging; - readonly CompositeDisposable disposables = new CompositeDisposable(); - IObservable<T> source; - IObservable<T> sourceQueue; Func<T, T, int> comparer; Func<T, int, IList<T>, bool> filter; - readonly IScheduler scheduler; - ConcurrentQueue<ActionData> queue; + Func<T, T, int> newer; // comparer to check whether the item being processed is newer than the existing one + + IObservable<T> source; + IConnectableObservable<T> dataPump; + IConnectableObservable<Unit> cachePump; + ConcurrentQueue<ActionData> cache; + ReplaySubject<Unit> signalHaveData; + ReplaySubject<Unit> signalNeedData; + ReplaySubject<ActionData> dataListener; + + bool resetting = false; + + readonly CompositeDisposable disposables = new CompositeDisposable(); + readonly CompositeDisposable pumpDisposables = new CompositeDisposable(); + + readonly IScheduler scheduler; readonly List<T> original = new List<T>(); #if DEBUG public IList<T> DebugInternalList => original; @@ -60,22 +211,32 @@ enum TheAction // for speeding up IndexOf in the filtered list readonly Dictionary<T, int> filteredIndexCache = new Dictionary<T, int>(); - TimeSpan delay; + bool originalSourceIsCompleted; + bool sourceHasData; + ReplaySubject<Unit> originalSourceCompleted; + public IObservable<Unit> OriginalCompleted => originalSourceCompleted; + TimeSpan requestedDelay; readonly TimeSpan fuzziness; + public TimeSpan ProcessingDelay { get { return requestedDelay; } - set - { - requestedDelay = value; - delay = value; - } + set { requestedDelay = value; } } - public TrackingCollection(Func<T, T, int> comparer = null, Func<T, int, IList<T>, bool> filter = null, IScheduler scheduler = null) + /// <summary> + /// Returns the number of elements that the collection contains + /// regardless of filtering + /// </summary> + public int UnfilteredCount => original.Count; + + bool ManualProcessing => cache.IsEmpty && originalSourceIsCompleted; + + public TrackingCollection(Func<T, T, int> comparer = null, Func<T, int, IList<T>, bool> filter = null, + Func<T, T, int> newer = null, IScheduler scheduler = null) { - queue = new ConcurrentQueue<ActionData>(); + cache = new ConcurrentQueue<ActionData>(); ProcessingDelay = TimeSpan.FromMilliseconds(10); fuzziness = TimeSpan.FromMilliseconds(1); @@ -86,13 +247,15 @@ public TrackingCollection(Func<T, T, int> comparer = null, Func<T, int, IList<T> #endif this.comparer = comparer ?? Comparer<T>.Default.Compare; this.filter = filter; + this.newer = newer; } public TrackingCollection(IObservable<T> source, Func<T, T, int> comparer = null, Func<T, int, IList<T>, bool> filter = null, + Func<T, T, int> newer = null, IScheduler scheduler = null) - : this(comparer, filter, scheduler) + : this(comparer, filter, newer, scheduler) { Listen(source); } @@ -109,35 +272,105 @@ public IObservable<T> Listen(IObservable<T> obs) if (disposed) throw new ObjectDisposedException("TrackingCollection"); - sourceQueue = obs - .Do(data => queue.Enqueue(new ActionData(data))); + Reset(); - source = Observable - .Generate(StartQueue(), - i => !disposed, - i => i + 1, - i => GetFromQueue(), - i => delay - ) - .Where(data => data.Item != null) - .ObserveOn(scheduler) - .Select(x => ProcessItem(x, original)) - // if we're removing an item that doesn't exist, ignore it - .Where(data => !(data.TheAction == TheAction.Remove && data.OldPosition < 0)) - .Select(SortedNone) - .Select(SortedAdd) - .Select(SortedInsert) - .Select(SortedMove) - .Select(SortedRemove) - .Select(CheckFilter) - .Select(FilteredAdd) - .Select(CalculateIndexes) - .Select(FilteredNone) - .Select(FilteredInsert) - .Select(FilteredMove) - .Select(FilteredRemove) + // ManualResetEvent uses the realtime clock for accurate <50ms delays + var waitHandle = new ManualResetEventSlim(); + + // empty the source observable as fast as possible + // to the cache queue, and signal that data is available + // for processing + dataPump = obs + .Catch<T, Exception>(ex => + { + originalSourceCompleted.OnError(ex); + return Observable.Throw<T>(ex); + }) + .Do(data => + { + sourceHasData = true; + cache.Enqueue(new ActionData(data)); + signalHaveData.OnNext(Unit.Default); + }) + .Finally(() => + { + if (disposed) + return; + + originalSourceIsCompleted = true; + if (!sourceHasData) + { + originalSourceCompleted.OnNext(Unit.Default); + originalSourceCompleted.OnCompleted(); + } + else + { + cache.Enqueue(new ActionData(TheAction.End, null)); + signalHaveData.OnNext(Unit.Default); + } + }) + .Publish(); + + // when both signalHaveData and signalNeedData produce a value, dataListener gets a value + // this will empty the queue of items that have been cached in regular intervals according + // to the requested delay + cachePump = signalHaveData + .Zip(signalNeedData, (a, b) => Unit.Default) + .ObserveOn(TaskPoolScheduler.Default) .TimeInterval() - .Select(UpdateProcessingDelay) + .Select(interval => + { + var delay = CalculateProcessingDelay(interval); + waitHandle.Wait(delay); + var data = GetFromQueue(); + if (!data.Equals(ActionData.Default)) + { + dataListener.OnNext(data); + } + return Unit.Default; + }) + .Publish(); + + source = dataListener + .Where(data => data.Item != null || data.TheAction == TheAction.End) + .ObserveOn(scheduler) + .Select(data => + { + if (data.TheAction == TheAction.End) + return data; + + data = ProcessItem(data, original); + + // if we're removing an item that doesn't exist, ignore it + if (data.TheAction == TheAction.Remove && data.OldPosition < 0) + return ActionData.Default; + + data = SortedNone(data); + data = SortedAdd(data); + data = SortedInsert(data); + data = SortedMove(data); + data = SortedRemove(data); + data = CheckFilter(data); + data = FilteredAdd(data); + data = CalculateIndexes(data); + data = FilteredNone(data); + data = FilteredInsert(data); + data = FilteredMove(data); + data = FilteredRemove(data); + return data; + }) + .Do(data => + { + if (data.TheAction == TheAction.End) + { + originalSourceCompleted.OnNext(Unit.Default); + originalSourceCompleted.OnCompleted(); + } + + if (!ManualProcessing) + signalNeedData.OnNext(Unit.Default); + }) + .Where(data => data.Item != null) .Select(data => data.Item) .Publish() .RefCount(); @@ -150,23 +383,58 @@ public IObservable<T> Listen(IObservable<T> obs) /// collection to be resorted and refiltered. /// </summary> /// <param name="theComparer">The comparer method for sorting, or null if not sorting</param> - public void SetComparer(Func<T, T, int> theComparer) + public Func<T, T, int> Comparer { - if (disposed) - throw new ObjectDisposedException("TrackingCollection"); - SetAndRecalculateSort(theComparer); - SetAndRecalculateFilter(filter); + get + { + return comparer; + } + set + { + if (disposed) + throw new ObjectDisposedException("TrackingCollection"); + SetAndRecalculateSort(value); + Filter = filter; + } } /// <summary> /// Set a new filter. This will cause the collection to be filtered /// </summary> /// <param name="theFilter">The new filter, or null to not have any filtering</param> - public void SetFilter(Func<T, int, IList<T>, bool> theFilter) + public Func<T, int, IList<T>, bool> Filter { - if (disposed) - throw new ObjectDisposedException("TrackingCollection"); - SetAndRecalculateFilter(theFilter); + get + { + return filter; + } + set + { + if (disposed) + throw new ObjectDisposedException("TrackingCollection"); + SetAndRecalculateFilter(value); + } + } + + /// <summary> + /// Set a comparer that determines whether the item being processed is newer than the same + /// item seen before. This is to prevent stale items from overriding newer items when data + /// is coming simultaneously from cache and from live data. Use a timestamp-like comparison + /// for best results + /// </summary> + /// <param name="theComparer">The comparer method for sorting, or null if not sorting</param> + public Func<T, T, int> NewerComparer + { + get + { + return newer; + } + set + { + if (disposed) + throw new ObjectDisposedException("TrackingCollection"); + newer = value; + } } public IDisposable Subscribe() @@ -175,7 +443,8 @@ public IDisposable Subscribe() throw new InvalidOperationException("No source observable has been set. Call Listen or pass an observable to the constructor"); if (disposed) throw new ObjectDisposedException("TrackingCollection"); - disposables.Add(source.Subscribe()); + pumpDisposables.Add(source.Subscribe()); + StartQueue(); return this; } @@ -185,22 +454,41 @@ public IDisposable Subscribe(Action<T> onNext, Action onCompleted) throw new InvalidOperationException("No source observable has been set. Call Listen or pass an observable to the constructor"); if (disposed) throw new ObjectDisposedException("TrackingCollection"); - disposables.Add(source.Subscribe(onNext, onCompleted)); + pumpDisposables.Add(source.Subscribe(onNext, onCompleted)); + StartQueue(); return this; } public void AddItem(T item) { + if (source == null) + throw new InvalidOperationException("No source observable has been set. Call Listen or pass an observable to the constructor"); if (disposed) throw new ObjectDisposedException("TrackingCollection"); - queue.Enqueue(new ActionData(item)); + + if (ManualProcessing) + dataListener.OnNext(new ActionData(item)); + else + { + cache.Enqueue(new ActionData(item)); + signalHaveData.OnNext(Unit.Default); + } } public void RemoveItem(T item) { + if (source == null) + throw new InvalidOperationException("No source observable has been set. Call Listen or pass an observable to the constructor"); if (disposed) throw new ObjectDisposedException("TrackingCollection"); - queue.Enqueue(new ActionData(TheAction.Remove, item)); + + if (ManualProcessing) + dataListener.OnNext(new ActionData(TheAction.Remove, item)); + else + { + cache.Enqueue(new ActionData(TheAction.Remove, item)); + signalHaveData.OnNext(Unit.Default); + } } void SetAndRecalculateSort(Func<T, T, int> theComparer) @@ -211,6 +499,7 @@ void SetAndRecalculateSort(Func<T, T, int> theComparer) void RecalculateSort(List<T> list, int start, int end) { + sortedIndexCache.Clear(); list.Sort(start, end, new LambdaComparer<T>(comparer)); } @@ -223,19 +512,11 @@ void SetAndRecalculateFilter(Func<T, int, IList<T>, bool> newFilter) #region Source pipeline processing - ActionData CheckFilter(ActionData data) - { - var isIncluded = true; - if (data.TheAction == TheAction.Remove) - isIncluded = false; - else if (filter != null) - isIncluded = filter(data.Item, data.Position, this); - return new ActionData(data, isIncluded); - } - int StartQueue() { - disposables.Add(sourceQueue.Subscribe()); + pumpDisposables.Add(cachePump.Connect()); + pumpDisposables.Add(dataPump.Connect()); + signalNeedData.OnNext(Unit.Default); return 0; } @@ -244,7 +525,7 @@ ActionData GetFromQueue() try { ActionData d = ActionData.Default; - if (queue?.TryDequeue(out d) ?? false) + if (cache?.TryDequeue(out d) ?? false) return d; } catch { } @@ -259,11 +540,18 @@ ActionData ProcessItem(ActionData data, List<T> list) var idx = GetIndexUnfiltered(item); if (data.TheAction == TheAction.Remove) - return new ActionData(TheAction.Remove, original, item, null, idx - 1, idx); + return new ActionData(TheAction.Remove, original, item, null, idx, idx); if (idx >= 0) { var old = list[idx]; + if (newer != null) + { + // the object is not "newer" than the one we have, ignore it + if (newer(item, old) >= 0) + return new ActionData(TheAction.Ignore, list, item, null, idx, idx); + } + var comparison = comparer(item, old); // no sorting to be done, just replacing the element in-place @@ -313,6 +601,7 @@ ActionData SortedAdd(ActionData data) if (data.TheAction != TheAction.Add) return data; data.List.Add(data.Item); + RaiseUnfilteredCountPropertyChange(); return data; } @@ -322,6 +611,7 @@ ActionData SortedInsert(ActionData data) return data; data.List.Insert(data.Position, data.Item); UpdateIndexCache(data.Position, data.List.Count, data.List, sortedIndexCache); + RaiseUnfilteredCountPropertyChange(); return data; } ActionData SortedMove(ActionData data) @@ -342,10 +632,21 @@ ActionData SortedRemove(ActionData data) // unfiltered list update sortedIndexCache.Remove(data.Item); UpdateIndexCache(data.List.Count - 1, data.OldPosition, data.List, sortedIndexCache); - original.Remove(data.Item); + data.List.Remove(data.Item); + RaiseUnfilteredCountPropertyChange(); return data; } + ActionData CheckFilter(ActionData data) + { + var isIncluded = true; + if (data.TheAction == TheAction.Remove) + isIncluded = false; + else if (filter != null) + isIncluded = filter(data.Item, data.Position, this); + return new ActionData(data, isIncluded); + } + ActionData FilteredAdd(ActionData data) { if (data.TheAction != TheAction.Add) @@ -493,17 +794,20 @@ ActionData FilteredRemove(ActionData data) /// so that the average time between an item being processed /// is +- the requested processing delay. /// </summary> - ActionData UpdateProcessingDelay(TimeInterval<ActionData> data) + TimeSpan CalculateProcessingDelay(TimeInterval<Unit> interval) { - if (requestedDelay == TimeSpan.Zero) - return data.Value; - var time = data.Interval; - if (time > requestedDelay + fuzziness) - delay -= time - requestedDelay; - else if (time < requestedDelay + fuzziness) - delay += requestedDelay - time; - delay = delay < TimeSpan.Zero ? TimeSpan.Zero : delay; - return data.Value; + var delay = TimeSpan.Zero; + if (requestedDelay > TimeSpan.Zero) + { + var time = interval.Interval; + if (time > requestedDelay + fuzziness) + delay -= time - requestedDelay; + else if (time < requestedDelay + fuzziness) + delay += requestedDelay - time; + delay = delay < TimeSpan.Zero ? TimeSpan.Zero : delay; + } + + return delay; } #endregion @@ -579,11 +883,9 @@ void MoveAndRecalculate(IList<T> list, int from, int to, int start, int end) throw new ArgumentOutOfRangeException(nameof(start), "Start cannot be bigger than end, evaluation of the filter goes forward."); InternalMoveItem(from, to); - start++; - RecalculateFilter(list, (from < to ? from : to) + 1, start, end); + RecalculateFilter(list, (from < to ? from : to), start, end); } - /// <summary> /// Go through the list of objects and adjust their "visibility" in the live list /// (by removing/inserting as needed). @@ -676,8 +978,16 @@ void InternalInsertItem(T item, int position) protected override void InsertItem(int index, T item) { +#if DEBUG && !DISABLE_REACTIVEUI + if (Splat.ModeDetector.InDesignMode() && !isChanging) + { + base.InsertItem(index, item); + return; + } +#endif + if (!isChanging) - throw new InvalidOperationException("Collection cannot be changed manually."); + throw new InvalidOperationException("Items cannot be manually inserted into the collection."); isChanging = false; filteredIndexCache.Add(item, index); @@ -704,6 +1014,14 @@ void InternalRemoveItem(T item) protected override void RemoveItem(int index) { +#if DEBUG && !DISABLE_REACTIVEUI + if (Splat.ModeDetector.InDesignMode() && !isChanging) + { + base.RemoveItem(index); + return; + } +#endif + if (!isChanging) throw new InvalidOperationException("Items cannot be removed from the collection except via RemoveItem(T)."); isChanging = false; @@ -724,8 +1042,16 @@ void InternalMoveItem(int positionFrom, int positionTo) protected override void MoveItem(int oldIndex, int newIndex) { +#if DEBUG && !DISABLE_REACTIVEUI + if (Splat.ModeDetector.InDesignMode() && !isChanging) + { + base.MoveItem(oldIndex, newIndex); + return; + } +#endif + if (!isChanging) - throw new InvalidOperationException("Collection cannot be changed manually."); + throw new InvalidOperationException("Items cannot be manually moved in the collection."); isChanging = false; if (oldIndex != newIndex) @@ -767,7 +1093,8 @@ int GetIndexUnfiltered(T item) { ret = original.IndexOf(item); if (ret >= 0) - sortedIndexCache.Add(item, ret); + sortedIndexCache.Add(original[ret], ret); + } return ret; } @@ -837,8 +1164,37 @@ static IScheduler GetScheduler(IScheduler scheduler) return scheduler ?? (d != null ? new DispatcherScheduler(d) : null as IScheduler) ?? CurrentThreadScheduler.Instance; } #endif + void RaiseUnfilteredCountPropertyChange() + { + OnPropertyChanged(new PropertyChangedEventArgs(nameof(UnfilteredCount))); + } + + void Reset() + { + if (resetting) + return; + + resetting = true; + + pumpDisposables.Clear(); + disposables.Clear(); + originalSourceIsCompleted = false; + sourceHasData = false; + cache = new ConcurrentQueue<ActionData>(); + dataListener = new ReplaySubject<ActionData>(); + disposables.Add(dataListener); + signalHaveData = new ReplaySubject<Unit>(); + disposables.Add(signalHaveData); + signalNeedData = new ReplaySubject<Unit>(); + disposables.Add(signalNeedData); + originalSourceCompleted = new ReplaySubject<Unit>(); + + resetting = false; + } bool disposed = false; + public bool Disposed => disposed; + void Dispose(bool disposing) { if (disposing) @@ -846,8 +1202,9 @@ void Dispose(bool disposing) if (!disposed) { disposed = true; - queue = null; + pumpDisposables.Dispose(); disposables.Dispose(); + cache = null; } } } diff --git a/src/GitHub.Exports.Reactive/Extensions/ConnectionManagerExtensions.cs b/src/GitHub.Exports.Reactive/Extensions/ConnectionManagerExtensions.cs index 7d9b9c779c..a13cd327d2 100644 --- a/src/GitHub.Exports.Reactive/Extensions/ConnectionManagerExtensions.cs +++ b/src/GitHub.Exports.Reactive/Extensions/ConnectionManagerExtensions.cs @@ -1,28 +1,20 @@ -using System.Reactive.Linq; +using System; +using System.Threading.Tasks; +using GitHub.Factories; using GitHub.Models; -using System; -using ReactiveUI; -using GitHub.Primitives; +using GitHub.Services; namespace GitHub.Extensions { public static class ConnectionManagerExtensions { - public static IObservable<bool> IsLoggedIn(this IConnectionManager cm, IRepositoryHosts hosts) + public static async Task<IModelService> GetModelService( + this IConnectionManager cm, + ILocalRepositoryModel repository, + IModelServiceFactory factory) { - return cm.Connections.ToObservable() - .SelectMany(c => c.Login()) - .Select(c => hosts.LookupHost(c.HostAddress)) - .Any(h => h.IsLoggedIn); - } - - public static IObservable<bool> IsLoggedIn(this IConnectionManager cm, IRepositoryHosts hosts, HostAddress address) - { - return cm.Connections.ToObservable() - .Where(c => c.HostAddress.Equals(address)) - .SelectMany(c => c.Login()) - .Select(c => hosts.LookupHost(c.HostAddress)) - .Any(h => h.IsLoggedIn); + var connection = await cm.GetConnection(repository); + return connection?.IsLoggedIn == true ? await factory.CreateAsync(connection) : null; } } } diff --git a/src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs b/src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs index 0d338a8ef6..5253d3f470 100644 --- a/src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs +++ b/src/GitHub.Exports.Reactive/Factories/IApiClientFactory.cs @@ -1,10 +1,16 @@ using GitHub.Api; using GitHub.Primitives; +using System; +using System.Threading.Tasks; +using Octokit; +using GitHub.Services; namespace GitHub.Factories { public interface IApiClientFactory { - IApiClient Create(HostAddress hostAddress); + Task<IGitHubClient> CreateGitHubClient(HostAddress hostAddress); + + Task<IApiClient> Create(HostAddress hostAddress); } } diff --git a/src/GitHub.Exports.Reactive/Factories/IModelServiceFactory.cs b/src/GitHub.Exports.Reactive/Factories/IModelServiceFactory.cs new file mode 100644 index 0000000000..8e476cd607 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Factories/IModelServiceFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; + +namespace GitHub.Factories +{ + public interface IModelServiceFactory + { + Task<IModelService> CreateAsync(IConnection connection); + IModelService CreateBlocking(IConnection connection); + } +} diff --git a/src/GitHub.Exports.Reactive/Factories/IRepositoryHostFactory.cs b/src/GitHub.Exports.Reactive/Factories/IRepositoryHostFactory.cs deleted file mode 100644 index 44b87bc2a9..0000000000 --- a/src/GitHub.Exports.Reactive/Factories/IRepositoryHostFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using GitHub.Models; -using GitHub.Primitives; - -namespace GitHub.Factories -{ - public interface IRepositoryHostFactory : IDisposable - { - IRepositoryHost Create(HostAddress hostAddress); - } -} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index 709a442059..1c26c0e103 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -9,9 +10,12 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub</RootNamespace> <AssemblyName>GitHub.Exports.Reactive</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> <NuGetPackageImportStamp> </NuGetPackageImportStamp> </PropertyGroup> @@ -19,41 +23,118 @@ <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="PresentationCore" /> <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.Core" /> <Reference Include="System.Net.Http" /> - <Reference Include="Microsoft.TeamFoundation.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Controls.dll</HintPath> - <Private>False</Private> - </Reference> <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> <Private>True</Private> @@ -87,52 +168,96 @@ <Compile Include="Collections\ITrackingCollection.cs" /> <Compile Include="Collections\TrackingCollection.cs" /> <Compile Include="Extensions\ConnectionManagerExtensions.cs" /> + <Compile Include="Factories\IModelServiceFactory.cs" /> + <Compile Include="GlobalSuppressions.cs" /> <Compile Include="Models\IAvatarContainer.cs" /> - <Compile Include="Models\IConnectionRepositoryHostMap.cs" /> - <Compile Include="Models\IRepositoryHosts.cs" /> + <Compile Include="Models\InlineCommentModel.cs" /> + <Compile Include="Models\IInlineCommentThreadModel.cs" /> + <Compile Include="Models\IPullRequestSessionFile.cs" /> + <Compile Include="Models\PullRequestTextBufferInfo.cs" /> <Compile Include="Services\IModelService.cs" /> - <Compile Include="ViewModels\ILoginControlViewModel.cs" /> - <Compile Include="ViewModels\ILoginToGitHubForEnterpriseViewModel.cs" /> - <Compile Include="ViewModels\ILoginToGitHubViewModel.cs" /> + <Compile Include="Services\IGistPublishService.cs" /> + <Compile Include="Services\IPullRequestEditorService.cs" /> + <Compile Include="Services\IPullRequestSession.cs" /> + <Compile Include="Services\IPullRequestService.cs" /> + <Compile Include="Services\IPullRequestSessionManager.cs" /> + <Compile Include="Services\IRepositoryForkService.cs" /> + <Compile Include="Services\IShowDialogService.cs" /> + <Compile Include="Services\LocalRepositoriesExtensions.cs" /> + <Compile Include="Services\ModelServiceExtensions.cs" /> + <Compile Include="Services\PullRequestSessionExtensions.cs" /> + <Compile Include="ViewModels\Dialog\IDialogContentViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IForkRepositoryExecuteViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IForkRepositorySwitchViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IForkRepositoryViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IForkRepositorySelectViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IGitHubDialogWindowViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IGistCreationViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ILogin2FaViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ILoginCredentialsViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ILoginViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IRepositoryCloneViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IRepositoryCreationViewModel.cs" /> + <Compile Include="ViewModels\Dialog\IRepositoryRecloneViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IIssueListItemViewModelBase.cs" /> + <Compile Include="ViewModels\GitHubPane\IIssueListViewModelBase.cs" /> + <Compile Include="ViewModels\GitHubPane\ILoggedOutViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\INavigationViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\ILoginFailedViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPanePageViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestCheckViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestFilesViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestListItemViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestListViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestReviewAuthoringViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestReviewFileCommentViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestReviewSummaryViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestReviewViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestUserReviewsViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\ISearchablePageViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestCreationViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestDetailViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\INotAGitHubRepositoryViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\INotAGitRepositoryViewModel.cs" /> + <Compile Include="Services\NotificationDispatcher.cs" /> <Compile Include="Caches\IImageCache.cs" /> - <Compile Include="Caches\ILoginCache.cs" /> <Compile Include="Caches\ISharedCache.cs" /> <Compile Include="Services\IImageDownloader.cs" /> <Compile Include="Services\IGitClient.cs" /> <Compile Include="Services\IRepositoryPublishService.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestChangeNode.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestDirectoryNode.cs" /> + <Compile Include="ViewModels\GitHubPane\IPullRequestFileNode.cs" /> + <Compile Include="ViewModels\Dialog\IRepositoryCreationTarget.cs" /> + <Compile Include="ViewModels\Dialog\ILoginToGitHubForEnterpriseViewModel.cs" /> + <Compile Include="ViewModels\Dialog\ILoginToGitHubViewModel.cs" /> + <Compile Include="ViewModels\IActorViewModel.cs" /> <Compile Include="ViewModels\ILoginToHostViewModel.cs" /> - <Compile Include="ViewModels\IRepositoryCreationTarget.cs" /> <Compile Include="ViewModels\IRepositoryForm.cs" /> - <Compile Include="ViewModels\IRepositoryPublishViewModel.cs" /> - <Compile Include="ViewModels\ITwoFactorDialogViewModel.cs" /> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> <Compile Include="Helpers\ExceptionHelper.cs" /> - <Compile Include="Helpers\Guard.cs" /> <Compile Include="Models\GitIgnoreItem.cs" /> <Compile Include="Models\LicenseItem.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> </Compile> + <Compile Include="ViewModels\IUserFilterViewModel.cs" /> + <Compile Include="ViewModels\ViewModelBase.cs" /> + <Compile Include="ViewModels\TeamExplorer\IRepositoryPublishViewModel.cs" /> </ItemGroup> <ItemGroup> <Compile Include="Factories\IApiClientFactory.cs" /> - <Compile Include="Authentication\ITwoFactorChallengeHandler.cs" /> - <Compile Include="Factories\IRepositoryHostFactory.cs" /> - <Compile Include="Models\IRepositoryHost.cs" /> - <Compile Include="Collections\ISelectable.cs" /> + <Compile Include="Authentication\IDelegatingTwoFactorChallengeHandler.cs" /> <Compile Include="Api\IApiClient.cs" /> <Compile Include="Services\IAvatarProvider.cs" /> <Compile Include="Services\IRepositoryCloneService.cs" /> <Compile Include="Services\IRepositoryCreationService.cs" /> - <Compile Include="ViewModels\IRepositoryCloneViewModel.cs" /> - <Compile Include="ViewModels\IRepositoryCreationViewModel.cs" /> <Compile Include="Validation\ReactivePropertyValidator.cs" /> </ItemGroup> <ItemGroup> - <None Include="packages.config" /> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\submodules\akavache\Akavache\Akavache_Net45.csproj"> @@ -140,10 +265,6 @@ <Name>Akavache_Net45</Name> <Private>False</Private> </ProjectReference> - <ProjectReference Include="..\..\submodules\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj"> - <Project>{ee6ed99f-cb12-4683-b055-d28fc7357a34}</Project> - <Name>LibGit2Sharp</Name> - </ProjectReference> <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> <Name>Octokit</Name> @@ -156,6 +277,10 @@ <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> <Name>Splat-Net45</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> + <Project>{B389ADAF-62CC-486E-85B4-2D8B078DF763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> <Name>GitHub.Exports</Name> @@ -164,9 +289,19 @@ <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> + </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/GitHub.Exports.Reactive/GlobalSuppressions.cs b/src/GitHub.Exports.Reactive/GlobalSuppressions.cs new file mode 100644 index 0000000000..2538500fd8 Binary files /dev/null and b/src/GitHub.Exports.Reactive/GlobalSuppressions.cs differ diff --git a/src/GitHub.Exports.Reactive/Helpers/Guard.cs b/src/GitHub.Exports.Reactive/Helpers/Guard.cs deleted file mode 100644 index 659e474c4d..0000000000 --- a/src/GitHub.Exports.Reactive/Helpers/Guard.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using Splat; - -namespace GitHub -{ - public static class Guard - { - /// <summary> - /// Validates that the string is not empty. - /// </summary> - /// <param name="value">The string to check.</param> - /// <param name="name">The name of the argument</param> - public static void ArgumentNotEmptyString(string value, string name) - { - // We already know the value is not null because of NullGuard.Fody. - if (!string.IsNullOrWhiteSpace(value)) return; - - string message = string.Format(CultureInfo.InvariantCulture, "The value for '{0}' must not be empty", name); -#if DEBUG - if (!ModeDetector.InUnitTestRunner()) - { - Debug.Fail(message); - } -#endif - throw new ArgumentException(message, name); - } - - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class ValidatedNotNullAttribute : Attribute - { - } - } -} diff --git a/src/GitHub.Exports.Reactive/Models/GitIgnoreItem.cs b/src/GitHub.Exports.Reactive/Models/GitIgnoreItem.cs index 5ae3e0c61d..7d4312f55c 100644 --- a/src/GitHub.Exports.Reactive/Models/GitIgnoreItem.cs +++ b/src/GitHub.Exports.Reactive/Models/GitIgnoreItem.cs @@ -1,4 +1,5 @@ -using System; +using GitHub.Collections; +using System; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -6,7 +7,7 @@ namespace GitHub.Models { [DebuggerDisplay("{DebuggerDisplay,nq}")] - public sealed class GitIgnoreItem + public sealed class GitIgnoreItem : ICopyable<GitIgnoreItem> { static readonly string[] recommendedIgnoreFiles = { "None", "VisualStudio", "Node", "Eclipse", "C++", "Windows" }; @@ -24,6 +25,12 @@ public static GitIgnoreItem Create(string name) Recommended = IsRecommended(name); } + public void CopyFrom(GitIgnoreItem other) + { + Name = other.Name; + Recommended = other.Recommended; + } + public string Name { get; private set; } public bool Recommended { get; private set; } diff --git a/src/GitHub.Exports.Reactive/Models/IConnectionRepositoryHostMap.cs b/src/GitHub.Exports.Reactive/Models/IConnectionRepositoryHostMap.cs deleted file mode 100644 index e1a7050355..0000000000 --- a/src/GitHub.Exports.Reactive/Models/IConnectionRepositoryHostMap.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace GitHub.Models -{ - /// <summary> - /// Used to associate the current connection to its related repository host. - /// </summary> - public interface IConnectionRepositoryHostMap - { - /// <summary> - /// The current repository host associated with the current action. - /// </summary> - IRepositoryHost CurrentRepositoryHost { get; } - } -} diff --git a/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs b/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs new file mode 100644 index 0000000000..ed5cd93908 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Represents a thread of inline comments on an <see cref="IPullRequestSessionFile"/>. + /// </summary> + public interface IInlineCommentThreadModel + { + /// <summary> + /// Gets the comments in the thread. + /// </summary> + IReadOnlyList<InlineCommentModel> Comments { get; } + + /// <summary> + /// Gets the last five lines of the thread's diff hunk, in reverse order. + /// </summary> + IList<DiffLine> DiffMatch { get; } + + /// <summary> + /// Gets the type of diff line that the thread was left on + /// </summary> + DiffChangeType DiffLineType { get; } + + /// <summary> + /// Gets or sets a value indicating that the <see cref="LineNumber"/> is approximate and + /// needs to be updated. + /// </summary> + /// <remarks> + /// As edits are made, the <see cref="LineNumber"/> for a thread can be shifted up or down, + /// but until <see cref="IPullRequestSession.RecaluateLineNumbers"/> is called we can't tell + /// whether the comment is still valid at the new position. This property indicates such a + /// state. + /// </remarks> + bool IsStale { get; set; } + + /// <summary> + /// Gets or sets the 0-based line number of the comment, or -1 of the thread is outdated. + /// </summary> + int LineNumber { get; set; } + + /// <summary> + /// Gets the SHA of the commit that the thread appears on. + /// </summary> + string CommitSha { get; } + + /// <summary> + /// Gets the relative path to the file that the thread is on. + /// </summary> + string RelativePath { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs b/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs new file mode 100644 index 0000000000..e13eb31a6c --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace GitHub.Models +{ + public enum DiffSide + { + Left, + Right, + } + + /// <summary> + /// Represents a file in a pull request. + /// </summary> + /// <remarks> + /// A <see cref="IPullRequestSessionFile"/> holds the review comments for a file in a pull + /// request together with associated information such as the commit SHA of the file and the + /// diff with the file's merge base. + /// </remarks> + /// <seealso cref="IPullRequestSession"/> + /// <seealso cref="IPullRequestSessionManager"/> + public interface IPullRequestSessionFile : INotifyPropertyChanged + { + /// <summary> + /// Gets the SHA of the base commit of the file in the pull request. + /// </summary> + string BaseSha { get; } + + /// <summary> + /// Gets the SHA of the current commit of the file, or null if the file has uncommitted + /// changes. + /// </summary> + string CommitSha { get; } + + /// <summary> + /// Gets a value indicating whether <see cref="CommitSha"/> is tracking the related pull + /// request HEAD or whether it is pinned at a particular commit. + /// </summary> + bool IsTrackingHead { get; } + + /// <summary> + /// Gets the path to the file relative to the repository. + /// </summary> + string RelativePath { get; } + + /// <summary> + /// Gets the diff between the PR merge base and the current state of the file. + /// </summary> + IReadOnlyList<DiffChunk> Diff { get; } + + /// <summary> + /// Gets the inline comments threads for the file. + /// </summary> + IReadOnlyList<IInlineCommentThreadModel> InlineCommentThreads { get; } + + /// <summary> + /// Gets an observable that is raised with a collection of 0-based line numbers when the + /// review comments on the file are changed. + /// </summary> + IObservable<IReadOnlyList<Tuple<int, DiffSide>>> LinesChanged { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs b/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs deleted file mode 100644 index 2dca26cdd4..0000000000 --- a/src/GitHub.Exports.Reactive/Models/IRepositoryHost.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Reactive; -using GitHub.Api; -using GitHub.Authentication; -using GitHub.Primitives; -using GitHub.Services; - -namespace GitHub.Models -{ - public interface IRepositoryHost : IDisposable - { - HostAddress Address { get; } - IApiClient ApiClient { get; } - IModelService ModelService { get; } - bool IsLoggedIn { get; } - string Title { get; } - - IObservable<AuthenticationResult> LogIn(string usernameOrEmail, string password); - IObservable<AuthenticationResult> LogInFromCache(); - IObservable<Unit> LogOut(); - } -} diff --git a/src/GitHub.Exports.Reactive/Models/IRepositoryHosts.cs b/src/GitHub.Exports.Reactive/Models/IRepositoryHosts.cs deleted file mode 100644 index e9b9b45096..0000000000 --- a/src/GitHub.Exports.Reactive/Models/IRepositoryHosts.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using GitHub.Authentication; -using GitHub.Factories; -using GitHub.Primitives; -using ReactiveUI; - -namespace GitHub.Models -{ - public interface IRepositoryHosts : IReactiveObject, IDisposable - { - IRepositoryHost EnterpriseHost { get; set; } - IRepositoryHost GitHubHost { get; } - IObservable<AuthenticationResult> LogIn( - HostAddress hostAddress, - string usernameOrEmail, - string password); - IObservable<AuthenticationResult> LogInFromCache(HostAddress hostAddress); - IRepositoryHostFactory RepositoryHostFactory { get; } - bool IsLoggedInToAnyHost { get; } - IRepositoryHost LookupHost(HostAddress address); - } -} diff --git a/src/GitHub.Exports.Reactive/Models/InlineCommentModel.cs b/src/GitHub.Exports.Reactive/Models/InlineCommentModel.cs new file mode 100644 index 0000000000..8593b45169 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/InlineCommentModel.cs @@ -0,0 +1,26 @@ +using System; + +namespace GitHub.Models +{ + /// <summary> + /// Relates a <see cref="PullRequestReviewCommentModel"/> to an + /// <see cref="IInlineCommentThreadModel"/>. + /// </summary> + public class InlineCommentModel + { + /// <summary> + /// Gets or sets the thread that the comment appears in. + /// </summary> + public IInlineCommentThreadModel Thread { get; set; } + + /// <summary> + /// Gets or sets the review that the comment appears in. + /// </summary> + public PullRequestReviewModel Review { get; set; } + + /// <summary> + /// Gets or sets the comment. + /// </summary> + public PullRequestReviewCommentModel Comment { get; set; } + } +} diff --git a/src/GitHub.Exports.Reactive/Models/LicenseItem.cs b/src/GitHub.Exports.Reactive/Models/LicenseItem.cs index db06fa76b3..fdffaf0eaa 100644 --- a/src/GitHub.Exports.Reactive/Models/LicenseItem.cs +++ b/src/GitHub.Exports.Reactive/Models/LicenseItem.cs @@ -1,9 +1,11 @@ using System.Globalization; using Octokit; +using GitHub.Collections; +using System; namespace GitHub.Models { - public class LicenseItem + public class LicenseItem : ICopyable<LicenseItem> { static readonly LicenseItem none = new LicenseItem(); public static LicenseItem None { get { return none; } } @@ -27,6 +29,13 @@ public static bool IsRecommended(string licenseKey) return licenseKey == "mit" || licenseKey == "gpl-2.0" || licenseKey == "apache-2.0"; } + public void CopyFrom(LicenseItem other) + { + Key = other.Key; + Name = other.Name; + Recommended = other.Recommended; + } + public string Key { get; private set; } public string Name { get; private set; } diff --git a/src/GitHub.Exports.Reactive/Models/PullRequestTextBufferInfo.cs b/src/GitHub.Exports.Reactive/Models/PullRequestTextBufferInfo.cs new file mode 100644 index 0000000000..704002064c --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/PullRequestTextBufferInfo.cs @@ -0,0 +1,57 @@ +using System; +using GitHub.Extensions; +using GitHub.Services; + +namespace GitHub.Models +{ + /// <summary> + /// When attached as a property to a Visual Studio ITextBuffer, informs the inline comment + /// tagger that the buffer represents a buffer opened from a pull request at the specified + /// commit of a pull request. + /// </summary> + public class PullRequestTextBufferInfo + { + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestTextBufferInfo"/> class. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="relativePath">The relative path to the file in the repository.</param> + /// <param name="commitSha">The SHA of the commit.</param> + /// <param name="side">Which side of a diff comparision the buffer represents.</param> + public PullRequestTextBufferInfo( + IPullRequestSession session, + string relativePath, + string commitSha, + DiffSide? side) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + Guard.ArgumentNotEmptyString(commitSha, nameof(commitSha)); + + Session = session; + RelativePath = relativePath; + CommitSha = commitSha; + Side = side; + } + + /// <summary> + /// Gets the pull request session. + /// </summary> + public IPullRequestSession Session { get; } + + /// <summary> + /// Gets the relative path to the file in the repository. + /// </summary> + public string RelativePath { get; } + + /// <summary> + /// Gets the SHA of the commit. + /// </summary> + public string CommitSha { get; } + + /// <summary> + /// Gets a value indicating which side of a diff comparision the buffer represents. + /// </summary> + public DiffSide? Side { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs b/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs index a969fcf414..9cbe24857f 100644 --- a/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs +++ b/src/GitHub.Exports.Reactive/Properties/AssemblyInfo.cs @@ -1,6 +1,11 @@ using System.Reflection; using System.Runtime.InteropServices; +using System.Windows.Markup; [assembly: AssemblyTitle("GitHub.Exports.Reactive")] [assembly: AssemblyDescription("GitHub interfaces for mef exports with reactive dependencies")] [assembly: Guid("e4ed0537-d1d9-44b6-9212-3096d7c3f7a1")] + +[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.GitHubPane")] \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs b/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs index ccf656280a..1006256d96 100644 --- a/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs +++ b/src/GitHub.Exports.Reactive/Services/IAvatarProvider.cs @@ -10,6 +10,7 @@ public interface IAvatarProvider : IDisposable BitmapImage DefaultUserBitmapImage { get; } BitmapImage DefaultOrgBitmapImage { get; } IObservable<BitmapSource> GetAvatar(IAvatarContainer account); + IObservable<BitmapSource> GetAvatar(string avatarUri); IObservable<Unit> InvalidateAvatar(IAvatarContainer account); } } diff --git a/src/GitHub.Exports.Reactive/Services/IGistPublishService.cs b/src/GitHub.Exports.Reactive/Services/IGistPublishService.cs new file mode 100644 index 0000000000..eecb67cb1e --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IGistPublishService.cs @@ -0,0 +1,17 @@ +using System; +using Octokit; +using GitHub.Api; + +namespace GitHub.Services +{ + public interface IGistPublishService + { + /// <summary> + /// Publishes a gist to GitHub. + /// </summary> + /// <param name="apiClient">The client to use to post to GitHub.</param> + /// <param name="gist">The new gist to post.</param> + /// <returns>The created gist.</returns> + IObservable<Gist> PublishGist(IApiClient apiClient, NewGist gist); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IGitClient.cs b/src/GitHub.Exports.Reactive/Services/IGitClient.cs index a00f96252f..81fbbafebe 100644 --- a/src/GitHub.Exports.Reactive/Services/IGitClient.cs +++ b/src/GitHub.Exports.Reactive/Services/IGitClient.cs @@ -1,19 +1,29 @@ using System; -using System.Reactive; +using System.Threading.Tasks; using LibGit2Sharp; +using GitHub.Primitives; +using System.Collections.Generic; +using GitHub.Models; namespace GitHub.Services { public interface IGitClient { /// <summary> - /// Pushes the specified branch to the remote. + /// Pull changes from the configured upstream remote and branch into the branch pointed at by HEAD. /// </summary> /// <param name="repository">The repository to pull</param> + /// <returns></returns> + Task Pull(IRepository repository); + + /// <summary> + /// Pushes HEAD to specified branch on the remote. + /// </summary> + /// <param name="repository">The repository to push</param> + /// <param name="branchName">the branch remote to push to</param> /// <param name="remoteName">The name of the remote</param> - /// <param name="branchName">the branch to pull</param> /// <returns></returns> - IObservable<Unit> Push(IRepository repository, string branchName, string remoteName); + Task Push(IRepository repository, string branchName, string remoteName); /// <summary> /// Fetches the remote. @@ -21,7 +31,101 @@ public interface IGitClient /// <param name="repository">The repository to pull</param> /// <param name="remoteName">The name of the remote</param> /// <returns></returns> - IObservable<Unit> Fetch(IRepository repository, string remoteName); + Task Fetch(IRepository repository, string remoteName); + + /// <summary> + /// Fetches from the remote, using custom refspecs. + /// </summary> + /// <param name="repository">The repository to pull</param> + /// <param name="remoteName">The name of the remote</param> + /// <param name="refspecs">The custom refspecs</param> + /// <returns></returns> + Task Fetch(IRepository repository, string remoteName, params string[] refspecs); + + /// <summary> + /// Fetches from a remote URI, using custom refspecs. + /// </summary> + /// <remarks> + /// If the URI is the same as origin then origin will be used, otherwise a + /// temporary remote will be created for the fetch. The fetch will always be made + /// using HTTPS. + /// </remarks> + /// <param name="repository">The repository to pull</param> + /// <param name="remoteUri">The remote URI to fetch from</param> + /// <param name="refspecs">The custom refspecs</param> + /// <returns></returns> + Task Fetch(IRepository repository, UriString remoteUri, params string[] refspecs); + + /// <summary> + /// Checks out a branch. + /// </summary> + /// <param name="repository">The repository to carry out the checkout on</param> + /// <param name="branchName">The name of the branch</param> + /// <returns></returns> + Task Checkout(IRepository repository, string branchName); + + /// <summary> + /// Creates a new branch. + /// </summary> + /// <param name="repository">The repository to carry out the checkout on</param> + /// <param name="branchName">The name of the branch</param> + /// <returns></returns> + Task CreateBranch(IRepository repository, string branchName); + + /// <summary> + /// Compares two commits. + /// </summary> + /// <param name="repository">The repository</param> + /// <param name="sha1">The SHA of the first commit.</param> + /// <param name="sha2">The SHA of the second commit.</param> + /// <param name="detectRenames">Whether to detect renames</param> + /// <returns> + /// A <see cref="TreeChanges"/> object or null if one of the commits could not be found in the repository, + /// (e.g. it is from a fork). + /// </returns> + Task<TreeChanges> Compare(IRepository repository, string sha1, string sha2, bool detectRenames = false); + + /// <summary> + /// Compares a file in two commits. + /// </summary> + /// <param name="repository">The repository</param> + /// <param name="sha1">The SHA of the first commit.</param> + /// <param name="sha2">The SHA of the second commit.</param> + /// <param name="path">The relative path to the file.</param> + /// <returns> + /// A <see cref="Patch"/> object or null if one of the commits could not be found in the repository. + /// </returns> + Task<Patch> Compare(IRepository repository, string sha1, string sha2, string path); + + /// <summary> + /// Compares a file in a commit to a string. + /// </summary> + /// <param name="repository">The repository</param> + /// <param name="sha1">The SHA of the first commit.</param> + /// <param name="sha2">The SHA of the second commit.</param> + /// <param name="path">The relative path to the file.</param> + /// <param name="contents">The contents to compare with the file.</param> + /// <returns> + /// A <see cref="Patch"/> object or null if the commit could not be found in the repository. + /// </returns> + Task<ContentChanges> CompareWith(IRepository repository, string sha1, string sha2, string path, byte[] contents); + + /// <summary> + /// Gets the value of a configuration key. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="key">The configuration key. Keys are in the form 'section.name'.</param> + /// <returns></returns> + Task<T> GetConfig<T>(IRepository repository, string key); + + /// <summary> + /// Sets the configuration key to the specified value in the local config. + /// </summary> + /// <param name="repository">The repository</param> + /// <param name="key">The configuration key. Keys are in the form 'section.name'.</param> + /// <param name="value">The value.</param> + /// <returns></returns> + Task SetConfig(IRepository repository, string key, string value); /// <summary> /// Sets the specified remote to the specified URL. @@ -30,15 +134,95 @@ public interface IGitClient /// <param name="remoteName">The name of the remote</param> /// <param name="url">The URL to set as the remote</param> /// <returns></returns> - IObservable<Unit> SetRemote(IRepository repository, string remoteName, Uri url); + Task SetRemote(IRepository repository, string remoteName, Uri url); /// <summary> /// Sets the remote branch that the local branch tracks /// </summary> /// <param name="repository">The repository to set</param> - /// <param name="branchName">The name of the remote</param> - /// <param name="remoteName">The name of the branch (local and remote)</param> + /// <param name="branchName">The name of the local branch</param> + /// <param name="remoteName">The name of the remote</param> /// <returns></returns> - IObservable<Unit> SetTrackingBranch(IRepository repository, string branchName, string remoteName); + Task SetTrackingBranch(IRepository repository, string branchName, string remoteName); + + /// <summary> + /// Unsets the configuration key in the local config. + /// </summary> + /// <param name="repository">The repository</param> + /// <param name="key">The configuration key. Keys are in the form 'section.name'.</param> + /// <returns></returns> + Task UnsetConfig(IRepository repository, string key); + + Task<Remote> GetHttpRemote(IRepository repo, string remote); + + /// <summary> + /// Extracts a file at a specified commit from the repository. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="commitSha">The SHA of the commit.</param> + /// <param name="fileName">The path to the file, relative to the repository.</param> + /// <returns> + /// The contents of the file, or null if the file was not found at the specified commit. + /// </returns> + Task<string> ExtractFile(IRepository repository, string commitSha, string fileName); + + /// <summary> + /// Extracts a file at a specified commit from the repository as binary data. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="commitSha">The SHA of the commit.</param> + /// <param name="fileName">The path to the file, relative to the repository.</param> + /// <returns> + /// The contents of the file, or null if the file was not found at the specified commit. + /// </returns> + Task<byte[]> ExtractFileBinary(IRepository repository, string commitSha, string fileName); + + /// <summary> + /// Checks whether the latest commit of a file in the repository has the specified file + /// contents. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="path">The relative path to the file.</param> + /// <param name="contents">The file contents to test.</param> + /// <returns></returns> + Task<bool> IsModified(IRepository repository, string path, byte[] contents); + + /// <summary> + /// Find the merge base SHA between two commits. + /// </summary> + /// <param name="repo">The repository.</param> + /// <param name="targetCloneUrl">The clone url of the PR target repo.</param> + /// <param name="baseSha">The PR base SHA.</param> + /// <param name="headSha">The PR head SHA.</param> + /// <param name="baseRef">The PR base ref (e.g. 'master').</param> + /// <param name="pullNumber">The PR number.</param> + /// <returns> + /// The merge base SHA or null. + /// </returns> + /// <exception cref="LibGit2Sharp.NotFoundException">Thrown when the merge base can't be found.</exception> + Task<string> GetPullRequestMergeBase(IRepository repo, UriString targetCloneUrl, string baseSha, string headSha, string baseRef, int pullNumber); + + /// <summary> + /// Checks whether the current head is pushed to its remote tracking branch. + /// </summary> + /// <param name="repo">The repository.</param> + /// <returns></returns> + Task<bool> IsHeadPushed(IRepository repo); + + /// <summary> + /// Gets the unique commits from <paramref name="compareBranch"/> to the merge base of + /// <paramref name="baseBranch"/> and <paramref name="compareBranch"/> and returns their + /// commit messages. + /// </summary> + /// <param name="repo">The repository.</param> + /// <param name="baseBranch">The base branch to find a merge base with.</param> + /// <param name="compareBranch">The compare branch to find a merge base with.</param> + /// <param name="maxCommits">The maximum number of commits to return.</param> + /// <returns>An enumerable of unique commits from the merge base to the compareBranch.</returns> + Task<IReadOnlyList<CommitMessage>> GetMessagesForUniqueCommits( + IRepository repo, + string baseBranch, + string compareBranch, + int maxCommits); } } diff --git a/src/GitHub.Exports.Reactive/Services/IModelService.cs b/src/GitHub.Exports.Reactive/Services/IModelService.cs index fc846e1b39..76f970248a 100644 --- a/src/GitHub.Exports.Reactive/Services/IModelService.cs +++ b/src/GitHub.Exports.Reactive/Services/IModelService.cs @@ -4,6 +4,7 @@ using GitHub.Models; using GitHub.Caches; using GitHub.Collections; +using GitHub.Api; namespace GitHub.Services { @@ -13,13 +14,24 @@ namespace GitHub.Services /// </summary> public interface IModelService : IDisposable { - IObservable<AccountCacheItem> GetUserFromCache(); + IApiClient ApiClient { get; } + + IObservable<IAccount> GetCurrentUser(); + IObservable<IAccount> GetUser(string login); IObservable<Unit> InsertUser(AccountCacheItem user); IObservable<IReadOnlyList<IAccount>> GetAccounts(); - IObservable<IReadOnlyList<IRepositoryModel>> GetRepositories(); - IObservable<IReadOnlyList<LicenseItem>> GetLicenses(); - IObservable<IReadOnlyList<GitIgnoreItem>> GetGitIgnoreTemplates(); - ITrackingCollection<IPullRequestModel> GetPullRequests(ISimpleRepositoryModel repo); + IObservable<IRemoteRepositoryModel> GetRepository(string owner, string repo); + ITrackingCollection<IRemoteRepositoryModel> GetRepositories(ITrackingCollection<IRemoteRepositoryModel> collection); + IObservable<IRemoteRepositoryModel> GetForks(IRepositoryModel repository); + IObservable<LicenseItem> GetLicenses(); + IObservable<GitIgnoreItem> GetGitIgnoreTemplates(); + IObservable<IPullRequestModel> GetPullRequest(string owner, string name, int number); + ITrackingCollection<IPullRequestModel> GetPullRequests(IRepositoryModel repo, ITrackingCollection<IPullRequestModel> collection); + IObservable<IPullRequestModel> CreatePullRequest(ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, + IBranch sourceBranch, IBranch targetBranch, + string title, string body); + IObservable<IBranch> GetBranches(IRepositoryModel repo); IObservable<Unit> InvalidateAll(); + IObservable<string> GetFileContents(IRepositoryModel repo, string commitSha, string path, string fileSha); } } diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs new file mode 100644 index 0000000000..ec5e274422 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs @@ -0,0 +1,78 @@ +using System.Threading.Tasks; +using GitHub.Models; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace GitHub.Services +{ + /// <summary> + /// Services for opening views of pull request files in Visual Studio. + /// </summary> + public interface IPullRequestEditorService + { + /// <summary> + /// Opens an editor for a file in a pull request. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="relativePath">The path to the file, relative to the repository.</param> + /// <param name="workingDirectory"> + /// If true opens the file in the working directory, if false opens the file in the HEAD + /// commit of the pull request. + /// </param> + /// <returns>The opened file.</returns> + Task<ITextView> OpenFile(IPullRequestSession session, string relativePath, bool workingDirectory); + + /// <summary> + /// Opens an diff viewer for a file in a pull request. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="relativePath">The path to the file, relative to the repository.</param> + /// <param name="headSha"> + /// 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. + /// </param> + /// <param name="scrollToFirstDiff">True to scroll to first difference in file. Set to false if caret is being moved after opening.</param> + /// <returns>The opened diff viewer.</returns> + Task<IDifferenceViewer> OpenDiff(IPullRequestSession session, string relativePath, string headSha = null, bool scrollToFirstDiff = true); + + /// <summary> + /// Opens an diff viewer for a file in a pull request with the specified inline comment + /// thread open. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="relativePath">The path to the file, relative to the repository.</param> + /// <param name="thread">The thread to open</param> + /// <returns>The opened diff viewer.</returns> + Task<IDifferenceViewer> OpenDiff(IPullRequestSession session, string relativePath, IInlineCommentThreadModel thread); + + /// <summary> + /// Find the active text view. + /// </summary> + /// <returns>The active view or null if view can't be found.</returns> + IVsTextView FindActiveView(); + + /// <summary> + /// Place the caret at the best guess equivalent position in <see cref="targetView"/>. + /// </summary> + /// <param name="sourceView">The text view to navigate from.</param> + /// <param name="targetView">The text view to navigate to.</param> + void NavigateToEquivalentPosition(IVsTextView sourceView, IVsTextView targetView); + + /// <summary> + /// Check to see if text view is an ediatable document. + /// </summary> + /// <param name="textView">The text view to check.</param> + /// <returns>True if text view is editable and part of a diff view.</returns> + bool IsEditableDiff(ITextView textView); + + /// <summary> + /// Open the active document in a new code view. + /// </summary> + /// <remarks> + /// If the active document is part of a diff view, open in a new code view. + /// </remarks> + /// <param name="sourceView">The source view to use the line and position from.</param> + void OpenActiveDocumentInCodeView(IVsTextView sourceView); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs new file mode 100644 index 0000000000..72ac6ec532 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Text; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using LibGit2Sharp; + +namespace GitHub.Services +{ + public interface IPullRequestService + { + /// <summary> + /// Reads a page of pull request items. + /// </summary> + /// <param name="address">The host address.</param> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="after">The end cursor of the previous page, or null for the first page.</param> + /// <param name="states">The pull request states to filter by</param> + /// <returns>A page of pull request item models.</returns> + Task<Page<PullRequestListItemModel>> ReadPullRequests( + HostAddress address, + string owner, + string name, + string after, + PullRequestStateEnum[] states); + + /// <summary> + /// Reads a page of users that can be assigned to pull requests. + /// </summary> + /// <param name="address">The host address.</param> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="after">The end cursor of the previous page, or null for the first page.</param> + /// <returns>A page of author models.</returns> + Task<Page<ActorModel>> ReadAssignableUsers( + HostAddress address, + string owner, + string name, + string after); + + IObservable<IPullRequestModel> CreatePullRequest(IModelService modelService, + ILocalRepositoryModel sourceRepository, IRepositoryModel targetRepository, + IBranch sourceBranch, IBranch targetBranch, + string title, string body); + + /// <summary> + /// Checks whether the working directory for the specified repository is in a clean state. + /// </summary> + /// <param name="repository">The repository.</param> + /// <returns></returns> + IObservable<bool> IsWorkingDirectoryClean(ILocalRepositoryModel repository); + + /// <summary> + /// Count the number of submodules that require syncing. + /// </summary> + /// <param name="repository">The repository.</param> + /// <returns>The number of submodules that need to be synced.</returns> + IObservable<int> CountSubmodulesToSync(ILocalRepositoryModel repository); + + /// <summary> + /// Checks out a pull request to a local branch. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <param name="localBranchName">The name of the local branch.</param> + /// <returns></returns> + IObservable<Unit> Checkout(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest, string localBranchName); + + /// <summary> + /// Carries out a pull on the current branch. + /// </summary> + /// <param name="repository">The repository.</param> + IObservable<Unit> Pull(ILocalRepositoryModel repository); + + /// <summary> + /// Carries out a push of the current branch. + /// </summary> + /// <param name="repository">The repository.</param> + IObservable<Unit> Push(ILocalRepositoryModel repository); + + /// <summary> + /// Sync submodules on the current branch. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="progress">A method that will be called with progress messages</param> + Task<bool> SyncSubmodules(ILocalRepositoryModel repository, Action<string> progress); + + /// <summary> + /// Calculates the name of a local branch for a pull request avoiding clashes with existing branches. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequestNumber">The pull request number.</param> + /// <param name="pullRequestTitle">The pull request title.</param> + /// <returns></returns> + IObservable<string> GetDefaultLocalBranchName(ILocalRepositoryModel repository, int pullRequestNumber, string pullRequestTitle); + + /// <summary> + /// Gets the local branches that exist for the specified pull request. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <returns></returns> + IObservable<IBranch> GetLocalBranches(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Ensures that all local branches for the specified pull request are marked as PR branches. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <returns> + /// An observable that produces a single value indicating whether a change to the repository was made. + /// </returns> + /// <remarks> + /// Pull request branches are marked in the local repository with a config value so that they can + /// be easily identified without a roundtrip to the server. This method ensures that the local branches + /// for the specified pull request are indeed marked and returns a value indicating whether any branches + /// have had the mark added. + /// </remarks> + IObservable<bool> EnsureLocalBranchesAreMarkedAsPullRequests(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Determines whether the specified pull request is from the specified repository. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <returns></returns> + bool IsPullRequestFromRepository(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Switches to an existing branch for the specified pull request. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <returns></returns> + IObservable<Unit> SwitchToBranch(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Gets the history divergence between the current HEAD and the specified pull request. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequestNumber">The pull request number.</param> + /// <returns></returns> + IObservable<BranchTrackingDetails> CalculateHistoryDivergence(ILocalRepositoryModel repository, int pullRequestNumber); + + /// <summary> + /// Gets the SHA of the merge base for a pull request. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <returns></returns> + Task<string> GetMergeBase(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Gets the changes between the pull request base and head. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <returns></returns> + IObservable<TreeChanges> GetTreeChanges(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Gets the pull request associated with the current branch. + /// </summary> + /// <param name="repository">The repository.</param> + /// <returns> + /// An observable that produces a single tuple which contains the owner of the fork and the + /// pull request number. Returns null if the current branch is not a PR branch. + /// </returns> + /// <remarks> + /// This method does not do an API request - it simply checks the mark left in the git + /// config by <see cref="Checkout(ILocalRepositoryModel, PullRequestDetailModel, string)"/>. + /// </remarks> + IObservable<Tuple<string, int>> GetPullRequestForCurrentBranch(ILocalRepositoryModel repository); + + /// <summary> + /// Gets the encoding for the specified file. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="relativePath">The relative path to the file in the repository.</param> + /// <returns> + /// The file's encoding or <see cref="Encoding.Default"/> if the file doesn't exist. + /// </returns> + Encoding GetEncoding(ILocalRepositoryModel repository, string relativePath); + + /// <summary> + /// Extracts a file at the specified commit to a temporary file. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request details.</param> + /// <param name="relativePath">The path to the file, relative to the repository root.</param> + /// <param name="commitSha">The SHA of the commit.</param> + /// <param name="encoding">The encoding to use.</param> + /// <returns>The path to the temporary file.</returns> + Task<string> ExtractToTempFile( + ILocalRepositoryModel repository, + PullRequestDetailModel pullRequest, + string relativePath, + string commitSha, + Encoding encoding); + + /// <summary> + /// Remotes all unused remotes that were created by GitHub for Visual Studio to track PRs + /// from forks. + /// </summary> + /// <param name="repository">The repository.</param> + /// <returns></returns> + IObservable<Unit> RemoveUnusedRemotes(ILocalRepositoryModel repository); + + IObservable<string> GetPullRequestTemplate(ILocalRepositoryModel repository); + + /// <summary> + /// Gets the unique commits from <paramref name="compareBranch"/> to the merge base of + /// <paramref name="baseBranch"/> and <paramref name="compareBranch"/> and returns their + /// commit messages. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="baseBranch">The base branch to find a merge base with.</param> + /// <param name="compareBranch">The compare branch to find a merge base with.</param> + /// <param name="maxCommits">The maximum number of commits to return.</param> + /// <returns>An enumerable of unique commits from the merge base to the compareBranch.</returns> + IObservable<IReadOnlyList<CommitMessage>> GetMessagesForUniqueCommits( + ILocalRepositoryModel repository, + string baseBranch, + string compareBranch, + int maxCommits); + + /// <summary> + /// Displays a confirmation diaglog to ask if the user wants to cancel a pending review. + /// </summary> + /// <returns></returns> + bool ConfirmCancelPendingReview(); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs new file mode 100644 index 0000000000..dcc60b4d6e --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestSession.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using Octokit; + +namespace GitHub.Services +{ + /// <summary> + /// A pull request session used to display inline comments. + /// </summary> + public interface IPullRequestSession + { + /// <summary> + /// Gets a value indicating whether the pull request branch is the currently checked out branch. + /// </summary> + bool IsCheckedOut { get; } + + /// <summary> + /// Gets the current user. + /// </summary> + ActorModel User { get; } + + /// <summary> + /// Gets the pull request. + /// </summary> + PullRequestDetailModel PullRequest { get; } + + /// <summary> + /// Gets an observable that indicates that<see cref="PullRequest"/> has been updated. + /// </summary> + /// <remarks> + /// This notification is different to listening for a PropertyChanged event because the + /// pull request model may be updated in-place which will not result in a PropertyChanged + /// notification. + /// </remarks> + IObservable<PullRequestDetailModel> PullRequestChanged { get; } + + /// <summary> + /// Gets the local repository. + /// </summary> + ILocalRepositoryModel LocalRepository { get; } + + /// <summary> + /// Gets the owner of the repository that contains the pull request. + /// </summary> + /// <remarks> + /// If the pull request is targeting <see cref="LocalRepository"/> then the owner will be + /// the owner of the local repository. If however the pull request targets a different fork + /// then this property describes the owner of the fork. + /// </remarks> + string RepositoryOwner { get; } + + /// <summary> + /// Gets a value indicating whether the pull request has a pending review for the current + /// user. + /// </summary> + bool HasPendingReview { get; } + + /// <summary> + /// Gets the ID of the current pending pull request review for the user. + /// </summary> + string PendingReviewId { get; } + + /// <summary> + /// Gets all files touched by the pull request. + /// </summary> + /// <returns> + /// A list of the files touched by the pull request. + /// </returns> + Task<IReadOnlyList<IPullRequestSessionFile>> GetAllFiles(); + + /// <summary> + /// Gets a file touched by the pull request. + /// </summary> + /// <param name="relativePath">The relative path to the file.</param> + /// <param name="commitSha"> + /// The commit at which to get the file contents, or "HEAD" to track the pull request head. + /// </param> + /// <returns> + /// A <see cref="IPullRequestSessionFile"/> object or null if the file was not touched by + /// the pull request. + /// </returns> + Task<IPullRequestSessionFile> GetFile(string relativePath, string commitSha = "HEAD"); + + /// <summary> + /// Gets the merge base SHA for the pull request. + /// </summary> + /// <returns>The merge base SHA.</returns> + Task<string> GetMergeBase(); + + /// <summary> + /// Posts a new PR review comment. + /// </summary> + /// <param name="body">The comment body.</param> + /// <param name="commitId">THe SHA of the commit to comment on.</param> + /// <param name="path">The relative path of the file to comment on.</param> + /// <param name="fileDiff">The diff between the PR head and base.</param> + /// <param name="position">The line index in the diff to comment on.</param> + Task PostReviewComment( + string body, + string commitId, + string path, + IReadOnlyList<DiffChunk> fileDiff, + int position); + + /// <summary> + /// Posts a PR review comment reply. + /// </summary> + /// <param name="body">The comment body.</param> + /// <param name="inReplyTo">The GraphQL ID of the comment to reply to.</param> + /// <returns></returns> + Task PostReviewComment( + string body, + string inReplyTo); + + /// <summary> + /// Starts a new pending pull request review. + /// </summary> + Task StartReview(); + + /// <summary> + /// Cancels the currently pending review. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// There is no pending review. + /// </exception> + Task CancelReview(); + + /// <summary> + /// Posts the currently pending review. + /// </summary> + /// <param name="body">The review body.</param> + /// <param name="e">The review event.</param> + /// <returns>The review model.</returns> + Task PostReview(string body, PullRequestReviewEvent e); + + /// <summary> + /// Deletes a pull request comment. + /// </summary> + /// <param name="pullRequestId">The number of the pull request id of the comment</param> + /// <param name="commentDatabaseId">The number of the pull request comment to delete</param> + /// <returns>A task which completes when the session has completed updating.</returns> + Task DeleteComment(int pullRequestId, int commentDatabaseId); + + /// <summary> + /// Edit a PR review comment reply. + /// </summary> + /// <param name="commentNodeId">The node id of the pull request comment</param> + /// <param name="body">The replacement comment body.</param> + /// <returns>A comment model.</returns> + Task EditComment(string commentNodeId, string body); + + /// <summary> + /// Refreshes the pull request session. + /// </summary> + /// <returns>A task which completes when the session has completed refreshing.</returns> + Task Refresh(); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs new file mode 100644 index 0000000000..da9b407908 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestSessionManager.cs @@ -0,0 +1,91 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using GitHub.Models; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace GitHub.Services +{ + /// <summary> + /// Manages pull request sessions. + /// </summary> + /// <remarks> + /// If the currently checked out branch represents a pull request then <see cref="CurrentSession"/> + /// will return an <see cref="IPullRequestSession"/> containing the details of that pull request. + /// A session for any other pull request can also be retrieved by calling + /// <see cref="GetSession(string, string, int)"/>. + /// + /// Calling <see cref="GetLiveFile(string, ITextView, ITextBuffer)"/> will return an + /// <see cref="IPullRequestSessionFile"/> which tracks both the contents of a text buffer and the + /// current session, and updates the review comments in real-time. + /// </remarks> + public interface IPullRequestSessionManager : INotifyPropertyChanged + { + /// <summary> + /// Gets the current pull request session. + /// </summary> + /// <returns> + /// The current pull request session, or null if the currently checked out branch is not + /// a pull request branch. + /// </returns> + IPullRequestSession CurrentSession { get; } + + /// <summary> + /// Ensures that the service is initialized. + /// </summary> + /// <returns>A task that when completed indicates that the service is initialized.</returns> + /// <remarks> + /// If you are simply monitoring changes to the <see cref="CurrentSession"/> then you + /// don't need to call this method: <see cref="CurrentSession"/> will be updated on + /// initialization. If however, you want to be sure that <see cref="CurrentSession"/> is + /// initialized, you can await the task returned from this method. + /// </remarks> + Task EnsureInitialized(); + + /// <summary> + /// Gets an <see cref="IPullRequestSessionFile"/> that tracks the live state of a document. + /// </summary> + /// <param name="relativePath">The relative path to the file in the repository.</param> + /// <param name="textView">The text view that is showing the file.</param> + /// <param name="textBuffer">The text buffer with the file contents.</param> + /// <returns>An <see cref="IPullRequestSessionLiveFile"/>.</returns> + Task<IPullRequestSessionFile> GetLiveFile( + string relativePath, + ITextView textView, + ITextBuffer textBuffer); + + /// <summary> + /// Gets the path of a document displayed in a text buffer, relative to the current + /// repository. + /// </summary> + /// <param name="buffer">The text buffer.</param> + /// <returns> + /// The relative path, or null if the buffer does not represent a file in the repository. + /// </returns> + string GetRelativePath(ITextBuffer buffer); + + /// <summary> + /// Gets an <see cref="IPullRequestSession"/> for a pull request. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request number.</param> + /// <returns>An <see cref="IPullRequestSession"/>.</returns> + Task<IPullRequestSession> GetSession(string owner, string name, int number); + + /// <summary> + /// Gets information about the pull request that a Visual Studio text buffer is a part of. + /// </summary> + /// <param name="buffer">The text buffer.</param> + /// <returns> + /// A <see cref="PullRequestTextBufferInfo"/> or null if the pull request for the text + /// buffer could not be determined. + /// </returns> + /// <remarks> + /// This method looks for a <see cref="PullRequestTextBufferInfo"/> object stored in the text + /// buffer's properties. + /// </remarks> + PullRequestTextBufferInfo GetTextBufferInfo(ITextBuffer buffer); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs b/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs index f871a2c607..0c35127851 100644 --- a/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs +++ b/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs @@ -1,5 +1,6 @@ using System; using System.Reactive; +using System.Threading.Tasks; namespace GitHub.Services { @@ -20,7 +21,16 @@ public interface IRepositoryCloneService /// <param name="cloneUrl">The url of the repository to clone.</param> /// <param name="repositoryName">The name of the repository to clone.</param> /// <param name="repositoryPath">The directory that will contain the repository directory.</param> + /// <param name="progress"> + /// An object through which to report progress. This must be of type + /// System.IProgress<Microsoft.VisualStudio.Shell.ServiceProgressData>, but + /// as that type is only available in VS2017+ it is typed as <see cref="object"/> here. + /// </param> /// <returns></returns> - IObservable<Unit> CloneRepository(string cloneUrl, string repositoryName, string repositoryPath); + Task CloneRepository( + string cloneUrl, + string repositoryName, + string repositoryPath, + object progress = null); } } diff --git a/src/GitHub.Exports.Reactive/Services/IRepositoryForkService.cs b/src/GitHub.Exports.Reactive/Services/IRepositoryForkService.cs new file mode 100644 index 0000000000..07e57e4315 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IRepositoryForkService.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Models; +using Octokit; + +namespace GitHub.Services +{ + public interface IRepositoryForkService + { + IObservable<Repository> ForkRepository(IApiClient apiClient, IRepositoryModel sourceRepository, NewRepositoryFork repositoryFork, bool updateOrigin, bool addUpstream, bool trackMasterUpstream); + IObservable<object> SwitchRemotes(IRepositoryModel destinationRepository, bool updateOrigin, bool addUpstream, bool trackMasterUpstream); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IShowDialogService.cs b/src/GitHub.Exports.Reactive/Services/IShowDialogService.cs new file mode 100644 index 0000000000..5f3a9bf332 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IShowDialogService.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using GitHub.Primitives; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; + +namespace GitHub.Services +{ + /// <summary> + /// Service for displaying the GitHub for Visual Studio dialog. + /// </summary> + /// <remarks> + /// This is a low-level service used by <see cref="IDialogService"/> to carry out the actual + /// showing of the dialog. You probably want to use <see cref="IDialogService"/> instead if + /// you want to show the dialog for login/clone etc. + /// </remarks> + public interface IShowDialogService + { + /// <summary> + /// Shows a view model in the dialog. + /// </summary> + /// <param name="viewModel">The view model to show.</param> + /// <returns> + /// The value returned by the <paramref name="viewModel"/>'s + /// <see cref="IDialogContentViewModel.Done"/> observable, or null if the dialog was + /// canceled. + /// </returns> + Task<object> Show(IDialogContentViewModel viewModel); + + /// <summary> + /// Shows a view model that requires a connection in the dialog. + /// </summary> + /// <param name="viewModel">The view model to show.</param> + /// <returns> + /// The value returned by the <paramref name="viewModel"/>'s + /// <see cref="IDialogContentViewModel.Done"/> observable, or null if the dialog was + /// canceled. + /// </returns> + /// <remarks> + /// The first existing connection will be used. If there is no existing connection, the + /// login dialog will be shown first. + /// </remarks> + Task<object> ShowWithFirstConnection<TViewModel>(TViewModel viewModel) + where TViewModel : IDialogContentViewModel, IConnectionInitializedViewModel; + } +} diff --git a/src/GitHub.Exports.Reactive/Services/LocalRepositoriesExtensions.cs b/src/GitHub.Exports.Reactive/Services/LocalRepositoriesExtensions.cs new file mode 100644 index 0000000000..76c808724a --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/LocalRepositoriesExtensions.cs @@ -0,0 +1,29 @@ +using System; +using GitHub.Models; +using GitHub.Primitives; +using ReactiveUI; + +namespace GitHub.Services +{ + /// <summary> + /// Implements extension methods for <see cref="ILocalRepositories"/>. + /// </summary> + public static class LocalRepositoriesExtensions + { + /// <summary> + /// Gets a derived collection that contains all known repositories with the specified + /// clone URL, ordered by name. + /// </summary> + /// <param name="repos">The local repositories object.</param> + /// <param name="address">The address.</param> + public static IReactiveDerivedList<ILocalRepositoryModel> GetRepositoriesForAddress( + this ILocalRepositories repos, + HostAddress address) + { + return repos.Repositories.CreateDerivedCollection( + x => x, + x => x.CloneUrl != null && address.Equals(HostAddress.Create(x.CloneUrl)), + OrderedComparer<ILocalRepositoryModel>.OrderBy(x => x.Name).Compare); + } + } +} diff --git a/src/GitHub.Exports.Reactive/Services/ModelServiceExtensions.cs b/src/GitHub.Exports.Reactive/Services/ModelServiceExtensions.cs new file mode 100644 index 0000000000..abe5e35a88 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/ModelServiceExtensions.cs @@ -0,0 +1,13 @@ +using System; +using GitHub.Models; + +namespace GitHub.Services +{ + public static class ModelServiceExtensions + { + public static IObservable<IPullRequestModel> GetPullRequest(this IModelService service, IRepositoryModel repo, int number) + { + return service.GetPullRequest(repo.Owner, repo.Name, number); + } + } +} diff --git a/src/GitHub.Exports.Reactive/Services/NotificationDispatcher.cs b/src/GitHub.Exports.Reactive/Services/NotificationDispatcher.cs new file mode 100644 index 0000000000..8a70f54d4f --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/NotificationDispatcher.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Windows.Input; + +namespace GitHub.Services +{ + [Export(typeof(INotificationDispatcher))] + [Export(typeof(INotificationService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public sealed class NotificationDispatcher : INotificationDispatcher, IDisposable + { + Subject<Notification> notifications; + Stack<INotificationService> notificationHandlers; + + public NotificationDispatcher() + { + notifications = new Subject<Notification>(); + notificationHandlers = new Stack<INotificationService>(); + } + + public IObservable<Notification> Listen() + { + return notifications; + } + + public void AddListener(INotificationService handler) + { + notificationHandlers.Push(handler); + } + + public void RemoveListener() + { + notificationHandlers.Pop(); + } + + public void RemoveListener(INotificationService handler) + { + Stack<INotificationService> handlers = new Stack<INotificationService>(); + while(notificationHandlers.TryPeek() != handler) + handlers.Push(notificationHandlers.Pop()); + if (notificationHandlers.Count > 0) + notificationHandlers.Pop(); + while (handlers.Count > 0) + notificationHandlers.Push(handlers.Pop()); + } + + public void ShowMessage(string message) + { + notifications.OnNext(new Notification(message, Notification.NotificationType.Message)); + var handler = notificationHandlers.TryPeek(); + handler?.ShowMessage(message); + } + + public void ShowMessage(string message, ICommand command, bool showToolTips = true, Guid guid = default(Guid)) + { + notifications.OnNext(new Notification(message, Notification.NotificationType.Message, command)); + var handler = notificationHandlers.TryPeek(); + handler?.ShowMessage(message, command, showToolTips, guid); + } + + public void ShowWarning(string message) + { + notifications.OnNext(new Notification(message, Notification.NotificationType.Warning)); + var handler = notificationHandlers.TryPeek(); + handler?.ShowWarning(message); + } + + public void ShowError(string message) + { + notifications.OnNext(new Notification(message, Notification.NotificationType.Error)); + var handler = notificationHandlers.TryPeek(); + handler?.ShowError(message); + } + + public void HideNotification(Guid guid) + { + var handler = notificationHandlers.TryPeek(); + handler?.HideNotification(guid); + } + + public bool IsNotificationVisible(Guid guid) + { + var handler = notificationHandlers.TryPeek(); + return handler?.IsNotificationVisible(guid) ?? false; + } + + bool disposed; // To detect redundant calls + void Dispose(bool disposing) + { + if (disposing) + { + if (disposed) return; + disposed = true; + notifications.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs b/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs new file mode 100644 index 0000000000..05fc393f58 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/PullRequestSessionExtensions.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using GitHub.Extensions; + +namespace GitHub.Services +{ + /// <summary> + /// Extension methods for <see cref="IPullRequestSession"/>. + /// </summary> + public static class PullRequestSessionExtensions + { + /// <summary> + /// Gets the head (source) branch label for a pull request, stripping the owner if the pull + /// request is not from a fork. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <returns>The head branch label</returns> + public static string GetHeadBranchDisplay(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + return GetBranchDisplay( + session.IsPullRequestFromFork(), + session.PullRequest?.HeadRepositoryOwner, + session.PullRequest?.HeadRefName); + } + + /// <summary> + /// Gets the head (target) branch label for a pull request, stripping the owner if the pull + /// request is not from a fork. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <returns>The head branch label</returns> + public static string GetBaseBranchDisplay(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + return GetBranchDisplay( + session.IsPullRequestFromFork(), + session.PullRequest?.BaseRepositoryOwner, + session.PullRequest?.BaseRefName); + } + + /// <summary> + /// Returns a value that determines whether the pull request comes from a fork. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <returns>True if the pull request is from a fork, otherwise false.</returns> + public static bool IsPullRequestFromFork(this IPullRequestSession session) + { + Guard.ArgumentNotNull(session, nameof(session)); + + return session.PullRequest != null && + session.PullRequest.HeadRepositoryOwner != session.PullRequest.BaseRepositoryOwner; + } + + static string GetBranchDisplay(bool fork, string owner, string label) + { + if (owner != null && label != null) + { + return fork ? owner + ':' + label : label; + } + + return "[invalid]"; + } + } +} diff --git a/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs b/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs index ec664c5854..4320d09f64 100644 --- a/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs +++ b/src/GitHub.Exports.Reactive/Validation/ReactivePropertyValidator.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reactive; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Text.RegularExpressions; @@ -69,7 +70,7 @@ public enum ValidationStatus Valid = 2, } - public abstract class ReactivePropertyValidator : ReactiveObject + public abstract class ReactivePropertyValidator : ReactiveObject, IDisposable { public static ReactivePropertyValidator<TProp> For<TObj, TProp>(TObj This, Expression<Func<TObj, TProp>> property) { @@ -92,9 +93,18 @@ protected ReactivePropertyValidator() public abstract Task<ReactivePropertyValidationResult> ExecuteAsync(); public abstract Task ResetAsync(); + + protected virtual void Dispose(bool disposing) + { + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } - [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] public class ReactivePropertyValidator<TProp> : ReactivePropertyValidator { readonly ReactiveCommand<ReactivePropertyValidationResult> validateCommand; @@ -266,12 +276,21 @@ public static ReactivePropertyValidator<string> IfNullOrEmpty(this ReactivePrope return This.IfTrue(String.IsNullOrEmpty, errorMessage); } + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.UriBuilder", + Justification = "We're using UriBuilder to validate the URL because Uri.TryCreate fails if no scheme specified.")] public static ReactivePropertyValidator<string> IfNotUri(this ReactivePropertyValidator<string> This, string errorMessage) { return This.IfFalse(s => { - Uri uri; - return Uri.TryCreate(s, UriKind.Absolute, out uri); + try + { + new UriBuilder(s); + return true; + } + catch + { + return false; + } }, errorMessage); } diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IDialogContentViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IDialogContentViewModel.cs new file mode 100644 index 0000000000..4202f71f3c --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IDialogContentViewModel.cs @@ -0,0 +1,20 @@ +using System; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Represents a view that can be shown in the GitHub dialog. + /// </summary> + public interface IDialogContentViewModel : IViewModel + { + /// <summary> + /// Gets a title to display in the dialog title bar. + /// </summary> + string Title { get; } + + /// <summary> + /// Gets an observable that is signalled with a return value when the dialog has completed. + /// </summary> + IObservable<object> Done { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositoryExecuteViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositoryExecuteViewModel.cs new file mode 100644 index 0000000000..03e5df55b6 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositoryExecuteViewModel.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; +using Octokit; +using ReactiveUI; +using IConnection = GitHub.Models.IConnection; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// View model for selecting the account to fork a repository to. + /// </summary> + public interface IForkRepositoryExecuteViewModel : IDialogContentViewModel + { + IRepositoryModel SourceRepository { get; } + + IRepositoryModel DestinationRepository { get; } + + IAccount DestinationAccount { get; } + + /// <summary> + /// Gets a command that is executed when the user clicks the "Fork" button. + /// </summary> + IReactiveCommand<Repository> CreateFork { get; } + + IReactiveCommand<object> BackCommand { get; } + + bool ResetMasterTracking { get; set; } + + bool AddUpstream { get; set; } + + bool UpdateOrigin { get; set; } + + bool CanAddUpstream { get; } + + bool CanResetMasterTracking { get; } + + string Error { get; } + IObservable<object> Back { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="sourceRepository">The repository to fork.</param> + /// <param name="destinationAccount">The account to fork to.</param> + /// <param name="connection">The connection to use.</param> + Task InitializeAsync( + ILocalRepositoryModel sourceRepository, + IAccount destinationAccount, + IConnection connection); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositorySelectViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositorySelectViewModel.cs new file mode 100644 index 0000000000..05832ec649 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositorySelectViewModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// View model for selecting the account to fork a repository to. + /// </summary> + public interface IForkRepositorySelectViewModel : IDialogContentViewModel + { + /// <summary> + /// Gets a list of accounts that the repository can be forked to. + /// </summary> + IReadOnlyList<IAccount> Accounts { get; } + + /// <summary> + /// Gets a list of existing forks for accounts that the owner has access to. + /// </summary> + IReadOnlyList<IRemoteRepositoryModel> ExistingForks { get; } + + /// <summary> + /// Gets a value indicating whether the view model is loading. + /// </summary> + bool IsLoading { get; } + + /// <summary> + /// Gets a command that is executed when the user selects an item in <see cref="Accounts"/>. + /// </summary> + ReactiveCommand<object> SelectedAccount { get; } + + /// <summary> + /// Gets a command that is executed when the user selects an item in <see cref="ExistingForks"/>. + /// </summary> + ReactiveCommand<object> SwitchOrigin { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="repository">The repository to fork.</param> + /// <param name="connection">The connection to use.</param> + Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositorySwitchViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositorySwitchViewModel.cs new file mode 100644 index 0000000000..9a2ee9b8af --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositorySwitchViewModel.cs @@ -0,0 +1,33 @@ +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// View model for selecting the fork to switch to + /// </summary> + public interface IForkRepositorySwitchViewModel : IDialogContentViewModel + { + IRepositoryModel SourceRepository { get; } + + IRepositoryModel DestinationRepository { get; } + + /// <summary> + /// Gets a command that is executed when the user clicks the "Fork" button. + /// </summary> + IReactiveCommand<object> SwitchFork { get; } + + bool ResetMasterTracking { get; set; } + + bool AddUpstream { get; set; } + + bool UpdateOrigin { get; set; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="sourceRepository">The repository to fork.</param> + /// <param name="remoteRepository"></param> + void Initialize(ILocalRepositoryModel sourceRepository, IRemoteRepositoryModel remoteRepository); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositoryViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositoryViewModel.cs new file mode 100644 index 0000000000..e02e3e64a6 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IForkRepositoryViewModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// View model for forking a repository. + /// </summary> + public interface IForkRepositoryViewModel : IDialogContentViewModel + { + /// <summary> + /// Gets the currently displayed page. + /// </summary> + IDialogContentViewModel Content { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="repository">The repository to fork.</param> + /// <param name="connection">The connection to use.</param> + Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IGistCreationViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IGistCreationViewModel.cs new file mode 100644 index 0000000000..f52763ce74 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IGistCreationViewModel.cs @@ -0,0 +1,34 @@ +using GitHub.Models; +using Octokit; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + public interface IGistCreationViewModel : IDialogContentViewModel, IConnectionInitializedViewModel + { + /// <summary> + /// Gets the command to create a new gist. + /// </summary> + IReactiveCommand<Gist> CreateGist { get; } + + /// <summary> + /// True if the gist should be private. + /// </summary> + bool IsPrivate { get; set; } + + /// <summary> + /// Gets or sets the optional description used in the gist description field. + /// </summary> + string Description { get; set; } + + /// <summary> + /// Gets or sets the file name of the gist (should include extension). + /// </summary> + string FileName { get; set; } + + /// <summary> + /// The account or organization that will be the owner of the created gist. + /// </summary> + IAccount Account { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IGitHubDialogWindowViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IGitHubDialogWindowViewModel.cs new file mode 100644 index 0000000000..aec4977fc8 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IGitHubDialogWindowViewModel.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Represents the top-level view model for the GitHub dialog. + /// </summary> + public interface IGitHubDialogWindowViewModel : IDisposable + { + /// <summary> + /// Gets the content to display in the dialog. + /// </summary> + IDialogContentViewModel Content { get; } + + /// <summary> + /// Gets an observable that is signalled when when the dialog should be closed. + /// </summary> + /// <remarks> + /// If the content being displayed has a return value, then this wil be returned here. + /// </remarks> + IObservable<object> Done { get; } + + /// <summary> + /// Starts displaying a view model. + /// </summary> + /// <param name="viewModel">The view model to display.</param> + void Start(IDialogContentViewModel viewModel); + + /// <summary> + /// Starts displaying a view model that requires a connection. + /// </summary> + /// <param name="viewModel">The view model to display.</param> + Task StartWithConnection<T>(T viewModel) + where T : IDialogContentViewModel, IConnectionInitializedViewModel; + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILogin2FaViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILogin2FaViewModel.cs new file mode 100644 index 0000000000..dfc0908b52 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILogin2FaViewModel.cs @@ -0,0 +1,32 @@ +using System; +using GitHub.Validation; +using Octokit; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + public interface ILogin2FaViewModel : IDialogContentViewModel + { + ReactiveCommand<object> OkCommand { get; } + ReactiveCommand<object> NavigateLearnMore { get; } + ReactiveCommand<object> ResendCodeCommand { get; } + + IObservable<TwoFactorChallengeResult> Show(UserError error); + void Cancel(); + + bool IsBusy { get; } + bool IsSms { get; } + bool IsAuthenticationCodeSent { get; } + bool ShowErrorMessage { get; } + bool InvalidAuthenticationCode { get; } + string Description { get; } + string AuthenticationCode { get; set; } + TwoFactorType TwoFactorType { get; } + + /// <summary> + /// Gets the validator instance used for validating the + /// <see cref="AuthenticationCode"/> property + /// </summary> + ReactivePropertyValidator AuthenticationCodeValidator { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginCredentialsViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginCredentialsViewModel.cs new file mode 100644 index 0000000000..a1673ba919 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginCredentialsViewModel.cs @@ -0,0 +1,20 @@ +using System; + +namespace GitHub.ViewModels.Dialog +{ + [Flags] + public enum LoginMode + { + None = 0x00, + DotComOnly = 0x01, + EnterpriseOnly = 0x02, + DotComOrEnterprise = 0x03, + } + + public interface ILoginCredentialsViewModel : IDialogContentViewModel + { + LoginMode LoginMode { get; } + ILoginToGitHubViewModel GitHubLogin { get; } + ILoginToGitHubForEnterpriseViewModel EnterpriseLogin { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubForEnterpriseViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubForEnterpriseViewModel.cs new file mode 100644 index 0000000000..266f6450e7 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubForEnterpriseViewModel.cs @@ -0,0 +1,67 @@ +using System.Reactive; +using GitHub.Services; +using GitHub.Validation; +using ReactiveUI; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Details the possible values for <see cref="ILoginToGitHubForEnterpriseViewModel.ProbeStatus"/>. + /// </summary> + public enum EnterpriseProbeStatus + { + /// <summary> + /// No checking is underway. + /// </summary> + None, + + /// <summary> + /// A probe is underway to see if the URL is a valid enterprise instance. + /// </summary> + Checking, + + /// <summary> + /// A valid enterprise instance was found. + /// </summary> + Valid, + + /// <summary> + /// A valid enterprise instance was not found. + /// </summary> + Invalid + } + + /// <summary> + /// Represents a view model responsible for authenticating a user + /// against a GitHub Enterprise instance. + /// </summary> + public interface ILoginToGitHubForEnterpriseViewModel : ILoginToHostViewModel + { + /// <summary> + /// Gets or sets the URL to the GitHub Enterprise instance + /// </summary> + string EnterpriseUrl { get; set; } + + /// <summary> + /// Gets the status of the enterprise probe. + /// </summary> + EnterpriseProbeStatus ProbeStatus { get; } + + /// <summary> + /// Gets the supported login methods for the GitHub Enterprise instance at + /// <see cref="EnterpriseUrl"/>. + /// </summary> + EnterpriseLoginMethods? SupportedLoginMethods { get; } + + /// <summary> + /// Gets the validator instance used for validating the + /// <see cref="EnterpriseUrl"/> property + /// </summary> + ReactivePropertyValidator EnterpriseUrlValidator { get; } + + /// <summary> + /// Gets a command which, when invoked, directs the user to a learn more page + /// </summary> + IReactiveCommand<Unit> NavigateLearnMore { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/ILoginToGitHubViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubViewModel.cs similarity index 93% rename from src/GitHub.Exports.Reactive/ViewModels/ILoginToGitHubViewModel.cs rename to src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubViewModel.cs index b56713c654..b79e2368af 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ILoginToGitHubViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginToGitHubViewModel.cs @@ -1,7 +1,7 @@ using System.Reactive; using ReactiveUI; -namespace GitHub.ViewModels +namespace GitHub.ViewModels.Dialog { /// <summary> /// Represents a view model responsible for authenticating a user diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginViewModel.cs new file mode 100644 index 0000000000..9667652cab --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/ILoginViewModel.cs @@ -0,0 +1,19 @@ +using System; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// Represents the Login dialog content. + /// </summary> + public interface ILoginViewModel : IDialogContentViewModel + { + /// <summary> + /// Gets the currently displayed login content. + /// </summary> + /// <remarks> + /// The value of this property will either be a <see cref="ILoginCredentialsViewModel"/> + /// or a <see cref="ILogin2FaViewModel"/>. + /// </remarks> + IDialogContentViewModel Content { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCloneViewModel.cs new file mode 100644 index 0000000000..f8742f0636 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCloneViewModel.cs @@ -0,0 +1,36 @@ +using System; +using GitHub.Models; +using System.Collections.ObjectModel; + +namespace GitHub.ViewModels.Dialog +{ + /// <summary> + /// ViewModel for the the Clone Repository dialog + /// </summary> + public interface IRepositoryCloneViewModel : IDialogContentViewModel, IConnectionInitializedViewModel + { + /// <summary> + /// The list of repositories the current user may clone from the specified host. + /// </summary> + ObservableCollection<IRemoteRepositoryModel> Repositories { get; } + + bool FilterTextIsEnabled { get; } + + /// <summary> + /// If true, then we failed to load the repositories. + /// </summary> + bool LoadingFailed { get; } + + /// <summary> + /// Set to true if no repositories were found. + /// </summary> + bool NoRepositoriesFound { get; } + + /// <summary> + /// Set to true if a repository is selected. + /// </summary> + bool CanClone { get; } + + string FilterText { get; set; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCreationTarget.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCreationTarget.cs similarity index 95% rename from src/GitHub.Exports.Reactive/ViewModels/IRepositoryCreationTarget.cs rename to src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCreationTarget.cs index 756c7e71e9..d8e2ff37c5 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCreationTarget.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCreationTarget.cs @@ -1,7 +1,7 @@ using System.Windows.Input; using GitHub.Validation; -namespace GitHub.ViewModels +namespace GitHub.ViewModels.Dialog { public interface IRepositoryCreationTarget { diff --git a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCreationViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCreationViewModel.cs similarity index 80% rename from src/GitHub.Exports.Reactive/ViewModels/IRepositoryCreationViewModel.cs rename to src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCreationViewModel.cs index d699663a9c..50d5c26701 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCreationViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryCreationViewModel.cs @@ -3,9 +3,12 @@ using GitHub.Models; using ReactiveUI; -namespace GitHub.ViewModels +namespace GitHub.ViewModels.Dialog { - public interface IRepositoryCreationViewModel : IRepositoryForm, IRepositoryCreationTarget + public interface IRepositoryCreationViewModel : IDialogContentViewModel, + IConnectionInitializedViewModel, + IRepositoryForm, + IRepositoryCreationTarget { /// <summary> /// Command that creates the repository. diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryRecloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryRecloneViewModel.cs new file mode 100644 index 0000000000..9feef83bf2 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryRecloneViewModel.cs @@ -0,0 +1,13 @@ +using System; +using GitHub.Models; + +namespace GitHub.ViewModels.Dialog +{ + public interface IRepositoryRecloneViewModel : IDialogContentViewModel, IConnectionInitializedViewModel + { + /// <summary> + /// Gets or sets the repository to clone. + /// </summary> + IRepositoryModel SelectedRepository { get; set; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IIssueListItemViewModelBase.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IIssueListItemViewModelBase.cs new file mode 100644 index 0000000000..cfb62747f7 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IIssueListItemViewModelBase.cs @@ -0,0 +1,25 @@ +using System; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Base interface for items in a issue or pull request list. + /// </summary> + public interface IIssueListItemViewModelBase : IViewModel + { + /// <summary> + /// Gets the issue or pull request number. + /// </summary> + int Number { get; } + + /// <summary> + /// Gets the issue or pull request title. + /// </summary> + string Title { get; } + + /// <summary> + /// Gets the author of the issue or pull request. + /// </summary> + IActorViewModel Author { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IIssueListViewModelBase.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IIssueListViewModelBase.cs new file mode 100644 index 0000000000..459ddf75d7 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IIssueListViewModelBase.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Describes a message that should be displayed in place of a list of items in + /// an <see cref="IIssueListItemViewModelBase"/>. + /// </summary> + public enum IssueListMessage + { + /// <summary> + /// No message should be displayed; the items should be displayed. + /// </summary> + None, + + /// <summary> + /// A "No Open Items" message should be displayed. + /// </summary> + NoOpenItems, + + /// <summary> + /// A "No Items Match Search Criteria" message should be displayed. + /// </summary> + NoItemsMatchCriteria, + } + + /// <summary> + /// Represents a view model which displays an issue or pull request list. + /// </summary> + public interface IIssueListViewModelBase : ISearchablePageViewModel + { + /// <summary> + /// Gets the filter view model. + /// </summary> + IUserFilterViewModel AuthorFilter { get; } + + /// <summary> + /// Gets a list consisting of the fork and parent repositories if the current repository is + /// a fork. + /// </summary> + /// <remarks> + /// Returns null if the current repository is not a fork. + /// </remarks> + IReadOnlyList<IRepositoryModel> Forks { get; } + + /// <summary> + /// Gets the list of issues or pull requests. + /// </summary> + IReadOnlyList<IIssueListItemViewModelBase> Items { get; } + + /// <summary> + /// Gets a filtered view of <see cref="Items"/> based on <see cref="SearchQuery"/> and + /// <see cref="AuthorFilter"/>. + /// </summary> + ICollectionView ItemsView { get; } + + /// <summary> + /// Gets the local repository. + /// </summary> + ILocalRepositoryModel LocalRepository { get; } + + /// <summary> + /// Gets an enum indicating a message that should be displayed in place of a list of items. + /// </summary> + IssueListMessage Message { get; } + + /// <summary> + /// Gets the remote repository. + /// </summary> + /// <remarks> + /// This may differ from <see cref="LocalRepository"/> if <see cref="LocalRepository"/> is + /// a fork. + /// </remarks> + IRepositoryModel RemoteRepository { get; set; } + + /// <summary> + /// Gets the currently selected item in <see cref="States"/>. + /// </summary> + string SelectedState { get; set; } + + /// <summary> + /// Gets a list of the available states (e.g. Open, Closed, All). + /// </summary> + IReadOnlyList<string> States { get; } + + /// <summary> + /// Gets the caption to display as the header on the <see cref="States"/> dropdown. + /// </summary> + string StateCaption { get; } + + /// <summary> + /// Gets a command which opens the item passed as a parameter. + /// </summary> + ReactiveCommand<Unit> OpenItem { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="repository">The local repository.</param> + /// <param name="connection">The connection/</param> + /// <returns>A task tracking the operation.</returns> + Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ILoggedOutViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ILoggedOutViewModel.cs new file mode 100644 index 0000000000..9a615c4377 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ILoggedOutViewModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Reactive; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Defines the view model for the "Sign in to GitHub" view in the GitHub pane. + /// </summary> + public interface ILoggedOutViewModel : IPanePageViewModel + { + /// <summary> + /// Gets the command executed when the user clicks the "Sign in" link. + /// </summary> + IReactiveCommand<object> SignIn { get; } + + /// <summary> + /// Gets the command executed when the user clicks the "Create an Account" link. + /// </summary> + IReactiveCommand<object> Register { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ILoginFailedViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ILoginFailedViewModel.cs new file mode 100644 index 0000000000..bf27ac3153 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ILoginFailedViewModel.cs @@ -0,0 +1,26 @@ +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Defines the view model for the "Login Failed" view in the GitHub pane. + /// </summary> + public interface ILoginFailedViewModel : IPanePageViewModel + { + /// <summary> + /// Gets a description of the login failure. + /// </summary> + UserError LoginError { get; } + + /// <summary> + /// Gets a command which opens the Team Explorer Connect page. + /// </summary> + ReactiveCommand<object> OpenTeamExplorer { get; } + + /// <summary> + /// Initializes the view model with an error. + /// </summary> + /// <param name="error">The error.</param> + void Initialize(UserError error); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INavigationViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INavigationViewModel.cs new file mode 100644 index 0000000000..df2aea0c69 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INavigationViewModel.cs @@ -0,0 +1,66 @@ +using System; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.ViewModels +{ + /// <summary> + /// A view model that supports back/forward navigation of an inner content page. + /// </summary> + public interface INavigationViewModel : IViewModel + { + /// <summary> + /// Gets or sets the current content page. + /// </summary> + IPanePageViewModel Content { get; } + + /// <summary> + /// Gets or sets the current index in the history list. + /// </summary> + int Index { get; set; } + + /// <summary> + /// Gets the back and forward history. + /// </summary> + IReadOnlyReactiveList<IPanePageViewModel> History { get; } + + /// <summary> + /// Gets a command that navigates back in the history. + /// </summary> + ReactiveCommand<object> NavigateBack { get; } + + /// <summary> + /// Gets a command that navigates forwards in the history. + /// </summary> + ReactiveCommand<object> NavigateForward { get; } + + /// <summary> + /// Navigates back if possible. + /// </summary> + /// <returns>True if there was a page to navigate back to.</returns> + bool Back(); + + /// <summary> + /// Clears the current page and all history . + /// </summary> + void Clear(); + + /// <summary> + /// Navigates forwards if possible. + /// </summary> + /// <returns>True if there was a page to navigate forwards to.</returns> + bool Forward(); + + /// <summary> + /// Navigates to a new page. + /// </summary> + /// <param name="page">The page view model.</param> + void NavigateTo(IPanePageViewModel page); + + /// <summary> + /// Removes all instances of a page from the history. + /// </summary> + /// <param name="page">The page to remove.</param> + int RemoveAll(IPanePageViewModel page); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INotAGitHubRepositoryViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INotAGitHubRepositoryViewModel.cs new file mode 100644 index 0000000000..c96b9faf54 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INotAGitHubRepositoryViewModel.cs @@ -0,0 +1,15 @@ +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Defines the view model for the "Sign in to GitHub" view in the GitHub pane. + /// </summary> + public interface INotAGitHubRepositoryViewModel : IPanePageViewModel + { + /// <summary> + /// Gets the command executed when the user clicks the "Publish to GitHub" link. + /// </summary> + IReactiveCommand<object> Publish { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INotAGitRepositoryViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INotAGitRepositoryViewModel.cs new file mode 100644 index 0000000000..296673bc5d --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INotAGitRepositoryViewModel.cs @@ -0,0 +1,12 @@ +using System.Diagnostics.CodeAnalysis; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Defines the view model for the "Not a git repository" view in the GitHub pane. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces")] + public interface INotAGitRepositoryViewModel : IPanePageViewModel + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPanePageViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPanePageViewModel.cs new file mode 100644 index 0000000000..3596bea2cb --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPanePageViewModel.cs @@ -0,0 +1,66 @@ +using System; +using System.Reactive; +using System.Threading.Tasks; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model that represents a page in the GitHub pane. + /// </summary> + public interface IPanePageViewModel : IViewModel, IDisposable + { + /// <summary> + /// Gets an exception representing an error state to display. + /// </summary> + Exception Error { get; } + + /// <summary> + /// Gets a value indicating whether the page is busy. + /// </summary> + /// <remarks> + /// When <see cref="IsBusy"/> is set to true, an indeterminate progress bar will be + /// displayed at the top of the GitHub pane but the pane contents will remain visible. + /// </remarks> + bool IsBusy { get; } + + /// <summary> + /// Gets a value indicating whether the page is loading. + /// </summary> + /// <remarks> + /// When <see cref="IsLoading"/> is set to true, a spinner will be displayed instead of the + /// pane contents. + /// </remarks> + bool IsLoading { get; } + + /// <summary> + /// Gets the title to display in the pane when the page is shown. + /// </summary> + string Title { get; } + + /// <summary> + /// Gets an observable that is fired when the pane wishes to close itself. + /// </summary> + IObservable<Unit> CloseRequested { get; } + + /// <summary> + /// Gets an observable that is fired with a URI when the pane wishes to navigate to another + /// pane. + /// </summary> + IObservable<Uri> NavigationRequested { get; } + + /// <summary> + /// Called when the page becomes the current page in the GitHub pane. + /// </summary> + void Activated(); + + /// <summary> + /// Called when the page stops being the current page in the GitHub pane. + /// </summary> + void Deactivated(); + + /// <summary> + /// Refreshes the view model. + /// </summary> + Task Refresh(); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs new file mode 100644 index 0000000000..a47f4889d1 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestChangeNode.cs @@ -0,0 +1,15 @@ +using System; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a file or directory node in a pull request changes tree. + /// </summary> + public interface IPullRequestChangeNode + { + /// <summary> + /// Gets the path to the file or directory, relative to the root of the repository. + /// </summary> + string RelativePath { get; } + } +} \ 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 new file mode 100644 index 0000000000..32ea3ebc20 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Windows.Media.Imaging; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a view model for displaying details of a pull request Status or Check. + /// </summary> + public interface IPullRequestCheckViewModel: IViewModel + { + /// <summary> + /// The title of the Status/Check + /// </summary> + string Title { get; } + + /// <summary> + /// The description of the Status/Check + /// </summary> + string Description { get; } + + /// <summary> + /// The status of the Status/Check + /// </summary> + PullRequestCheckStatus Status { get; } + + /// <summary> + /// The url where more information about the Status/Check can be found + /// </summary> + Uri DetailsUrl { get; } + + /// <summary> + /// A command that opens the DetailsUrl in a browser + /// </summary> + + ReactiveCommand<object> OpenDetailsUrl { get; } + } + + public enum PullRequestCheckStatus + { + Pending, + Success, + Failure + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs new file mode 100644 index 0000000000..5452675dc2 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs @@ -0,0 +1,21 @@ +using GitHub.Models; +using System.Collections.Generic; +using GitHub.Validation; +using ReactiveUI; +using System.Threading.Tasks; + +namespace GitHub.ViewModels.GitHubPane +{ + public interface IPullRequestCreationViewModel : IPanePageViewModel + { + IBranch SourceBranch { get; set; } + IBranch TargetBranch { get; set; } + IReadOnlyList<IBranch> Branches { get; } + IReactiveCommand<IPullRequestModel> CreatePullRequest { get; } + IReactiveCommand<object> Cancel { get; } + string PRTitle { get; set; } + ReactivePropertyValidator TitleValidator { get; } + + Task InitializeAsync(ILocalRepositoryModel repository, IConnection connection); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs new file mode 100644 index 0000000000..73ba93d390 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Holds immutable state relating to the <see cref="IPullRequestDetailViewModel.Checkout"/> command. + /// </summary> + public interface IPullRequestCheckoutState + { + /// <summary> + /// Gets the message to display on the checkout button. + /// </summary> + string Caption { get; } + + /// <summary> + /// Gets a value indicating whether checkout is available. + /// </summary> + bool IsEnabled { get; } + + /// <summary> + /// Gets the message to display as the checkout button's tooltip. + /// </summary> + string ToolTip { get; } + } + + /// <summary> + /// Holds immutable state relating to the <see cref="IPullRequestDetailViewModel.Pull"/> and + /// <see cref="IPullRequestDetailViewModel.Push"/> commands. + /// </summary> + public interface IPullRequestUpdateState + { + /// <summary> + /// Gets the number of commits that the current branch is ahead of the PR branch. + /// </summary> + int CommitsAhead { get; } + + /// <summary> + /// Gets the number of commits that the current branch is behind the PR branch. + /// </summary> + int CommitsBehind { get; } + + /// <summary> + /// Gets a value indicating whether the current branch is up to date. + /// </summary> + bool UpToDate { get; } + + /// <summary> + /// Gets the message to display when a pull cannot be carried out. + /// </summary> + string PullToolTip { get; } + + /// <summary> + /// Gets the message to display when a push cannot be carried out. + /// </summary> + string PushToolTip { get; } + } + + /// <summary> + /// Represents a view model for displaying details of a pull request. + /// </summary> + public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowser + { + /// <summary> + /// Gets the underlying pull request model. + /// </summary> + PullRequestDetailModel Model { get; } + + /// <summary> + /// Gets the session for the pull request. + /// </summary> + IPullRequestSession Session { get; } + + /// <summary> + /// Gets the local repository. + /// </summary> + ILocalRepositoryModel LocalRepository { get; } + + /// <summary> + /// Gets the owner of the remote repository that contains the pull request. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + string RemoteRepositoryOwner { get; } + + /// <summary> + /// Gets the Pull Request number. + /// </summary> + int Number { get; } + + /// <summary> + /// Gets the Pull Request author. + /// </summary> + IActorViewModel Author { get; } + + /// <summary> + /// Gets a string describing how to display the pull request's source branch. + /// </summary> + string SourceBranchDisplayName { get; } + + /// <summary> + /// Gets a string describing how to display the pull request's target branch. + /// </summary> + string TargetBranchDisplayName { get; } + + /// <summary> + /// Gets a value indicating whether the pull request branch is checked out. + /// </summary> + bool IsCheckedOut { get; } + + /// <summary> + /// Gets a value indicating whether the pull request comes from a fork. + /// </summary> + bool IsFromFork { get; } + + /// <summary> + /// Gets the pull request body. + /// </summary> + string Body { get; } + + /// <summary> + /// Gets the latest pull request review for each user. + /// </summary> + IReadOnlyList<IPullRequestReviewSummaryViewModel> Reviews { get; } + + /// <summary> + /// Gets the pull request's changed files. + /// </summary> + IPullRequestFilesViewModel Files { get; } + + /// <summary> + /// Gets the state associated with the <see cref="Checkout"/> command. + /// </summary> + IPullRequestCheckoutState CheckoutState { get; } + + /// <summary> + /// Gets the state associated with the <see cref="Pull"/> and <see cref="Push"/> commands. + /// </summary> + IPullRequestUpdateState UpdateState { get; } + + /// <summary> + /// Gets the error message to be displayed below the checkout button. + /// </summary> + string OperationError { get; } + + /// <summary> + /// Gets a command that checks out the pull request locally. + /// </summary> + ReactiveCommand<Unit> Checkout { get; } + + /// <summary> + /// Gets a command that pulls changes to the current branch. + /// </summary> + ReactiveCommand<Unit> Pull { get; } + + /// <summary> + /// Gets a command that pushes changes from the current branch. + /// </summary> + ReactiveCommand<Unit> Push { get; } + + /// <summary> + /// Gets a command that opens the pull request on GitHub. + /// </summary> + ReactiveCommand<object> OpenOnGitHub { get; } + + /// <summary> + /// Gets a command that navigates to a pull request review. + /// </summary> + ReactiveCommand<object> ShowReview { get; } + + /// <summary> + /// Gets the latest pull request Checks & Statuses + /// </summary> + IReadOnlyList<IPullRequestCheckViewModel> Checks { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="connection">The connection to the repository host.</param> + /// <param name="owner">The pull request's repository owner.</param> + /// <param name="repo">The pull request's repository name.</param> + /// <param name="number">The pull request number.</param> + Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int number); + + /// <summary> + /// Gets the full path to a file in the working directory. + /// </summary> + /// <param name="file">The file.</param> + /// <returns>The full path to the file in the working directory.</returns> + string GetLocalFilePath(IPullRequestFileNode file); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDirectoryNode.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDirectoryNode.cs new file mode 100644 index 0000000000..7e2b23c1c3 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDirectoryNode.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a directory node in a pull request changes tree. + /// </summary> + public interface IPullRequestDirectoryNode : IPullRequestChangeNode + { + /// <summary> + /// Gets the name of the directory without path information. + /// </summary> + string DirectoryName { get; } + + /// <summary> + /// Gets the children of the directory. + /// </summary> + IEnumerable<IPullRequestChangeNode> Children { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs new file mode 100644 index 0000000000..2eaac8c82b --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs @@ -0,0 +1,37 @@ +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + public interface IPullRequestFileNode : IPullRequestChangeNode + { + /// <summary> + /// Gets the name of the file without path information. + /// </summary> + string FileName { get; } + + /// <summary> + /// Gets the old path of a moved/renamed file, relative to the root of the repository. + /// </summary> + string OldPath { get; } + + /// <summary> + /// Gets the SHA of the file. + /// </summary> + string Sha { get; } + + /// <summary> + /// Gets the type of change that was made to the file. + /// </summary> + PullRequestFileStatus Status { get; } + + /// <summary> + /// Gets the string to display in the [message] box next to the filename. + /// </summary> + string StatusDisplay { get; } + + /// <summary> + /// Gets the number of review comments on the file. + /// </summary> + int CommentCount { 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 new file mode 100644 index 0000000000..6953271341 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a tree of changed files in a pull request. + /// </summary> + public interface IPullRequestFilesViewModel : IViewModel, IDisposable + { + /// <summary> + /// Gets the number of changed files in the pull request. + /// </summary> + int ChangedFilesCount { get; } + + /// <summary> + /// Gets the root nodes of the tree. + /// </summary> + IReadOnlyList<IPullRequestChangeNode> Items { get; } + + /// <summary> + /// Gets a command that diffs an <see cref="IPullRequestFileNode"/> between BASE and HEAD. + /// </summary> + ReactiveCommand<Unit> DiffFile { get; } + + /// <summary> + /// Gets a command that opens an <see cref="IPullRequestFileNode"/> as it appears in the PR. + /// </summary> + ReactiveCommand<Unit> ViewFile { get; } + + /// <summary> + /// Gets a command that diffs an <see cref="IPullRequestFileNode"/> between the version in + /// the working directory and HEAD. + /// </summary> + ReactiveCommand<Unit> DiffFileWithWorkingDirectory { get; } + + /// <summary> + /// Gets a command that opens an <see cref="IPullRequestFileNode"/> from disk. + /// </summary> + ReactiveCommand<Unit> OpenFileInWorkingDirectory { get; } + + /// <summary> + /// Gets a command that opens the first comment for a <see cref="IPullRequestFileNode"/> in + /// the diff viewer. + /// </summary> + ReactiveCommand<Unit> OpenFirstComment { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="commentFilter">An optional review comment filter.</param> + Task InitializeAsync( + IPullRequestSession session, + Func<IInlineCommentThreadModel, bool> commentFilter = null); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs new file mode 100644 index 0000000000..f8fa89c351 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs @@ -0,0 +1,37 @@ +using System; +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents an item in the pull request list. + /// </summary> + public interface IPullRequestListItemViewModel : IIssueListItemViewModelBase + { + /// <summary> + /// Gets the ID of the pull request. + /// </summary> + string Id { get; } + + /// <summary> + /// Gets the number of comments in the pull request. + /// </summary> + int CommentCount { get; } + + /// <summary> + /// Gets a value indicating whether the currently checked out branch is the pull request + /// branch. + /// </summary> + bool IsCurrent { get; } + + /// <summary> + /// Gets the last updated time of the pull request. + /// </summary> + DateTimeOffset UpdatedAt { get; } + + /// <summary> + /// Gets the pull request checks and statuses summary + /// </summary> + PullRequestChecksState Checks { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs new file mode 100644 index 0000000000..a873cee164 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs @@ -0,0 +1,21 @@ +using System; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a view model which displays a pull request list. + /// </summary> + public interface IPullRequestListViewModel : IIssueListViewModelBase, IOpenInBrowser + { + /// <summary> + /// Gets a command which navigates to the "Create Pull Request" view. + /// </summary> + ReactiveCommand<object> CreatePullRequest { get; } + + /// <summary> + /// Gets a command that opens pull request item on GitHub. + /// </summary> + ReactiveCommand<object> OpenItemInBrowser { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs new file mode 100644 index 0000000000..2a76cb5654 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a view model for displaying details of a pull request review that is being + /// authored. + /// </summary> + public interface IPullRequestReviewAuthoringViewModel : IPanePageViewModel, IDisposable + { + /// <summary> + /// Gets the local repository. + /// </summary> + ILocalRepositoryModel LocalRepository { get; } + + /// <summary> + /// Gets the owner of the remote repository that contains the pull request. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + string RemoteRepositoryOwner { get; } + + /// <summary> + /// Gets the underlying pull request review model. + /// </summary> + PullRequestReviewModel Model { get; } + + /// <summary> + /// Gets the underlying pull request model. + /// </summary> + PullRequestDetailModel PullRequestModel { get; } + + /// <summary> + /// Gets or sets the body of the pull request review to be submitted. + /// </summary> + string Body { get; } + + /// <summary> + /// Gets a value indicating whether the user can approve/request changes on the pull request. + /// </summary> + bool CanApproveRequestChanges { get; } + + /// <summary> + /// Gets the pull request's changed files. + /// </summary> + IPullRequestFilesViewModel Files { get; } + + /// <summary> + /// Gets a list of the file comments in the review. + /// </summary> + IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments { get; } + + /// <summary> + /// Gets the error message to be displayed in the action area as a result of an error submitting. + /// </summary> + string OperationError { get; } + + /// <summary> + /// Gets a command which navigates to the parent pull request. + /// </summary> + ReactiveCommand<object> NavigateToPullRequest { get; } + + /// <summary> + /// Gets a command which submits the review as an approval. + /// </summary> + ReactiveCommand<Unit> Approve { get; } + + /// <summary> + /// Gets a command which submits the review as a comment. + /// </summary> + ReactiveCommand<Unit> Comment { get; } + + /// <summary> + /// Gets a command which submits the review requesting changes. + /// </summary> + ReactiveCommand<Unit> RequestChanges { get; } + + /// <summary> + /// Gets a command which cancels the review. + /// </summary> + ReactiveCommand<Unit> Cancel { get; } + + /// <summary> + /// Initializes the view model for creating a new review. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="connection">The connection to the repository host.</param> + /// <param name="owner">The pull request's repository owner.</param> + /// <param name="repo">The pull request's repository name.</param> + /// <param name="pullRequestNumber">The pull request number.</param> + Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs new file mode 100644 index 0000000000..58a7cfb0aa --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewFileCommentViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Reactive; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a view model for a file comment in an <see cref="IPullRequestReviewViewModel"/>. + /// </summary> + public interface IPullRequestReviewFileCommentViewModel + { + /// <summary> + /// Gets the body of the comment. + /// </summary> + string Body { get; } + + /// <summary> + /// Gets the path to the file, relative to the root of the repository. + /// </summary> + string RelativePath { get; } + + /// <summary> + /// Gets a command which opens the comment in a diff view. + /// </summary> + ReactiveCommand<Unit> Open { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs new file mode 100644 index 0000000000..c4af548211 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewSummaryViewModel.cs @@ -0,0 +1,35 @@ +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Displays a short overview of a pull request review in the <see cref="IPullRequestDetailViewModel"/>. + /// </summary> + public interface IPullRequestReviewSummaryViewModel + { + /// <summary> + /// Gets the ID of the pull request review. + /// </summary> + string Id { get; set; } + + /// <summary> + /// Gets the user who submitted the review. + /// </summary> + IActorViewModel User { get; set; } + + /// <summary> + /// Gets the state of the review. + /// </summary> + PullRequestReviewState State { get; set; } + + /// <summary> + /// Gets a string representing the state of the review. + /// </summary> + string StateDisplay { get; } + + /// <summary> + /// Gets the number of file comments in the review. + /// </summary> + int FileCommentCount { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs new file mode 100644 index 0000000000..a211ea5d11 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewViewModel.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Represents a view model that displays a pull request review. + /// </summary> + public interface IPullRequestReviewViewModel : IViewModel + { + /// <summary> + /// Gets the underlying pull request review model. + /// </summary> + PullRequestReviewModel Model { get; } + + /// <summary> + /// Gets the body of the review. + /// </summary> + string Body { get; } + + /// <summary> + /// Gets the state of the pull request review as a string. + /// </summary> + string StateDisplay { get; } + + /// <summary> + /// Gets a value indicating whether the pull request review should initially be expanded. + /// </summary> + bool IsExpanded { get; } + + /// <summary> + /// Gets a value indicating whether the pull request review has a body or file comments. + /// </summary> + bool HasDetails { get; } + + /// <summary> + /// Gets a list of the file comments in the review. + /// </summary> + IReadOnlyList<IPullRequestReviewFileCommentViewModel> FileComments { get; } + + /// <summary> + /// Gets a list of outdated file comments in the review. + /// </summary> + IReadOnlyList<IPullRequestReviewFileCommentViewModel> OutdatedFileComments { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs new file mode 100644 index 0000000000..0974ff65aa --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestUserReviewsViewModel.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Displays all reviews made by a user on a pull request. + /// </summary> + public interface IPullRequestUserReviewsViewModel : IPanePageViewModel + { + /// <summary> + /// Gets the local repository. + /// </summary> + ILocalRepositoryModel LocalRepository { get; } + + /// <summary> + /// Gets the owner of the remote repository that contains the pull request. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + string RemoteRepositoryOwner { get; } + + /// <summary> + /// Gets the number of the pull request. + /// </summary> + int PullRequestNumber { get; } + + /// <summary> + /// Gets the reviews made by the <see cref="User"/>. + /// </summary> + IReadOnlyList<IPullRequestReviewViewModel> Reviews { get; } + + /// <summary> + /// Gets the title of the pull request. + /// </summary> + string PullRequestTitle { get; } + + /// <summary> + /// Gets the user whose reviews are being shown. + /// </summary> + IActorViewModel User { get; } + + /// <summary> + /// Gets a command that navigates to the parent pull request in the GitHub pane. + /// </summary> + ReactiveCommand<object> NavigateToPullRequest { get; } + + /// <summary> + /// Initializes the view model, loading data from the API. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="connection">The connection to the repository host.</param> + /// <param name="owner">The pull request's repository owner.</param> + /// <param name="repo">The pull request's repository name.</param> + /// <param name="pullRequestNumber">The pull request number.</param> + /// <param name="login">The user's login.</param> + Task InitializeAsync( + ILocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber, + string login); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ISearchablePageViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ISearchablePageViewModel.cs new file mode 100644 index 0000000000..808bdc1e92 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/ISearchablePageViewModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// A view model that represents a searchable page in the GitHub pane. + /// </summary> + public interface ISearchablePageViewModel : IPanePageViewModel + { + /// <summary> + /// Gets or sets the current search query. + /// </summary> + string SearchQuery { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/IActorViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IActorViewModel.cs new file mode 100644 index 0000000000..4f7da2000f --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/IActorViewModel.cs @@ -0,0 +1,11 @@ +using System.Windows.Media.Imaging; + +namespace GitHub.ViewModels +{ + public interface IActorViewModel : IViewModel + { + BitmapSource Avatar { get; } + string AvatarUrl { get; } + string Login { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/ILoginControlViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ILoginControlViewModel.cs deleted file mode 100644 index f23c583826..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/ILoginControlViewModel.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.ComponentModel; -using System.Reactive; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - public enum LoginMode - { - None = 0, - DotComOrEnterprise, - DotComOnly = 3, - EnterpriseOnly = 4, - } - - /// <summary> - /// Represents a view model responsible for providing log in to - /// either GitHub.com or a GitHub Enterprise instance. - /// </summary> - public interface ILoginControlViewModel : IReactiveObject, ILoginViewModel - { - /// <summary> - /// Gets a value indicating the currently available login modes - /// for the control. - /// </summary> - LoginMode LoginMode { get; } - - /// <summary> - /// Gets a value indicating whether this instance is currently - /// in the process of logging in. - /// </summary> - bool IsLoginInProgress { get; } - - /// <summary> - /// Gets a view model responsible for authenticating a user - /// against GitHub.com. - /// </summary> - ILoginToGitHubViewModel GitHubLogin { get; } - - /// <summary> - /// Gets a view model responsible for authenticating a user - /// against a GitHub Enterprise instance. - /// </summary> - ILoginToGitHubForEnterpriseViewModel EnterpriseLogin { get; } - } -} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/ILoginToGitHubForEnterpriseViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ILoginToGitHubForEnterpriseViewModel.cs deleted file mode 100644 index fe1352bb0a..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/ILoginToGitHubForEnterpriseViewModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Reactive; -using GitHub.Validation; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - /// <summary> - /// Represents a view model responsible for authenticating a user - /// against a GitHub Enterprise instance. - /// </summary> - public interface ILoginToGitHubForEnterpriseViewModel : ILoginToHostViewModel - { - /// <summary> - /// Gets or sets the URL to the GitHub Enterprise instance - /// </summary> - string EnterpriseUrl { get; set; } - - /// <summary> - /// Gets the validator instance used for validating the - /// <see cref="EnterpriseUrl"/> property - /// </summary> - ReactivePropertyValidator EnterpriseUrlValidator { get; } - - /// <summary> - /// Gets a command which, when invoked, directs the user to a learn more page - /// </summary> - IReactiveCommand<Unit> NavigateLearnMore { get; } - } -} diff --git a/src/GitHub.Exports.Reactive/ViewModels/ILoginToHostViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ILoginToHostViewModel.cs index ebe11b9a5c..e6ec380549 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ILoginToHostViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/ILoginToHostViewModel.cs @@ -1,5 +1,5 @@ using System.Reactive; -using GitHub.Authentication; +using GitHub.Models; using GitHub.Validation; using ReactiveUI; @@ -33,7 +33,12 @@ public interface ILoginToHostViewModel /// Gets a command which, when invoked, performs the actual /// login procedure. /// </summary> - IReactiveCommand<AuthenticationResult> Login { get; } + IReactiveCommand<IConnection> Login { get; } + + /// <summary> + /// Gets a command which, when invoked, performs an OAuth login. + /// </summary> + IReactiveCommand<IConnection> LoginViaOAuth { get; } /// <summary> /// Gets a command which, when invoked, direct the user to a @@ -53,17 +58,6 @@ public interface ILoginToHostViewModel /// </summary> bool IsLoggingIn { get; } - /// <summary> - /// Gets a value indicating whether to show log an error - /// message due to a failed log in. - /// </summary> - bool ShowLogInFailedError { get; } - - /// <summary> - /// The message to show if login failed. - /// </summary> - string LoginFailedMessage { get; } - /// <summary> /// Gets a command which, when invoked, resets all properties /// and validators. @@ -74,12 +68,16 @@ public interface ILoginToHostViewModel /// Gets a command which, when invoked, directs the user to /// a GitHub.com lost password flow. /// </summary> - IReactiveCommand<Unit> NavigateForgotPassword { get; } + IRecoveryCommand NavigateForgotPassword { get; } + + /// <summary> + /// Gets an error to display to the user. + /// </summary> + UserError Error { get; } /// <summary> - /// Gets a value indicating whether to show an error message - /// due to being unable to connect to the host. + /// Called when the login UI is hidden or dismissed. /// </summary> - bool ShowConnectingToHostFailed { get; } + void Deactivated(); } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs deleted file mode 100644 index 6a63508af5..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryCloneViewModel.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.Reactive; -using GitHub.Models; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - /// <summary> - /// ViewModel for the the Clone Repository dialog - /// </summary> - public interface IRepositoryCloneViewModel : IViewModel, IRepositoryCreationTarget - { - /// <summary> - /// Command to load the repositories. - /// </summary> - IReactiveCommand<IReadOnlyList<IRepositoryModel>> LoadRepositoriesCommand { get; } - - /// <summary> - /// Command to clone the currently selected repository. - /// </summary> - IReactiveCommand<Unit> CloneCommand { get; } - - /// <summary> - /// The list of repositories the current user may clone from the specified host. - /// </summary> - IReactiveDerivedList<IRepositoryModel> FilteredRepositories { get; } - - IRepositoryModel SelectedRepository { get; set; } - - bool FilterTextIsEnabled { get; } - - /// <summary> - /// Whether or not we are currently loading repositories. - /// </summary> - bool IsLoading { get; } - - /// <summary> - /// If true, then we failed to load the repositories. - /// </summary> - bool LoadingFailed { get; } - - /// <summary> - /// Set to true if no repositories were found. - /// </summary> - bool NoRepositoriesFound { get; } - - /// <summary> - /// Set to true if a repository is selected. - /// </summary> - bool CanClone { get; } - - string FilterText { get; set; } - } -} diff --git a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryPublishViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IRepositoryPublishViewModel.cs deleted file mode 100644 index 4f052aa6dc..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryPublishViewModel.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Reactive; -using GitHub.Models; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - public interface IRepositoryPublishViewModel : IRepositoryForm - { - ReactiveList<IConnection> Connections { get; } - - /// <summary> - /// Command that creates the repository. - /// </summary> - IReactiveCommand<ProgressState> PublishRepository { get; } - - /// <summary> - /// True when publishing is in progress. - /// </summary> - bool IsPublishing { get; } - - /// <summary> - /// Determines whether the host combo box is visible. Only true if the user is logged into more than one host. - /// </summary> - bool IsHostComboBoxVisible { get; } - - /// <summary> - /// The selected host to publish to. - /// </summary> - IConnection SelectedConnection { get; set; } - - /// <summary> - /// Sets the default repository name when prepopulating the form. - /// </summary> - string DefaultRepositoryName { get; } - } - - public enum ProgressState - { - Idle, - Running, - Success, - Fail - } - -} diff --git a/src/GitHub.Exports.Reactive/ViewModels/ITwoFactorDialogViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ITwoFactorDialogViewModel.cs deleted file mode 100644 index 547112d1f4..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/ITwoFactorDialogViewModel.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using GitHub.Validation; -using ReactiveUI; - -namespace GitHub.ViewModels -{ - public interface ITwoFactorDialogViewModel : IViewModel - { - ReactiveCommand<object> OkCommand { get; } - ReactiveCommand<object> CancelCommand { get; } - ReactiveCommand<object> NavigateLearnMore { get; } - ReactiveCommand<object> ResendCodeCommand { get; } - - IObservable<RecoveryOptionResult> Show(UserError error); - - bool IsBusy { get; } - bool IsSms { get; } - bool IsAuthenticationCodeSent { get; } - bool ShowErrorMessage { get; } - bool InvalidAuthenticationCode { get; } - string Description { get; } - string AuthenticationCode { get; set; } - - /// <summary> - /// Gets the validator instance used for validating the - /// <see cref="AuthenticationCode"/> property - /// </summary> - ReactivePropertyValidator AuthenticationCodeValidator { get; } - } -} diff --git a/src/GitHub.Exports.Reactive/ViewModels/IUserFilterViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IUserFilterViewModel.cs new file mode 100644 index 0000000000..8654cc630c --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/IUserFilterViewModel.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace GitHub.ViewModels +{ + public interface IUserFilterViewModel : IViewModel + { + string Filter { get; set; } + IActorViewModel Selected { get; set; } + IReadOnlyList<IActorViewModel> Users { get; } + ICollectionView UsersView { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs new file mode 100644 index 0000000000..5cb230dad1 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs @@ -0,0 +1,39 @@ +using GitHub.Extensions; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.TeamExplorer +{ + public interface IRepositoryPublishViewModel : IViewModel, IRepositoryForm + { + IReadOnlyObservableCollection<IConnection> Connections { get; } + + /// <summary> + /// Gets the busy state of the publish. + /// </summary> + bool IsBusy { get; } + + /// <summary> + /// Command that creates the repository. + /// </summary> + IReactiveCommand<ProgressState> PublishRepository { get; } + + /// <summary> + /// Determines whether the host combo box is visible. Only true if the user is logged into more than one host. + /// </summary> + bool IsHostComboBoxVisible { get; } + + /// <summary> + /// The selected host to publish to. + /// </summary> + IConnection SelectedConnection { get; set; } + } + + public enum ProgressState + { + Idle, + Running, + Success, + Fail + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs b/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000000..be2bb1780a --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs @@ -0,0 +1,17 @@ +using System; +using GitHub.UI; +using ReactiveUI; + +namespace GitHub.ViewModels +{ + /// <summary> + /// Base class for view models. + /// </summary> + /// <remarks> + /// A view model must inherit from this class in order for a view to be automatically + /// found by the ViewLocator. + /// </remarks> + public abstract class ViewModelBase : ReactiveObject, IViewModel + { + } +} diff --git a/src/GitHub.Exports.Reactive/packages.config b/src/GitHub.Exports.Reactive/packages.config index 47a7b2d55e..de7191228b 100644 --- a/src/GitHub.Exports.Reactive/packages.config +++ b/src/GitHub.Exports.Reactive/packages.config @@ -1,5 +1,26 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Imaging" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.111" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" /> <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> diff --git a/src/GitHub.Exports/Api/ISimpleApiClient.cs b/src/GitHub.Exports/Api/ISimpleApiClient.cs index f8e89da9fc..28004d6951 100644 --- a/src/GitHub.Exports/Api/ISimpleApiClient.cs +++ b/src/GitHub.Exports/Api/ISimpleApiClient.cs @@ -6,10 +6,12 @@ namespace GitHub.Api { public interface ISimpleApiClient { + IGitHubClient Client { get; } HostAddress HostAddress { get; } UriString OriginalUrl { get; } Task<Repository> GetRepository(); bool HasWiki(); - bool IsEnterprise(); + Task<bool> IsEnterprise(); + bool IsAuthenticated(); } } diff --git a/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs b/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs index ba4f3b92e3..67cf946cc5 100644 --- a/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs +++ b/src/GitHub.Exports/Api/ISimpleApiClientFactory.cs @@ -1,10 +1,11 @@ -using GitHub.Primitives; +using System.Threading.Tasks; +using GitHub.Primitives; namespace GitHub.Api { public interface ISimpleApiClientFactory { - ISimpleApiClient Create(UriString repositoryUrl); + Task<ISimpleApiClient> Create(UriString repositoryUrl); void ClearFromCache(ISimpleApiClient client); } } diff --git a/src/GitHub.Exports/Authentication/AuthenticationResult.cs b/src/GitHub.Exports/Authentication/AuthenticationResult.cs deleted file mode 100644 index f2623e2774..0000000000 --- a/src/GitHub.Exports/Authentication/AuthenticationResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace GitHub.Authentication -{ - public enum AuthenticationResult - { - /// <summary> - /// Could not authenticate using the credentials provided. - /// </summary> - CredentialFailure, - /// <summary> - /// The two factor authentication challenge failed. - /// </summary> - VerificationFailure, - /// <summary> - /// The given remote Uri is not an enterprise Uri. - /// </summary> - EnterpriseServerNotFound, - /// <summary> - /// Aaaawwww yeeeaah - /// </summary> - Success - } -} diff --git a/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs b/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs deleted file mode 100644 index 3a57e63c61..0000000000 --- a/src/GitHub.Exports/Authentication/AuthenticationResultExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace GitHub.Authentication -{ - - public static class AuthenticationResultExtensions - { - public static bool IsFailure(this AuthenticationResult result) - { - return result != AuthenticationResult.Success; - } - - public static bool IsSuccess(this AuthenticationResult result) - { - return result == AuthenticationResult.Success; - } - } -} diff --git a/src/GitHub.Exports/Commands/IAddConnectionCommand.cs b/src/GitHub.Exports/Commands/IAddConnectionCommand.cs new file mode 100644 index 0000000000..b863423ca6 --- /dev/null +++ b/src/GitHub.Exports/Commands/IAddConnectionCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Opens the login dialog to add a new connection to Team Explorer. + /// </summary> + public interface IAddConnectionCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IBlameLinkCommand.cs b/src/GitHub.Exports/Commands/IBlameLinkCommand.cs new file mode 100644 index 0000000000..aa80f45685 --- /dev/null +++ b/src/GitHub.Exports/Commands/IBlameLinkCommand.cs @@ -0,0 +1,12 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Opens the blame view for the currently selected text on GitHub.com or an Enterprise + /// instance. + /// </summary> + public interface IBlameLinkCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/ICopyLinkCommand.cs b/src/GitHub.Exports/Commands/ICopyLinkCommand.cs new file mode 100644 index 0000000000..605f427b03 --- /dev/null +++ b/src/GitHub.Exports/Commands/ICopyLinkCommand.cs @@ -0,0 +1,12 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Copies a link to the clipboard of the currently selected text on GitHub.com or an + /// Enterprise instance. + /// </summary> + public interface ICopyLinkCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/ICreateGistCommand.cs b/src/GitHub.Exports/Commands/ICreateGistCommand.cs new file mode 100644 index 0000000000..2cc5d11128 --- /dev/null +++ b/src/GitHub.Exports/Commands/ICreateGistCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Creates a gist from the currently selected text. + /// </summary> + public interface ICreateGistCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/ICreateGistEnterpriseCommand.cs b/src/GitHub.Exports/Commands/ICreateGistEnterpriseCommand.cs new file mode 100644 index 0000000000..702daa422a --- /dev/null +++ b/src/GitHub.Exports/Commands/ICreateGistEnterpriseCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Creates a GitHub Enterprise gist from the currently selected text. + /// </summary> + public interface ICreateGistEnterpriseCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IGoToSolutionOrPullRequestFileCommand.cs b/src/GitHub.Exports/Commands/IGoToSolutionOrPullRequestFileCommand.cs new file mode 100644 index 0000000000..1db3e75c2c --- /dev/null +++ b/src/GitHub.Exports/Commands/IGoToSolutionOrPullRequestFileCommand.cs @@ -0,0 +1,15 @@ +namespace GitHub.Commands +{ + /// <summary> + /// Navigate from a PR file to the equivalent file and location in the editor (or the reverse). + /// </summary> + /// <remarks> + /// This command will do one of the following depending on context. + /// Navigate from PR file diff to the working file in the solution. + /// Navigate from the working file in the solution to the PR file diff. + /// Navigate from an editable diff (e.g. 'View Changes in Solution') to the editor view. + /// </remarks> + public interface IGoToSolutionOrPullRequestFileCommand : IVsCommand + { + } +} diff --git a/src/GitHub.Exports/Commands/INextInlineCommentCommand.cs b/src/GitHub.Exports/Commands/INextInlineCommentCommand.cs new file mode 100644 index 0000000000..72aa5f544e --- /dev/null +++ b/src/GitHub.Exports/Commands/INextInlineCommentCommand.cs @@ -0,0 +1,12 @@ +using System; +using GitHub.Commands; + +namespace GitHub.Commands +{ + /// <summary> + /// Navigates to and opens the the next inline comment thread in the currently active text view. + /// </summary> + public interface INextInlineCommentCommand : IVsCommand<InlineCommentNavigationParams> + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IOpenFromClipboardCommand.cs b/src/GitHub.Exports/Commands/IOpenFromClipboardCommand.cs new file mode 100644 index 0000000000..783ac965f1 --- /dev/null +++ b/src/GitHub.Exports/Commands/IOpenFromClipboardCommand.cs @@ -0,0 +1,19 @@ +namespace GitHub.Commands +{ + /// <summary> + /// Open a file in the current repository based on a URL in the clipboard. + /// </summary> + /// <remarks> + /// This command appears as `Code context > GitHub > Open from clipboard`. + /// + /// Open a working directory file at the same location as a GitHub URL in the clipboard. If the URL links to + /// a line or range of lines, these lines will be selected. If the working directory file is different to the + /// target file, the target file will be opened in the `Blame (Annotate)` view. If the URL is from a different + /// fork, it will still open the target file assuming that the target commit/branch exists. + /// Currently only GitHub `blob` URLs are supported. In a future version we can add support for `pull`, `issue`, + /// `tree`, `commits`, `blame` and any other URL types that might make sense. + /// </remarks> + public interface IOpenFromClipboardCommand : IVsCommand<string> + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IOpenFromUrlCommand.cs b/src/GitHub.Exports/Commands/IOpenFromUrlCommand.cs new file mode 100644 index 0000000000..af552848c3 --- /dev/null +++ b/src/GitHub.Exports/Commands/IOpenFromUrlCommand.cs @@ -0,0 +1,17 @@ +namespace GitHub.Commands +{ + /// <summary> + /// Open a repository from a URL in the clipboard. + /// </summary> + /// <remarks> + /// This appears as the named command `GitHub.OpenFromUrl` and must be bound to a keyboard shortcut or executed + /// via the `Command Window`. In future it will appear on `File > Open > Open from GitHub`. + /// + /// When executed it will offer to clone, open and navigate to the file pointed to by a URL in the clipboard. + /// This spike uses Yes/No/Cancel dialogs, but the final version will use a UI to control what action is performed + /// and allow the user to override the default repository location. + /// </remarks> + public interface IOpenFromUrlCommand : IVsCommand<string> + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IOpenLinkCommand.cs b/src/GitHub.Exports/Commands/IOpenLinkCommand.cs new file mode 100644 index 0000000000..c6495183bc --- /dev/null +++ b/src/GitHub.Exports/Commands/IOpenLinkCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Opens the currently selected text on GitHub.com or an Enterprise instance. + /// </summary> + public interface IOpenLinkCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IOpenPullRequestsCommand.cs b/src/GitHub.Exports/Commands/IOpenPullRequestsCommand.cs new file mode 100644 index 0000000000..b29adfc21e --- /dev/null +++ b/src/GitHub.Exports/Commands/IOpenPullRequestsCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Opens the GitHub pane and shows the pull request list. + /// </summary> + public interface IOpenPullRequestsCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IPreviousInlineCommentCommand.cs b/src/GitHub.Exports/Commands/IPreviousInlineCommentCommand.cs new file mode 100644 index 0000000000..571356dc25 --- /dev/null +++ b/src/GitHub.Exports/Commands/IPreviousInlineCommentCommand.cs @@ -0,0 +1,12 @@ +using System; +using GitHub.Commands; + +namespace GitHub.Commands +{ + /// <summary> + /// Navigates to and opens the the previous inline comment thread in the currently active text view. + /// </summary> + public interface IPreviousInlineCommentCommand : IVsCommand<InlineCommentNavigationParams> + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IShowCurrentPullRequestCommand.cs b/src/GitHub.Exports/Commands/IShowCurrentPullRequestCommand.cs new file mode 100644 index 0000000000..f20a6d18af --- /dev/null +++ b/src/GitHub.Exports/Commands/IShowCurrentPullRequestCommand.cs @@ -0,0 +1,14 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Opens the GitHub pane and shows the currently checked out pull request. + /// </summary> + /// <remarks> + /// Does nothing if there is no checked out pull request. + /// </remarks> + public interface IShowCurrentPullRequestCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IShowGitHubPaneCommand.cs b/src/GitHub.Exports/Commands/IShowGitHubPaneCommand.cs new file mode 100644 index 0000000000..a6c05cec32 --- /dev/null +++ b/src/GitHub.Exports/Commands/IShowGitHubPaneCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Opens the GitHub pane. + /// </summary> + public interface IShowGitHubPaneCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/ISyncSubmodulesCommand.cs b/src/GitHub.Exports/Commands/ISyncSubmodulesCommand.cs new file mode 100644 index 0000000000..63cd379d3f --- /dev/null +++ b/src/GitHub.Exports/Commands/ISyncSubmodulesCommand.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.Commands +{ + /// <summary> + /// Sync submodules in local repository. + /// </summary> + public interface ISyncSubmodulesCommand : IVsCommand + { + /// <summary> + /// Sync submodules in local repository. + /// </summary> + /// <returns>Tuple with bool that is true if command completed successfully and string with + /// output from sync submodules Git command.</returns> + Task<Tuple<bool, string>> SyncSubmodules(); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IToggleInlineCommentMarginCommand.cs b/src/GitHub.Exports/Commands/IToggleInlineCommentMarginCommand.cs new file mode 100644 index 0000000000..4ac530580f --- /dev/null +++ b/src/GitHub.Exports/Commands/IToggleInlineCommentMarginCommand.cs @@ -0,0 +1,6 @@ +namespace GitHub.Commands +{ + public interface IToggleInlineCommentMarginCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/IVsCommand.cs b/src/GitHub.Exports/Commands/IVsCommand.cs new file mode 100644 index 0000000000..de2d0211c6 --- /dev/null +++ b/src/GitHub.Exports/Commands/IVsCommand.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.Commands +{ + /// <summary> + /// Represents a Visual Studio command that does not accept a parameter. + /// </summary> + public interface IVsCommand : IVsCommandBase + { + /// <summary> + /// Executes the command. + /// </summary> + /// <returns>A task that tracks the execution of the command.</returns> + Task Execute(); + } + + /// <summary> + /// Represents a Visual Studio command that accepts a parameter. + /// </summary> + public interface IVsCommand<TParam> : IVsCommandBase + { + /// <summary> + /// Executes the command. + /// </summary> + /// <param name="parameter">The command parameter.</param> + /// <returns>A task that tracks the execution of the command.</returns> + Task Execute(TParam parameter); + } +} diff --git a/src/GitHub.Exports/Commands/IVsCommandBase.cs b/src/GitHub.Exports/Commands/IVsCommandBase.cs new file mode 100644 index 0000000000..749d46e808 --- /dev/null +++ b/src/GitHub.Exports/Commands/IVsCommandBase.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Input; + +namespace GitHub.Commands +{ + /// <summary> + /// Represents a Visual Studio command exposed as an <see cref="ICommand"/>. + /// </summary> + public interface IVsCommandBase : ICommand + { + /// <summary> + /// Gets a value indicating whether the command is enabled. + /// </summary> + bool Enabled { get; } + + /// <summary> + /// Gets a value indicating whether the command is visible. + /// </summary> + bool Visible { get; } + } +} diff --git a/src/GitHub.Exports/Commands/InlineCommentNavigationParams.cs b/src/GitHub.Exports/Commands/InlineCommentNavigationParams.cs new file mode 100644 index 0000000000..2d99c5af33 --- /dev/null +++ b/src/GitHub.Exports/Commands/InlineCommentNavigationParams.cs @@ -0,0 +1,26 @@ +using System; + +namespace GitHub.Commands +{ + /// <summary> + /// Supplies parameters to <see cref="INextInlineCommentCommand"/> and + /// <see cref="IPreviousInlineCommentCommand"/>. + /// </summary> + public class InlineCommentNavigationParams + { + /// <summary> + /// Gets or sets the line that should be used as the start point for navigation. + /// </summary> + /// <remarks> + /// If null, the current line will be used. If -1 then the absolute first or last + /// comment in the file will be navigated to. + /// </remarks> + public int? FromLine { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the cursor will be moved to the newly opened + /// comment. + /// </summary> + public bool MoveCursor { get; set; } = true; + } +} diff --git a/src/GitHub.Exports/ExceptionExtensions.cs b/src/GitHub.Exports/ExceptionExtensions.cs new file mode 100644 index 0000000000..a9f54c6eff --- /dev/null +++ b/src/GitHub.Exports/ExceptionExtensions.cs @@ -0,0 +1,16 @@ +using System; +using Octokit; + +namespace GitHub.Extensions +{ + public static class ApiExceptionExtensions + { + const string GithubHeader = "X-GitHub-Request-Id"; + public static bool IsGitHubApiException(this Exception ex) + { + var apiex = ex as ApiException; + return apiex?.HttpResponse?.Headers.ContainsKey(GithubHeader) ?? false; + } + } + +} diff --git a/src/GitHub.Exports/Exports/ExportForProcessAttribute.cs b/src/GitHub.Exports/Exports/ExportForProcessAttribute.cs new file mode 100644 index 0000000000..aa47ff4cd9 --- /dev/null +++ b/src/GitHub.Exports/Exports/ExportForProcessAttribute.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.ComponentModel.Composition; + +namespace GitHub.Exports +{ + /// <summary> + /// Only expose export when executing in specific named process. + /// </summary> + /// <remarks> + /// This attribute is used to mark exports that mustn't be loaded into Blend. + /// See: https://github.com/github/VisualStudio/pull/1055 + /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Extended by ExportForVisualStudioProcessAttribute")] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] + public class ExportForProcessAttribute : ExportAttribute + { + // Unique name for exports that have been disabled + const string DisabledContractName = "GitHub.Disabled"; + + /// <summary> + /// Define an export that is only exposed in a specific named process. + /// </summary> + /// <param name="processName">Name of the process to expose export from (e.g. 'devenv').</param> + /// <param name="contractType">The contract type to expose.</param> + public ExportForProcessAttribute(string processName, Type contractType = null) : + base(ContractNameForProcess(processName), contractType) + { + ProcessName = processName; + } + + static string ContractNameForProcess(string processName) + { + var enabled = IsProcess(processName); + return enabled ? null : DisabledContractName; + } + + public static bool IsProcess(string processName) => Process.GetCurrentProcess().ProcessName == processName; + + /// <summary> + /// The process name export will be exposed in. + /// </summary> + public string ProcessName { get; } + } +} diff --git a/src/GitHub.Exports/Exports/ExportForVisualStudioProcessAttribute.cs b/src/GitHub.Exports/Exports/ExportForVisualStudioProcessAttribute.cs new file mode 100644 index 0000000000..4e39cd28d3 --- /dev/null +++ b/src/GitHub.Exports/Exports/ExportForVisualStudioProcessAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace GitHub.Exports +{ + /// <summary> + /// Only expose export when executing in Visual Studio (devenv) process. + /// </summary> + /// <remarks> + /// This attribute is used to mark exports that mustn't be loaded into Blend. + /// See: https://github.com/github/VisualStudio/pull/1055 + /// </remarks> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] + public sealed class ExportForVisualStudioProcessAttribute : ExportForProcessAttribute + { + const string VisualStudioProcessName = "devenv"; + + /// <summary> + /// Define an export that is only exposed in a Visual Studio (devenv) process. + /// </summary> + /// <param name="contractType">The contract type to expose.</param> + public ExportForVisualStudioProcessAttribute(Type contractType = null) : + base(VisualStudioProcessName, contractType) + { + } + + public static bool IsVisualStudioProcess() => IsProcess(VisualStudioProcessName); + } +} diff --git a/src/GitHub.Exports/Exports/ExportMetadata.cs b/src/GitHub.Exports/Exports/ExportMetadata.cs index b0834f0e93..bdab361eb2 100644 --- a/src/GitHub.Exports/Exports/ExportMetadata.cs +++ b/src/GitHub.Exports/Exports/ExportMetadata.cs @@ -1,65 +1,59 @@ using System; using System.ComponentModel.Composition; -using GitHub.UI; -using GitHub.ViewModels; -using System.Windows.Controls; -using System.Linq; -using System.Diagnostics; -using System.Reflection; - -namespace GitHub.Exports { - - public enum UIViewType { - None, - Login, - TwoFactor, - Create, - Clone, - Publish, - End = 100, - Finished, +using System.Diagnostics.CodeAnalysis; +using System.Windows; + +namespace GitHub.Exports +{ + /// <summary> + /// Defines the types of global visual studio menus available. + /// </summary> + public enum MenuType + { GitHubPane, + OpenPullRequests } - [MetadataAttribute] - [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] - public sealed class ExportViewModelAttribute : ExportAttribute + /// <summary> + /// Defines the type of repository link to navigate to + /// </summary> + public enum LinkType { - public ExportViewModelAttribute() : base(typeof(IViewModel)) - { - } - - public UIViewType ViewType { get; set; } + Unknown, + Blob, + Blame } + /// <summary> + /// A MEF export attribute that defines an export of type <see cref="FrameworkElement"/> with + /// <see cref="ViewModelType"/> metadata. + /// </summary> [MetadataAttribute] - [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] - public sealed class ExportViewAttribute : ExportAttribute + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", + Justification = "Store string rather than Type as metadata")] + public sealed class ExportViewForAttribute : ExportAttribute { - public ExportViewAttribute() : base(typeof(IView)) + public ExportViewForAttribute(Type viewModelType) + : base(typeof(FrameworkElement)) { + ViewModelType = viewModelType.FullName; } - public UIViewType ViewType { get; set; } + public string ViewModelType { get; } } + /// <summary> + /// Defines a MEF metadata view that matches <see cref="ExportViewModelForAttribute"/> and + /// <see cref="ExportViewForAttribute"/>. + /// </summary> + /// <remarks> + /// For more information see the Metadata and Metadata views section at + /// https://msdn.microsoft.com/en-us/library/ee155691(v=vs.110).aspx#Anchor_3 + /// </remarks> public interface IViewModelMetadata { - UIViewType ViewType { get; } - } - - public static class ExportViewAttributeExtensions - { - public static bool IsViewType(this UserControl c, UIViewType type) - { - return c.GetType().GetCustomAttributesData().Any(attr => IsViewType(attr, type)); - } - - private static bool IsViewType(CustomAttributeData attributeData, UIViewType viewType) - { - Debug.Assert(attributeData.NamedArguments != null); - return attributeData.AttributeType == typeof(ExportViewAttribute) - && (UIViewType)attributeData.NamedArguments[0].TypedValue.Value == viewType; - } + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + string[] ViewModelType { get; } } -} \ No newline at end of file +} diff --git a/src/GitHub.Exports/Extensions/ConnectionManagerExtensions.cs b/src/GitHub.Exports/Extensions/ConnectionManagerExtensions.cs new file mode 100644 index 0000000000..50ede8930b --- /dev/null +++ b/src/GitHub.Exports/Extensions/ConnectionManagerExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; + +namespace GitHub.Extensions +{ + public static class ConnectionManagerExtensions + { + public static async Task<bool> IsLoggedIn(this IConnectionManager cm) + { + Guard.ArgumentNotNull(cm, nameof(cm)); + + var connections = await cm.GetLoadedConnections(); + return connections.Any(x => x.ConnectionError == null); + } + + public static async Task<bool> IsLoggedIn(this IConnectionManager cm, HostAddress address) + { + Guard.ArgumentNotNull(cm, nameof(cm)); + Guard.ArgumentNotNull(address, nameof(address)); + + var connections = await cm.GetLoadedConnections(); + return connections.Any(x => x.HostAddress == address && x.ConnectionError == null); + } + + public static async Task<IConnection> GetFirstLoggedInConnection(this IConnectionManager cm) + { + Guard.ArgumentNotNull(cm, nameof(cm)); + + var connections = await cm.GetLoadedConnections(); + return connections.FirstOrDefault(x => x.IsLoggedIn); + } + + public static Task<IConnection> GetConnection(this IConnectionManager cm, IRepositoryModel repository) + { + if (repository?.CloneUrl != null) + { + var hostAddress = HostAddress.Create(repository.CloneUrl); + return cm.GetConnection(hostAddress); + } + + return null; + } + } +} diff --git a/src/GitHub.Exports/Extensions/LocalRepositoryModelExtensions.cs b/src/GitHub.Exports/Extensions/LocalRepositoryModelExtensions.cs new file mode 100644 index 0000000000..4cb75caa17 --- /dev/null +++ b/src/GitHub.Exports/Extensions/LocalRepositoryModelExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using System.IO; +using GitHub.Models; +using GitHub.Services; + +namespace GitHub.Extensions +{ + public static class LocalRepositoryModelExtensions + { + public static bool HasCommits(this ILocalRepositoryModel repository) + { + using (var repo = GitService.GitServiceHelper.GetRepository(repository.LocalPath)) + { + return repo?.Commits.Any() ?? false; + } + } + + public static bool MightContainSolution(this ILocalRepositoryModel repository) + { + var dir = new DirectoryInfo(repository.LocalPath); + return dir.EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly) + .Any(x => ((x.Attributes.HasFlag(FileAttributes.Directory) || x.Attributes.HasFlag(FileAttributes.Normal)) && + !x.Name.StartsWith(".", StringComparison.Ordinal) && !x.Name.StartsWith("readme", StringComparison.OrdinalIgnoreCase))); + } + } +} diff --git a/src/GitHub.Exports/Extensions/ServiceProviderExtensions.cs b/src/GitHub.Exports/Extensions/ServiceProviderExtensions.cs new file mode 100644 index 0000000000..694155fdce --- /dev/null +++ b/src/GitHub.Exports/Extensions/ServiceProviderExtensions.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using GitHub.Logging; +using GitHub.Services; +using Serilog; + +namespace GitHub.Extensions +{ + public static class IServiceProviderExtensions + { + static readonly ILogger log = LogManager.ForContext<VSServices>(); + + /// <summary> + /// Safe variant of GetService that doesn't throw exceptions if the service is + /// not found. + /// </summary> + /// <returns>The service, or null if not found</returns> + public static object GetServiceSafe(this IServiceProvider serviceProvider, Type type) + { + var ui = serviceProvider as IGitHubServiceProvider; + if (ui == null) + { + try + { + var ret = serviceProvider.GetService(type); + if (ret != null) + return ret; + } + catch { } + } + + try + { + if (ui == null) + { + ui = serviceProvider.GetService(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider; + } + if (type.IsEquivalentTo(typeof(IGitHubServiceProvider))) + return ui; + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + log.Error(ex, "GetServiceSafe: Could not obtain instance of '{Type}'", type); + } + return ui?.TryGetService(type); + } + + /// <summary> + /// Safe generic variant that calls <see cref="TryGetService(IServiceProvider, Type)"/> + /// so it doesn't throw exceptions if the service is not found + /// </summary> + /// <returns>The service, or null if not found</returns> + public static T GetServiceSafe<T>(this IServiceProvider serviceProvider) where T : class + { + return serviceProvider.GetServiceSafe(typeof(T)) as T; + } + } +} diff --git a/src/GitHub.Exports/Extensions/SimpleRepositoryModelExtensions.cs b/src/GitHub.Exports/Extensions/SimpleRepositoryModelExtensions.cs deleted file mode 100644 index c5089e3d82..0000000000 --- a/src/GitHub.Exports/Extensions/SimpleRepositoryModelExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; -using System.IO; -using GitHub.Models; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; -using GitHub.Services; - -namespace GitHub.Extensions -{ - public static class SimpleRepositoryModelExtensions - { - /// <summary> - /// Create a SimpleRepositoryModel from a VS git repo object - /// </summary> - public static ISimpleRepositoryModel ToModel(this IGitRepositoryInfo repo) - { - return repo == null ? null : new SimpleRepositoryModel(repo.RepositoryPath); - } - - public static bool HasCommits(this ISimpleRepositoryModel repository) - { - var repo = GitService.GitServiceHelper.GetRepo(repository.LocalPath); - return repo?.Commits.Any() ?? false; - } - - public static bool MightContainSolution(this ISimpleRepositoryModel repository) - { - var dir = new DirectoryInfo(repository.LocalPath); - return dir.EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly) - .Any(x => ((x.Attributes.HasFlag(FileAttributes.Directory) || x.Attributes.HasFlag(FileAttributes.Normal)) && - !x.Name.StartsWith(".", StringComparison.Ordinal) && !x.Name.StartsWith("readme", StringComparison.OrdinalIgnoreCase))); - } - } -} diff --git a/src/GitHub.Exports/Extensions/VSExtensions.cs b/src/GitHub.Exports/Extensions/VSExtensions.cs index c5bf385b54..4fd129db7b 100644 --- a/src/GitHub.Exports/Extensions/VSExtensions.cs +++ b/src/GitHub.Exports/Extensions/VSExtensions.cs @@ -1,134 +1,20 @@ -using System; -using System.ComponentModel.Design; -using System.Diagnostics; -using GitHub.Primitives; -using GitHub.Services; +using GitHub.Services; using LibGit2Sharp; -using Microsoft.TeamFoundation.Controls; using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; namespace GitHub.Extensions { - public static class IServiceProviderExtensions - { - static IUIProvider cachedUIProvider = null; - - public static T TryGetService<T>(this IServiceProvider serviceProvider) where T : class - { - return serviceProvider.TryGetService(typeof(T)) as T; - } - - public static object TryGetService(this IServiceProvider serviceProvider, Type type) - { - if (cachedUIProvider != null && type == typeof(IUIProvider)) - return cachedUIProvider; - - var ui = serviceProvider as IUIProvider; - if (ui != null) - return ui.TryGetService(type); - else - { - try - { - return GetServiceAndCache(serviceProvider, type, ref cachedUIProvider); - } - catch (Exception ex) - { - Debug.Print(ex.ToString()); - } - return null; - } - } - - public static T GetService<T>(this IServiceProvider serviceProvider) - { - if (cachedUIProvider != null && typeof(T) == typeof(IUIProvider)) - return (T)cachedUIProvider; - - return (T)GetServiceAndCache(serviceProvider, typeof(T), ref cachedUIProvider); - } - - public static T GetExportedValue<T>(this IServiceProvider serviceProvider) - { - if (cachedUIProvider != null && typeof(T) == typeof(IUIProvider)) - return (T)cachedUIProvider; - - var ui = serviceProvider as IUIProvider; - return ui != null - ? ui.GetService<T>() - : GetExportedValueAndCache<T, IUIProvider>(ref cachedUIProvider); - } - - public static ITeamExplorerSection GetSection(this IServiceProvider serviceProvider, Guid section) - { - return serviceProvider?.GetService<ITeamExplorerPage>()?.GetSection(section); - } - - static object GetServiceAndCache<CacheType>(IServiceProvider provider, Type type, ref CacheType cache) - { - var ret = provider.GetService(type); - if (type == typeof(CacheType)) - cache = (CacheType)ret; - return ret; - } - - static T GetExportedValueAndCache<T, CacheType>(ref CacheType cache) - { - var ret = VisualStudio.Services.ComponentModel.DefaultExportProvider.GetExportedValue<T>(); - if (typeof(T) == typeof(CacheType)) - cache = (CacheType)(object)ret; - return ret; - } - - public static void AddTopLevelMenuItem(this IServiceProvider provider, - Guid guid, - int cmdId, - EventHandler eventHandler) - { - var mcs = provider.GetService(typeof(IMenuCommandService)) as IMenuCommandService; - Debug.Assert(mcs != null, "No IMenuCommandService? Something is wonky"); - if (mcs == null) - return; - var id = new CommandID(guid, cmdId); - var item = new MenuCommand(eventHandler, id); - mcs.AddCommand(item); - } - - public static void AddDynamicMenuItem(this IServiceProvider provider, - Guid guid, - int cmdId, - Func<bool> canEnable, - Action execute) - { - var mcs = provider.GetService(typeof(IMenuCommandService)) as IMenuCommandService; - Debug.Assert(mcs != null, "No IMenuCommandService? Something is wonky"); - if (mcs == null) - return; - var id = new CommandID(guid, cmdId); - var item = new OleMenuCommand( - (s, e) => execute(), - (s, e) => { }, - (s, e) => - { - ((OleMenuCommand)s).Visible = canEnable(); - }, - id); - mcs.AddCommand(item); - } - } - public static class ISolutionExtensions { - public static IRepository GetRepoFromSolution(this IVsSolution solution) + public static IRepository GetRepositoryFromSolution(this IVsSolution solution) { string solutionDir, solutionFile, userFile; if (!ErrorHandler.Succeeded(solution.GetSolutionInfo(out solutionDir, out solutionFile, out userFile))) return null; if (solutionDir == null) return null; - return GitService.GitServiceHelper.GetRepo(solutionDir); + return GitService.GitServiceHelper.GetRepository(solutionDir); } } } diff --git a/src/GitHub.Exports/Factories/IViewViewModelFactory.cs b/src/GitHub.Exports/Factories/IViewViewModelFactory.cs new file mode 100644 index 0000000000..e0db076839 --- /dev/null +++ b/src/GitHub.Exports/Factories/IViewViewModelFactory.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using GitHub.ViewModels; + +namespace GitHub.Factories +{ + /// <summary> + /// Factory for creating views and view models. + /// </summary> + public interface IViewViewModelFactory + { + /// <summary> + /// Creates a view model based on the specified interface type. + /// </summary> + /// <typeparam name="TViewModel">The view model interface type.</typeparam> + /// <returns>The view model.</returns> + TViewModel CreateViewModel<TViewModel>() where TViewModel : IViewModel; + + /// <summary> + /// Creates a view based on a view model interface type. + /// </summary> + /// <typeparam name="TViewModel">The view model interface type.</typeparam> + /// <returns>The view.</returns> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] + FrameworkElement CreateView<TViewModel>() where TViewModel : IViewModel; + + /// <summary> + /// Creates a view based on a view model interface type. + /// </summary> + /// <param name="viewModel">The view model interface type.</param> + /// <returns>The view.</returns> + FrameworkElement CreateView(Type viewModel); + } +} diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 3e296d7e00..a148a9adf0 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -9,9 +10,12 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub</RootNamespace> <AssemblyName>GitHub.Exports</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> <NuGetPackageImportStamp> </NuGetPackageImportStamp> </PropertyGroup> @@ -19,32 +23,32 @@ <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> <Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <EmbedInteropTypes>False</EmbedInteropTypes> @@ -52,38 +56,88 @@ <Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <EmbedInteropTypes>false</EmbedInteropTypes> </Reference> - <Reference Include="Microsoft.TeamFoundation.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\\Microsoft.TeamFoundation.Controls.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Controls, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Provider, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0" /> - <Reference Include="Microsoft.VisualStudio.OLE.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0" /> - <Reference Include="Microsoft.VisualStudio.TextManager.Interop" /> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Setup.Configuration.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Interop.1.15.103\lib\net35\Microsoft.VisualStudio.Setup.Configuration.Interop.dll</HintPath> + <EmbedInteropTypes>True</EmbedInteropTypes> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> + <Reference Include="rothko, Version=0.0.3.0, Culture=neutral, PublicKeyToken=9f664c41f503810a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rothko.0.0.3-ghfvs\lib\net45\rothko.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.Core" /> <Reference Include="System.Management" /> <Reference Include="System.Management.Instrumentation" /> <Reference Include="System.Net.Http" /> + <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> + </Reference> <Reference Include="System.Xaml" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> @@ -93,38 +147,142 @@ <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> + <Compile Include="Commands\ICopyLinkCommand.cs" /> + <Compile Include="Commands\IBlameLinkCommand.cs" /> + <Compile Include="Commands\ICreateGistEnterpriseCommand.cs" /> + <Compile Include="Commands\IOpenFromClipboardCommand.cs" /> + <Compile Include="Commands\IOpenFromUrlCommand.cs" /> + <Compile Include="Commands\IToggleInlineCommentMarginCommand.cs" /> + <Compile Include="Commands\IOpenLinkCommand.cs" /> + <Compile Include="Commands\ICreateGistCommand.cs" /> + <Compile Include="Commands\IShowCurrentPullRequestCommand.cs" /> + <Compile Include="Commands\ISyncSubmodulesCommand.cs" /> + <Compile Include="Commands\IShowGitHubPaneCommand.cs" /> + <Compile Include="Commands\IOpenPullRequestsCommand.cs" /> + <Compile Include="Commands\IAddConnectionCommand.cs" /> + <Compile Include="Commands\INextInlineCommentCommand.cs" /> + <Compile Include="Commands\InlineCommentNavigationParams.cs" /> + <Compile Include="Commands\IPreviousInlineCommentCommand.cs" /> + <Compile Include="Commands\IGoToSolutionOrPullRequestFileCommand.cs" /> + <Compile Include="Commands\IVsCommand.cs" /> + <Compile Include="Commands\IVsCommandBase.cs" /> + <Compile Include="Exports\ExportForVisualStudioProcessAttribute.cs" /> + <Compile Include="Exports\ExportForProcessAttribute.cs" /> + <Compile Include="Extensions\ConnectionManagerExtensions.cs" /> + <Compile Include="GitHubLogicException.cs" /> + <Compile Include="Models\ActorModel.cs" /> + <Compile Include="Models\AnnotationModel.cs" /> + <Compile Include="Models\CheckAnnotationLevel.cs" /> + <Compile Include="Models\CheckConclusionState.cs" /> + <Compile Include="Models\CheckRunModel.cs" /> + <Compile Include="Models\CheckStatusState.cs" /> + <Compile Include="Models\CheckSuiteModel.cs" /> + <Compile Include="Models\CommitMessage.cs" /> + <Compile Include="Models\DiffChangeType.cs" /> + <Compile Include="Models\DiffChunk.cs" /> + <Compile Include="Models\DiffLine.cs" /> + <Compile Include="Models\DiffUtilities.cs" /> + <Compile Include="Models\CommentModel.cs" /> + <Compile Include="Models\ILocalRepositoryModelFactory.cs" /> + <Compile Include="Models\IPullRequestModel.cs" /> + <Compile Include="Models\Page.cs" /> + <Compile Include="Models\PullRequestFileModel.cs" /> + <Compile Include="Models\PullRequestDetailModel.cs" /> + <Compile Include="Models\PullRequestListItemModel.cs" /> + <Compile Include="Models\PullRequestReviewCommentModel.cs" /> + <Compile Include="Models\PullRequestReviewModel.cs" /> + <Compile Include="Models\PullRequestReviewThreadModel.cs" /> + <Compile Include="Models\StatusModel.cs" /> + <Compile Include="Models\StatusState.cs" /> + <Compile Include="Services\GitHubContext.cs" /> + <Compile Include="Services\IEnterpriseCapabilitiesService.cs" /> + <Compile Include="Services\IGitHubContextService.cs" /> + <Compile Include="Services\IGlobalConnection.cs" /> + <Compile Include="Services\ILocalRepositories.cs" /> + <Compile Include="Services\IRepositoryService.cs" /> + <Compile Include="Services\ITeamExplorerContext.cs" /> + <Compile Include="Services\IVisualStudioBrowser.cs" /> + <Compile Include="Models\UsageData.cs" /> + <Compile Include="Services\IUsageService.cs" /> + <Compile Include="Settings\PkgCmdID.cs" /> + <Compile Include="ViewModels\GitHubPane\IGitHubPaneViewModel.cs" /> + <Compile Include="ViewModels\GitHubPane\IGitHubToolWindowManager.cs" /> + <Compile Include="ViewModels\IConnectionInitializedViewModel.cs" /> + <Compile Include="ViewModels\IInfoPanel.cs" /> + <Compile Include="ViewModels\IViewModel.cs" /> + <Compile Include="ViewModels\IOpenInBrowser.cs" /> + <None Include="..\common\settings.json"> + <Link>Properties\settings.json</Link> </None> + <Compile Include="Services\MetricsService.cs" /> <Compile Include="Collections\ICopyable.cs" /> + <Compile Include="ExceptionExtensions.cs" /> + <Compile Include="Extensions\VSExtensions.cs" /> + <Compile Include="GlobalSuppressions.cs" /> <Compile Include="Helpers\INotifyPropertySource.cs" /> <Compile Include="Extensions\PropertyNotifierExtensions.cs" /> - <Compile Include="Extensions\SimpleRepositoryModelExtensions.cs" /> - <Compile Include="Info\ApplicationInfo.cs" /> + <Compile Include="Extensions\LocalRepositoryModelExtensions.cs" /> + <Compile Include="Helpers\SettingsStore.cs" /> + <Compile Include="Helpers\ThreadingHelper.cs" /> + <Compile Include="Models\BranchModel.cs" /> + <Compile Include="Models\ConnectionDetails.cs" /> + <Compile Include="Models\CloneDialogResult.cs" /> + <Compile Include="Models\GitReferenceModel.cs" /> <Compile Include="Models\IAccount.cs" /> - <Compile Include="Models\IConnectionManager.cs" /> - <Compile Include="Models\IExportFactoryProvider.cs" /> - <Compile Include="Models\ISimpleRepositoryModel.cs" /> - <Compile Include="Models\SimpleRepositoryModel.cs" /> + <Compile Include="Models\IBranch.cs" /> + <Compile Include="Services\IConnectionCache.cs" /> + <Compile Include="Services\IConnectionManager.cs" /> + <Compile Include="Factories\IViewViewModelFactory.cs" /> + <Compile Include="Models\IRepositoryModel.cs" /> + <Compile Include="Models\ILocalRepositoryModel.cs" /> + <Compile Include="Models\RepositoryModel.cs" /> + <Compile Include="Models\LocalRepositoryModel.cs" /> <Compile Include="Helpers\NotificationAwareObject.cs" /> + <Compile Include="Models\UsageModel.cs" /> + <Compile Include="Primitives\RelayCommand.cs" /> + <Compile Include="Primitives\UriStringConverter.cs" /> <Compile Include="Services\Connection.cs" /> <Compile Include="Services\GitService.cs" /> + <Compile Include="Services\IActiveDocumentSnapshot.cs" /> + <Compile Include="Services\IDialogService.cs" /> <Compile Include="Services\IGitService.cs" /> + <Compile Include="Services\IMetricsService.cs" /> + <Compile Include="Services\ISelectedTextProvider.cs" /> + <Compile Include="Services\INotificationDispatcher.cs" /> + <Compile Include="Services\IStatusBarNotificationService.cs" /> + <Compile Include="Services\ITeamExplorerServices.cs" /> <Compile Include="Services\ITeamExplorerServiceHolder.cs" /> - <Compile Include="Services\Logger.cs" /> + <Compile Include="Services\INotificationService.cs" /> + <Compile Include="Services\IUsageTracker.cs" /> + <Compile Include="Services\IVSGitServices.cs" /> + <Compile Include="Services\IVSServices.cs" /> <Compile Include="Services\Services.cs" /> - <Compile Include="Services\VSServices.cs" /> + <Compile Include="Services\StatusBarNotificationService.cs" /> <Compile Include="Models\IConnection.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> </Compile> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> </ItemGroup> <ItemGroup> - <Compile Include="Authentication\AuthenticationResultExtensions.cs" /> - <Compile Include="Extensions\VSExtensions.cs" /> + <Compile Include="Extensions\ServiceProviderExtensions.cs" /> + <Compile Include="Services\VSServices.cs" /> + <Compile Include="Settings\generated\IPackageSettings.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>IPackageSettings.tt</DependentUpon> + </Compile> + <Compile Include="Settings\GitHubConnectSectionState.cs" /> + <Compile Include="Settings\Guids.cs" /> + <Compile Include="Settings\PullRequestDetailUIState.cs" /> + <Compile Include="Settings\PullRequestListUIState.cs" /> + <Compile Include="Settings\RepositoryUIState.cs" /> + <Compile Include="Settings\UIState.cs" /> + <Compile Include="SimpleJson.cs" /> <Compile Include="ViewModels\IServiceProviderAware.cs" /> - <Compile Include="UI\IView.cs" /> <Compile Include="UI\Octicon.cs" /> <Compile Include="ViewModels\IGitHubConnectSection.cs" /> <Compile Include="ViewModels\IGitHubInvitationSection.cs" /> @@ -132,42 +290,52 @@ <Compile Include="Models\IProgram.cs" /> <Compile Include="Api\ISimpleApiClient.cs" /> <Compile Include="Api\ISimpleApiClientFactory.cs" /> - <Compile Include="Authentication\AuthenticationResult.cs" /> - <Compile Include="Models\IRepositoryModel.cs" /> + <Compile Include="Models\IRemoteRepositoryModel.cs" /> <Compile Include="Exports\ExportMetadata.cs" /> <Compile Include="Primitives\StringEquivalent.cs" /> <Compile Include="Primitives\UriString.cs" /> - <Compile Include="Services\EnterpriseProbeTask.cs" /> - <Compile Include="Services\ExportFactoryProvider.cs" /> - <Compile Include="Services\IVisualStudioBrowser.cs" /> - <Compile Include="Services\IEnterpriseProbeTask.cs" /> - <Compile Include="Services\IUIProvider.cs" /> + <Compile Include="Services\IGitHubServiceProvider.cs" /> <Compile Include="Services\IWikiProbe.cs" /> <Compile Include="Services\WikiProbe.cs" /> - <Compile Include="ViewModels\IGitHubPaneViewModel.cs" /> - <Compile Include="ViewModels\ILoginViewModel.cs" /> <Compile Include="Primitives\HostAddress.cs" /> - <Compile Include="UI\IUIController.cs" /> - <Compile Include="ViewModels\IViewModel.cs" /> - <Compile Include="Models\IPullRequestModel.cs" /> + <Compile Include="Services\IVSGitExt.cs" /> + <Compile Include="Services\IVSUIContextFactory.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> <Name>Octokit</Name> </ProjectReference> - <ProjectReference Include="..\..\submodules\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj"> - <Project>{EE6ED99F-CB12-4683-B055-D28FC7357A34}</Project> - <Name>LibGit2Sharp</Name> - <Private>True</Private> - </ProjectReference> <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> <Private>True</Private> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Content Include="Settings\generated\IPackageSettings.tt"> + <Generator>TextTemplatingFileGenerator</Generator> + <LastGenOutput>IPackageSettings.cs</LastGenOutput> + </Content> + </ItemGroup> + <ItemGroup> + <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="$(SolutionDir)\src\common\t4.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> + </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/GitHub.Exports/GitHubLogicException.cs b/src/GitHub.Exports/GitHubLogicException.cs new file mode 100644 index 0000000000..d41d0520be --- /dev/null +++ b/src/GitHub.Exports/GitHubLogicException.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.Serialization; + +namespace GitHub +{ + /// <summary> + /// An exception that signals an error in program logic. + /// </summary> + /// <remarks> + /// This error is often used instead of Debug.Assert because Debug.Assert causes problems with + /// unit tests and the XAML designer. + /// </remarks> + [Serializable] + public class GitHubLogicException : Exception + { + public GitHubLogicException() + { + } + + public GitHubLogicException(string message) + :base(message) + { + } + + public GitHubLogicException(string message, Exception inner) + : base(message, inner) + { + } + + protected GitHubLogicException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/GitHub.Exports/GlobalSuppressions.cs b/src/GitHub.Exports/GlobalSuppressions.cs new file mode 100644 index 0000000000..d5fcc9b07a Binary files /dev/null and b/src/GitHub.Exports/GlobalSuppressions.cs differ diff --git a/src/GitHub.Exports/Helpers/NotificationAwareObject.cs b/src/GitHub.Exports/Helpers/NotificationAwareObject.cs index 184bd5da00..4818ccb94c 100644 --- a/src/GitHub.Exports/Helpers/NotificationAwareObject.cs +++ b/src/GitHub.Exports/Helpers/NotificationAwareObject.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Runtime.CompilerServices; using GitHub.VisualStudio.Helpers; namespace GitHub.Primitives @@ -7,7 +8,7 @@ public abstract class NotificationAwareObject : INotifyPropertyChanged, INotifyP { public event PropertyChangedEventHandler PropertyChanged; - public void RaisePropertyChanged(string propertyName) + public void RaisePropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } diff --git a/src/GitHub.Exports/Helpers/SettingsStore.cs b/src/GitHub.Exports/Helpers/SettingsStore.cs new file mode 100644 index 0000000000..a108a7552e --- /dev/null +++ b/src/GitHub.Exports/Helpers/SettingsStore.cs @@ -0,0 +1,83 @@ +using System.IO; +using Microsoft.VisualStudio.Settings; +using GitHub.Extensions; + +namespace GitHub.Helpers +{ + public class SettingsStore + { + readonly WritableSettingsStore store; + readonly string root; + public SettingsStore(WritableSettingsStore store, string root) + { + Guard.ArgumentNotNull(store, nameof(store)); + Guard.ArgumentNotNull(root, nameof(root)); + Guard.ArgumentNotEmptyString(root, nameof(root)); + this.store = store; + this.root = root; + } + + public object Read(string property, object defaultValue) + { + return Read(null, property, defaultValue); + } + + /// <summary> + /// Read from a settings store + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="subpath">The subcollection path (appended to the path passed to the constructor)</param> + /// <param name="property">The property name to read</param> + /// <param name="defaultValue">The default value to use in case the property doesn't exist. + /// The type of the default value will be used to figure out the proper way to read the property, so if pass null, + /// the property will be read as a string (which may or may not be what you want)</param> + /// <returns></returns> + public object Read(string subpath, string property, object defaultValue) + { + Guard.ArgumentNotNull(property, nameof(property)); + Guard.ArgumentNotEmptyString(property, nameof(property)); + + var collection = subpath != null ? Path.Combine(root, subpath) : root; + store.CreateCollection(collection); + + if (defaultValue is bool) + return store.GetBoolean(collection, property, (bool)defaultValue); + else if (defaultValue is int) + return store.GetInt32(collection, property, (int)defaultValue); + else if (defaultValue is uint) + return store.GetUInt32(collection, property, (uint)defaultValue); + else if (defaultValue is long) + return store.GetInt64(collection, property, (long)defaultValue); + else if (defaultValue is ulong) + return store.GetUInt64(collection, property, (ulong)defaultValue); + return store.GetString(collection, property, defaultValue?.ToString() ?? ""); + } + + public void Write(string property, object value) + { + Write(null, property, value); + } + + public void Write(string subpath, string property, object value) + { + Guard.ArgumentNotNull(property, nameof(property)); + Guard.ArgumentNotEmptyString(property, nameof(property)); + + var collection = subpath != null ? Path.Combine(root, subpath) : root; + store.CreateCollection(collection); + + if (value is bool) + store.SetBoolean(collection, property, (bool)value); + else if (value is int) + store.SetInt32(collection, property, (int)value); + else if (value is uint) + store.SetUInt32(collection, property, (uint)value); + else if (value is long) + store.SetInt64(collection, property, (long)value); + else if (value is ulong) + store.SetUInt64(collection, property, (ulong)value); + else + store.SetString(collection, property, value?.ToString() ?? ""); + } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Helpers/ThreadingHelper.cs b/src/GitHub.Exports/Helpers/ThreadingHelper.cs new file mode 100644 index 0000000000..cb678173b6 --- /dev/null +++ b/src/GitHub.Exports/Helpers/ThreadingHelper.cs @@ -0,0 +1,115 @@ +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(); + + /// <summary> + /// Gets the Dispatcher for the main thread. + /// </summary> + public static Dispatcher MainThreadDispatcher => Application.Current.Dispatcher; + + /// <summary> + /// Switch to the UI thread using ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync + /// Auto-disables switching when running in unit test mode + /// </summary> + /// <returns></returns> + public static IAwaitable SwitchToMainThreadAsync() + { + return Guard.InUnitTestRunner ? + new AwaitableWrapper() : + new AwaitableWrapper(ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()); + } + + /// <summary> + /// 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 + /// </summary> + /// <param name="scheduler"></param> + /// <returns></returns> + public static IAwaitable SwitchToPoolThreadAsync(TaskScheduler scheduler = null) + { + return Guard.InUnitTestRunner ? + new AwaitableWrapper() : + new AwaitableWrapper(scheduler ?? TaskScheduler.Default); + } + + class AwaitableWrapper : IAwaitable + { + Func<IAwaiter> 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<bool> isCompleted; + Action<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/Info/ApplicationInfo.cs b/src/GitHub.Exports/Info/ApplicationInfo.cs deleted file mode 100644 index afb8b875d5..0000000000 --- a/src/GitHub.Exports/Info/ApplicationInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace GitHub.Info -{ - public static class ApplicationInfo - { -#if DEBUG - public const string ApplicationName = "GìtHūbVisualStudio"; - public const string ApplicationProvider = "GitHub"; -#else - public const string ApplicationName = "GitHubVisualStudio"; - public const string ApplicationProvider = "GitHub"; -#endif - public const string ApplicationSafeName = "GitHubVisualStudio"; - public const string ApplicationDescription = "GitHub Extension for Visual Studio"; - } -} diff --git a/src/GitHub.Exports/Models/ActorModel.cs b/src/GitHub.Exports/Models/ActorModel.cs new file mode 100644 index 0000000000..aa387747f5 --- /dev/null +++ b/src/GitHub.Exports/Models/ActorModel.cs @@ -0,0 +1,20 @@ +using System; + +namespace GitHub.Models +{ + /// <summary> + /// Represents an actor (a User or a Bot). + /// </summary> + public class ActorModel + { + /// <summary> + /// Gets or sets the URL of the actor's avatar. + /// </summary> + public string AvatarUrl { get; set; } + + /// <summary> + /// Gets or sets the actor's login. + /// </summary> + public string Login { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/AnnotationModel.cs b/src/GitHub.Exports/Models/AnnotationModel.cs new file mode 100644 index 0000000000..c986fc8aa3 --- /dev/null +++ b/src/GitHub.Exports/Models/AnnotationModel.cs @@ -0,0 +1,48 @@ +namespace GitHub.Models +{ + /// <summary> + /// Model for a single check annotation. + /// </summary> + public class CheckRunAnnotationModel + { + /// <summary> + /// The path to the file that this annotation was made on. + /// </summary> + public string BlobUrl { get; set; } + + /// <summary> + /// The starting line number (1 indexed). + /// </summary> + public int StartLine { get; set; } + + /// <summary> + /// The ending line number (1 indexed). + /// </summary> + public int EndLine { get; set; } + + /// <summary> + /// The path that this annotation was made on. + /// </summary> + public string Filename { get; set; } + + /// <summary> + /// The annotation's message. + /// </summary> + public string Message { get; set; } + + /// <summary> + /// The annotation's title. + /// </summary> + public string Title { get; set; } + + /// <summary> + /// The annotation's severity level. + /// </summary> + public CheckAnnotationLevel? AnnotationLevel { get; set; } + + /// <summary> + /// Additional information about the annotation. + /// </summary> + public string RawDetails { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/BranchModel.cs b/src/GitHub.Exports/Models/BranchModel.cs new file mode 100644 index 0000000000..9a02091871 --- /dev/null +++ b/src/GitHub.Exports/Models/BranchModel.cs @@ -0,0 +1,113 @@ +using System; +using System.Globalization; +using GitHub.Services; + +namespace GitHub.Models +{ + public class BranchModel : IBranch + { + public BranchModel(string name, IRepositoryModel repo) + { + Extensions.Guard.ArgumentNotEmptyString(name, nameof(name)); + Extensions.Guard.ArgumentNotNull(repo, nameof(repo)); + + Name = DisplayName = name; + Repository = repo; + Id = String.Format(CultureInfo.InvariantCulture, "{0}/{1}", Repository.Owner, Name); + } + + public BranchModel(Octokit.Branch branch, IRepositoryModel repo) + { + Extensions.Guard.ArgumentNotNull(branch, nameof(branch)); + Extensions.Guard.ArgumentNotNull(repo, nameof(repo)); + + Name = DisplayName = branch.Name; + Repository = repo; + Id = String.Format(CultureInfo.InvariantCulture, "{0}/{1}", Repository.Owner, Name); + } + + public BranchModel(LibGit2Sharp.Branch branch, IRepositoryModel repo, IGitService gitService) + { + Extensions.Guard.ArgumentNotNull(branch, nameof(branch)); + Extensions.Guard.ArgumentNotNull(repo, nameof(repo)); + Name = DisplayName = branch.FriendlyName; +#pragma warning disable 0618 // TODO: Replace `Branch.Remote` with `Repository.Network.Remotes[branch.RemoteName]`. + Repository = branch.IsRemote ? new LocalRepositoryModel(branch.Remote.Url, gitService) : repo; +#pragma warning restore 0618 + IsTracking = branch.IsTracking; + Sha = branch.Tip?.Sha; + TrackedSha = branch.TrackedBranch?.Tip?.Sha; + Id = String.Format(CultureInfo.InvariantCulture, "{0}/{1}", Repository.Owner, Name); + } + + public string Id { get; private set; } + public string Name { get; private set; } + public IRepositoryModel Repository { get; private set; } + public bool IsTracking { get; private set; } + public string DisplayName { get; set; } + public string Sha { get; private set; } + public string TrackedSha { get; private set; } + + #region Equality things + public void CopyFrom(IBranch other) + { + if (!Equals(other)) + throw new ArgumentException("Instance to copy from doesn't match this instance. this:(" + this + ") other:(" + other + ")", nameof(other)); + Id = other.Id; + Name = other.Name; + Repository = other.Repository; + DisplayName = other.DisplayName; + IsTracking = other.IsTracking; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + return true; + var other = obj as BranchModel; + return other != null && Id == other.Id; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + bool IEquatable<IBranch>.Equals(IBranch other) + { + if (ReferenceEquals(this, other)) + return true; + return other != null && Id == other.Id; + } + + public int CompareTo(IBranch other) + { + return other != null ? String.Compare(Id, other.Id, StringComparison.CurrentCulture) : 1; + } + + public static bool operator >(BranchModel lhs, BranchModel rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return lhs?.CompareTo(rhs) > 0; + } + + public static bool operator <(BranchModel lhs, BranchModel rhs) + { + if (ReferenceEquals(lhs, rhs)) + return false; + return (object)lhs == null || lhs.CompareTo(rhs) < 0; + } + + public static bool operator ==(BranchModel lhs, BranchModel rhs) + { + return Equals(lhs, rhs) && ((object)lhs == null || lhs.CompareTo(rhs) == 0); + } + + public static bool operator !=(BranchModel lhs, BranchModel rhs) + { + return !(lhs == rhs); + } + #endregion + } +} diff --git a/src/GitHub.Exports/Models/CheckAnnotationLevel.cs b/src/GitHub.Exports/Models/CheckAnnotationLevel.cs new file mode 100644 index 0000000000..b0efb7e1ec --- /dev/null +++ b/src/GitHub.Exports/Models/CheckAnnotationLevel.cs @@ -0,0 +1,9 @@ +namespace GitHub.Models +{ + public enum CheckAnnotationLevel + { + Failure, + Notice, + Warning, + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CheckConclusionState.cs b/src/GitHub.Exports/Models/CheckConclusionState.cs new file mode 100644 index 0000000000..3659c3dadb --- /dev/null +++ b/src/GitHub.Exports/Models/CheckConclusionState.cs @@ -0,0 +1,12 @@ +namespace GitHub.Models +{ + public enum CheckConclusionState + { + ActionRequired, + TimedOut, + Cancelled, + Failure, + Success, + Neutral, + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CheckRunModel.cs b/src/GitHub.Exports/Models/CheckRunModel.cs new file mode 100644 index 0000000000..a25335c8bb --- /dev/null +++ b/src/GitHub.Exports/Models/CheckRunModel.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Model for a single check run. + /// </summary> + public class CheckRunModel + { + /// <summary>The conclusion of the check run.</summary> + public CheckConclusionState? Conclusion { get; set; } + + /// <summary> + /// The current status of a Check Run. + /// </summary> + public CheckStatusState Status { get; set; } + + /// <summary> + /// Identifies the date and time when the check run was completed. + /// </summary> + public DateTimeOffset? CompletedAt { get; set; } + + /// <summary>The name of the check for this check run.</summary> + public string Name { get; set; } + + /// <summary> + /// The URL from which to find full details of the check run on the integrator's site. + /// </summary> + public string DetailsUrl { get; set; } + + /// <summary> + /// The summary of a Check Run. + /// </summary> + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CheckStatusState.cs b/src/GitHub.Exports/Models/CheckStatusState.cs new file mode 100644 index 0000000000..6c7e0a2f14 --- /dev/null +++ b/src/GitHub.Exports/Models/CheckStatusState.cs @@ -0,0 +1,10 @@ +namespace GitHub.Models +{ + public enum CheckStatusState + { + Queued, + InProgress, + Completed, + Requested, + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CheckSuiteModel.cs b/src/GitHub.Exports/Models/CheckSuiteModel.cs new file mode 100644 index 0000000000..c6e82cc1be --- /dev/null +++ b/src/GitHub.Exports/Models/CheckSuiteModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Model for a single check suite. + /// </summary> + public class CheckSuiteModel + { + /// <summary> + /// The check runs associated with a check suite. + /// </summary> + public List<CheckRunModel> CheckRuns { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CloneDialogResult.cs b/src/GitHub.Exports/Models/CloneDialogResult.cs new file mode 100644 index 0000000000..05da3d10cd --- /dev/null +++ b/src/GitHub.Exports/Models/CloneDialogResult.cs @@ -0,0 +1,32 @@ +using System; +using GitHub.Services; + +namespace GitHub.Models +{ + /// <summary> + /// Holds the result of a call to <see cref="IDialogService.ShowCloneDialog"/>. + /// </summary> + public class CloneDialogResult + { + /// <summary> + /// Initializes a new instance of the <see cref="CloneDialogResult"/> class. + /// </summary> + /// <param name="basePath">The selected base path for the clone.</param> + /// <param name="repository">The selected repository.</param> + public CloneDialogResult(string basePath, IRepositoryModel repository) + { + BasePath = basePath; + Repository = repository; + } + + /// <summary> + /// Gets the filesystem path to which the user wants to clone. + /// </summary> + public string BasePath { get; } + + /// <summary> + /// Gets the repository selected by the user. + /// </summary> + public IRepositoryModel Repository { get; } + } +} diff --git a/src/GitHub.Exports/Models/CommentModel.cs b/src/GitHub.Exports/Models/CommentModel.cs new file mode 100644 index 0000000000..2b601714dd --- /dev/null +++ b/src/GitHub.Exports/Models/CommentModel.cs @@ -0,0 +1,48 @@ +using System; + +namespace GitHub.Models +{ + /// <summary> + /// An issue or pull request review comment. + /// </summary> + public class CommentModel + { + /// <summary> + /// Gets the ID of the comment. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets the DatabaseId of the comment. + /// </summary> + public int DatabaseId { get; set; } + + /// <summary> + /// Gets the PullRequestId of the comment. + /// </summary> + 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. + + /// <summary> + /// Gets the author of the comment. + /// </summary> + public ActorModel Author { get; set; } + + /// <summary> + /// Gets the body of the comment. + /// </summary> + public string Body { get; set; } + + /// <summary> + /// Gets the creation time of the comment. + /// </summary> + public DateTimeOffset CreatedAt { get; set; } + + /// <summary> + /// Gets the HTTP URL permalink for the comment. + /// </summary> + public string Url { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/CommitMessage.cs b/src/GitHub.Exports/Models/CommitMessage.cs new file mode 100644 index 0000000000..70d0034fe7 --- /dev/null +++ b/src/GitHub.Exports/Models/CommitMessage.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; + +namespace GitHub.Models +{ + public class CommitMessage : IEquatable<CommitMessage> + { + public string Summary { get; private set; } + public string Details { get; private set; } + public string FullMessage { get; private set; } + + /// <summary> + /// This is for mocking porpoises. + /// http://cl.ly/image/0q2A2W0U3O2t + /// </summary> + public CommitMessage() { } + + public CommitMessage(string fullMessage) + { + if (string.IsNullOrEmpty(fullMessage)) return; + + var lines = fullMessage.Replace("\r\n", "\n").Split('\n'); + Summary = lines.FirstOrDefault(); + + FullMessage = fullMessage; + var detailsLines = lines + .Skip(1) + .SkipWhile(string.IsNullOrEmpty) + .ToList(); + Details = detailsLines.Any(x => !string.IsNullOrWhiteSpace(x)) + ? string.Join(Environment.NewLine, detailsLines).Trim() + : null; + } + + public bool Equals(CommitMessage other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + + return string.Equals(Summary, other.Summary) + && string.Equals(Details, other.Details); + } + + public override bool Equals(object obj) + { + return Equals(obj as CommitMessage); + } + + public override int GetHashCode() + { + return Tuple.Create(Summary, Details).GetHashCode(); + } + } +} diff --git a/src/GitHub.Exports/Models/ConnectionDetails.cs b/src/GitHub.Exports/Models/ConnectionDetails.cs new file mode 100644 index 0000000000..67298d7c8b --- /dev/null +++ b/src/GitHub.Exports/Models/ConnectionDetails.cs @@ -0,0 +1,75 @@ +using System; +using GitHub.Primitives; + +namespace GitHub.Models +{ + /// <summary> + /// Represents details about a connection stored in an <see cref="IConnectionCache"/>. + /// </summary> + public struct ConnectionDetails : IEquatable<ConnectionDetails> + { + /// <summary> + /// Initializes a new instance of the <see cref="ConnectionDetails"/> struct. + /// </summary> + /// <param name="hostAddress">The address of the host.</param> + /// <param name="userName">The username for the host.</param> + public ConnectionDetails(string hostAddress, string userName) + { + HostAddress = HostAddress.Create(hostAddress); + UserName = userName; + } + + /// <summary> + /// Initializes a new instance of the <see cref="ConnectionDetails"/> struct. + /// </summary> + /// <param name="hostAddress">The address of the host.</param> + /// <param name="userName">The username for the host.</param> + public ConnectionDetails(HostAddress hostAddress, string userName) + { + HostAddress = hostAddress; + UserName = userName; + } + + /// <summary> + /// Gets the address of the host. + /// </summary> + public HostAddress HostAddress { get; } + + /// <summary> + /// Gets the username for the host. + /// </summary> + public string UserName { get; } + + public bool Equals(ConnectionDetails other) + { + if (ReferenceEquals(this, other)) + return true; + + return HostAddress.Equals(other.HostAddress) && + string.Equals(UserName, other.UserName, StringComparison.OrdinalIgnoreCase); + } + + public override bool Equals(object obj) + { + return obj is ConnectionDetails && Equals((ConnectionDetails)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (HostAddress.GetHashCode()*397) ^ StringComparer.InvariantCultureIgnoreCase.GetHashCode(UserName); + } + } + + public static bool operator ==(ConnectionDetails left, ConnectionDetails right) + { + return Equals(left, right); + } + + public static bool operator !=(ConnectionDetails left, ConnectionDetails right) + { + return !Equals(left, right); + } + } +} diff --git a/src/GitHub.Exports/Models/DiffChangeType.cs b/src/GitHub.Exports/Models/DiffChangeType.cs new file mode 100644 index 0000000000..372bd270a0 --- /dev/null +++ b/src/GitHub.Exports/Models/DiffChangeType.cs @@ -0,0 +1,12 @@ +using System; + +namespace GitHub.Models +{ + public enum DiffChangeType + { + None, + Add, + Delete, + Control + } +} diff --git a/src/GitHub.Exports/Models/DiffChunk.cs b/src/GitHub.Exports/Models/DiffChunk.cs new file mode 100644 index 0000000000..91c29b9c53 --- /dev/null +++ b/src/GitHub.Exports/Models/DiffChunk.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GitHub.Models +{ + public class DiffChunk + { + public int OldLineNumber { get; set; } + public int NewLineNumber { get; set; } + public int DiffLine { get; set; } + public IList<DiffLine> Lines { get; } = new List<DiffLine>(); + + public override string ToString() + { + var builder = new StringBuilder(); + + foreach (var line in Lines) + { + builder.AppendLine(line.Content); + } + + return builder.ToString(); + } + } +} diff --git a/src/GitHub.Exports/Models/DiffLine.cs b/src/GitHub.Exports/Models/DiffLine.cs new file mode 100644 index 0000000000..449ed4eb27 --- /dev/null +++ b/src/GitHub.Exports/Models/DiffLine.cs @@ -0,0 +1,34 @@ +using System; + +namespace GitHub.Models +{ + public class DiffLine + { + /// <summary> + /// Was the line added, deleted or unchanged. + /// </summary> + public DiffChangeType Type { get; set; } + + /// <summary> + /// Gets the old 1-based line number. + /// </summary> + public int OldLineNumber { get; set; } = -1; + + /// <summary> + /// Gets the new 1-based line number. + /// </summary> + public int NewLineNumber { get; set; } = -1; + + /// <summary> + /// Gets the unified diff line number where the first chunk header is line 0. + /// </summary> + public int DiffLineNumber { get; set; } = -1; + + /// <summary> + /// Gets the content of the diff line (including +, - or space). + /// </summary> + public string Content { get; set; } + + public override string ToString() => Content; + } +} diff --git a/src/GitHub.Exports/Models/DiffUtilities.cs b/src/GitHub.Exports/Models/DiffUtilities.cs new file mode 100644 index 0000000000..fff85765bd --- /dev/null +++ b/src/GitHub.Exports/Models/DiffUtilities.cs @@ -0,0 +1,200 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Diagnostics.CodeAnalysis; +using GitHub.Extensions; + +namespace GitHub.Models +{ + public static class DiffUtilities + { + static readonly Regex ChunkHeaderRegex = new Regex(@"^@@\s+\-(\d+),?\d*\s+\+(\d+),?\d*\s@@"); + + public static IEnumerable<DiffChunk> ParseFragment(string diff) + { + Guard.ArgumentNotNull(diff, nameof(diff)); + + var reader = new LineReader(diff); + string line; + DiffChunk chunk = null; + int diffLine = -1; + int oldLine = -1; + int newLine = -1; + + while ((line = reader.ReadLine()) != null) + { + var headerMatch = ChunkHeaderRegex.Match(line); + + if (headerMatch.Success) + { + if (chunk != null) + { + yield return chunk; + } + + if (diffLine == -1) diffLine = 0; + + chunk = new DiffChunk + { + OldLineNumber = oldLine = int.Parse(headerMatch.Groups[1].Value), + NewLineNumber = newLine = int.Parse(headerMatch.Groups[2].Value), + DiffLine = diffLine, + }; + } + else if (chunk != null) + { + var type = GetLineChange(line[0]); + + // This might contain info about previous line (e.g. "\ No newline at end of file"). + if (type != DiffChangeType.Control) + { + chunk.Lines.Add(new DiffLine + { + Type = type, + OldLineNumber = type != DiffChangeType.Add ? oldLine : -1, + NewLineNumber = type != DiffChangeType.Delete ? newLine : -1, + DiffLineNumber = diffLine, + Content = line, + }); + + var lineCount = 1; + lineCount += LineReader.CountCarriageReturns(line); + + switch (type) + { + case DiffChangeType.None: + oldLine += lineCount; + newLine += lineCount; + break; + case DiffChangeType.Delete: + oldLine += lineCount; + break; + case DiffChangeType.Add: + newLine += lineCount; + break; + } + } + } + + if (diffLine != -1) ++diffLine; + } + + if (chunk != null) + { + yield return chunk; + } + } + + public static DiffLine Match(IEnumerable<DiffChunk> diff, IList<DiffLine> target) + { + if (target.Count == 0) + { + return null; // no lines to match + } + + foreach (var source in diff) + { + var matches = 0; + for (var i = source.Lines.Count - 1; i >= 0; --i) + { + if (source.Lines[i].Content == target[matches].Content) + { + matches++; + if (matches == target.Count || i == 0) + { + return source.Lines[i + matches - 1]; + } + } + else + { + i += matches; + matches = 0; + } + } + } + + return null; + } + + /// Here are some alternative implementations we tried: + /// https://gist.github.com/shana/200e4719d4f571caab9dbf5921fa5276 + /// Scanning with `text.IndexOf('\n', index)` appears to the the best compromise for average .diff files. + /// It's likely that `text.IndexOfAny(new [] {'\r', '\n'}, index)` would be faster if lines were much longer. + public class LineReader + { + readonly string text; + int index = 0; + + public LineReader(string text) + { + Guard.ArgumentNotNull(text, nameof(text)); + + this.text = text; + } + + public string ReadLine() + { + if (EndOfText) + { + if (StartOfText) + { + index = -1; + return string.Empty; + } + + return null; + } + + var startIndex = index; + index = text.IndexOf('\n', index); + var endIndex = index != -1 ? index : text.Length; + var length = endIndex - startIndex; + + if (index != -1) + { + if (index > 0 && text[index - 1] == '\r') + { + length--; + } + + index++; + } + + return text.Substring(startIndex, length); + } + + public static int CountCarriageReturns(string text) + { + Guard.ArgumentNotNull(text, nameof(text)); + + int count = 0; + int index = 0; + while ((index = text.IndexOf('\r', index)) != -1) + { + index++; + count++; + } + + return count; + } + + bool StartOfText => index == 0; + + bool EndOfText => index == -1 || index == text.Length; + } + + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)")] + static DiffChangeType GetLineChange(char c) + { + switch (c) + { + case ' ': return DiffChangeType.None; + case '+': return DiffChangeType.Add; + case '-': return DiffChangeType.Delete; + case '\\': return DiffChangeType.Control; + default: throw new InvalidDataException($"Invalid diff line change char: '{c}'."); + } + } + } +} diff --git a/src/GitHub.Exports/Models/GitReferenceModel.cs b/src/GitHub.Exports/Models/GitReferenceModel.cs new file mode 100644 index 0000000000..72bd583005 --- /dev/null +++ b/src/GitHub.Exports/Models/GitReferenceModel.cs @@ -0,0 +1,29 @@ +using GitHub.Extensions; +using GitHub.Primitives; + +namespace GitHub.Models +{ + public class GitReferenceModel + { + public GitReferenceModel(string @ref, string label, string sha, string repositoryCloneUri) + : this(@ref, label, sha, new UriString(repositoryCloneUri)) + { + } + + public GitReferenceModel(string @ref, string label, string sha, UriString repositoryCloneUri) + { + Guard.ArgumentNotEmptyString(@ref, nameof(@ref)); + Guard.ArgumentNotEmptyString(sha, nameof(sha)); + + Ref = @ref; + Label = label; + Sha = sha; + RepositoryCloneUrl = repositoryCloneUri; + } + + public string Ref { get; } + public string Label { get; } + public string Sha { get; } + public UriString RepositoryCloneUrl { get; } + } +} diff --git a/src/GitHub.Exports/Models/IAccount.cs b/src/GitHub.Exports/Models/IAccount.cs index 2e0a783439..617d770b91 100644 --- a/src/GitHub.Exports/Models/IAccount.cs +++ b/src/GitHub.Exports/Models/IAccount.cs @@ -1,8 +1,11 @@ -using System.Windows.Media.Imaging; +using System; +using System.Windows.Media.Imaging; +using GitHub.Collections; namespace GitHub.Models { - public interface IAccount + public interface IAccount : ICopyable<IAccount>, + IEquatable<IAccount>, IComparable<IAccount> { bool IsEnterprise { get; } bool IsOnFreePlan { get; } @@ -11,6 +14,7 @@ public interface IAccount string Login { get; } int OwnedPrivateRepos { get; } long PrivateReposInPlan { get; } - BitmapSource Avatar { get; } + string AvatarUrl { get; } + BitmapSource Avatar { get; } } } diff --git a/src/GitHub.Exports/Models/IBranch.cs b/src/GitHub.Exports/Models/IBranch.cs new file mode 100644 index 0000000000..223172e8fe --- /dev/null +++ b/src/GitHub.Exports/Models/IBranch.cs @@ -0,0 +1,17 @@ +using System; +using GitHub.Collections; + +namespace GitHub.Models +{ + public interface IBranch : ICopyable<IBranch>, + IEquatable<IBranch>, IComparable<IBranch> + { + string Id { get; } + string Name { get; } + IRepositoryModel Repository { get; } + bool IsTracking { get; } + string DisplayName { get; set; } + string Sha { get; } + string TrackedSha { get; } + } +} diff --git a/src/GitHub.Exports/Models/IConnection.cs b/src/GitHub.Exports/Models/IConnection.cs index a160be6112..d05945e637 100644 --- a/src/GitHub.Exports/Models/IConnection.cs +++ b/src/GitHub.Exports/Models/IConnection.cs @@ -1,17 +1,48 @@ -using GitHub.Primitives; -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; +using System; +using System.ComponentModel; using System.Threading.Tasks; +using GitHub.Primitives; +using Octokit; namespace GitHub.Models { - public interface IConnection : IDisposable + /// <summary> + /// Represents a configured connection to a GitHub account. + /// </summary> + public interface IConnection : INotifyPropertyChanged { + /// <summary> + /// Gets the host address of the GitHub instance. + /// </summary> HostAddress HostAddress { get; } + + /// <summary> + /// Gets the username of the GitHub account. + /// </summary> string Username { get; } - IObservable<IConnection> Login(); - void Logout(); - ObservableCollection<ISimpleRepositoryModel> Repositories { get; } + + /// <summary> + /// Gets the logged in user. + /// </summary> + /// <remarks> + /// This may be null if <see cref="IsLoggedIn"/> is false. + /// </remarks> + User User { get; } + + /// <summary> + /// Gets a value indicating whether the login of the account succeeded. + /// </summary> + bool IsLoggedIn { get; } + + /// <summary> + /// Gets a value indicating whether a login is currently being attempted on the connection. + /// </summary> + bool IsLoggingIn { get; } + + /// <summary> + /// Gets the exception that occurred when trying to log in, if <see cref="IsLoggedIn"/> is + /// false. + /// </summary> + Exception ConnectionError { get; } } } diff --git a/src/GitHub.Exports/Models/IConnectionManager.cs b/src/GitHub.Exports/Models/IConnectionManager.cs deleted file mode 100644 index b2bd8f2742..0000000000 --- a/src/GitHub.Exports/Models/IConnectionManager.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using GitHub.Primitives; -using GitHub.Services; - -namespace GitHub.Models -{ - public interface IConnectionManager - { - IConnection CreateConnection(HostAddress address, string username); - bool AddConnection(HostAddress address, string username); - bool RemoveConnection(HostAddress address); - ObservableCollection<IConnection> Connections { get; } - - IObservable<IConnection> RequestLogin(IConnection connection); - void RequestLogout(IConnection connection); - - // for telling IRepositoryHosts that we need to login from cache - [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")] - event Func<IConnection, IObservable<IConnection>> DoLogin; - void RefreshRepositories(); - } -} diff --git a/src/GitHub.Exports/Models/IExportFactoryProvider.cs b/src/GitHub.Exports/Models/IExportFactoryProvider.cs deleted file mode 100644 index 4db9753dc4..0000000000 --- a/src/GitHub.Exports/Models/IExportFactoryProvider.cs +++ /dev/null @@ -1,17 +0,0 @@ -using GitHub.Exports; -using GitHub.UI; -using GitHub.ViewModels; -using System.Collections.Generic; -using System.ComponentModel.Composition; - -namespace GitHub.Models -{ - public interface IExportFactoryProvider - { - ExportFactory<IUIController> UIControllerFactory { get; set; } - IEnumerable<ExportFactory<IViewModel, IViewModelMetadata>> ViewModelFactory { get; set; } - IEnumerable<ExportFactory<IView, IViewModelMetadata>> ViewFactory { get; set; } - ExportLifetimeContext<IViewModel> GetViewModel(UIViewType viewType); - ExportLifetimeContext<IView> GetView(UIViewType viewType); - } -} diff --git a/src/GitHub.Exports/Models/ILocalRepositoryModel.cs b/src/GitHub.Exports/Models/ILocalRepositoryModel.cs new file mode 100644 index 0000000000..1295599b6a --- /dev/null +++ b/src/GitHub.Exports/Models/ILocalRepositoryModel.cs @@ -0,0 +1,39 @@ +using GitHub.Exports; +using GitHub.Primitives; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace GitHub.Models +{ + /// <summary> + /// Represents a locally cloned repository. + /// </summary> + public interface ILocalRepositoryModel : IRepositoryModel, INotifyPropertyChanged + { + /// <summary> + /// Gets the path to the repository on the filesystem. + /// </summary> + string LocalPath { get; } + + /// <summary> + /// Gets the current branch. + /// </summary> + IBranch CurrentBranch { get; } + + /// <summary> + /// Updates the url information based on the local path + /// </summary> + void Refresh(); + + /// <summary> + /// Generates a http(s) url to the repository in the remote server, optionally + /// pointing to a specific file and specific line range in it. + /// </summary> + /// <param name="linkType">The type of repository link to create</param> + /// <param name="path">The file to generate an url to. Optional.</param> + /// <param name="startLine">A specific line, or (if specifying the <paramref name="endLine"/> as well) the start of a range</param> + /// <param name="endLine">The end of a line range on the specified file.</param> + /// <returns>An UriString with the generated url, or null if the repository has no remote server configured or if it can't be found locally</returns> + Task<UriString> GenerateUrl(LinkType linkType, string path = null, int startLine = -1, int endLine = -1); + } +} diff --git a/src/GitHub.Exports/Models/ILocalRepositoryModelFactory.cs b/src/GitHub.Exports/Models/ILocalRepositoryModelFactory.cs new file mode 100644 index 0000000000..3c8dfed0ce --- /dev/null +++ b/src/GitHub.Exports/Models/ILocalRepositoryModelFactory.cs @@ -0,0 +1,15 @@ +namespace GitHub.Models +{ + /// <summary> + /// A factory for <see cref="ILocalRepositoryModelFactory" /> objects. + /// </summary> + public interface ILocalRepositoryModelFactory + { + /// <summary> + /// Construct a new <see cref="ILocalRepositoryModelFactory" />. + /// </summary> + /// <param name="localPath">The local path for the repository.</param> + /// <returns>A new repository model.</returns> + ILocalRepositoryModel Create(string localPath); + } +} diff --git a/src/GitHub.Exports/Models/IPullRequestModel.cs b/src/GitHub.Exports/Models/IPullRequestModel.cs index 87b28b49a7..4d78de08ab 100644 --- a/src/GitHub.Exports/Models/IPullRequestModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestModel.cs @@ -1,19 +1,43 @@ using System; -using System.ComponentModel; -using System.Windows.Media.Imaging; +using System.Collections.Generic; using GitHub.Collections; 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 + { + Open, + Closed, + Merged, + } + + public enum PullRequestChecksState + { + None, + Pending, + Success, + Failure + } + public interface IPullRequestModel : ICopyable<IPullRequestModel>, IEquatable<IPullRequestModel>, IComparable<IPullRequestModel> { int Number { get; } string Title { get; } + PullRequestStateEnum State { get; } int CommentCount { get; } + int CommitCount { get; } + bool IsOpen { get; } + bool Merged { get; } bool HasNewComments { get; } + GitReferenceModel Base { get; } + GitReferenceModel Head { get; } + string Body { get; } DateTimeOffset CreatedAt { get; } DateTimeOffset UpdatedAt { get; } IAccount Author { get; } + IAccount Assignee { get; } } } diff --git a/src/GitHub.Exports/Models/IRemoteRepositoryModel.cs b/src/GitHub.Exports/Models/IRemoteRepositoryModel.cs new file mode 100644 index 0000000000..d6c3224cbd --- /dev/null +++ b/src/GitHub.Exports/Models/IRemoteRepositoryModel.cs @@ -0,0 +1,47 @@ +using GitHub.Collections; +using System; + +namespace GitHub.Models +{ + /// <summary> + /// Represents a repository read from the GitHub API. + /// </summary> + public interface IRemoteRepositoryModel : IRepositoryModel, ICopyable<IRemoteRepositoryModel>, + IEquatable<IRemoteRepositoryModel>, IComparable<IRemoteRepositoryModel> + { + /// <summary> + /// Gets the repository's API ID. + /// </summary> + long Id { get; } + + /// <summary> + /// Gets the account that is the ower of the repository. + /// </summary> + IAccount OwnerAccount { get; } + + /// <summary> + /// Gets the date and time at which the repository was created. + /// </summary> + DateTimeOffset CreatedAt { get; } + + /// <summary> + /// Gets the repository's last update date and time. + /// </summary> + DateTimeOffset UpdatedAt { get; } + + /// <summary> + /// Gets a value indicating whether the repository is a fork. + /// </summary> + bool IsFork { get; } + + /// <summary> + /// Gets the repository from which this repository was forked, if any. + /// </summary> + IRemoteRepositoryModel Parent { get; } + + /// <summary> + /// Gets the default branch for the repository. + /// </summary> + IBranch DefaultBranch { get; } + } +} diff --git a/src/GitHub.Exports/Models/IRepositoryModel.cs b/src/GitHub.Exports/Models/IRepositoryModel.cs index 21b884c1f1..c3c1d577ae 100644 --- a/src/GitHub.Exports/Models/IRepositoryModel.cs +++ b/src/GitHub.Exports/Models/IRepositoryModel.cs @@ -1,10 +1,39 @@ -using GitHub.Primitives; +using System; +using GitHub.Primitives; using GitHub.UI; namespace GitHub.Models { - public interface IRepositoryModel : ISimpleRepositoryModel + /// <summary> + /// Represents a repository, either local or retreived via the GitHub API. + /// </summary> + public interface IRepositoryModel { - IAccount Owner { get; } + /// <summary> + /// Gets the name of the repository. + /// </summary> + string Name { get; } + + /// <summary> + /// Gets the repository clone URL. + /// </summary> + UriString CloneUrl { get; } + + /// <summary> + /// Gets the name of the owner of the repository, taken from the clone URL. + /// </summary> + string Owner { get; } + + /// <summary> + /// Gets an icon for the repository that displays its private and fork state. + /// </summary> + Octicon Icon { get; } + + /// <summary> + /// Sets the <see cref="Icon"/> based on a private and fork state. + /// </summary> + /// <param name="isPrivate">Whether the repository is a private repository.</param> + /// <param name="isFork">Whether the repository is a fork.</param> + void SetIcon(bool isPrivate, bool isFork); } } diff --git a/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs b/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs deleted file mode 100644 index 197a09378f..0000000000 --- a/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using GitHub.Primitives; -using GitHub.UI; -using System.ComponentModel; - -namespace GitHub.Models -{ - public interface ISimpleRepositoryModel : INotifyPropertyChanged - { - string Name { get; } - UriString CloneUrl { get; } - string LocalPath { get; } - Octicon Icon { get; } - - void SetIcon(bool isPrivate, bool isFork); - - - /// <summary> - /// Updates the url information based on the local path - /// </summary> - void Refresh(); - } -} diff --git a/src/GitHub.Exports/Models/LocalRepositoryModel.cs b/src/GitHub.Exports/Models/LocalRepositoryModel.cs new file mode 100644 index 0000000000..fe07ef5ab8 --- /dev/null +++ b/src/GitHub.Exports/Models/LocalRepositoryModel.cs @@ -0,0 +1,229 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using GitHub.Primitives; +using GitHub.UI; +using GitHub.Exports; +using GitHub.Services; +using GitHub.Extensions; +using System.Threading.Tasks; + +namespace GitHub.Models +{ + /// <summary> + /// A locally cloned repository. + /// </summary> + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class LocalRepositoryModel : RepositoryModel, ILocalRepositoryModel, IEquatable<LocalRepositoryModel> + { + readonly IGitService gitService; + + /// <summary> + /// Initializes a new instance of the <see cref="LocalRepositoryModel"/> class. + /// </summary> + /// <param name="name">The repository name.</param> + /// <param name="cloneUrl">The repository's clone URL.</param> + /// <param name="localPath">The repository's local path.</param> + /// <param name="gitService">The service used to refresh the repository's URL.</param> + public LocalRepositoryModel(string name, UriString cloneUrl, string localPath, IGitService gitService) + : base(name, cloneUrl) + { + Guard.ArgumentNotEmptyString(localPath, nameof(localPath)); + Guard.ArgumentNotNull(gitService, nameof(gitService)); + + this.gitService = gitService; + LocalPath = localPath; + Icon = Octicon.repo; + } + + /// <summary> + /// Initializes a new instance of the <see cref="LocalRepositoryModel"/> class. + /// </summary> + /// <param name="path">The repository's local path.</param> + /// <param name="gitService">The service used to find the repository's URL.</param> + public LocalRepositoryModel(string path, IGitService gitService) + : base(path, gitService) + { + Guard.ArgumentNotNull(gitService, nameof(gitService)); + + this.gitService = gitService; + LocalPath = path; + Icon = Octicon.repo; + } + + /// <summary> + /// Updates the clone URL from the local repository. + /// </summary> + public void Refresh() + { + if (LocalPath == null) + return; + CloneUrl = gitService.GetUri(LocalPath); + } + + /// <summary> + /// Generates a http(s) url to the repository in the remote server, optionally + /// pointing to a specific file and specific line range in it. + /// </summary> + /// <param name="linkType">Type of link to generate</param> + /// <param name="path">The file to generate an url to. Optional.</param> + /// <param name="startLine">A specific line, or (if specifying the <paramref name="endLine"/> as well) the start of a range</param> + /// <param name="endLine">The end of a line range on the specified file.</param> + /// <returns>An UriString with the generated url, or null if the repository has no remote server configured or if it can't be found locally</returns> + public async Task<UriString> GenerateUrl(LinkType linkType, string path = null, int startLine = -1, int endLine = -1) + { + if (CloneUrl == null) + return null; + + var sha = await gitService.GetLatestPushedSha(path ?? LocalPath); + // this also incidentally checks whether the repo has a valid LocalPath + if (String.IsNullOrEmpty(sha)) + return CloneUrl.ToRepositoryUrl().AbsoluteUri; + + if (path != null && Path.IsPathRooted(path)) + { + // if the path root doesn't match the repository local path, then ignore it + if (!path.StartsWith(LocalPath, StringComparison.OrdinalIgnoreCase)) + { + Debug.Assert(false, String.Format(CultureInfo.CurrentCulture, "GenerateUrl: path {0} doesn't match repository {1}", path, LocalPath)); + path = null; + } + else + path = path.Substring(LocalPath.Length + 1); + } + + if (startLine > 0 && endLine > 0 && startLine > endLine) + { + // if startLine is greater than endLine and both are set, swap them + var temp = startLine; + startLine = endLine; + endLine = temp; + } + + if (startLine == endLine) + { + // if startLine is the same as endLine don't generate a range link + endLine = -1; + } + + return new UriString(GenerateUrl(linkType, CloneUrl.ToRepositoryUrl().AbsoluteUri, sha, path, startLine, endLine)); + } + + const string CommitFormat = "{0}/commit/{1}"; + const string BlobFormat = "{0}/blob/{1}/{2}"; + const string BlameFormat = "{0}/blame/{1}/{2}"; + const string StartLineFormat = "{0}#L{1}"; + const string EndLineFormat = "{0}-L{1}"; + static string GenerateUrl(LinkType linkType, string basePath, string sha, string path, int startLine = -1, int endLine = -1) + { + if (sha == null) + return basePath; + + if (String.IsNullOrEmpty(path)) + return String.Format(CultureInfo.InvariantCulture, CommitFormat, basePath, sha); + + var ret = String.Format(CultureInfo.InvariantCulture, GetLinkFormat(linkType), basePath, sha, path.Replace(@"\", "/")); + + if (startLine < 0) + return ret; + ret = String.Format(CultureInfo.InvariantCulture, StartLineFormat, ret, startLine); + if (endLine < 0) + return ret; + return String.Format(CultureInfo.InvariantCulture, EndLineFormat, ret, endLine); + } + + /// <summary> + /// Selects the proper format for the link type, defaults to the blob url when link type is not selected. + /// </summary> + /// <param name="linkType">Type of link to generate</param> + /// <returns>The string format of the selected link type</returns> + static string GetLinkFormat(LinkType linkType) + { + switch (linkType) + { + case LinkType.Blame: + return BlameFormat; + + case LinkType.Blob: + return BlobFormat; + + default: + return BlobFormat; + } + } + + /// <summary> + /// Gets the local path of the repository. + /// </summary> + public string LocalPath { get; } + + /// <summary> + /// Gets the head SHA of the repository. + /// </summary> + public string HeadSha + { + get + { + using (var repo = gitService.GetRepository(LocalPath)) + { + return repo?.Commits.FirstOrDefault()?.Sha ?? string.Empty; + } + } + } + + /// <summary> + /// Gets the current branch of the repository. + /// </summary> + public IBranch CurrentBranch + { + get + { + // BranchModel doesn't keep a reference to Repository + using (var repo = gitService.GetRepository(LocalPath)) + { + return new BranchModel(repo?.Head, this, gitService); + } + } + } + + /// <summary> + /// 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 + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + return 17 * 23 + (Name?.GetHashCode() ?? 0) * 23 + (Owner?.GetHashCode() ?? 0) * 23 + (LocalPath?.TrimEnd('\\').ToUpperInvariant().GetHashCode() ?? 0); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + return true; + var other = obj as LocalRepositoryModel; + return Equals(other); + } + + 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(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase); + } + + internal string DebuggerDisplay => String.Format( + CultureInfo.InvariantCulture, + "{4}\tOwner: {0} Name: {1} CloneUrl: {2} LocalPath: {3}", + Owner, + Name, + CloneUrl, + LocalPath, + GetHashCode()); + } +} diff --git a/src/GitHub.Exports/Models/Page.cs b/src/GitHub.Exports/Models/Page.cs new file mode 100644 index 0000000000..b3413bf9f6 --- /dev/null +++ b/src/GitHub.Exports/Models/Page.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Represents a page in a GraphQL paged collection. + /// </summary> + /// <typeparam name="T">The item type.</typeparam> + public class Page<T> + { + /// <summary> + /// Gets or sets the cursor for the last item. + /// </summary> + public string EndCursor { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether there are more items after this page. + /// </summary> + public bool HasNextPage { get; set; } + + /// <summary> + /// Gets or sets the total count of items in all pages. + /// </summary> + public int TotalCount { get; set; } + + /// <summary> + /// Gets or sets the items in the page. + /// </summary> + public IReadOnlyList<T> Items { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/PullRequestDetailModel.cs b/src/GitHub.Exports/Models/PullRequestDetailModel.cs new file mode 100644 index 0000000000..975c6511d2 --- /dev/null +++ b/src/GitHub.Exports/Models/PullRequestDetailModel.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Holds the details of a Pull Request. + /// </summary> + public class PullRequestDetailModel + { + /// <summary> + /// Gets or sets the GraphQL ID of the pull request. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the pull request number. + /// </summary> + public int Number { get; set; } + + /// <summary> + /// Gets or sets the pull request author. + /// </summary> + public ActorModel Author { get; set; } + + /// <summary> + /// Gets or sets the pull request title. + /// </summary> + public string Title { get; set; } + + /// <summary> + /// Gets or sets the pull request state (open, closed, merged). + /// </summary> + public PullRequestStateEnum State { get; set; } + + /// <summary> + /// Gets or sets the pull request body markdown. + /// </summary> + public string Body { get; set; } + + /// <summary> + /// Gets or sets the name of the base branch (e.g. "master"). + /// </summary> + public string BaseRefName { get; set; } + + /// <summary> + /// Gets or sets the SHA of the base branch. + /// </summary> + public string BaseRefSha { get; set; } + + /// <summary> + /// Gets or sets the owner login of the repository containing the base branch. + /// </summary> + public string BaseRepositoryOwner { get; set; } + + /// <summary> + /// Gets or sets the name of the head branch (e.g. "feature-branch"). + /// </summary> + public string HeadRefName { get; set; } + + /// <summary> + /// Gets or sets the SHA of the head branch. + /// </summary> + public string HeadRefSha { get; set; } + + /// <summary> + /// Gets or sets the owner login of the repository containing the head branch. + /// </summary> + public string HeadRepositoryOwner { get; set; } + + /// <summary> + /// Gets or sets the date/time at which the pull request was last updated. + /// </summary> + public DateTimeOffset UpdatedAt { get; set; } + + /// <summary> + /// Gets or sets a collection of files changed by the pull request. + /// </summary> + public IReadOnlyList<PullRequestFileModel> ChangedFiles { get; set; } + + /// <summary> + /// Gets or sets a collection of pull request reviews. + /// </summary> + public IReadOnlyList<PullRequestReviewModel> Reviews { get; set; } + + /// <summary> + /// Gets or sets a collection of pull request review comment threads. + /// </summary> + /// <remarks> + /// The <see cref="Threads"/> collection groups the comments in the various <see cref="Reviews"/> + /// into threads, as such each pull request review comment will appear in both collections. + /// </remarks> + public IReadOnlyList<PullRequestReviewThreadModel> Threads { get; set; } + + /// <summary> + /// Gets or sets a collection of pull request Checks Suites + /// </summary> + public IReadOnlyList<CheckSuiteModel> CheckSuites { get; set; } + + /// <summary> + /// Gets or sets a collection of pull request Statuses + /// </summary> + public IReadOnlyList<StatusModel> Statuses { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/PullRequestFileModel.cs b/src/GitHub.Exports/Models/PullRequestFileModel.cs new file mode 100644 index 0000000000..773be8577c --- /dev/null +++ b/src/GitHub.Exports/Models/PullRequestFileModel.cs @@ -0,0 +1,51 @@ +using System; + +namespace GitHub.Models +{ + /// <summary> + /// Describes the possible values for <see cref="PullRequestFileModel.Status"/>. + /// </summary> + public enum PullRequestFileStatus + { + /// <summary> + /// The file was modified in the pull request. + /// </summary> + Modified, + + /// <summary> + /// The file was added by the pull request. + /// </summary> + Added, + + /// <summary> + /// The file was removed by the pull request. + /// </summary> + Removed, + + /// <summary> + /// The file was moved or renamed by the pull request. + /// </summary> + Renamed, + } + + /// <summary> + /// Holds details of a file changed by a pull request. + /// </summary> + public class PullRequestFileModel + { + /// <summary> + /// Gets or sets the path to the changed file, relative to the repository. + /// </summary> + public string FileName { get; set; } + + /// <summary> + /// Gets or sets the SHA of the changed file. + /// </summary> + public string Sha { get; set; } + + /// <summary> + /// Gets or sets the status of the changed file (modified, added, removed etc). + /// </summary> + public PullRequestFileStatus Status { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/PullRequestListItemModel.cs b/src/GitHub.Exports/Models/PullRequestListItemModel.cs new file mode 100644 index 0000000000..f6a9a7fe55 --- /dev/null +++ b/src/GitHub.Exports/Models/PullRequestListItemModel.cs @@ -0,0 +1,50 @@ +using System; + +namespace GitHub.Models +{ + /// <summary> + /// Holds an overview of a pull request for display in the PR list. + /// </summary> + public class PullRequestListItemModel + { + /// <summary> + /// Gets or sets the GraphQL ID of the pull request. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the pull request number. + /// </summary> + public int Number { get; set; } + + /// <summary> + /// Gets or sets the pull request author. + /// </summary> + public ActorModel Author { get; set; } + + /// <summary> + /// Gets or sets the number of comments on the pull request. + /// </summary> + public int CommentCount { get; set; } + + /// <summary> + /// Gets or sets the pull request title. + /// </summary> + public string Title { get; set; } + + /// <summary> + /// Gets or sets the pull request state (open, closed, merged). + /// </summary> + public PullRequestStateEnum State { get; set; } + + /// <summary> + /// Gets the pull request checks and statuses summary + /// </summary> + public PullRequestChecksState Checks { get; set; } + + /// <summary> + /// Gets or sets the date/time at which the pull request was last updated. + /// </summary> + public DateTimeOffset UpdatedAt { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs b/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs new file mode 100644 index 0000000000..ef224242e1 --- /dev/null +++ b/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace GitHub.Models +{ + /// <summary> + /// Holds details about a pull request review comment. + /// </summary> + public class PullRequestReviewCommentModel : CommentModel + { + /// <summary> + /// Gets or sets the associated thread that contains the comment. + /// </summary> + public PullRequestReviewThreadModel Thread { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/PullRequestReviewModel.cs b/src/GitHub.Exports/Models/PullRequestReviewModel.cs new file mode 100644 index 0000000000..eb0719b262 --- /dev/null +++ b/src/GitHub.Exports/Models/PullRequestReviewModel.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// The possible states of a pull request review. + /// </summary> + public enum PullRequestReviewState + { + /// <summary> + /// A review that has not yet been submitted. + /// </summary> + Pending, + + /// <summary> + /// An informational review. + /// </summary> + Commented, + + /// <summary> + /// A review allowing the pull request to merge. + /// </summary> + Approved, + + /// <summary> + /// A review blocking the pull request from merging. + /// </summary> + ChangesRequested, + + /// <summary> + /// A review that has been dismissed. + /// </summary> + Dismissed, + } + + /// <summary> + /// Holds details about a pull request review. + /// </summary> + public class PullRequestReviewModel + { + /// <summary> + /// Gets or sets the GraphQL ID of the pull request review. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the author of the pull request review. + /// </summary> + public ActorModel Author { get; set; } + + /// <summary> + /// Gets or sets the review's body markdown. + /// </summary> + public string Body { get; set; } + + /// <summary> + /// Gets or sets the review's state (approved, requested changes, commented etc). + /// </summary> + public PullRequestReviewState State { get; set; } + + /// <summary> + /// Gets or sets the SHA at which the review was left. + /// </summary> + public string CommitId { get; set; } + + /// <summary> + /// Gets or sets the date/time at which the review was submitted. + /// </summary> + public DateTimeOffset? SubmittedAt { get; set; } + + /// <summary> + /// Gets or sets the review comments. + /// </summary> + public IReadOnlyList<PullRequestReviewCommentModel> Comments { get; set; } = Array.Empty<PullRequestReviewCommentModel>(); + } +} diff --git a/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs b/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs new file mode 100644 index 0000000000..76e05823c5 --- /dev/null +++ b/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Represents a thread of <see cref="PullRequestReviewCommentModel"/>s. + /// </summary> + public class PullRequestReviewThreadModel + { + /// <summary> + /// Gets or sets the GraphQL ID of the thread. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the path to the file that the thread is on, relative to the repository. + /// </summary> + public string Path { get; set; } + + /// <summary> + /// Gets or sets the SHA of the commmit that the thread starts on. + /// </summary> + public string CommitSha { get; set; } + + /// <summary> + /// Gets or sets the diff hunk for the thread. + /// </summary> + public string DiffHunk { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the thread is outdated. + /// </summary> + public bool IsOutdated { get; set; } + + /// <summary> + /// Gets or sets the line position in the diff that the thread starts on. + /// </summary> + /// <remarks> + /// This property reflects the <see cref="OriginalPosition"/> updated for the current + /// <see cref="CommitSha"/>. If the thread is outdated, it will return null. + /// </remarks> + public int? Position { get; set; } + + /// <summary> + /// Gets or sets the line position in the diff that the thread was originally started on. + /// </summary> + /// <remarks> + /// This property represents a line in the diff between the <see cref="OriginalCommitSha"/> + /// and the pull request branch's merge base at which the thread was originally started. + /// </remarks> + public int OriginalPosition { get; set; } + + /// <summary> + /// Gets or sets the SHA of the commmit that the thread was originally started on. + /// </summary> + public string OriginalCommitSha { get; set; } + + /// <summary> + /// Gets or sets the comments in the thread. + /// </summary> + public IReadOnlyList<PullRequestReviewCommentModel> Comments { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/RepositoryModel.cs b/src/GitHub.Exports/Models/RepositoryModel.cs new file mode 100644 index 0000000000..3ec99829b5 --- /dev/null +++ b/src/GitHub.Exports/Models/RepositoryModel.cs @@ -0,0 +1,112 @@ +using System; +using System.Diagnostics; +using System.IO; +using GitHub.Extensions; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.UI; + +namespace GitHub.Models +{ + /// <summary> + /// The base class for local and remote repository models. + /// </summary> + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RepositoryModel : NotificationAwareObject, IRepositoryModel + { + UriString cloneUrl; + Octicon icon; + + /// <summary> + /// Initializes a new instance of the <see cref="RepositoryModel"/> class. + /// </summary> + /// <param name="name">The repository name.</param> + /// <param name="cloneUrl">The repository's clone URL.</param> + public RepositoryModel( + string name, + UriString cloneUrl) + { + Guard.ArgumentNotEmptyString(name, nameof(name)); + Guard.ArgumentNotNull(cloneUrl, nameof(cloneUrl)); + + Name = name; + CloneUrl = cloneUrl; + } + + /// <summary> + /// Initializes a new instance of the <see cref="RepositoryModel"/> class. + /// </summary> + /// <param name="path"> + /// The path to the local repository from which repository name and clone URL will be + /// extracted. + /// </param> + /// <param name="gitService">The service used to find the repository's <see cref="Name"/> and <see cref="CloneUrl"/>.</param> + protected RepositoryModel(string path, IGitService gitService) + { + Guard.ArgumentNotNull(path, nameof(path)); + + var dir = new DirectoryInfo(path); + if (!dir.Exists) + throw new ArgumentException("Path does not exist", nameof(path)); + var uri = gitService.GetUri(path); + Name = uri?.RepositoryName ?? dir.Name; + CloneUrl = gitService.GetUri(path); + } + + /// <summary> + /// Gets the name of the repository. + /// </summary> + public string Name { get; } + + /// <summary> + /// Gets the repository clone URL. + /// </summary> + public UriString CloneUrl + { + get { return cloneUrl; } + protected set + { + if (cloneUrl != value) + { + cloneUrl = value; + RaisePropertyChanged(); + } + } + } + + /// <summary> + /// Gets the name of the owner of the repository, taken from the clone URL. + /// </summary> + public string Owner => CloneUrl?.Owner ?? string.Empty; + + /// <summary> + /// Gets an icon for the repository that displays its private and fork state. + /// </summary> + public Octicon Icon + { + get { return icon; } + protected set + { + if (icon != value) + { + icon = value; + RaisePropertyChanged(); + } + } + } + + /// <summary> + /// Sets the <see cref="Icon"/> based on a private and fork state. + /// </summary> + /// <param name="isPrivate">Whether the repository is a private repository.</param> + /// <param name="isFork">Whether the repository is a fork.</param> + public void SetIcon(bool isPrivate, bool isFork) + { + Icon = isPrivate + ? Octicon.@lock + : isFork + ? Octicon.repo_forked + : Octicon.repo; + } + } +} diff --git a/src/GitHub.Exports/Models/SimpleRepositoryModel.cs b/src/GitHub.Exports/Models/SimpleRepositoryModel.cs deleted file mode 100644 index 9bb2672660..0000000000 --- a/src/GitHub.Exports/Models/SimpleRepositoryModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using GitHub.Primitives; -using GitHub.UI; -using GitHub.VisualStudio.Helpers; -using GitHub.Services; - -namespace GitHub.Models -{ - [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class SimpleRepositoryModel : NotificationAwareObject, ISimpleRepositoryModel, INotifyPropertySource, IEquatable<SimpleRepositoryModel> - { - public SimpleRepositoryModel(string name, UriString cloneUrl, string localPath = null) - { - Name = name; - CloneUrl = cloneUrl; - LocalPath = localPath; - Icon = Octicon.repo; - } - - public SimpleRepositoryModel(string path) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - var dir = new DirectoryInfo(path); - if (!dir.Exists) - throw new ArgumentException("Path does not exist", nameof(path)); - var uri = GitService.GitServiceHelper.GetUri(path); - var name = uri?.NameWithOwner ?? dir.Name; - Name = name; - LocalPath = path; - CloneUrl = uri; - Icon = Octicon.repo; - } - - public void SetIcon(bool isPrivate, bool isFork) - { - Icon = isPrivate - ? Octicon.@lock - : isFork - ? Octicon.repo_forked - : Octicon.repo; - } - - public void Refresh() - { - if (LocalPath == null) - return; - var uri = GitService.GitServiceHelper.GetUri(LocalPath); - if (CloneUrl != uri) - CloneUrl = uri; - } - - public string Name { get; } - UriString cloneUrl; - public UriString CloneUrl { get { return cloneUrl; } set { cloneUrl = value; this.RaisePropertyChange(); } } - public string LocalPath { get; } - Octicon icon; - public Octicon Icon { get { return icon; } set { icon = value; this.RaisePropertyChange(); } } - - /// <summary> - /// 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 - /// </summary> - /// <returns></returns> - public override int GetHashCode() - { - return (Name?.GetHashCode() ?? 0) ^ (LocalPath?.TrimEnd('\\').ToUpperInvariant().GetHashCode() ?? 0); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) - return true; - var other = obj as SimpleRepositoryModel; - return other != null && String.Equals(Name, other.Name) && String.Equals(CloneUrl, other.CloneUrl) && String.Equals(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase); - } - - bool IEquatable<SimpleRepositoryModel>.Equals(SimpleRepositoryModel other) - { - if (ReferenceEquals(this, other)) - return true; - return other != null && String.Equals(Name, other.Name) && String.Equals(CloneUrl, other.CloneUrl) && String.Equals(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase); - } - - internal string DebuggerDisplay => String.Format( - CultureInfo.InvariantCulture, - "{3}\tName: {0} CloneUrl: {1} LocalPath: {2}", - Name, - CloneUrl, - LocalPath, - GetHashCode()); - } -} diff --git a/src/GitHub.Exports/Models/StatusModel.cs b/src/GitHub.Exports/Models/StatusModel.cs new file mode 100644 index 0000000000..3b0832caa0 --- /dev/null +++ b/src/GitHub.Exports/Models/StatusModel.cs @@ -0,0 +1,28 @@ +namespace GitHub.Models +{ + /// <summary> + /// Model for a single pull request Status. + /// </summary> + public class StatusModel + { + /// <summary> + /// The state of the Status + /// </summary> + public StatusState State { get; set; } + + /// <summary> + /// The Status context or title + /// </summary> + public string Context { get; set; } + + /// <summary> + /// The url where more information about the Status can be found + /// </summary> + public string TargetUrl { get; set; } + + /// <summary> + /// The descritption for the Status + /// </summary> + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/StatusState.cs b/src/GitHub.Exports/Models/StatusState.cs new file mode 100644 index 0000000000..57830b3dd0 --- /dev/null +++ b/src/GitHub.Exports/Models/StatusState.cs @@ -0,0 +1,11 @@ +namespace GitHub.Models +{ + public enum StatusState + { + Expected, + Error, + Failure, + Pending, + Success, + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/UsageData.cs b/src/GitHub.Exports/Models/UsageData.cs new file mode 100644 index 0000000000..c95bfe1f05 --- /dev/null +++ b/src/GitHub.Exports/Models/UsageData.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// <summary> + /// Holds a collection of <see cref="UsageModel"/> daily usage reports. + /// </summary> + public class UsageData + { + /// <summary> + /// Gets a list of unsent daily usage reports. + /// </summary> + public List<UsageModel> Reports { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs new file mode 100644 index 0000000000..74c5e6c057 --- /dev/null +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -0,0 +1,90 @@ +using System; + +namespace GitHub.Models +{ + public class UsageModel + { + public DimensionsModel Dimensions { get; set; } = new DimensionsModel(); + public MeasuresModel Measures { get; set; } = new MeasuresModel(); + + // this should never be called by our code but it's required to be public by the serialization code + public UsageModel() { } + + public static UsageModel Create(Guid guid) + { + return new UsageModel + { + Dimensions = new DimensionsModel + { + Guid = guid, + Date = DateTimeOffset.Now, + } + }; + } + + public class DimensionsModel + { + public Guid Guid { get; set; } + public DateTimeOffset Date { get; set; } + public bool IsGitHubUser { get; set; } + public bool IsEnterpriseUser { get; set; } + public string AppVersion { get; set; } + public string VSVersion { get; set; } + public string Lang { get; set; } + public string CurrentLang { get; set; } + } + + public class MeasuresModel + { + public int NumberOfStartups { get; set; } + public int NumberOfUpstreamPullRequests { get; set; } + public int NumberOfClones { get; set; } + public int NumberOfReposCreated { get; set; } + public int NumberOfReposPublished { get; set; } + public int NumberOfGists { get; set; } + public int NumberOfOpenInGitHub { get; set; } + public int NumberOfLinkToGitHub { get; set; } + public int NumberOfLogins { get; set; } + public int NumberOfOAuthLogins { get; set; } + public int NumberOfTokenLogins { get; set; } + public int NumberOfPullRequestsOpened { get; set; } + public int NumberOfLocalPullRequestsCheckedOut { get; set; } + public int NumberOfLocalPullRequestPulls { get; set; } + public int NumberOfLocalPullRequestPushes { get; set; } + public int NumberOfForkPullRequestsCheckedOut { get; set; } + public int NumberOfForkPullRequestPulls { get; set; } + public int NumberOfForkPullRequestPushes { get; set; } + public int NumberOfSyncSubmodules { get; set; } + public int NumberOfWelcomeDocsClicks { get; set; } + public int NumberOfWelcomeTrainingClicks { get; set; } + public int NumberOfGitHubPaneHelpClicks { get; set; } + public int NumberOfPRDetailsOpenInGitHub { get; set; } + public int NumberOfPRStatusesOpenInGitHub { get; set; } + public int NumberOfPRChecksOpenInGitHub { get; set; } + public int NumberOfPRDetailsViewChanges { get; set; } + public int NumberOfPRDetailsViewFile { get; set; } + public int NumberOfPRDetailsCompareWithSolution { get; set; } + public int NumberOfPRDetailsOpenFileInSolution { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentOpen { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentPost { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentDelete { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentEdit { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentStartReview { get; set; } + public int NumberOfPRReviewPosts { get; set; } + public int NumberOfShowCurrentPullRequest { get; set; } + public int NumberOfStatusBarOpenPullRequestList { get; set; } + public int NumberOfTeamExplorerHomeOpenPullRequestList { get; set; } + public int NumberOfStartPageClones { get; set; } + public int NumberOfGitHubConnectSectionClones { get; set; } + public int NumberOfShowRepoForkDialogClicks { get; set; } + public int NumberOfReposForked { get; set; } + public int ExecuteGoToSolutionOrPullRequestFileCommand { get; set; } + public int NumberOfPRDetailsNavigateToEditor { get; set; } // Should rename to NumberOfNavigateToEditor + public int NumberOfNavigateToPullRequestFileDiff { get; set; } + public int NumberOfNavigateToCodeView { get; set; } + public int ExecuteToggleInlineCommentMarginCommand { get; set; } + public int NumberOfPullRequestFileMarginToggleInlineCommentMargin { get; set; } + public int NumberOfPullRequestFileMarginViewChanges { get; set; } + } + } +} diff --git a/src/GitHub.Exports/Primitives/HostAddress.cs b/src/GitHub.Exports/Primitives/HostAddress.cs index 5d7b6135e1..bcea51d364 100644 --- a/src/GitHub.Exports/Primitives/HostAddress.cs +++ b/src/GitHub.Exports/Primitives/HostAddress.cs @@ -44,7 +44,7 @@ private HostAddress(Uri enterpriseUri) { WebUri = new Uri(enterpriseUri, new Uri("/", UriKind.Relative)); ApiUri = new Uri(enterpriseUri, new Uri("/api/v3/", UriKind.Relative)); - //CredentialCacheKeyHost = ApiUri.Host; + GraphQLUri = new Uri(enterpriseUri, new Uri("/api/graphql", UriKind.Relative)); CredentialCacheKeyHost = WebUri.ToString(); } @@ -52,21 +52,27 @@ public HostAddress() { WebUri = new Uri("https://github.com"); ApiUri = new Uri("https://api.github.com"); - //CredentialCacheKeyHost = "github.com"; + GraphQLUri = new Uri("https://api.github.com/graphql"); CredentialCacheKeyHost = WebUri.ToString(); } /// <summary> - /// The Base URL to the host. For example, "https://github.com" or "https://ghe.io" + /// The Base URL to the host. For example, "https://github.com" or "https://github-enterprise.com" /// </summary> public Uri WebUri { get; set; } /// <summary> /// The Base Url to the host's API endpoint. For example, "https://api.github.com" or - /// "https://ghe.io/api/v3" + /// "https://github-enterprise.com/api/v3" /// </summary> public Uri ApiUri { get; set; } + /// <summary> + /// The Base Url to the host's GraphQL API endpoint. For example, "https://api.github.com/graphql" or + /// "https://github-enterprise.com/api/graphql" + /// </summary> + public Uri GraphQLUri { get; set; } + // If the host name is "api.github.com" or "gist.github.com", we really only want "github.com", // since that's the same cache key for all the other github.com operations. public string CredentialCacheKeyHost { get; private set; } @@ -78,6 +84,16 @@ public static bool IsGitHubDotComUri(Uri hostUri) || hostUri.IsSameHost(gistUri); } + public static bool operator ==(HostAddress a, HostAddress b) + { + return object.ReferenceEquals(a, null) ? object.ReferenceEquals(b, null) : a.Equals(b); + } + + public static bool operator !=(HostAddress a, HostAddress b) + { + return !(a == b); + } + public bool IsGitHubDotCom() { return IsGitHubDotComUri(ApiUri); diff --git a/src/GitHub.Exports/Primitives/RelayCommand.cs b/src/GitHub.Exports/Primitives/RelayCommand.cs new file mode 100644 index 0000000000..ab0f4bdbe4 --- /dev/null +++ b/src/GitHub.Exports/Primitives/RelayCommand.cs @@ -0,0 +1,46 @@ +using GitHub.Extensions; +using System; +using System.Windows.Input; + +namespace GitHub.Primitives +{ + public class RelayCommand : ICommand + { + readonly Func<object, bool> canExecute; + readonly Action<object> execute; + + public event EventHandler CanExecuteChanged; + + public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null) + { + Guard.ArgumentNotNull(execute, nameof(execute)); + this.execute = execute; + this.canExecute = canExecute ?? (_ => true); + } + + public bool CanExecute(object parameter) + { + bool ret = false; + try + { + ret = canExecute(parameter); + } + catch {} + return ret; + } + + public void Execute(object parameter) + { + var handler = CanExecuteChanged; + handler?.Invoke(this, EventArgs.Empty); + try + { + execute(parameter); + } + finally + { + handler?.Invoke(this, EventArgs.Empty); + } + } + } +} diff --git a/src/GitHub.Exports/Primitives/UriString.cs b/src/GitHub.Exports/Primitives/UriString.cs index 32628f1a78..55842ccbfc 100644 --- a/src/GitHub.Exports/Primitives/UriString.cs +++ b/src/GitHub.Exports/Primitives/UriString.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; @@ -21,16 +22,15 @@ namespace GitHub.Primitives /// </remarks> [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly", Justification = "GetObjectData is implemented in the base class")] [Serializable] + [TypeConverter(typeof(UriStringConverter))] public class UriString : StringEquivalent<UriString>, IEquatable<UriString> { - //static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); static readonly Regex sshRegex = new Regex(@"^.+@(?<host>(\[.*?\]|[a-z0-9-.]+?))(:(?<owner>.*?))?(/(?<repo>.*)(\.git)?)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); readonly Uri url; public UriString(string uriString) : base(NormalizePath(uriString)) { - if (uriString == null) throw new ArgumentNullException(nameof(uriString), "Cannot create a null UriString"); - if (uriString.Length == 0) return; + if (uriString == null || uriString.Length == 0) return; if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) { if (!url.IsFile) @@ -43,11 +43,17 @@ public UriString(string uriString) : base(NormalizePath(uriString)) SetFilePath(uriString); } - if (RepositoryName != null) + if (Owner != null && RepositoryName != null) { - NameWithOwner = Owner != null - ? string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Owner, RepositoryName) - : RepositoryName; + NameWithOwner = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", Owner, RepositoryName); + } + else if (Owner != null) + { + NameWithOwner = Owner; + } + else if (RepositoryName != null) + { + NameWithOwner = RepositoryName; } } @@ -65,18 +71,16 @@ public Uri ToUri() void SetUri(Uri uri) { + var ownerSegment = FindSegment(uri.Segments, 0); + var repositorySegment = FindSegment(uri.Segments, 1); + Host = uri.Host; - if (uri.Segments.Any()) - { - RepositoryName = GetRepositoryName(uri.Segments.Last()); - } - - if (uri.Segments.Length > 2) - { - Owner = (uri.Segments[uri.Segments.Length - 2] ?? "").TrimEnd('/').ToNullIfEmpty(); - } - + Owner = ownerSegment; + RepositoryName = GetRepositoryName(repositorySegment); IsHypertextTransferProtocol = uri.IsHypertextTransferProtocol(); + + string FindSegment(string[] segments, int number) + => segments.Skip(number + 1).FirstOrDefault()?.TrimEnd("/"); } void SetFilePath(Uri uri) @@ -129,10 +133,12 @@ bool ParseScpSyntax(string scpString) public bool IsValidUri => url != null; /// <summary> - /// Attempts a best-effort to convert the remote origin to a GitHub Repository URL. + /// Attempts a best-effort to convert the remote origin to a GitHub Repository URL, + /// optionally changing the owner. /// </summary> + /// <param name="owner">The owner to use, if null uses <see cref="Owner"/>.</param> /// <returns>A converted uri, or the existing one if we can't convert it (which might be null)</returns> - public Uri ToRepositoryUrl() + public Uri ToRepositoryUrl(string owner = null) { // we only want to process urls that represent network resources if (!IsScpUri && (!IsValidUri || IsFileUri)) return url; @@ -141,11 +147,15 @@ public Uri ToRepositoryUrl() ? url.Scheme : Uri.UriSchemeHttps; + var nameWithOwner = owner != null && RepositoryName != null ? + string.Format(CultureInfo.InvariantCulture, "{0}/{1}", owner, RepositoryName) : + NameWithOwner; + return new UriBuilder { Scheme = scheme, Host = Host, - Path = NameWithOwner, + Path = nameWithOwner, Port = url?.Port == 80 ? -1 : (url?.Port ?? -1) @@ -204,12 +214,37 @@ public override UriString Combine(string addition) return String.Concat(Value, addition); } + /// <summary> + /// Compare repository URLs ignoring any trailing ".git" or difference in case. + /// </summary> + /// <returns>True if URLs reference the same repository.</returns> + public static bool RepositoryUrlsAreEqual(UriString uri1, UriString uri2) + { + if (!uri1.IsHypertextTransferProtocol || !uri2.IsHypertextTransferProtocol) + { + // Not a repository URL + return false; + } + + // Normalize repository URLs + var str1 = uri1.ToRepositoryUrl().ToString(); + var str2 = uri2.ToRepositoryUrl().ToString(); + return string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase); + } + public override string ToString() { // Makes this look better in the debugger. return Value; } + /// <summary> + /// Makes a copy of the URI with the specified owner. + /// </summary> + /// <param name="owner">The owner.</param> + /// <returns>A new <see cref="UriString"/>.</returns> + public UriString WithOwner(string owner) => ToUriString(ToRepositoryUrl(owner)); + protected UriString(SerializationInfo info, StreamingContext context) : this(GetSerializedValue(info)) { @@ -238,7 +273,7 @@ static string NormalizePath(string path) static string GetRepositoryName(string repositoryNameSegment) { - if (String.IsNullOrEmpty(repositoryNameSegment) + if (String.IsNullOrEmpty(repositoryNameSegment) || repositoryNameSegment.Equals("/", StringComparison.Ordinal)) { return null; @@ -249,7 +284,7 @@ static string GetRepositoryName(string repositoryNameSegment) bool IEquatable<UriString>.Equals(UriString other) { - return other != null && ToString().Equals(other.ToString()); + return other != null && Equals(ToString(), other.ToString()); } } } diff --git a/src/GitHub.Exports/Primitives/UriStringConverter.cs b/src/GitHub.Exports/Primitives/UriStringConverter.cs new file mode 100644 index 0000000000..e48d3fb039 --- /dev/null +++ b/src/GitHub.Exports/Primitives/UriStringConverter.cs @@ -0,0 +1,12 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace GitHub.Primitives +{ + public class UriStringConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string); + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => new UriString((string)value); + } +} diff --git a/src/GitHub.Exports/Properties/AssemblyInfo.cs b/src/GitHub.Exports/Properties/AssemblyInfo.cs index 6669743307..a36a1082dc 100644 --- a/src/GitHub.Exports/Properties/AssemblyInfo.cs +++ b/src/GitHub.Exports/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("GitHub.Exports")] diff --git a/src/GitHub.Exports/Services/Connection.cs b/src/GitHub.Exports/Services/Connection.cs index e71116ac1f..0ab312fb1f 100644 --- a/src/GitHub.Exports/Services/Connection.cs +++ b/src/GitHub.Exports/Services/Connection.cs @@ -1,56 +1,112 @@ using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; using GitHub.Models; using GitHub.Primitives; -using System.Threading.Tasks; -using System.Collections.ObjectModel; -using System.Collections.Specialized; namespace GitHub.Services { + /// <summary> + /// Represents a configured connection to a GitHub account. + /// </summary> public class Connection : IConnection { - readonly IConnectionManager manager; + string username; + Octokit.User user; + bool isLoggedIn; + bool isLoggingIn; + Exception connectionError; - public Connection(IConnectionManager cm, HostAddress hostAddress, string userName) + public Connection(HostAddress hostAddress) { - manager = cm; HostAddress = hostAddress; - Username = userName; - Repositories = new ObservableCollection<ISimpleRepositoryModel>(); + isLoggedIn = false; + isLoggingIn = true; } - public HostAddress HostAddress { get; private set; } - public string Username { get; private set; } - public ObservableCollection<ISimpleRepositoryModel> Repositories { get; } + public Connection( + HostAddress hostAddress, + string username, + Octokit.User user) + { + HostAddress = hostAddress; + this.username = username; + this.user = user; + isLoggedIn = true; + } + + /// <inheritdoc/> + public HostAddress HostAddress { get; } + + /// <inheritdoc/> + public string Username + { + get => username; + private set => RaiseAndSetIfChanged(ref username, value); + } - public IObservable<IConnection> Login() + /// <inheritdoc/> + public Octokit.User User { - return manager.RequestLogin(this); + get => user; + private set => RaiseAndSetIfChanged(ref user, value); } - public void Logout() + /// <inheritdoc/> + public bool IsLoggedIn { - manager.RequestLogout(this); + get => isLoggedIn; + private set => RaiseAndSetIfChanged(ref isLoggedIn, value); } - #region IDisposable Support - private bool disposed = false; // To detect redundant calls + /// <inheritdoc/> + public bool IsLoggingIn + { + get => isLoggingIn; + private set => RaiseAndSetIfChanged(ref isLoggingIn, value); + } - protected virtual void Dispose(bool disposing) + /// <inheritdoc/> + public Exception ConnectionError { - if (!disposed) - { - if (disposing) - Repositories.Clear(); - disposed = true; - } + get => connectionError; + private set => RaiseAndSetIfChanged(ref connectionError, value); } - public void Dispose() + /// <inheritdoc/> + public event PropertyChangedEventHandler PropertyChanged; + + public void SetLoggingIn() { - Dispose(true); - GC.SuppressFinalize(this); + ConnectionError = null; + IsLoggedIn = false; + IsLoggingIn = true; + User = null; + Username = null; + } + + public void SetError(Exception e) + { + ConnectionError = e; + IsLoggingIn = false; + IsLoggedIn = false; + } + + public void SetSuccess(Octokit.User user) + { + User = user; + Username = user.Login; + IsLoggingIn = false; + IsLoggedIn = true; + } + + void RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (!Equals(field, value)) + { + field = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } - #endregion } } diff --git a/src/GitHub.Exports/Services/EnterpriseProbeTask.cs b/src/GitHub.Exports/Services/EnterpriseProbeTask.cs deleted file mode 100644 index 5ec6744042..0000000000 --- a/src/GitHub.Exports/Services/EnterpriseProbeTask.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using GitHub.Extensions; -using GitHub.Models; -using Octokit; -using Octokit.Internal; - -namespace GitHub.Services -{ - [Export(typeof(IEnterpriseProbeTask))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class EnterpriseProbeTask : IEnterpriseProbeTask - { - static readonly Uri endPoint = new Uri("/site/sha", UriKind.Relative); - readonly ProductHeaderValue productHeader; - readonly IHttpClient httpClient; - - [ImportingConstructor] - public EnterpriseProbeTask(IProgram program, IHttpClient httpClient) - { - productHeader = program.ProductHeader; - this.httpClient = httpClient; - } - - public async Task<EnterpriseProbeResult> ProbeAsync(Uri enterpriseBaseUrl) - { - var request = new Request - { - Method = HttpMethod.Get, - BaseAddress = enterpriseBaseUrl, - Endpoint = endPoint, - Timeout = TimeSpan.FromSeconds(3), - }; - request.Headers.Add("User-Agent", productHeader.ToString()); - - var ret = await httpClient - .Send(request, CancellationToken.None) - .Catch(ex => null); - - if (ret == null) - return EnterpriseProbeResult.Failed; - else if (ret.StatusCode == HttpStatusCode.OK) - return EnterpriseProbeResult.Ok; - return EnterpriseProbeResult.NotFound; - } - } - - public enum EnterpriseProbeResult - { - /// <summary> - /// Yep! It's an Enterprise server - /// </summary> - Ok, - - /// <summary> - /// Got a response from a server, but it wasn't an Enterprise server - /// </summary> - NotFound, - - /// <summary> - /// Request timed out or DNS failed. So it's probably the case it's not an enterprise server but - /// we can't know for sure. - /// </summary> - Failed - } -} diff --git a/src/GitHub.Exports/Services/ExportFactoryProvider.cs b/src/GitHub.Exports/Services/ExportFactoryProvider.cs deleted file mode 100644 index 02b62e2377..0000000000 --- a/src/GitHub.Exports/Services/ExportFactoryProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using GitHub.Exports; -using GitHub.UI; -using GitHub.ViewModels; -using GitHub.Models; - -namespace GitHub.Services -{ - [Export(typeof(IExportFactoryProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class ExportFactoryProvider : IExportFactoryProvider - { - [ImportingConstructor] - public ExportFactoryProvider(ICompositionService cc) - { - cc.SatisfyImportsOnce(this); - } - - [Import(AllowRecomposition = true)] - public ExportFactory<IUIController> UIControllerFactory { get; set; } - - [ImportMany(AllowRecomposition = true)] - public IEnumerable<ExportFactory<IViewModel, IViewModelMetadata>> ViewModelFactory { get; set; } - - [ImportMany(AllowRecomposition = true)] - public IEnumerable<ExportFactory<IView, IViewModelMetadata>> ViewFactory { get; set; } - - public ExportLifetimeContext<IViewModel> GetViewModel(UIViewType viewType) - { - Debug.Assert(ViewModelFactory != null, "Attempted to obtain a view model before we imported the ViewModelFactory"); - var f = ViewModelFactory.FirstOrDefault(x => x.Metadata.ViewType == viewType); - Debug.Assert(f != null, string.Format(CultureInfo.InvariantCulture, "Could not locate view model for {0}.", viewType)); - return f.CreateExport(); - } - - public ExportLifetimeContext<IView> GetView(UIViewType viewType) - { - var f = ViewFactory.FirstOrDefault(x => x.Metadata.ViewType == viewType); - Debug.Assert(f != null, string.Format(CultureInfo.InvariantCulture, "Could not locate view for {0}.", viewType)); - return f.CreateExport(); - } - } -} diff --git a/src/GitHub.Exports/Services/GitHubContext.cs b/src/GitHub.Exports/Services/GitHubContext.cs new file mode 100644 index 0000000000..e91677db15 --- /dev/null +++ b/src/GitHub.Exports/Services/GitHubContext.cs @@ -0,0 +1,64 @@ +using GitHub.Exports; +using GitHub.Primitives; + +namespace GitHub.Services +{ + /// <summary> + /// Information used to map betwen a GitHub URL and some other context. + /// </summary> + /// <remarks> + /// This might be used to navigate between a GitHub URL and the location a repository file. Alternatively it + /// might be used to map between the line in a blame view and GitHub URL. + /// </remarks> + public class GitHubContext + { + /// <summary> + /// The owner of a repository. + /// </summary> + public string Owner { get; set; } + /// <summary> + /// The name of a repository. + /// </summary> + public string RepositoryName { get; set; } + /// <summary> + /// The host of a repository ("github.com" or a GitHub Enterprise host). + /// </summary> + public string Host { get; set; } + /// <summary> + /// The name of a branch stored on GitHub (not the local branch name). + /// </summary> + public string BranchName { get; set; } + /// <summary> + /// Like a tree-ish but with ':' changed to '/' (e.g. "master/src" not "master:src"). + /// </summary> + public string TreeishPath { get; set; } + /// <summary> + /// The name of a file on GitHub. + /// </summary> + public string BlobName { get; set; } + /// <summary> + /// A PR number if this context represents a PR. + /// </summary> + public int? PullRequest { get; set; } + /// <summary> + /// An issue number if this context represents an issue. + /// </summary> + public int? Issue { get; set; } + /// <summary> + /// The line number in a file. + /// </summary> + public int? Line { get; set; } + /// <summary> + /// The end line number if this context represents a range. + /// </summary> + public int? LineEnd { get; set; } + /// <summary> + /// The source Url of the context (when context originated from a URL). + /// </summary> + public UriString Url { get; set; } + /// <summary> + /// The type of context if known (blob, blame etc). + /// </summary> + public LinkType LinkType { get; set; } + } +} diff --git a/src/GitHub.Exports/Services/GitService.cs b/src/GitHub.Exports/Services/GitService.cs index d08efb0219..02126f20d7 100644 --- a/src/GitHub.Exports/Services/GitService.cs +++ b/src/GitHub.Exports/Services/GitService.cs @@ -1,9 +1,11 @@ -using System; -using System.ComponentModel.Composition; -using System.Linq; +using System.ComponentModel.Composition; using GitHub.Primitives; using LibGit2Sharp; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; +using System; +using System.Threading.Tasks; +using GitHub.Models; +using System.Linq; +using GitHub.Extensions; namespace GitHub.Services { @@ -12,165 +14,108 @@ namespace GitHub.Services public class GitService : IGitService { /// <summary> - /// Returns the URL of the remote named "origin" for the specified <see cref="repository"/>. If the repository + /// Returns the URL of the remote for the specified <see cref="repository"/>. If the repository /// is null or no remote named origin exists, this method returns null /// </summary> /// <param name="repository">The repository to look at for the remote.</param> - /// <returns>Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found.</returns> - public UriString GetUri(IRepository repository) + /// <param name="remote">The name of the remote to look for</param> + /// <returns>Returns a <see cref="UriString"/> representing the uri of the remote normalized to a GitHub repository url or null if none found.</returns> + public UriString GetUri(IRepository repository, string remote = "origin") { - return UriString.ToUriString(GetOriginUri(repository)?.ToRepositoryUrl()); - } - - /// <summary> - /// Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found. - /// </summary> - /// <param name="repository"></param> - /// <returns>Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found.</returns> - public static UriString GetGitHubUri(IRepository repository) - { - return GitServiceHelper.GetUri(repository); + return UriString.ToUriString(GetRemoteUri(repository, remote)?.ToRepositoryUrl()); } /// <summary> /// Probes for a git repository and if one is found, returns a normalized GitHub uri <see cref="UriString"/> - /// for the repository's remote named "origin" if one is found + /// for the repository's remote if one is found /// </summary> /// <remarks> /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then /// walks up the parent directories until it either finds a repository, or reaches the root disk. /// </remarks> /// <param name="path">The path to start probing</param> - /// <returns>Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found.</returns> - public UriString GetUri(string path) + /// <param name="remote">The name of the remote to look for</param> + /// <returns>Returns a <see cref="UriString"/> representing the uri of the remote normalized to a GitHub repository url or null if none found.</returns> + public UriString GetUri(string path, string remote = "origin") { - return GetUri(GetRepo(path)); + using (var repo = GetRepository(path)) + { + return GetUri(repo, remote); + } } /// <summary> - /// Probes for a git repository and if one is found, returns a normalized GitHub uri <see cref="UriString"/> - /// for the repository's remote named "origin" if one is found + /// Probes for a git repository and if one is found, returns a <see cref="IRepositoryModel"/> instance for the + /// repository. /// </summary> /// <remarks> /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then /// walks up the parent directories until it either finds a repository, or reaches the root disk. /// </remarks> /// <param name="path">The path to start probing</param> - /// <returns>Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found.</returns> - public static UriString GetUriFromPath(string path) + /// <returns>An instance of <see cref="IRepositoryModel"/> or null</returns> + public IRepository GetRepository(string path) { - return GitServiceHelper.GetUri(path); + var repoPath = Repository.Discover(path); + return repoPath == null ? null : new Repository(repoPath); } /// <summary> - /// Probes for a git repository and if one is found, returns a normalized GitHub uri - /// <see cref="UriString"/> for the repository's remote named "origin" if one is found + /// Returns a <see cref="UriString"/> representing the uri of a remote /// </summary> - /// <remarks> - /// The lookup checks to see if the path specified by the RepositoryPath property of the specified - /// <see cref="repoInfo"/> is a repository. If it's not, it then walks up the parent directories until it - /// either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="repoInfo">The repository information containing the path to start probing</param> - /// <returns>Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found.</returns> - public UriString GetUri(IGitRepositoryInfo repoInfo) + /// <param name="repo"></param> + /// <param name="remote">The name of the remote to look for</param> + /// <returns></returns> + public UriString GetRemoteUri(IRepository repo, string remote = "origin") { - return GetUri(GetRepo(repoInfo)); + return repo + ?.Network + .Remotes[remote] + ?.Url; } /// <summary> - /// Probes for a git repository and if one is found, returns a normalized GitHub uri - /// <see cref="UriString"/> for the repository's remote named "origin" if one is found + /// Get a new instance of <see cref="GitService"/>. /// </summary> /// <remarks> - /// The lookup checks to see if the path specified by the RepositoryPath property of the specified - /// <see cref="repoInfo"/> is a repository. If it's not, it then walks up the parent directories until it - /// either finds a repository, or reaches the root disk. + /// This is equivalent to creating it via MEF with <see cref="CreationPolicy.NonShared"/> /// </remarks> - /// <param name="repoInfo">The repository information containing the path to start probing</param> - /// <returns>Returns a <see cref="UriString"/> representing the uri of the "origin" remote normalized to a GitHub repository url or null if none found.</returns> - public static UriString GetUriFromVSGit(IGitRepositoryInfo repoInfo) - { - return GitServiceHelper.GetUri(repoInfo); - } + public static IGitService GitServiceHelper => new GitService(); /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="IRepository"/> instance for the - /// repository. + /// Finds the latest pushed commit of a file and returns the sha of that commit. Returns null when no commits have + /// been found in any remote branches or the current local branch. /// </summary> - /// <remarks> - /// The lookup checks to see if the path specified by the RepositoryPath property of the specified - /// <see cref="repoInfo"/> is a repository. If it's not, it then walks up the parent directories until it - /// either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="repoInfo">The repository information containing the path to start probing</param> - /// <returns>An instance of <see cref="IRepository"/> or null</returns> - - public IRepository GetRepo(IGitRepositoryInfo repoInfo) + /// <param name="path">The local path of a repository or a file inside a repository. This cannot be null.</param> + /// <returns></returns> + public Task<string> GetLatestPushedSha(string path) { - return GetRepo(repoInfo?.RepositoryPath); - } + Guard.ArgumentNotNull(path, nameof(path)); - /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="IRepository"/> instance for the - /// repository. - /// </summary> - /// <remarks> - /// The lookup checks to see if the path specified by the RepositoryPath property of the specified - /// <see cref="repoInfo"/> is a repository. If it's not, it then walks up the parent directories until it - /// either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="repoInfo">The repository information containing the path to start probing</param> - /// <returns>An instance of <see cref="IRepository"/> or null</returns> - public static IRepository GetRepoFromVSGit(IGitRepositoryInfo repoInfo) - { - return GitServiceHelper.GetRepo(repoInfo); - } + return Task.Factory.StartNew(() => + { + using (var repo = GetRepository(path)) + { + if (repo != null) + { + if (repo.Head.IsTracking && repo.Head.Tip.Sha == repo.Head.TrackedBranch.Tip.Sha) + { + return repo.Head.Tip.Sha; + } - /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="IRepository"/> instance for the - /// repository. - /// </summary> - /// <remarks> - /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then - /// walks up the parent directories until it either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="path">The path to start probing</param> - /// <returns>An instance of <see cref="IRepository"/> or null</returns> - public IRepository GetRepo(string path) - { - var repoPath = Repository.Discover(path); - return repoPath == null ? null : new Repository(repoPath); - } - - /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="IRepository"/> instance for the - /// repository. - /// </summary> - /// <remarks> - /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then - /// walks up the parent directories until it either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="path">The path to start probing</param> - /// <returns>An instance of <see cref="IRepository"/> or null</returns> - public static IRepository GetRepoFromPath(string path) - { - return GitServiceHelper.GetRepo(path); - } + var remoteHeads = repo.Refs.Where(r => r.IsRemoteTrackingBranch).ToList(); + foreach (var c in repo.Commits) + { + if (repo.Refs.ReachableFrom(remoteHeads, new[] { c }).Any()) + { + return c.Sha; + } + } + } - /// <summary> - /// Returns a <see cref="UriString"/> representing the uri of the "origin" remote with no modifications. - /// </summary> - /// <param name="repo"></param> - /// <returns></returns> - public static UriString GetOriginUri(IRepository repo) - { - return repo - ?.Network - .Remotes["origin"] - ?.Url; + return null; + } + }); } - - public static IGitService GitServiceHelper => VisualStudio.Services.DefaultExportProvider.GetExportedValueOrDefault<IGitService>() ?? new GitService(); } } diff --git a/src/GitHub.Exports/Services/IActiveDocumentSnapshot.cs b/src/GitHub.Exports/Services/IActiveDocumentSnapshot.cs new file mode 100644 index 0000000000..60fcdca44b --- /dev/null +++ b/src/GitHub.Exports/Services/IActiveDocumentSnapshot.cs @@ -0,0 +1,9 @@ +namespace GitHub.VisualStudio +{ + public interface IActiveDocumentSnapshot + { + string Name { get; } + int StartLine { get; } + int EndLine { get; } + } +} diff --git a/src/GitHub.Exports/Services/IConnectionCache.cs b/src/GitHub.Exports/Services/IConnectionCache.cs new file mode 100644 index 0000000000..e5e262763a --- /dev/null +++ b/src/GitHub.Exports/Services/IConnectionCache.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Loads the configured connections from a cache. + /// </summary> + public interface IConnectionCache + { + /// <summary> + /// Loads the configured connections. + /// </summary> + /// <returns>A task returning the collection of configured collections.</returns> + Task<IEnumerable<ConnectionDetails>> Load(); + + /// <summary> + /// Saves the configured connections. + /// </summary> + /// <param name="connections">The collection of configured collections to save.</param> + /// <returns>A task tracking the operation.</returns> + Task Save(IEnumerable<ConnectionDetails> connections); + } +} diff --git a/src/GitHub.Exports/Services/IConnectionManager.cs b/src/GitHub.Exports/Services/IConnectionManager.cs new file mode 100644 index 0000000000..fbe09de045 --- /dev/null +++ b/src/GitHub.Exports/Services/IConnectionManager.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; + +namespace GitHub.Services +{ + /// <summary> + /// Manages the configured <see cref="IConnection"/>s to GitHub instances. + /// </summary> + public interface IConnectionManager + { + /// <summary> + /// Gets a collection containing the current connections. + /// </summary> + /// <remarks> + /// This collection is lazily initialized: the first time the <see cref="Connections"/> + /// property is accessed, an async load of the configured connections is started. If you + /// want to ensure that all connections have been loaded, call + /// <see cref="GetLoadedConnections"/>. + /// </remarks> + IReadOnlyObservableCollection<IConnection> Connections { get; } + + /// <summary> + /// Gets a connection with the specified host address. + /// </summary> + /// <param name="address">The address.</param> + /// <returns> + /// A task returning the requested connection, or null if the connection was not found. + /// </returns> + Task<IConnection> GetConnection(HostAddress address); + + /// <summary> + /// Gets the <see cref="Connections"/> collection, after ensuring that it is fully loaded. + /// </summary> + /// <returns> + /// A task returning the fully loaded connections collection. + /// </returns> + Task<IReadOnlyObservableCollection<IConnection>> GetLoadedConnections(); + + /// <summary> + /// Attempts to login to a GitHub instance. + /// </summary> + /// <param name="address">The instance address.</param> + /// <param name="username">The username.</param> + /// <param name="password">The password.</param> + /// <returns> + /// A connection if the login succeded. If the login fails, throws an exception. An + /// exception is also thrown if an existing connection with the same host address already + /// exists. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// A connection to the host already exists. + /// </exception> + Task<IConnection> LogIn(HostAddress address, string username, string password); + + /// <summary> + /// Attempts to log into a GitHub server via OAuth in the browser. + /// </summary> + /// <param name="address">The instance address.</param> + /// <param name="cancel">A cancellation token used to cancel the operation.</param> + /// <returns> + /// A connection if the login succeded. If the login fails, throws an exception. An + /// exception is also thrown if an existing connection with the same host address already + /// exists. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// A connection to the host already exists. + /// </exception> + Task<IConnection> LogInViaOAuth(HostAddress address, CancellationToken cancel); + + /// <summary> + /// Attempts to login to a GitHub instance with a token. + /// </summary> + /// <param name="address">The instance address.</param> + /// <param name="token">The token.</param> + /// <returns> + /// A connection if the login succeded. If the login fails, throws an exception. An + /// exception is also thrown if an existing connection with the same host address already + /// exists. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// A connection to the host already exists. + /// </exception> + Task<IConnection> LogInWithToken(HostAddress address, string token); + + /// <summary> + /// Logs out of a GitHub instance. + /// </summary> + /// <param name="address"></param> + /// <returns>A task tracking the operation.</returns> + Task LogOut(HostAddress address); + + /// <summary> + /// Retries logging in to a failed connection. + /// </summary> + /// <param name="connection">The connection.</param> + /// <returns>The resulting connection.</returns> + Task Retry(IConnection connection); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IDialogService.cs b/src/GitHub.Exports/Services/IDialogService.cs new file mode 100644 index 0000000000..083cbedead --- /dev/null +++ b/src/GitHub.Exports/Services/IDialogService.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Services for showing dialogs. + /// </summary> + public interface IDialogService + { + /// <summary> + /// Shows the Clone dialog. + /// </summary> + /// <param name="connection"> + /// The connection to use. If null, the first connection will be used, or the user promted + /// to log in if there are no connections. + /// </param> + /// <returns> + /// A task that returns an instance of <see cref="CloneDialogResult"/> on success, + /// or null if the dialog was cancelled. + /// </returns> + Task<CloneDialogResult> ShowCloneDialog(IConnection connection); + + /// <summary> + /// Shows the re-clone dialog. + /// </summary> + /// <param name="repository">The repository to clone.</param> + /// <returns> + /// A task that returns the base path for the clone on success, or null if the dialog was + /// cancelled. + /// </returns> + /// <remarks> + /// 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. + /// </remarks> + Task<string> ShowReCloneDialog(IRepositoryModel repository); + + /// <summary> + /// Shows the Create Gist dialog. + /// </summary> + /// <param name="connection"> + /// The connection to use. If null, the first connection will be used, or the user promted + /// to log in if there are no connections. + /// </param> + Task ShowCreateGist(IConnection connection); + + /// <summary> + /// Shows the Create Repository dialog. + /// </summary> + /// <param name="connection"> + /// The connection to use. May not be null. + /// </param> + Task ShowCreateRepositoryDialog(IConnection connection); + + /// <summary> + /// Shows the Login dialog. + /// </summary> + /// <returns> + /// The <see cref="IConnection"/> created by the login, or null if the login was + /// unsuccessful. + /// </returns> + Task<IConnection> ShowLoginDialog(); + + /// <summary> + /// Shows the Fork Repository dialog. + /// </summary> + /// <param name="repository">The repository to fork.</param> + /// <param name="connection">The connection to use. May not be null.</param> + Task ShowForkDialog(ILocalRepositoryModel repository, IConnection connection); + } +} diff --git a/src/GitHub.Exports/Services/IEnterpriseCapabilitiesService.cs b/src/GitHub.Exports/Services/IEnterpriseCapabilitiesService.cs new file mode 100644 index 0000000000..875916c722 --- /dev/null +++ b/src/GitHub.Exports/Services/IEnterpriseCapabilitiesService.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Octokit; + +namespace GitHub.Services +{ + /// <summary> + /// Describes the login methods supported by an enterprise instance. + /// </summary> + [Flags] + public enum EnterpriseLoginMethods + { + Token = 0x01, + UsernameAndPassword = 0x02, + OAuth = 0x04, + } + + /// <summary> + /// Services for checking the capabilities of enterprise installations. + /// </summary> + public interface IEnterpriseCapabilitiesService + { + /// <summary> + /// Makes a request to the specified URL and returns whether or not the probe could definitively determine that a GitHub + /// Enterprise Instance exists at the specified URL. + /// </summary> + /// <remarks> + /// The probe checks the absolute path /site/sha at the specified <paramref name="enterpriseBaseUrl" />. + /// </remarks> + /// <param name="enterpriseBaseUrl">The URL to test</param> + /// <returns>An <see cref="EnterpriseProbeResult" /> with either <see cref="EnterpriseProbeResult.Ok"/>, + /// <see cref="EnterpriseProbeResult.NotFound"/>, or <see cref="EnterpriseProbeResult.Failed"/> in the case the request failed</returns> + Task<EnterpriseProbeResult> Probe(Uri enterpriseBaseUrl); + + /// <summary> + /// Checks the login methods supported by an enterprise instance. + /// </summary> + /// <param name="enterpriseBaseUrl">The URL to test.</param> + /// <returns>The supported login methods.</returns> + Task<EnterpriseLoginMethods> ProbeLoginMethods(Uri enterpriseBaseUrl); + } +} diff --git a/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs b/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs deleted file mode 100644 index 2255a91a8d..0000000000 --- a/src/GitHub.Exports/Services/IEnterpriseProbeTask.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace GitHub.Services -{ - public interface IEnterpriseProbeTask - { - Task<EnterpriseProbeResult> ProbeAsync(Uri enterpriseBaseUrl); - } -} diff --git a/src/GitHub.Exports/Services/IGitHubContextService.cs b/src/GitHub.Exports/Services/IGitHubContextService.cs new file mode 100644 index 0000000000..8d8906233c --- /dev/null +++ b/src/GitHub.Exports/Services/IGitHubContextService.cs @@ -0,0 +1,110 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.Services +{ + /// <summary> + /// Methods for constructing a <see cref="GitHubContext"/> and navigating based on a <see cref="GitHubContext"/>. + /// </summary> + public interface IGitHubContextService + { + /// <summary> + /// Find the context from a URL in the clipboard if any. + /// </summary> + /// <returns>The context or null if clipboard doesn't contain a GitHub URL</returns> + GitHubContext FindContextFromClipboard(); + + /// <summary> + /// Find the context from the title of the topmost browser. + /// </summary> + /// <returns>A context or null if a context can't be found.</returns> + GitHubContext FindContextFromBrowser(); + + /// <summary> + /// Convert a GitHub URL to a context object. + /// </summary> + /// <param name="url">A GitHub URL</param> + /// <returns>The context from the URL or null</returns> + GitHubContext FindContextFromUrl(string url); + + /// <summary> + /// Convert a context to a repository URL. + /// </summary> + /// <param name="windowTitle">A browser window title.</param> + /// <returns>A repository URL</returns> + GitHubContext FindContextFromWindowTitle(string windowTitle); + + /// <summary> + /// Find a context from a browser window title. + /// </summary> + /// <param name="context"></param> + /// <returns>The context or null if none can be found</returns> + Uri ToRepositoryUrl(GitHubContext context); + + /// <summary> + /// Open a file in the working directory that corresponds to a context and navigate to a line/range. + /// </summary> + /// <param name="repositoryDir">The working directory.</param> + /// <param name="context">A context to navigate to.</param> + /// <returns>True if navigation was successful</returns> + bool TryOpenFile(string repositoryDir, GitHubContext context); + + /// <summary> + /// Attempt to open the Blame/Annotate view for a context. + /// </summary> + /// <remarks> + /// The access to the Blame/Annotate view was added in a version of Visual Studio 2017. This method will return + /// false is this functionality isn't available. + /// </remarks> + /// <param name="repositoryDir">The target repository</param> + /// <param name="currentBranch">A branch in the local repository. It isn't displayed on the UI but must exist. It can be a remote or local branch.</param> + /// <param name="context">The context to open.</param> + /// <returns>True if AnnotateFile functionality is available.</returns> + Task<bool> TryAnnotateFile(string repositoryDir, string currentBranch, GitHubContext context); + + /// <summary> + /// Map from a context to a repository blob object. + /// </summary> + /// <param name="repositoryDir">The target repository.</param> + /// <param name="context">The context to map from.</param> + /// <param name="remoteName">The name of the remote to search for branches.</param> + /// <returns>The resolved commit-ish, blob path and commit SHA for the blob. Path will be null if the commit-ish can be resolved but not the blob.</returns> + (string commitish, string path, string commitSha) ResolveBlob(string repositoryDir, GitHubContext context, string remoteName = "origin"); + + /// <summary> + /// Find the object-ish (first 8 chars of a blob SHA) from the path to historical blob created by Team Explorer. + /// </summary> + /// <remarks> + /// Team Explorer creates temporary blob files in the following format: + /// C:\Users\me\AppData\Local\Temp\TFSTemp\vctmp21996_181282.IOpenFromClipboardCommand.783ac965.cs + /// The object-ish appears immediately before the file extension and the path contains the folder "TFSTemp". + /// </remarks> + /// <param name="tempFile">The path to a possible Team Explorer temporary blob file.</param> + /// <returns>The target file's object-ish (blob SHA fragment) or null if the path isn't recognized as a Team Explorer blob file.</returns> + string FindObjectishForTFSTempFile(string tempFile); + + /// <summary> + /// Find a tree entry in the commit log where a blob appears and return its commit SHA and path. + /// </summary> + /// <remarks> + /// Search back through the commit log for the first tree entry where a blob appears. This operation only takes + /// a fraction of a seond on the `github/VisualStudio` repository even if a tree entry casn't be found. + /// </remarks> + /// <param name="repositoryDir">The target repository directory.</param> + /// <param name="objectish">The fragment of a blob SHA to find.</param> + /// <returns>The commit SHA and blob path or null if the blob can't be found.</returns> + (string commitSha, string blobPath) ResolveBlobFromHistory(string repositoryDir, string objectish); + + /// <summary> + /// Check if a file in the working directory has changed since a specified commit-ish. + /// </summary> + /// <remarks> + /// The commit-ish might be a commit SHA, a tag or a remote branch. + /// </remarks> + /// <param name="repositoryDir">The target repository.</param> + /// <param name="commitish">A commit SHA, remote branch or tag.</param> + /// <param name="path">The path for a blob.</param> + /// <returns>True if the working file is different.</returns> + bool HasChangesInWorkingDirectory(string repositoryDir, string commitish, string path); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IGitHubServiceProvider.cs b/src/GitHub.Exports/Services/IGitHubServiceProvider.cs new file mode 100644 index 0000000000..c1d142ed7f --- /dev/null +++ b/src/GitHub.Exports/Services/IGitHubServiceProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.Composition.Hosting; +using System.Runtime.InteropServices; +using GitHub.VisualStudio; + +namespace GitHub.Services +{ + [Guid(Guids.GitHubServiceProviderId)] + public interface IGitHubServiceProvider : IServiceProvider + { + ExportProvider ExportProvider { get; } + IServiceProvider GitServiceProvider { get; set; } + + T GetService<T>() where T : class; + Ret GetService<T, Ret>() where T : class + where Ret : class; + + object TryGetService(Type t); + object TryGetService(string typename); + T TryGetService<T>() where T : class; + + void AddService(Type t, object owner, object instance); + void AddService<T>(object owner, T instance) where T : class; + /// <summary> + /// Removes a service from the catalog + /// </summary> + /// <param name="t">The type we want to remove</param> + /// <param name="owner">The owner, which either has to match what was passed to AddService, + /// or if it's null, the service will be removed without checking for ownership</param> + void RemoveService(Type t, object owner); + } +} diff --git a/src/GitHub.Exports/Services/IGitService.cs b/src/GitHub.Exports/Services/IGitService.cs index dea51dbd41..b47987bb5b 100644 --- a/src/GitHub.Exports/Services/IGitService.cs +++ b/src/GitHub.Exports/Services/IGitService.cs @@ -1,68 +1,60 @@ +using System.Threading.Tasks; +using GitHub.Models; using GitHub.Primitives; using LibGit2Sharp; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; namespace GitHub.Services { public interface IGitService { /// <summary> - /// Returns the URL of the remote named "origin" for the specified <see cref="repository"/>. If the repository - /// is null or no remote named origin exists, this method returns null + /// Returns the URL of the remote for the specified <see cref="repository"/>. If the repository + /// is null or no remote exists, this method returns null /// </summary> /// <param name="repository">The repository to look at for the remote.</param> + /// <param name="remote">The remote name to look for</param> /// <returns>A <see cref="UriString"/> representing the origin or null if none found.</returns> - UriString GetUri(IRepository repository); + UriString GetUri(IRepository repository, string remote = "origin"); /// <summary> /// Probes for a git repository and if one is found, returns a <see cref="UriString"/> for the repository's - /// remote named "origin" if one is found + /// remote if one is found /// </summary> /// <remarks> /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then /// walks up the parent directories until it either finds a repository, or reaches the root disk. /// </remarks> /// <param name="path">The path to start probing</param> + /// <param name="remote">The remote name to look for</param> /// <returns>A <see cref="UriString"/> representing the origin or null if none found.</returns> - UriString GetUri(string path); + UriString GetUri(string path, string remote = "origin"); /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="UriString"/> for the repository's - /// remote named "origin" if one is found + /// Probes for a git repository and if one is found, returns a <see cref="IRepositoryModel"/> instance for the + /// repository. /// </summary> /// <remarks> - /// The lookup checks to see if the path specified by the RepositoryPath property of the specified - /// <see cref="repoInfo"/> is a repository. If it's not, it then walks up the parent directories until it - /// either finds a repository, or reaches the root disk. + /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then + /// walks up the parent directories until it either finds a repository, or reaches the root disk. /// </remarks> - /// <param name="repoInfo">The repository information containing the path to start probing</param> - /// <returns>A <see cref="UriString"/> representing the origin or null if none found.</returns> - UriString GetUri(IGitRepositoryInfo repoInfo); + /// <param name="path">The path to start probing</param> + /// <returns>An instance of <see cref="IRepositoryModel"/> or null</returns> + IRepository GetRepository(string path); /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="IRepository"/> instance for the - /// repository. + /// Returns a <see cref="UriString"/> representing the uri of a remote. /// </summary> - /// <remarks> - /// The lookup checks to see if the path specified by the RepositoryPath property of the specified - /// <see cref="repoInfo"/> is a repository. If it's not, it then walks up the parent directories until it - /// either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="repoInfo">The repository information containing the path to start probing</param> - /// <returns>An instance of <see cref="IRepository"/> or null</returns> - - IRepository GetRepo(IGitRepositoryInfo repoInfo); + /// <param name="repo">The repository to look at for the remote.</param> + /// <param name="remote">The remote name to look for</param> + /// <returns></returns> + UriString GetRemoteUri(IRepository repo, string remote = "origin"); /// <summary> - /// Probes for a git repository and if one is found, returns a <see cref="IRepository"/> instance for the - /// repository. + /// Finds the latest pushed commit of a file and returns the sha of that commit. Returns null when no commits have + /// been found in any remote branches or the current local branch. /// </summary> - /// <remarks> - /// The lookup checks to see if the specified <paramref name="path"/> is a repository. If it's not, it then - /// walks up the parent directories until it either finds a repository, or reaches the root disk. - /// </remarks> - /// <param name="path">The path to start probing</param> - /// <returns>An instance of <see cref="IRepository"/> or null</returns> - IRepository GetRepo(string path); + /// <param name="path">The local path of a repository or a file inside a repository. This cannot be null.</param> + /// <returns></returns> + Task<string> GetLatestPushedSha(string path); } } \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IGlobalConnection.cs b/src/GitHub.Exports/Services/IGlobalConnection.cs new file mode 100644 index 0000000000..22bf92689b --- /dev/null +++ b/src/GitHub.Exports/Services/IGlobalConnection.cs @@ -0,0 +1,22 @@ +using System; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Returns the current globally registered <see cref="IConnection"/>. + /// </summary> + /// <remarks> + /// Many view models require a connection with which to work. The UIController registers the + /// connection for the current flow with <see cref="IGitHubServiceProvider"/> in its Start() + /// method for this purpose. View models wishing to retreive this value should import this + /// interface and call <see cref="Get"/>. + /// </remarks> + public interface IGlobalConnection + { + /// <summary> + /// Gets the globally registered <see cref="IConnection"/>. + /// </summary> + IConnection Get(); + } +} diff --git a/src/GitHub.Exports/Services/ILocalRepositories.cs b/src/GitHub.Exports/Services/ILocalRepositories.cs new file mode 100644 index 0000000000..1d77fbd13c --- /dev/null +++ b/src/GitHub.Exports/Services/ILocalRepositories.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Stores a collection of known local repositories. + /// </summary> + public interface ILocalRepositories + { + /// <summary> + /// Gets the currently known local repositories. + /// </summary> + IReadOnlyObservableCollection<ILocalRepositoryModel> Repositories { get; } + + /// <summary> + /// Updates <see cref="Repositories"/>. + /// </summary> + Task Refresh(); + } +} diff --git a/src/GitHub.Exports/Services/IMetricsService.cs b/src/GitHub.Exports/Services/IMetricsService.cs new file mode 100644 index 0000000000..67700da8a4 --- /dev/null +++ b/src/GitHub.Exports/Services/IMetricsService.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.Services +{ + public interface IMetricsService + { + /// <summary> + /// Posts the provided usage model. + /// </summary> + Task PostUsage(UsageModel model); + + /// <summary> + /// Sends an empty request that indicates that the user has chosen to opt out of usage + /// tracking. + /// </summary> + Task SendOptOut(); + + /// <summary> + /// Sends an empty request that indicates that the user has chosen to opt back in to + /// usage tracking. + /// </summary> + Task SendOptIn(); + } +} diff --git a/src/GitHub.Exports/Services/INotificationDispatcher.cs b/src/GitHub.Exports/Services/INotificationDispatcher.cs new file mode 100644 index 0000000000..667219d8ea --- /dev/null +++ b/src/GitHub.Exports/Services/INotificationDispatcher.cs @@ -0,0 +1,38 @@ +using System; +using System.Windows.Input; + +namespace GitHub.Services +{ + public struct Notification + { + public enum NotificationType + { + Message, + MessageCommand, + Warning, + Error + } + public string Message { get; } + public NotificationType Type { get; } + public ICommand Command { get; } + + public Notification(string message, NotificationType type, ICommand command = null) + { + Message = message; + Type = type; + Command = command; + } + } + + /// <summary> + /// Dispatches notifications sent to the <see cref="INotificationService"/> + /// to registered listeners. + /// </summary> + public interface INotificationDispatcher : INotificationService + { + IObservable<Notification> Listen(); + void AddListener(INotificationService notificationHandler); + void RemoveListener(); + void RemoveListener(INotificationService notificationHandler); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/INotificationService.cs b/src/GitHub.Exports/Services/INotificationService.cs new file mode 100644 index 0000000000..4e406a0ea3 --- /dev/null +++ b/src/GitHub.Exports/Services/INotificationService.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Input; + +namespace GitHub.Services +{ + /// <summary> + /// Service to broadcast messages, warnings and errors. Listeners + /// can receive them by registering with <see cref="INotificationDispatcher"/> + /// </summary> + public interface INotificationService + { + void ShowMessage(string message); + void ShowMessage(string message, ICommand command, bool showToolTips = true, Guid guid = default(Guid)); + void ShowWarning(string message); + void ShowError(string message); + void HideNotification(Guid guid); + bool IsNotificationVisible(Guid guid); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IRepositoryService.cs b/src/GitHub.Exports/Services/IRepositoryService.cs new file mode 100644 index 0000000000..c71f492e29 --- /dev/null +++ b/src/GitHub.Exports/Services/IRepositoryService.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks; +using GitHub.Primitives; + +namespace GitHub.Services +{ + public interface IRepositoryService + { + /// <summary> + /// Finds the parent repository of a fork, if any. + /// </summary> + /// <param name="address">The host address.</param> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <returns> + /// A tuple of the parent repository's owner and name if the repository is a fork, + /// otherwise null. + /// </returns> + Task<(string owner, string name)?> FindParent(HostAddress address, string owner, string name); + } +} diff --git a/src/GitHub.Exports/Services/ISelectedTextProvider.cs b/src/GitHub.Exports/Services/ISelectedTextProvider.cs new file mode 100644 index 0000000000..7420e4c6f8 --- /dev/null +++ b/src/GitHub.Exports/Services/ISelectedTextProvider.cs @@ -0,0 +1,16 @@ +using System; + +namespace GitHub.Services +{ + /// <summary> + /// Provides a way to get any currently selected text in the text editor area. + /// </summary> + public interface ISelectedTextProvider + { + /// <summary> + /// Gets the currently selected text. + /// </summary> + /// <returns>The selected text in the active editor, or an empty string if no text is selected.</returns> + string GetSelectedText(); + } +} diff --git a/src/GitHub.Exports/Services/IStatusBarNotificationService.cs b/src/GitHub.Exports/Services/IStatusBarNotificationService.cs new file mode 100644 index 0000000000..0f5ba6c01d --- /dev/null +++ b/src/GitHub.Exports/Services/IStatusBarNotificationService.cs @@ -0,0 +1,8 @@ +namespace GitHub.Services +{ + // for mef purposes + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces")] + public interface IStatusBarNotificationService : INotificationService + { + } +} diff --git a/src/GitHub.Exports/Services/ITeamExplorerContext.cs b/src/GitHub.Exports/Services/ITeamExplorerContext.cs new file mode 100644 index 0000000000..d90c64e867 --- /dev/null +++ b/src/GitHub.Exports/Services/ITeamExplorerContext.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Responsible for watching the active repository in Team Explorer. + /// </summary> + /// <remarks> + /// A <see cref="PropertyChanged"/> event is fired when moving to a new repository. + /// A <see cref="StatusChanged"/> event is fired when the current branch, head SHA or tracked SHA changes. + /// </remarks> + public interface ITeamExplorerContext : INotifyPropertyChanged + { + /// <summary> + /// The active Git repository in Team Explorer. + /// This will be null if no repository is active. + /// </summary> + /// <remarks> + /// This property might be changed by a non-UI thread. + /// </remarks> + ILocalRepositoryModel ActiveRepository { get; } + + /// <summary> + /// Fired when the CurrentBranch or HeadSha changes. + /// </summary> + /// <remarks> + /// This event might fire on a non-UI thread. + /// </remarks> + event EventHandler StatusChanged; + } +} diff --git a/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs b/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs index eab5345aef..dd6ca75bd1 100644 --- a/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs +++ b/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs @@ -1,6 +1,5 @@ using System; using GitHub.Primitives; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; using GitHub.Models; namespace GitHub.Services @@ -29,13 +28,13 @@ public interface ITeamExplorerServiceHolder /// <summary> /// A IGitRepositoryInfo representing the currently active repository /// </summary> - ISimpleRepositoryModel ActiveRepo { get; } + ILocalRepositoryModel ActiveRepo { get; } /// <summary> /// Subscribe to be notified when the active repository is set and Notify is called. /// </summary> /// <param name="who">The instance that is interested in being called (or a unique key/object for that instance)</param> /// <param name="handler">The handler to call when ActiveRepo is set</param> - void Subscribe(object who, Action<ISimpleRepositoryModel> handler); + void Subscribe(object who, Action<ILocalRepositoryModel> handler); /// <summary> /// Unsubscribe from notifications /// </summary> @@ -43,16 +42,11 @@ public interface ITeamExplorerServiceHolder void Unsubscribe(object who); IGitAwareItem HomeSection { get; } - - /// <summary> - /// Refresh the information on the active repo (in case of remote url changes or other such things) - /// </summary> - void Refresh(); } public interface IGitAwareItem { - ISimpleRepositoryModel ActiveRepo { get; } + ILocalRepositoryModel ActiveRepo { get; } /// <summary> /// Represents the web URL of the repository on GitHub.com, even if the origin is an SSH address. diff --git a/src/GitHub.Exports/Services/ITeamExplorerServices.cs b/src/GitHub.Exports/Services/ITeamExplorerServices.cs new file mode 100644 index 0000000000..b92011a4f8 --- /dev/null +++ b/src/GitHub.Exports/Services/ITeamExplorerServices.cs @@ -0,0 +1,11 @@ +using System.Windows.Input; + +namespace GitHub.Services +{ + public interface ITeamExplorerServices : INotificationService + { + void ShowConnectPage(); + void ShowPublishSection(); + void ClearNotifications(); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IUIProvider.cs b/src/GitHub.Exports/Services/IUIProvider.cs deleted file mode 100644 index 532e58448e..0000000000 --- a/src/GitHub.Exports/Services/IUIProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.ComponentModel.Composition.Hosting; -using GitHub.Models; -using GitHub.UI; -using System.Windows.Controls; - -namespace GitHub.Services -{ - public interface IUIProvider : IServiceProvider - { - ExportProvider ExportProvider { get; } - IServiceProvider GitServiceProvider { get; set; } - T GetService<T>(); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] - Ret GetService<T, Ret>() where Ret : class; - - object TryGetService(Type t); - T TryGetService<T>() where T : class; - - void AddService(Type t, object instance); - void RemoveService(Type t); - - IObservable<UserControl> SetupUI(UIControllerFlow controllerFlow, IConnection connection); - void RunUI(); - void RunUI(UIControllerFlow controllerFlow, IConnection connection); - } -} diff --git a/src/GitHub.Exports/Services/IUsageService.cs b/src/GitHub.Exports/Services/IUsageService.cs new file mode 100644 index 0000000000..54fc9729a8 --- /dev/null +++ b/src/GitHub.Exports/Services/IUsageService.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Provides services for <see cref="IUsageTracker"/>. + /// </summary> + public interface IUsageService + { + /// <summary> + /// Gets a GUID that anonymously represents the user. + /// </summary> + Task<Guid> GetUserGuid(); + + /// <summary> + /// Starts a timer. + /// </summary> + /// <param name="callback">The callback to call when the timer ticks.</param> + /// <param name="dueTime">The timespan after which the callback will be called the first time.</param> + /// <param name="period">The timespan after which the callback will be called subsequent times.</param> + /// <returns>A disposable used to cancel the timer.</returns> + IDisposable StartTimer(Func<Task> callback, TimeSpan dueTime, TimeSpan period); + + /// <summary> + /// Reads the local usage data from disk. + /// </summary> + /// <returns>A task returning a <see cref="UsageData"/> object.</returns> + Task<UsageData> ReadLocalData(); + + /// <summary> + /// Writes the local usage data to disk. + /// </summary> + Task WriteLocalData(UsageData data); + } +} diff --git a/src/GitHub.Exports/Services/IUsageTracker.cs b/src/GitHub.Exports/Services/IUsageTracker.cs new file mode 100644 index 0000000000..98e8416d8d --- /dev/null +++ b/src/GitHub.Exports/Services/IUsageTracker.cs @@ -0,0 +1,15 @@ +using GitHub.VisualStudio; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System; +using System.Linq.Expressions; +using GitHub.Models; + +namespace GitHub.Services +{ + [Guid(Guids.UsageTrackerId)] + public interface IUsageTracker + { + Task IncrementCounter(Expression<Func<UsageModel.MeasuresModel, int>> counter); + } +} diff --git a/src/GitHub.Exports/Services/IVSGitExt.cs b/src/GitHub.Exports/Services/IVSGitExt.cs new file mode 100644 index 0000000000..3491aef125 --- /dev/null +++ b/src/GitHub.Exports/Services/IVSGitExt.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using GitHub.Models; + +namespace GitHub.Services +{ + public interface IVSGitExt + { + IReadOnlyList<ILocalRepositoryModel> ActiveRepositories { get; } + event Action ActiveRepositoriesChanged; + void RefreshActiveRepositories(); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IVSGitServices.cs b/src/GitHub.Exports/Services/IVSGitServices.cs new file mode 100644 index 0000000000..3795cd5974 --- /dev/null +++ b/src/GitHub.Exports/Services/IVSGitServices.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.Services +{ + public interface IVSGitServices + { + string GetLocalClonePathFromGitProvider(); + + /// <summary> + /// Clones a repository via Team Explorer. + /// </summary> + /// <param name="cloneUrl">The URL of the repository to clone.</param> + /// <param name="clonePath">The path to clone the repository to.</param> + /// <param name="recurseSubmodules">Whether to recursively clone submodules.</param> + /// <param name="progress"> + /// An object through which to report progress. This must be of type + /// System.IProgress<Microsoft.VisualStudio.Shell.ServiceProgressData>, but + /// as that type is only available in VS2017+ it is typed as <see cref="object"/> here. + /// </param> + /// <seealso cref="System.IProgress{T}"/> + /// <seealso cref="Microsoft.VisualStudio.Shell.ServiceProgressData"/> + Task Clone( + string cloneUrl, + string clonePath, + bool recurseSubmodules, + object progress = null); + + string GetActiveRepoPath(); + LibGit2Sharp.IRepository GetActiveRepo(); + IEnumerable<ILocalRepositoryModel> GetKnownRepositories(); + string SetDefaultProjectPath(string path); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IVSServices.cs b/src/GitHub.Exports/Services/IVSServices.cs new file mode 100644 index 0000000000..d0171ada58 --- /dev/null +++ b/src/GitHub.Exports/Services/IVSServices.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio; + +namespace GitHub.Services +{ + public interface IVSServices + { + /// <summary> + /// Get the full Visual Studio version from `VisualStudio\14.0_Config\SplashInfo|EnvVersion` on Visual Studoi 2015 + /// or `SetupConfiguration.GetInstanceForCurrentProcess()` on on Visual Studoi 2017. + /// </summary> + string VSVersion { get; } + + /// <summary>Open a repository in Team Explorer</summary> + /// <remarks> + /// There doesn't appear to be a command that directly opens a target repo. + /// Our workaround is to create, open and delete a solution in the repo directory. + /// This triggers an event that causes the target repo to open. ;) + /// </remarks> + /// <param name="directory">The path to the repository to open</param> + /// <returns>True if a transient solution was successfully created in target directory (which should trigger opening of repository).</returns> + bool TryOpenRepository(string directory); + + /// <summary> + /// Displays a message box with the specified message. + /// </summary> + /// <param name="message">The message to display</param> + /// <returns>The result.</returns> + VSConstants.MessageBoxResult ShowMessageBoxInfo(string message); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IVSUIContextFactory.cs b/src/GitHub.Exports/Services/IVSUIContextFactory.cs new file mode 100644 index 0000000000..57c6542d9b --- /dev/null +++ b/src/GitHub.Exports/Services/IVSUIContextFactory.cs @@ -0,0 +1,25 @@ +using System; + +namespace GitHub.Services +{ + public interface IVSUIContextFactory + { + IVSUIContext GetUIContext(Guid contextGuid); + } + + public sealed class VSUIContextChangedEventArgs + { + public VSUIContextChangedEventArgs(bool activated) + { + Activated = activated; + } + + public bool Activated { get; } + } + + public interface IVSUIContext + { + bool IsActive { get; } + void WhenActivated(Action action); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Services/Logger.cs b/src/GitHub.Exports/Services/Logger.cs deleted file mode 100644 index cefddad716..0000000000 --- a/src/GitHub.Exports/Services/Logger.cs +++ /dev/null @@ -1,84 +0,0 @@ -using GitHub.Info; -using System; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Text; - -namespace GitHub.VisualStudio -{ - public class VSTraceListener : TraceListener - { - public override void Write(string message) - { - VsOutputLogger.Write(message); - } - - public override void WriteLine(string message) - { - VsOutputLogger.WriteLine(message); - } - } - - public static class VsOutputLogger - { - static Lazy<Action<string>> logger = new Lazy<Action<string>>(() => DefaultLogger); - static readonly string defaultLogPath; - static readonly object fileLock = new object(); - - static Action<string> Logger - { - get { return logger.Value; } - } - - static VsOutputLogger() - { - //Debug.Listeners.Add(new VSTraceListener()); - var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ApplicationInfo.ApplicationName); - defaultLogPath = Path.Combine(dir, ApplicationInfo.ApplicationName + "-te.log"); - try - { - Directory.CreateDirectory(dir); - } - catch { } - } - - public static void SetLogger(Action<string> log) - { - logger = new Lazy<Action<string>>(() => log); - } - - public static void Write(string format, params object[] args) - { - var message = string.Format(CultureInfo.CurrentCulture, format, args); - Write(message); - } - - public static void Write(string message) - { - Logger(message); - } - - public static void WriteLine(string format, params object[] args) - { - var message = string.Format(CultureInfo.CurrentCulture, format, args); - WriteLine(message); - } - - public static void WriteLine(string message) - { - Logger(message + Environment.NewLine); - } - - static async void DefaultLogger(string msg) - { - // this codepath is called multiple times on loading - // doing a little delay so that calling code doesn't get slowed down by this - await System.Threading.Tasks.Task.Delay(500); - lock(fileLock) - { - File.AppendAllText(defaultLogPath, msg, Encoding.UTF8); - } - } - } -} diff --git a/src/GitHub.Exports/Services/MetricsService.cs b/src/GitHub.Exports/Services/MetricsService.cs new file mode 100644 index 0000000000..9f1e1231b7 --- /dev/null +++ b/src/GitHub.Exports/Services/MetricsService.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using Octokit; +using Octokit.Internal; + +namespace GitHub.Services +{ + [Export(typeof(IMetricsService))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class MetricsService : IMetricsService + { +#if DEBUG + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "We have conditional compilation")] + static readonly Uri centralUri = new Uri("http://localhost:4000/", UriKind.Absolute); +#else + static readonly Uri centralUri = new Uri("https://central.github.com/", UriKind.Absolute); +#endif + + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "We have conditional compilation")] + readonly Lazy<IHttpClient> httpClient; + + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "We have conditional compilation")] + readonly ProductHeaderValue productHeader; + + [ImportingConstructor] + public MetricsService(Lazy<IHttpClient> httpClient, IProgram program) + { + this.httpClient = httpClient; + this.productHeader = program.ProductHeader; + } + +#if DEBUG && !SEND_DEBUG_METRICS + public Task PostUsage(UsageModel model) + { + return Task.CompletedTask; + } +#else + public async Task PostUsage(UsageModel model) + { + var request = new Request + { + Method = HttpMethod.Post, + BaseAddress = centralUri, + Endpoint = new Uri("api/usage/visualstudio", UriKind.Relative), + }; + + request.Headers.Add("User-Agent", productHeader.ToString()); + + request.Body = SerializeRequest(model); + request.ContentType = "application/json"; + + await httpClient.Value.Send(request); + } +#endif + + public Task SendOptOut() + { + // Temporarily disabled until https://github.com/github/central/issues/213 gets resolved + return Task.FromResult(0); + + /* + var request = new Request + { + Method = HttpMethod.Post, + AllowAutoRedirect = true, + Endpoint = new Uri(centralUri, new Uri("api/usage/visualstudio?optout=1", UriKind.Relative)), + }; + request.Headers.Add("User-Agent", productHeader.ToString()); + request.Body = new StringContent(""); + request.ContentType = "application/json"; + await httpClient.Value.Send((IRequest)request, cancellationToken); + */ + } + + public Task SendOptIn() + { + // Temporarily disabled until https://github.com/github/central/issues/213 gets resolved + return Task.FromResult(0); + + /* + var request = new Request + { + Method = HttpMethod.Post, + AllowAutoRedirect = true, + Endpoint = new Uri(centralUri, new Uri("api/usage/visualstudio?optin=1", UriKind.Relative)), + }; + request.Headers.Add("User-Agent", productHeader.ToString()); + request.Body = new StringContent(""); + request.ContentType = "application/json"; + return Observable.FromAsync(cancellationToken => httpClient.Value.Send((IRequest)request, cancellationToken)) + .AsCompletion(); + */ + } + + public static StringContent SerializeRequest(UsageModel model) + { + var serializer = new SimpleJsonSerializer(); + var dictionary = new Dictionary<string, object> + { + {ToJsonPropertyName("Dimensions"), ToModelDictionary(model.Dimensions) }, + {ToJsonPropertyName("Measures"), ToModelDictionary(model.Measures) } + }; + + return new StringContent(serializer.Serialize(dictionary), Encoding.UTF8, "application/json"); + } + + static Dictionary<string, object> ToModelDictionary(object model) + { + var dict = new Dictionary<string, object>(); + var type = model.GetType(); + + foreach (var prop in type.GetProperties()) + { + if (prop.PropertyType.IsValueType || prop.PropertyType == typeof(string)) + { + dict.Add(ToJsonPropertyName(prop.Name), prop.GetValue(model)); + } + else + { + var value = prop.GetValue(model); + + if (value == null) + { + dict.Add(ToJsonPropertyName(prop.Name), value); + } + else + { + dict.Add(ToJsonPropertyName(prop.Name), ToModelDictionary(value)); + } + } + } + + return dict; + } + + + /// <summary> + /// Convert from PascalCase to camelCase. + /// </summary> + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] + static string ToJsonPropertyName(string propertyName) + { + if (propertyName.Length < 2) + { + return propertyName.ToLowerInvariant(); + } + + return propertyName.Substring(0, 1).ToLowerInvariant() + propertyName.Substring(1); + } + } +} diff --git a/src/GitHub.Exports/Services/Services.cs b/src/GitHub.Exports/Services/Services.cs index a6e08a9ac1..a8019ee6a7 100644 --- a/src/GitHub.Exports/Services/Services.cs +++ b/src/GitHub.Exports/Services/Services.cs @@ -4,22 +4,25 @@ using GitHub.Info; using GitHub.Primitives; using GitHub.Services; -using LibGit2Sharp; using Microsoft.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell; using System.ComponentModel.Composition.Hosting; +using System.IO; namespace GitHub.VisualStudio { public static class Services { - public static IServiceProvider PackageServiceProvider { get; set; } + /// <summary> + /// Gets a service provider which can be used by unit tests to inject services. + /// </summary> + public static IServiceProvider UnitTestServiceProvider { get; set; } /// <summary> /// Three ways of getting a service. First, trying the passed-in <paramref name="provider"/>, - /// then <see cref="PackageServiceProvider"/>, then <see cref="T:Microsoft.VisualStudio.Shell.Package"/> + /// then <see cref="UnitTestServiceProvider"/>, then <see cref="T:Microsoft.VisualStudio.Shell.Package"/> /// If the passed-in provider returns null, try PackageServiceProvider or Package, returning the fetched value /// regardless of whether it's null or not. Package.GetGlobalService is never called if PackageServiceProvider is set. /// This is on purpose, to support easy unit testing outside VS. @@ -35,11 +38,13 @@ static Ret GetGlobalService<T, Ret>(IServiceProvider provider = null) where T : ret = provider.GetService(typeof(T)) as Ret; if (ret != null) return ret; - if (PackageServiceProvider != null) - return PackageServiceProvider.GetService(typeof(T)) as Ret; + if (UnitTestServiceProvider != null) + return UnitTestServiceProvider.GetService(typeof(T)) as Ret; return Package.GetGlobalService(typeof(T)) as Ret; } + public static IGitHubServiceProvider GitHubServiceProvider => GetGlobalService<IGitHubServiceProvider, IGitHubServiceProvider>(); + public static IComponentModel ComponentModel => GetGlobalService<SComponentModel, IComponentModel>(); public static ExportProvider DefaultExportProvider => ComponentModel.DefaultExportProvider; @@ -81,6 +86,10 @@ public static IVsOutputWindowPane OutputWindowPane // ReSharper disable once SuspiciousTypeConversion.Global public static DTE2 Dte2 => Dte as DTE2; + public static IVsUIShell UIShell => GetGlobalService<SVsUIShell, IVsUIShell>(); + + public static IVsDifferenceService DifferenceService => GetGlobalService<SVsDifferenceService, IVsDifferenceService>(); + public static IVsActivityLog GetActivityLog(this IServiceProvider provider) { return GetGlobalService<SVsActivityLog, IVsActivityLog>(provider); @@ -100,5 +109,15 @@ public static UriString GetRepoUrlFromSolution(IVsSolution solution) return null; return GitService.GitServiceHelper.GetUri(solutionDir); } + + /// <summary> + /// Gets the file name of the currently active document in the text editor, + /// or null if there there is no active document. + /// </summary> + public static string GetFileNameFromActiveDocument() + { + var fullName = Dte2?.ActiveDocument?.FullName; + return Path.GetFileName(fullName); + } } } diff --git a/src/GitHub.Exports/Services/StatusBarNotificationService.cs b/src/GitHub.Exports/Services/StatusBarNotificationService.cs new file mode 100644 index 0000000000..0913a9ca74 --- /dev/null +++ b/src/GitHub.Exports/Services/StatusBarNotificationService.cs @@ -0,0 +1,67 @@ +using GitHub.Extensions; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using System.ComponentModel.Composition; +using System.Windows.Input; + +namespace GitHub.Services +{ + [Export(typeof(IStatusBarNotificationService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class StatusBarNotificationService : IStatusBarNotificationService + { + readonly IServiceProvider serviceProvider; + + [ImportingConstructor] + public StatusBarNotificationService([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public void HideNotification(Guid guid) + { + // status bar only shows text, this is a noop + } + + public bool IsNotificationVisible(Guid guid) + { + // it's only text, there's no way of checking + return false; + } + + public void ShowError(string message) + { + ShowText(message); + } + + public void ShowMessage(string message) + { + ShowText(message); + } + + public void ShowMessage(string message, ICommand command, bool showToolTips = true, Guid guid = default(Guid)) + { + ShowText(message); + } + + public void ShowWarning(string message) + { + ShowText(message); + } + + void ShowText(string text) + { + var statusBar = serviceProvider.GetServiceSafe<IVsStatusbar>(); + int frozen; + if (!ErrorHandler.Succeeded(statusBar.IsFrozen(out frozen))) + return; + // If it's frozen, someone else is grabbing the status bar and we + // can't show the message until they release it + // so might as well not show it. + if (frozen == 0) + ErrorHandler.Succeeded(statusBar.SetText(text)); + } + } +} diff --git a/src/GitHub.Exports/Services/VSServices.cs b/src/GitHub.Exports/Services/VSServices.cs index c89c0f8fc7..01a811eb38 100644 --- a/src/GitHub.Exports/Services/VSServices.cs +++ b/src/GitHub.Exports/Services/VSServices.cs @@ -1,260 +1,156 @@ using System; -using System.Collections.Generic; using System.ComponentModel.Composition; using System.Globalization; -using System.Linq; -using System.Windows.Input; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.VisualStudio; -using Microsoft.TeamFoundation.Controls; -using Microsoft.TeamFoundation.Git.Controls.Extensibility; +using System.IO; +using GitHub.Logging; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Setup.Configuration; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; -using Microsoft.Win32; -using System.Diagnostics; +using Rothko; +using Serilog; +using EnvDTE; namespace GitHub.Services { - public interface IVSServices - { - string GetLocalClonePathFromGitProvider(); - void Clone(string cloneUrl, string clonePath, bool recurseSubmodules); - string GetActiveRepoPath(); - LibGit2Sharp.IRepository GetActiveRepo(); - IEnumerable<ISimpleRepositoryModel> GetKnownRepositories(); - string SetDefaultProjectPath(string path); - - void ShowMessage(string message); - void ShowMessage(string message, ICommand command); - void ShowWarning(string message); - void ShowError(string message); - void ClearNotifications(); - - void ActivityLogMessage(string message); - void ActivityLogWarning(string message); - void ActivityLogError(string message); - } - [Export(typeof(IVSServices))] [PartCreationPolicy(CreationPolicy.Shared)] public class VSServices : IVSServices { - readonly IServiceProvider serviceProvider; + readonly ILogger log; + readonly IGitHubServiceProvider serviceProvider; + + // Use a prefix (~$) that is defined in the default VS gitignore. + public const string TempSolutionName = "~$GitHubVSTemp$~"; [ImportingConstructor] - public VSServices(IUIProvider serviceProvider) + public VSServices(IGitHubServiceProvider serviceProvider) : + this(serviceProvider, LogManager.ForContext<VSServices>()) { - this.serviceProvider = serviceProvider; } - // The Default Repository Path that VS uses is hidden in an internal - // service 'ISccSettingsService' registered in an internal service - // 'ISccServiceHost' in an assembly with no public types that's - // always loaded with VS if the git service provider is loaded - public string GetLocalClonePathFromGitProvider() + public VSServices(IGitHubServiceProvider serviceProvider, ILogger log) { - string ret = string.Empty; - - try - { - ret = PokeTheRegistryForLocalClonePath(); - } - catch (Exception ex) - { - VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error loading the default cloning path from the registry '{0}'", ex)); - } - return ret; + this.serviceProvider = serviceProvider; + this.log = log; } - public void Clone(string cloneUrl, string clonePath, bool recurseSubmodules) + string vsVersion; + /// <inheritdoc/> + public string VSVersion { - var gitExt = serviceProvider.GetService<IGitRepositoriesExt>(); - gitExt.Clone(cloneUrl, clonePath, recurseSubmodules ? CloneOptions.RecurseSubmodule : CloneOptions.None); + get + { + if (vsVersion == null) + vsVersion = GetVSVersion(); + return vsVersion; + } } - public LibGit2Sharp.IRepository GetActiveRepo() + /// <inheritdoc/> + public VSConstants.MessageBoxResult ShowMessageBoxInfo(string message) { - var gitExt = serviceProvider.GetService<IGitExt>(); - return gitExt.ActiveRepositories.Any() - ? serviceProvider.GetService<IGitService>().GetRepo(gitExt.ActiveRepositories.First()) - : serviceProvider.GetSolution().GetRepoFromSolution(); + return (VSConstants.MessageBoxResult)VsShellUtilities.ShowMessageBox(serviceProvider, message, null, + OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); } - public string GetActiveRepoPath() + /// <summary>Open a repository in Team Explorer</summary> + /// <remarks> + /// There doesn't appear to be a command that directly opens a target repo. + /// Our workaround is to create, open and delete a solution in the repo directory. + /// This triggers an event that causes the target repo to open. ;) + /// </remarks> + /// <param name="repoPath">The path to the repository to open</param> + /// <returns>True if a transient solution was successfully created in target directory (which should trigger opening of repository).</returns> + public bool TryOpenRepository(string repoPath) { - var gitExt = serviceProvider.GetService<IGitExt>(); - if (gitExt.ActiveRepositories.Any()) - return gitExt.ActiveRepositories.First().RepositoryPath; - var repo = serviceProvider.GetSolution().GetRepoFromSolution(); - return repo?.Info?.Path ?? string.Empty; - } + var os = serviceProvider.TryGetService<IOperatingSystem>(); + if (os == null) + { + log.Error("TryOpenRepository couldn't find IOperatingSystem service"); + return false; + } - public IEnumerable<ISimpleRepositoryModel> GetKnownRepositories() - { - try + var dte = serviceProvider.TryGetService<DTE>(); + if (dte == null) { - return PokeTheRegistryForRepositoryList(); + log.Error("TryOpenRepository couldn't find DTE service"); + return false; } - catch (Exception ex) + + var repoDir = os.Directory.GetDirectory(repoPath); + if (!repoDir.Exists) { - VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error loading the repository list from the registry '{0}'", ex)); - return Enumerable.Empty<ISimpleRepositoryModel>(); + return false; } - } - const string TEGitKey = @"Software\Microsoft\VisualStudio\14.0\TeamFoundation\GitSourceControl"; - static RegistryKey OpenGitKey(string path) - { - return Registry.CurrentUser.OpenSubKey(TEGitKey + "\\" + path, true); - } + bool solutionCreated = false; + try + { + dte.Solution.Create(repoPath, TempSolutionName); + solutionCreated = true; - static IEnumerable<ISimpleRepositoryModel> PokeTheRegistryForRepositoryList() - { - using (var key = OpenGitKey("Repositories")) + dte.Solution.Close(false); // Don't create a .sln file when we close. + } + catch (Exception e) { - return key.GetSubKeyNames().Select(x => - { - using (var subkey = key.OpenSubKey(x)) - { - try - { - var path = subkey?.GetValue("Path") as string; - if (path != null) - return new SimpleRepositoryModel(path); - } - catch (Exception ex) - { - VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error loading the repository from the registry '{0}'", ex)); - } - return null; - } - }) - .Where(x => x != null) - .ToList(); + log.Error(e, "Error opening repository"); } - } - - static string PokeTheRegistryForLocalClonePath() - { - using (var key = OpenGitKey("General")) + finally { - return (string)key?.GetValue("DefaultRepositoryPath", string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + TryCleanupSolutionUserFiles(os, repoPath, TempSolutionName); } + return solutionCreated; } - const string NewProjectDialogKeyPath = @"Software\Microsoft\VisualStudio\14.0\NewProjectDialog"; - const string MRUKeyPath = "MRUSettingsLocalProjectLocationEntries"; - public string SetDefaultProjectPath(string path) + void TryCleanupSolutionUserFiles(IOperatingSystem os, string repoPath, string slnName) { - var old = String.Empty; + var vsTempPath = Path.Combine(repoPath, ".vs", slnName); try { - var newProjectKey = Registry.CurrentUser.OpenSubKey(NewProjectDialogKeyPath, true) ?? - Registry.CurrentUser.CreateSubKey(NewProjectDialogKeyPath); - Debug.Assert(newProjectKey != null, string.Format(CultureInfo.CurrentCulture, "Could not open or create registry key '{0}'", NewProjectDialogKeyPath)); - - using (newProjectKey) + // Clean up the dummy solution's subdirectory inside `.vs`. + var vsTempDir = os.Directory.GetDirectory(vsTempPath); + if (vsTempDir.Exists) { - var mruKey = newProjectKey.OpenSubKey(MRUKeyPath, true) ?? - Registry.CurrentUser.CreateSubKey(MRUKeyPath); - Debug.Assert(mruKey != null, string.Format(CultureInfo.CurrentCulture, "Could not open or create registry key '{0}'", MRUKeyPath)); - - using (mruKey) - { - // is this already the default path? bail - old = (string)mruKey.GetValue("Value0", string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); - if (String.Equals(path.TrimEnd('\\'), old.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase)) - return old; - - // grab the existing list of recent paths, throwing away the last one - var numEntries = (int)mruKey.GetValue("MaximumEntries", 5); - var entries = new List<string>(numEntries); - for (int i = 0; i < numEntries - 1; i++) - { - var val = (string)mruKey.GetValue("Value" + i, String.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); - if (!String.IsNullOrEmpty(val)) - entries.Add(val); - } - - newProjectKey.SetValue("LastUsedNewProjectPath", path); - mruKey.SetValue("Value0", path); - // bump list of recent paths one entry down - for (int i = 0; i < entries.Count; i++) - mruKey.SetValue("Value" + (i + 1), entries[i]); - } + vsTempDir.Delete(true); } } - catch (Exception ex) - { - VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error setting the create project path in the registry '{0}'", ex)); - } - return old; - } - - public void ShowMessage(string message) - { - var manager = serviceProvider.TryGetService<ITeamExplorer>() as ITeamExplorerNotificationManager; - manager?.ShowNotification(message, NotificationType.Information, NotificationFlags.None, null, default(Guid)); - } - - public void ShowMessage(string message, ICommand command) - { - var manager = serviceProvider.TryGetService<ITeamExplorer>() as ITeamExplorerNotificationManager; - manager?.ShowNotification(message, NotificationType.Information, NotificationFlags.None, command, default(Guid)); - } - - public void ShowWarning(string message) - { - var manager = serviceProvider.TryGetService<ITeamExplorer>() as ITeamExplorerNotificationManager; - manager?.ShowNotification(message, NotificationType.Warning, NotificationFlags.None, null, default(Guid)); - } - - public void ShowError(string message) - { - var manager = serviceProvider.TryGetService<ITeamExplorer>() as ITeamExplorerNotificationManager; - manager?.ShowNotification(message, NotificationType.Error, NotificationFlags.None, null, default(Guid)); - } - - public void ClearNotifications() - { - var manager = serviceProvider.TryGetService<ITeamExplorer>() as ITeamExplorerNotificationManager; - manager?.ClearNotifications(); - } - - public void ActivityLogMessage(string message) - { - var log = serviceProvider.GetActivityLog(); - if (log != null) + catch (Exception e) { - if (!ErrorHandler.Succeeded(log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, - Info.ApplicationInfo.ApplicationSafeName, message))) - Console.WriteLine(string.Format(CultureInfo.CurrentCulture, "Could not log message to activity log: {0}", message)); + log.Error(e, "Couldn't clean up {TempPath}", vsTempPath); } } - public void ActivityLogError(string message) + const string RegistryRootKey = @"Software\Microsoft\VisualStudio"; + const string EnvVersionKey = "EnvVersion"; + const string InstallationNamePrefix = "VisualStudio/"; + string GetVSVersion() { - var log = serviceProvider.GetActivityLog(); - if (log != null) + var version = typeof(Microsoft.VisualStudio.Shell.ActivityLog).Assembly.GetName().Version; + var keyPath = String.Format(CultureInfo.InvariantCulture, "{0}\\{1}.{2}_Config\\SplashInfo", RegistryRootKey, version.Major, version.Minor); + try { - - if (!ErrorHandler.Succeeded(log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, - Info.ApplicationInfo.ApplicationSafeName, message))) - Console.WriteLine(string.Format(CultureInfo.CurrentCulture, "Could not log error to activity log: {0}", message)); + if (version.Major == 14) + { + using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(keyPath)) + { + var value = (string)key.GetValue(EnvVersionKey, String.Empty); + if (!String.IsNullOrEmpty(value)) + return value; + } + } + else + { + var setupConfiguration = new SetupConfiguration(); + var setupInstance = setupConfiguration.GetInstanceForCurrentProcess(); + return setupInstance.GetInstallationName().TrimPrefix(InstallationNamePrefix); + } } - } - - public void ActivityLogWarning(string message) - { - var log = serviceProvider.GetActivityLog(); - if (log != null) + catch (Exception ex) { - if (!ErrorHandler.Succeeded(log.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_WARNING, - Info.ApplicationInfo.ApplicationSafeName, message))) - Console.WriteLine(string.Format(CultureInfo.CurrentCulture, "Could not log warning to activity log: {0}", message)); + log.Error(ex, "Error getting the Visual Studio version"); } + return version.ToString(); } } -} \ No newline at end of file +} diff --git a/src/GitHub.Exports/Settings/GitHubConnectSectionState.cs b/src/GitHub.Exports/Settings/GitHubConnectSectionState.cs new file mode 100644 index 0000000000..1feb0cc11e --- /dev/null +++ b/src/GitHub.Exports/Settings/GitHubConnectSectionState.cs @@ -0,0 +1,22 @@ +using System; +using GitHub.Settings; + +namespace GitHub.Settings +{ + /// <summary> + /// Stores persistent UI state for a <see cref="GitHubConnectSection"/> in + /// <see cref="IPackageSettings"/>. + /// </summary> + public class GitHubConnectSectionState + { + /// <summary> + /// Gets or sets the name of the connection section. + /// </summary> + public string SectionName { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the section should be expanded. + /// </summary> + public bool IsExpanded { get; set; } = true; + } +} diff --git a/src/GitHub.Exports/Settings/Guids.cs b/src/GitHub.Exports/Settings/Guids.cs new file mode 100644 index 0000000000..dd2805aaea --- /dev/null +++ b/src/GitHub.Exports/Settings/Guids.cs @@ -0,0 +1,43 @@ +using System; + +namespace GitHub.VisualStudio +{ + public static class Guids + { + public const string PackageId = "c3d3dc68-c977-411f-b3e8-03b0dccf7dfc"; + public const string ImagesId = "27841f47-070a-46d6-90be-a5cbbfc724ac"; + public const string UsageTrackerId = "9362DD38-7E49-4B5D-9DE1-E843D4155716"; + public const string UIProviderId = "304F2186-17C4-4C66-8A54-5C96F9353A28"; + public const string GitHubServiceProviderId = "76909E1A-9D58-41AB-8957-C26B9550787B"; + public const string StartPagePackageId = "3b764d23-faf7-486f-94c7-b3accc44a70e"; + public const string CodeContainerProviderId = "6CE146CB-EF57-4F2C-A93F-5BA685317660"; + public const string InlineReviewsPackageId = "248325BE-4A2D-4111-B122-E7D59BF73A35"; + public const string PullRequestStatusPackageId = "5121BEC6-1088-4553-8453-0DDC7C8E2238"; + public const string GitHubPanePackageId = "0A40459D-6B6D-4110-B6CE-EC83C0BC6A09"; + public const string TeamExplorerWelcomeMessage = "C529627F-8AA6-4FDB-82EB-4BFB7DB753C3"; + public const string LoginManagerId = "7BA2071A-790A-4F95-BE4A-0EEAA5928AAF"; + + // VisualStudio IDs + public const string GitSccProviderId = "11B8E6D7-C08B-4385-B321-321078CDD1F8"; + public const string TeamExplorerInstall3rdPartyGitTools = "DF785C7C-8454-4836-9686-D1C4A01D0BB9"; + + // UIContexts + public const string UIContext_Git = "565515AD-F4C1-4D59-BC14-AE77396DDDD7"; + + // Guids defined in GitHub.VisualStudio.vsct + public const string guidGitHubPkgString = "c3d3dc68-c977-411f-b3e8-03b0dccf7dfc"; + public const string guidAssemblyResolverPkgString = "a6424dba-34cb-360d-a4de-1b0b0411e57d"; + public const string guidGitHubCmdSetString = "c4c91892-8881-4588-a5d9-b41e8f540f5a"; + public const string guidGitHubToolbarCmdSetString = "C5F1193E-F300-41B3-B4C4-5A703DD3C1C6"; + public const string guidContextMenuSetString = "31057D08-8C3C-4C5B-9F91-8682EA08EC27"; + public const string guidImageMonikerString = "27841f47-070a-46d6-90be-a5cbbfc724ac"; + public static readonly Guid guidGitHubCmdSet = new Guid(guidGitHubCmdSetString); + public static readonly Guid guidGitHubToolbarCmdSet = new Guid(guidGitHubToolbarCmdSetString); + public static readonly Guid guidContextMenuSet = new Guid(guidContextMenuSetString); + public static readonly Guid guidImageMoniker = new Guid(guidImageMonikerString); + + // Guids defined in InlineReviewsPackage.vsct + public const string CommandSetString = "C5F1193E-F300-41B3-B4C4-5A703DD3C1C6"; + public static readonly Guid CommandSetGuid = new Guid(CommandSetString); + } +} diff --git a/src/GitHub.Exports/Settings/PkgCmdID.cs b/src/GitHub.Exports/Settings/PkgCmdID.cs new file mode 100644 index 0000000000..636905c377 --- /dev/null +++ b/src/GitHub.Exports/Settings/PkgCmdID.cs @@ -0,0 +1,35 @@ +// PkgCmdID.cs +// MUST match PkgCmdID.h +namespace GitHub.VisualStudio +{ + public static class PkgCmdIDList + { + // IDs defined in GitHub.VisualStudio.vsct + public const int addConnectionCommand = 0x110; + public const int idGitHubToolbar = 0x1120; + public const int showGitHubPaneCommand = 0x200; + public const int openPullRequestsCommand = 0x201; + public const int showCurrentPullRequestCommand = 0x202; + public const int syncSubmodulesCommand = 0x203; + public const int openFromUrlCommand = 0x204; + public const int openFromClipboardCommand = 0x205; + + public const int backCommand = 0x300; + public const int forwardCommand = 0x301; + public const int refreshCommand = 0x302; + public const int pullRequestCommand = 0x310; + public const int createGistCommand = 0x400; + public const int createGistEnterpriseCommand = 0x401; + public const int openLinkCommand = 0x100; + public const int copyLinkCommand = 0x101; + public const int goToSolutionOrPullRequestFileCommand = 0x102; + public const int githubCommand = 0x320; + public const int helpCommand = 0x321; + public const int blameCommand = 0x500; + + // IDs defined in InlineReviewsPackage.vsct + public const int NextInlineCommentId = 0x1001; + public const int PreviousInlineCommentId = 0x1002; + public const int ToggleInlineCommentMarginId = 0x1003; + }; +} \ No newline at end of file diff --git a/src/GitHub.Exports/Settings/PullRequestDetailUIState.cs b/src/GitHub.Exports/Settings/PullRequestDetailUIState.cs new file mode 100644 index 0000000000..00251920be --- /dev/null +++ b/src/GitHub.Exports/Settings/PullRequestDetailUIState.cs @@ -0,0 +1,28 @@ +using System; + +namespace GitHub.Settings +{ + /// <summary> + /// Holds global settings for the Pull Request detail view. + /// </summary> + public class PullRequestDetailUIState + { + /// <summary> + /// Gets or sets a value indicating whether a "diff" or "open" should be carried out when + /// double clicking a changed file. + /// </summary> + /// <remarks> + /// Ideally this would be an enum but SimpleJson doesn't handle enums. + /// </remarks> + public bool DiffOnOpen { get; set; } = true; + + /// <summary> + /// Gets or sets a value indicating whether changed files should be displayed as a tree or + /// a list. + /// </summary> + /// <remarks> + /// Ideally this would be an enum but SimpleJson doesn't handle enums. + /// </remarks> + public bool ShowTree { get; set; } = true; + } +} diff --git a/src/GitHub.Exports/Settings/PullRequestListUIState.cs b/src/GitHub.Exports/Settings/PullRequestListUIState.cs new file mode 100644 index 0000000000..37227cc5e9 --- /dev/null +++ b/src/GitHub.Exports/Settings/PullRequestListUIState.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Settings +{ + public class PullRequestListUIState + { + public string SelectedState { get; set; } + public string SelectedAuthor { get; set; } + public string SelectedAssignee { get; set; } + } +} diff --git a/src/GitHub.Exports/Settings/RepositoryUIState.cs b/src/GitHub.Exports/Settings/RepositoryUIState.cs new file mode 100644 index 0000000000..f89692f61a --- /dev/null +++ b/src/GitHub.Exports/Settings/RepositoryUIState.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Settings +{ + public class RepositoryUIState + { + public string RepositoryUrl { get; set; } + + public PullRequestListUIState PullRequests { get; set; } + = new PullRequestListUIState(); + } +} diff --git a/src/GitHub.Exports/Settings/UIState.cs b/src/GitHub.Exports/Settings/UIState.cs new file mode 100644 index 0000000000..7de04a516d --- /dev/null +++ b/src/GitHub.Exports/Settings/UIState.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using GitHub.Settings; +using System.Linq; +using GitHub.VisualStudio.TeamExplorer.Connect; + +namespace GitHub.Settings +{ + /// <summary> + /// Stores persistent UI state in <see cref="IPackageSettings"/>. + /// </summary> + public class UIState + { + PullRequestDetailUIState prState = new PullRequestDetailUIState(); + + /// <summary> + /// Gets or sets the UI state for the <see cref="IGitHubConnectSection"/>s in Team Explorer. + /// </summary> + public List<GitHubConnectSectionState> GitHubConnectSections { get; set; } + = new List<GitHubConnectSectionState>(); + + /// <summary> + /// Gets or sets a a collection of UI state objects for repositories. + /// </summary> + public List<RepositoryUIState> RepositoryState { get; set; } + = new List<RepositoryUIState>(); + + /// <summary> + /// Gets or sets global settings for the Pull Request detail view. + /// </summary> + public PullRequestDetailUIState PullRequestDetailState + { + get { return prState; } + set + { + if (value != null) + { + prState = value; + } + } + } + + /// <summary> + /// Gets or creates the UI state for a repository. + /// </summary> + /// <param name="repositoryUrl">The URL of the repository.</param> + /// <returns>A <see cref="RepositoryUIState"/> object.</returns> + public RepositoryUIState GetOrCreateRepositoryState(string repositoryUrl) + { + var result = RepositoryState.FirstOrDefault(x => x.RepositoryUrl == repositoryUrl); + + if (result == null) + { + result = new RepositoryUIState { RepositoryUrl = repositoryUrl }; + RepositoryState.Add(result); + } + + return result; + } + + /// <summary> + /// Gets or creates the UI state for a named <see cref="IGitHubConnectSection"/>. + /// </summary> + /// <param name="sectionName">The name of the section.</param> + /// <returns>A <see cref="GitHubConnectSectionState"/> object.</returns> + public GitHubConnectSectionState GetOrCreateConnectSection(string sectionName) + { + var result = GitHubConnectSections.FirstOrDefault(x => x.SectionName == sectionName); + + if (result == null) + { + result = new GitHubConnectSectionState { SectionName = sectionName }; + GitHubConnectSections.Add(result); + } + + return result; + } + } +} diff --git a/src/GitHub.Exports/Settings/generated/IPackageSettings.cs b/src/GitHub.Exports/Settings/generated/IPackageSettings.cs new file mode 100644 index 0000000000..fa8d50b008 --- /dev/null +++ b/src/GitHub.Exports/Settings/generated/IPackageSettings.cs @@ -0,0 +1,54 @@ +// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt +/* settings.json content: +{ + "settings": [ + { + "name": "CollectMetrics", + "type": "bool", + "default": 'true' + }, + { + "name": "EditorComments", + "type": "bool", + "default": "false" + }, + { + "name": "ForkButton", + "type": "bool", + "default": "false" + }, + { + "name": "UIState", + "type": "object", + "typename": "UIState", + "default": "null" + }, + { + "name": "HideTeamExplorerWelcomeMessage", + "type": "bool", + "default": "false" + }, + { + "name": "EnableTraceLogging", + "type": "bool", + "default": "false" + } + ] +} +*/ + +using System.ComponentModel; + +namespace GitHub.Settings +{ + 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; } + } +} diff --git a/src/GitHub.Exports/Settings/generated/IPackageSettings.tt b/src/GitHub.Exports/Settings/generated/IPackageSettings.tt new file mode 100644 index 0000000000..298ab2405a --- /dev/null +++ b/src/GitHub.Exports/Settings/generated/IPackageSettings.tt @@ -0,0 +1,38 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.ComponentModel" #> +<#@ import namespace="System.IO" #> +<#@ assembly name="$(PackageDir)\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll" #> +<#@ import namespace="Newtonsoft.Json.Linq" #> +<#@ output extension=".cs" #> +<# +var file = this.Host.ResolvePath(@"..\..\..\common\settings.json"); +var json = JObject.Parse(File.ReadAllText(file)); +#> +// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt +/* settings.json content: +<#@ include file="..\..\..\common\settings.json" #> +*/ + +using System.ComponentModel; + +namespace GitHub.Settings +{ + public interface IPackageSettings : INotifyPropertyChanged + { + void Save(); +<# +foreach (var j in json["settings"].Children()) { + var type = j["type"].ToString(); + if (type == "object") + type = j["typename"].ToString(); +#> + <#= type #> <#= j["name"] #> { get; set; } +<# +} +#> + } +} diff --git a/src/GitHub.Exports/SimpleJson.cs b/src/GitHub.Exports/SimpleJson.cs new file mode 100644 index 0000000000..82360321e7 --- /dev/null +++ b/src/GitHub.Exports/SimpleJson.cs @@ -0,0 +1,2141 @@ +//----------------------------------------------------------------------- +// <copyright file="SimpleJson.cs" company="The Outercurve Foundation"> +// Copyright (c) 2011, The Outercurve Foundation. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.opensource.org/licenses/mit-license.php +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author> +// <website>https://github.com/facebook-csharp-sdk/simple-json</website> +//----------------------------------------------------------------------- + +// VERSION: 0.38.0 + +// NOTE: uncomment the following line to make SimpleJson class internal. +//#define SIMPLE_JSON_INTERNAL + +// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. +//#define SIMPLE_JSON_OBJARRAYINTERNAL + +// NOTE: uncomment the following line to enable dynamic support. +//#define SIMPLE_JSON_DYNAMIC + +// NOTE: uncomment the following line to enable DataContract support. +//#define SIMPLE_JSON_DATACONTRACT + +// NOTE: uncomment the following line to enable IReadOnlyCollection<T> and IReadOnlyList<T> support. +//#define SIMPLE_JSON_READONLY_COLLECTIONS + +// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). +// define if you are using .net framework <= 3.0 or < WP7.5 +//#define SIMPLE_JSON_NO_LINQ_EXPRESSION + +// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. +// usually already defined in properties +//#define NETFX_CORE; + +// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; + +// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +#if NETFX_CORE +#define SIMPLE_JSON_TYPEINFO +#endif + +using System; +using System.CodeDom.Compiler; +using System.Collections; +using System.Collections.Generic; +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION +using System.Linq.Expressions; +#endif +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +#if SIMPLE_JSON_DYNAMIC +using System.Dynamic; +#endif +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using GitHub.Reflection; +using System.Diagnostics; + +// ReSharper disable LoopCanBeConvertedToQuery +// ReSharper disable RedundantExplicitArrayCreation +// ReSharper disable SuggestUseVarKeywordEvident +namespace GitHub +{ + /// <summary> + /// Represents the json array. + /// </summary> + [GeneratedCode("simple-json", "1.0.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if SIMPLE_JSON_OBJARRAYINTERNAL + internal +#else + public +#endif + class JsonArray : List<object> + { + /// <summary> + /// Initializes a new instance of the <see cref="JsonArray"/> class. + /// </summary> + public JsonArray() { } + + /// <summary> + /// Initializes a new instance of the <see cref="JsonArray"/> class. + /// </summary> + /// <param name="capacity">The capacity of the json array.</param> + public JsonArray(int capacity) : base(capacity) { } + + /// <summary> + /// The json representation of the array. + /// </summary> + /// <returns>The json representation of the array.</returns> + public override string ToString() + { + return SimpleJson.SerializeObject(this) ?? string.Empty; + } + } + + /// <summary> + /// Represents the json object. + /// </summary> + [GeneratedCode("simple-json", "1.0.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if SIMPLE_JSON_OBJARRAYINTERNAL + internal +#else + public +#endif + class JsonObject : +#if SIMPLE_JSON_DYNAMIC + DynamicObject, +#endif + IDictionary<string, object> + { + /// <summary> + /// The internal member dictionary. + /// </summary> + private readonly Dictionary<string, object> _members; + + /// <summary> + /// Initializes a new instance of <see cref="JsonObject"/>. + /// </summary> + public JsonObject() + { + _members = new Dictionary<string, object>(); + } + + /// <summary> + /// Initializes a new instance of <see cref="JsonObject"/>. + /// </summary> + /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param> + public JsonObject(IEqualityComparer<string> comparer) + { + _members = new Dictionary<string, object>(comparer); + } + + /// <summary> + /// Gets the <see cref="System.Object"/> at the specified index. + /// </summary> + /// <value></value> + public object this[int index] + { + get { return GetAtIndex(_members, index); } + } + + internal static object GetAtIndex(IDictionary<string, object> obj, int index) + { + if (obj == null) + throw new ArgumentNullException("obj"); + if (index >= obj.Count) + throw new ArgumentOutOfRangeException("index"); + int i = 0; + foreach (KeyValuePair<string, object> o in obj) + if (i++ == index) return o.Value; + return null; + } + + /// <summary> + /// Adds the specified key. + /// </summary> + /// <param name="key">The key.</param> + /// <param name="value">The value.</param> + public void Add(string key, object value) + { + _members.Add(key, value); + } + + /// <summary> + /// Determines whether the specified key contains key. + /// </summary> + /// <param name="key">The key.</param> + /// <returns> + /// <c>true</c> if the specified key contains key; otherwise, <c>false</c>. + /// </returns> + public bool ContainsKey(string key) + { + return _members.ContainsKey(key); + } + + /// <summary> + /// Gets the keys. + /// </summary> + /// <value>The keys.</value> + public ICollection<string> Keys + { + get { return _members.Keys; } + } + + /// <summary> + /// Removes the specified key. + /// </summary> + /// <param name="key">The key.</param> + /// <returns></returns> + public bool Remove(string key) + { + return _members.Remove(key); + } + + /// <summary> + /// Tries the get value. + /// </summary> + /// <param name="key">The key.</param> + /// <param name="value">The value.</param> + /// <returns></returns> + public bool TryGetValue(string key, out object value) + { + return _members.TryGetValue(key, out value); + } + + /// <summary> + /// Gets the values. + /// </summary> + /// <value>The values.</value> + public ICollection<object> Values + { + get { return _members.Values; } + } + + /// <summary> + /// Gets or sets the <see cref="System.Object"/> with the specified key. + /// </summary> + /// <value></value> + public object this[string key] + { + get { return _members[key]; } + set { _members[key] = value; } + } + + /// <summary> + /// Adds the specified item. + /// </summary> + /// <param name="item">The item.</param> + public void Add(KeyValuePair<string, object> item) + { + _members.Add(item.Key, item.Value); + } + + /// <summary> + /// Clears this instance. + /// </summary> + public void Clear() + { + _members.Clear(); + } + + /// <summary> + /// Determines whether [contains] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <returns> + /// <c>true</c> if [contains] [the specified item]; otherwise, <c>false</c>. + /// </returns> + public bool Contains(KeyValuePair<string, object> item) + { + return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; + } + + /// <summary> + /// Copies to. + /// </summary> + /// <param name="array">The array.</param> + /// <param name="arrayIndex">Index of the array.</param> + public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException("array"); + int num = Count; + foreach (KeyValuePair<string, object> kvp in this) + { + array[arrayIndex++] = kvp; + if (--num <= 0) + return; + } + } + + /// <summary> + /// Gets the count. + /// </summary> + /// <value>The count.</value> + public int Count + { + get { return _members.Count; } + } + + /// <summary> + /// Gets a value indicating whether this instance is read only. + /// </summary> + /// <value> + /// <c>true</c> if this instance is read only; otherwise, <c>false</c>. + /// </value> + public bool IsReadOnly + { + get { return false; } + } + + /// <summary> + /// Removes the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns></returns> + public bool Remove(KeyValuePair<string, object> item) + { + return _members.Remove(item.Key); + } + + /// <summary> + /// Gets the enumerator. + /// </summary> + /// <returns></returns> + public IEnumerator<KeyValuePair<string, object>> GetEnumerator() + { + return _members.GetEnumerator(); + } + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + IEnumerator IEnumerable.GetEnumerator() + { + return _members.GetEnumerator(); + } + + /// <summary> + /// Returns a json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </summary> + /// <returns> + /// A json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. + /// </returns> + public override string ToString() + { + return SimpleJson.SerializeObject(this); + } + +#if SIMPLE_JSON_DYNAMIC + /// <summary> + /// Provides implementation for type conversion operations. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that convert an object from one type to another. + /// </summary> + /// <param name="binder">Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Type returns the <see cref="T:System.String"/> type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion.</param> + /// <param name="result">The result of the type conversion operation.</param> + /// <returns> + /// Alwasy returns true. + /// </returns> + public override bool TryConvert(ConvertBinder binder, out object result) + { + // <pex> + if (binder == null) + throw new ArgumentNullException("binder"); + // </pex> + Type targetType = binder.Type; + + if ((targetType == typeof(IEnumerable)) || + (targetType == typeof(IEnumerable<KeyValuePair<string, object>>)) || + (targetType == typeof(IDictionary<string, object>)) || + (targetType == typeof(IDictionary))) + { + result = this; + return true; + } + + return base.TryConvert(binder, out result); + } + + /// <summary> + /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. + /// </summary> + /// <param name="binder">Provides information about the deletion.</param> + /// <returns> + /// Alwasy returns true. + /// </returns> + public override bool TryDeleteMember(DeleteMemberBinder binder) + { + // <pex> + if (binder == null) + throw new ArgumentNullException("binder"); + // </pex> + return _members.Remove(binder.Name); + } + + /// <summary> + /// Provides the implementation for operations that get a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for indexing operations. + /// </summary> + /// <param name="binder">Provides information about the operation.</param> + /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, <paramref name="indexes"/> is equal to 3.</param> + /// <param name="result">The result of the index operation.</param> + /// <returns> + /// Alwasy returns true. + /// </returns> + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + if (indexes == null) throw new ArgumentNullException("indexes"); + if (indexes.Length == 1) + { + result = ((IDictionary<string, object>)this)[(string)indexes[0]]; + return true; + } + result = null; + return true; + } + + /// <summary> + /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as getting a value for a property. + /// </summary> + /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param> + /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result"/>.</param> + /// <returns> + /// Alwasy returns true. + /// </returns> + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + object value; + if (_members.TryGetValue(binder.Name, out value)) + { + result = value; + return true; + } + result = null; + return true; + } + + /// <summary> + /// Provides the implementation for operations that set a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that access objects by a specified index. + /// </summary> + /// <param name="binder">Provides information about the operation.</param> + /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="indexes"/> is equal to 3.</param> + /// <param name="value">The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="value"/> is equal to 10.</param> + /// <returns> + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. + /// </returns> + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + { + if (indexes == null) throw new ArgumentNullException("indexes"); + if (indexes.Length == 1) + { + ((IDictionary<string, object>)this)[(string)indexes[0]] = value; + return true; + } + return base.TrySetIndex(binder, indexes, value); + } + + /// <summary> + /// Provides the implementation for operations that set member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as setting a value for a property. + /// </summary> + /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param> + /// <param name="value">The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, the <paramref name="value"/> is "Test".</param> + /// <returns> + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) + /// </returns> + public override bool TrySetMember(SetMemberBinder binder, object value) + { + // <pex> + if (binder == null) + throw new ArgumentNullException("binder"); + // </pex> + _members[binder.Name] = value; + return true; + } + + /// <summary> + /// Returns the enumeration of all dynamic member names. + /// </summary> + /// <returns> + /// A sequence that contains dynamic member names. + /// </returns> + public override IEnumerable<string> GetDynamicMemberNames() + { + foreach (var key in Keys) + yield return key; + } +#endif + } +} + +namespace GitHub +{ + /// <summary> + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). + /// All numbers are parsed to doubles. + /// </summary> + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + static class SimpleJson + { + private const int TOKEN_NONE = 0; + private const int TOKEN_CURLY_OPEN = 1; + private const int TOKEN_CURLY_CLOSE = 2; + private const int TOKEN_SQUARED_OPEN = 3; + private const int TOKEN_SQUARED_CLOSE = 4; + private const int TOKEN_COLON = 5; + private const int TOKEN_COMMA = 6; + private const int TOKEN_STRING = 7; + private const int TOKEN_NUMBER = 8; + private const int TOKEN_TRUE = 9; + private const int TOKEN_FALSE = 10; + private const int TOKEN_NULL = 11; + private const int BUILDER_CAPACITY = 2000; + + private static readonly char[] EscapeTable; + private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; + private static readonly string EscapeCharactersString = new string(EscapeCharacters); + + static SimpleJson() + { + EscapeTable = new char[93]; + EscapeTable['"'] = '"'; + EscapeTable['\\'] = '\\'; + EscapeTable['\b'] = 'b'; + EscapeTable['\f'] = 'f'; + EscapeTable['\n'] = 'n'; + EscapeTable['\r'] = 'r'; + EscapeTable['\t'] = 't'; + } + + /// <summary> + /// Parses the string json into a value + /// </summary> + /// <param name="json">A JSON string.</param> + /// <returns>An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false</returns> + public static object DeserializeObject(string json) + { + object obj; + if (TryDeserializeObject(json, out obj)) + return obj; + throw new SerializationException("Invalid JSON string"); + } + + /// <summary> + /// Try parsing the json string into a value. + /// </summary> + /// <param name="json"> + /// A JSON string. + /// </param> + /// <param name="obj"> + /// The object. + /// </param> + /// <returns> + /// Returns true if successfull otherwise false. + /// </returns> + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + public static bool TryDeserializeObject(string json, out object obj) + { + bool success = true; + if (json != null) + { + char[] charArray = json.ToCharArray(); + int index = 0; + obj = ParseValue(charArray, ref index, ref success); + } + else + obj = null; + + return success; + } + + public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) + { + object jsonObject = DeserializeObject(json); + return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) + ? jsonObject + : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); + } + + public static object DeserializeObject(string json, Type type) + { + return DeserializeObject(json, type, null); + } + + public static T DeserializeObject<T>(string json, IJsonSerializerStrategy jsonSerializerStrategy) + { + return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); + } + + public static T DeserializeObject<T>(string json) + { + return (T)DeserializeObject(json, typeof(T), null); + } + + /// <summary> + /// Converts a IDictionary<string,object> / IList<object> object into a JSON string + /// </summary> + /// <param name="json">A IDictionary<string,object> / IList<object></param> + /// <param name="jsonSerializerStrategy">Serializer strategy to use</param> + /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns> + public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) + { + StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); + bool success = SerializeValue(jsonSerializerStrategy, json, builder); + return (success ? builder.ToString() : null); + } + + public static string SerializeObject(object json) + { + return SerializeObject(json, CurrentJsonSerializerStrategy); + } + + public static string EscapeToJavascriptString(string jsonString) + { + if (string.IsNullOrEmpty(jsonString)) + return jsonString; + + StringBuilder sb = new StringBuilder(); + char c; + + for (int i = 0; i < jsonString.Length; ) + { + c = jsonString[i++]; + + if (c == '\\') + { + int remainingLength = jsonString.Length - i; + if (remainingLength >= 2) + { + char lookahead = jsonString[i]; + if (lookahead == '\\') + { + sb.Append('\\'); + ++i; + } + else if (lookahead == '"') + { + sb.Append("\""); + ++i; + } + else if (lookahead == 't') + { + sb.Append('\t'); + ++i; + } + else if (lookahead == 'b') + { + sb.Append('\b'); + ++i; + } + else if (lookahead == 'n') + { + sb.Append('\n'); + ++i; + } + else if (lookahead == 'r') + { + sb.Append('\r'); + ++i; + } + } + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + + static IDictionary<string, object> ParseObject(char[] json, ref int index, ref bool success) + { + IDictionary<string, object> table = new JsonObject(); + int token; + + // { + NextToken(json, ref index); + + bool done = false; + while (!done) + { + token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_CURLY_CLOSE) + { + NextToken(json, ref index); + return table; + } + else + { + // name + string name = ParseString(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + // : + token = NextToken(json, ref index); + if (token != TOKEN_COLON) + { + success = false; + return null; + } + // value + object value = ParseValue(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + table[name] = value; + } + } + return table; + } + + static JsonArray ParseArray(char[] json, ref int index, ref bool success) + { + JsonArray array = new JsonArray(); + + // [ + NextToken(json, ref index); + + bool done = false; + while (!done) + { + int token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_SQUARED_CLOSE) + { + NextToken(json, ref index); + break; + } + else + { + object value = ParseValue(json, ref index, ref success); + if (!success) + return null; + array.Add(value); + } + } + return array; + } + + static object ParseValue(char[] json, ref int index, ref bool success) + { + switch (LookAhead(json, index)) + { + case TOKEN_STRING: + return ParseString(json, ref index, ref success); + case TOKEN_NUMBER: + return ParseNumber(json, ref index, ref success); + case TOKEN_CURLY_OPEN: + return ParseObject(json, ref index, ref success); + case TOKEN_SQUARED_OPEN: + return ParseArray(json, ref index, ref success); + case TOKEN_TRUE: + NextToken(json, ref index); + return true; + case TOKEN_FALSE: + NextToken(json, ref index); + return false; + case TOKEN_NULL: + NextToken(json, ref index); + return null; + case TOKEN_NONE: + break; + } + success = false; + return null; + } + + static string ParseString(char[] json, ref int index, ref bool success) + { + StringBuilder s = new StringBuilder(BUILDER_CAPACITY); + char c; + + EatWhitespace(json, ref index); + + // " + c = json[index++]; + bool complete = false; + while (!complete) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + c = json[index++]; + if (c == '"') + s.Append('"'); + else if (c == '\\') + s.Append('\\'); + else if (c == '/') + s.Append('/'); + else if (c == 'b') + s.Append('\b'); + else if (c == 'f') + s.Append('\f'); + else if (c == 'n') + s.Append('\n'); + else if (c == 'r') + s.Append('\r'); + else if (c == 't') + s.Append('\t'); + else if (c == 'u') + { + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + // parse the 32 bit hex into an integer codepoint + uint codePoint; + if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) + return ""; + + // convert the integer codepoint to a unicode char and add to string + if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate + { + index += 4; // skip 4 chars + remainingLength = json.Length - index; + if (remainingLength >= 6) + { + uint lowCodePoint; + if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) + { + if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate + { + s.Append((char)codePoint); + s.Append((char)lowCodePoint); + index += 6; // skip 6 chars + continue; + } + } + } + success = false; // invalid surrogate pair + return ""; + } + s.Append(ConvertFromUtf32((int)codePoint)); + // skip 4 chars + index += 4; + } + else + break; + } + } + else + s.Append(c); + } + if (!complete) + { + success = false; + return null; + } + return s.ToString(); + } + + private static string ConvertFromUtf32(int utf32) + { + // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm + if (utf32 < 0 || utf32 > 0x10FFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); + if (0xD800 <= utf32 && utf32 <= 0xDFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); + if (utf32 < 0x10000) + return new string((char)utf32, 1); + utf32 -= 0x10000; + return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); + } + + static object ParseNumber(char[] json, ref int index, ref bool success) + { + EatWhitespace(json, ref index); + int lastIndex = GetLastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + object returnNumber; + string str = new string(json, index, charLength); + if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) + { + double number; + success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); + returnNumber = number; + } + else + { + long number; + success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); + returnNumber = number; + } + index = lastIndex + 1; + return returnNumber; + } + + static int GetLastIndexOfNumber(char[] json, int index) + { + int lastIndex; + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; + return lastIndex - 1; + } + + static void EatWhitespace(char[] json, ref int index) + { + for (; index < json.Length; index++) + if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; + } + + static int LookAhead(char[] json, int index) + { + int saveIndex = index; + return NextToken(json, ref saveIndex); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + static int NextToken(char[] json, ref int index) + { + EatWhitespace(json, ref index); + if (index == json.Length) + return TOKEN_NONE; + char c = json[index]; + index++; + switch (c) + { + case '{': + return TOKEN_CURLY_OPEN; + case '}': + return TOKEN_CURLY_CLOSE; + case '[': + return TOKEN_SQUARED_OPEN; + case ']': + return TOKEN_SQUARED_CLOSE; + case ',': + return TOKEN_COMMA; + case '"': + return TOKEN_STRING; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN_NUMBER; + case ':': + return TOKEN_COLON; + } + index--; + int remainingLength = json.Length - index; + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') + { + index += 5; + return TOKEN_FALSE; + } + } + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') + { + index += 4; + return TOKEN_TRUE; + } + } + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') + { + index += 4; + return TOKEN_NULL; + } + } + return TOKEN_NONE; + } + + static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) + { + bool success = true; + string stringValue = value as string; + if (stringValue != null) + success = SerializeString(stringValue, builder); + else + { + IDictionary<string, object> dict = value as IDictionary<string, object>; + if (dict != null) + { + success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); + } + else + { + IDictionary<string, string> stringDictionary = value as IDictionary<string, string>; + if (stringDictionary != null) + { + success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); + } + else + { + IEnumerable enumerableValue = value as IEnumerable; + if (enumerableValue != null) + success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); + else if (IsNumeric(value)) + success = SerializeNumber(value, builder); + else if (value is bool) + builder.Append((bool)value ? "true" : "false"); + else if (value == null) + builder.Append("null"); + else + { + object serializedObject; + success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); + if (success) + SerializeValue(jsonSerializerStrategy, serializedObject, builder); + } + } + } + } + return success; + } + + static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) + { + builder.Append("{"); + IEnumerator ke = keys.GetEnumerator(); + IEnumerator ve = values.GetEnumerator(); + bool first = true; + while (ke.MoveNext() && ve.MoveNext()) + { + object key = ke.Current; + object value = ve.Current; + if (!first) + builder.Append(","); + string stringKey = key as string; + if (stringKey != null) + SerializeString(stringKey, builder); + else + if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; + builder.Append(":"); + if (!SerializeValue(jsonSerializerStrategy, value, builder)) + return false; + first = false; + } + builder.Append("}"); + return true; + } + + static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) + { + builder.Append("["); + bool first = true; + foreach (object value in anArray) + { + if (!first) + builder.Append(","); + if (!SerializeValue(jsonSerializerStrategy, value, builder)) + return false; + first = false; + } + builder.Append("]"); + return true; + } + + static bool SerializeString(string aString, StringBuilder builder) + { + // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) + if (aString.IndexOfAny(EscapeCharacters) == -1) + { + builder.Append('"'); + builder.Append(aString); + builder.Append('"'); + + return true; + } + + builder.Append('"'); + int safeCharacterCount = 0; + char[] charArray = aString.ToCharArray(); + + for (int i = 0; i < charArray.Length; i++) + { + char c = charArray[i]; + + // Non ascii characters are fine, buffer them up and send them to the builder + // in larger chunks if possible. The escape table is a 1:1 translation table + // with \0 [default(char)] denoting a safe character. + if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) + { + safeCharacterCount++; + } + else + { + if (safeCharacterCount > 0) + { + builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); + safeCharacterCount = 0; + } + + builder.Append('\\'); + builder.Append(EscapeTable[c]); + } + } + + if (safeCharacterCount > 0) + { + builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); + } + + builder.Append('"'); + return true; + } + + static bool SerializeNumber(object number, StringBuilder builder) + { + if (number is long) + builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); + else if (number is ulong) + builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); + else if (number is int) + builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); + else if (number is uint) + builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); + else if (number is decimal) + builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); + else if (number is float) + builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); + else + builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); + return true; + } + + /// <summary> + /// Determines if a given object is numeric in any way + /// (can be integer, double, null, etc). + /// </summary> + static bool IsNumeric(object value) + { + if (value is sbyte) return true; + if (value is byte) return true; + if (value is short) return true; + if (value is ushort) return true; + if (value is int) return true; + if (value is uint) return true; + if (value is long) return true; + if (value is ulong) return true; + if (value is float) return true; + if (value is double) return true; + if (value is decimal) return true; + return false; + } + + private static IJsonSerializerStrategy _currentJsonSerializerStrategy; + public static IJsonSerializerStrategy CurrentJsonSerializerStrategy + { + get + { + return _currentJsonSerializerStrategy ?? + (_currentJsonSerializerStrategy = +#if SIMPLE_JSON_DATACONTRACT + DataContractJsonSerializerStrategy +#else + PocoJsonSerializerStrategy +#endif +); + } + set + { + _currentJsonSerializerStrategy = value; + } + } + + private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy + { + get + { + return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); + } + } + +#if SIMPLE_JSON_DATACONTRACT + + private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; + [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] + public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy + { + get + { + return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); + } + } + +#endif + } + + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + interface IJsonSerializerStrategy + { + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + bool TrySerializeNonPrimitiveObject(object input, out object output); + object DeserializeObject(object value, Type type); + } + + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + class PocoJsonSerializerStrategy : IJsonSerializerStrategy + { + internal IDictionary<Type, ReflectionUtils.ConstructorDelegate> ConstructorCache; + internal IDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>> GetCache; + internal IDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>> SetCache; + + internal static readonly Type[] EmptyTypes = new Type[0]; + internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; + + private static readonly string[] Iso8601Format = new string[] + { + @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", + @"yyyy-MM-dd\THH:mm:ss\Z", + @"yyyy-MM-dd\THH:mm:ssK" + }; + + public PocoJsonSerializerStrategy() + { + ConstructorCache = new ReflectionUtils.ThreadSafeDictionary<Type, ReflectionUtils.ConstructorDelegate>(ContructorDelegateFactory); + GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory); + SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory); + } + + protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) + { + return clrPropertyName; + } + + internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key) + { + return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes); + } + + internal virtual IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type) + { + IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanRead) + { + MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (getMethod.IsStatic || !getMethod.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); + } + return result; + } + + internal virtual IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type) + { + IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanWrite) + { + MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); + if (setMethod.IsStatic || !setMethod.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); + } + return result; + } + + public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) + { + return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public virtual object DeserializeObject(object value, Type type) + { + if (type == null) throw new ArgumentNullException("type"); + string str = value as string; + + if (type == typeof (Guid) && string.IsNullOrEmpty(str)) + return default(Guid); + + if (value == null) + return null; + + object obj = null; + + if (str != null) + { + if (str.Length != 0) // We know it can't be null now. + { + if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) + return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) + return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) + return new Guid(str); + if (type == typeof(Uri)) + { + bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); + + Uri result; + if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) + return result; + + return null; + } + + if (type == typeof(string)) + return str; + + return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); + } + else + { + if (type == typeof(Guid)) + obj = default(Guid); + else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) + obj = null; + else + obj = str; + } + // Empty string case + if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) + return str; + } + else if (value is bool) + return value; + + bool valueIsLong = value is long; + bool valueIsDouble = value is double; + if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) + return value; + if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) + { + obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) + ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) + : value; + } + else + { + IDictionary<string, object> objects = value as IDictionary<string, object>; + if (objects != null) + { + IDictionary<string, object> jsonObject = objects; + + if (ReflectionUtils.IsTypeDictionary(type)) + { + // if dictionary then + Type[] types = ReflectionUtils.GetGenericTypeArguments(type); + Type keyType = types[0]; + Type valueType = types[1]; + + Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + + IDictionary dict = (IDictionary)ConstructorCache[genericType](); + + foreach (KeyValuePair<string, object> kvp in jsonObject) + dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); + + obj = dict; + } + else + { + if (type == typeof(object)) + obj = value; + else + { + obj = ConstructorCache[type](); + foreach (KeyValuePair<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> setter in SetCache[type]) + { + object jsonValue; + if (jsonObject.TryGetValue(setter.Key, out jsonValue)) + { + jsonValue = DeserializeObject(jsonValue, setter.Value.Key); + setter.Value.Value(obj, jsonValue); + } + } + } + } + } + else + { + IList<object> valueAsList = value as IList<object>; + if (valueAsList != null) + { + IList<object> jsonObject = valueAsList; + IList list = null; + + if (type.IsArray) + { + list = (IList)ConstructorCache[type](jsonObject.Count); + int i = 0; + foreach (object o in jsonObject) + list[i++] = DeserializeObject(o, type.GetElementType()); + } + else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) + { + Type innerType = ReflectionUtils.GetGenericListElementType(type); + list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); + foreach (object o in jsonObject) + list.Add(DeserializeObject(o, innerType)); + } + obj = list; + } + } + return obj; + } + if (ReflectionUtils.IsNullableType(type)) + return ReflectionUtils.ToNullableType(obj, type); + return obj; + } + + protected virtual object SerializeEnum(Enum p) + { + return Convert.ToDouble(p, CultureInfo.InvariantCulture); + } + + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + protected virtual bool TrySerializeKnownTypes(object input, out object output) + { + bool returnValue = true; + if (input is DateTime) + output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); + else if (input is DateTimeOffset) + output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); + else if (input is Guid) + output = ((Guid)input).ToString("D"); + else if (input is Uri) + output = input.ToString(); + else + { + Enum inputEnum = input as Enum; + if (inputEnum != null) + output = SerializeEnum(inputEnum); + else + { + returnValue = false; + output = null; + } + } + return returnValue; + } + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + protected virtual bool TrySerializeUnknownTypes(object input, out object output) + { + if (input == null) throw new ArgumentNullException("input"); + output = null; + Type type = input.GetType(); + if (type.FullName == null) + return false; + IDictionary<string, object> obj = new JsonObject(); + IDictionary<string, ReflectionUtils.GetDelegate> getters = GetCache[type]; + foreach (KeyValuePair<string, ReflectionUtils.GetDelegate> getter in getters) + { + if (getter.Value != null) + obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); + } + output = obj; + return true; + } + } + +#if SIMPLE_JSON_DATACONTRACT + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy + { + public DataContractJsonSerializerStrategy() + { + GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory); + SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory); + } + + internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type) + { + bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; + if (!hasDataContract) + return base.GetterValueFactory(type); + string jsonKey; + IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanRead) + { + MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) + result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) + result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); + } + return result; + } + + internal override IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type) + { + bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; + if (!hasDataContract) + return base.SetterValueFactory(type); + string jsonKey; + IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanWrite) + { + MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); + if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) + result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) + result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); + } + // todo implement sorting for DATACONTRACT. + return result; + } + + private static bool CanAdd(MemberInfo info, out string jsonKey) + { + jsonKey = null; + if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) + return false; + DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); + if (dataMemberAttribute == null) + return false; + jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; + return true; + } + } + +#endif + + namespace Reflection + { + // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules + // that might be in place in the target project. + [GeneratedCode("reflection-utils", "1.0.0")] +#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC + public +#else + internal +#endif + class ReflectionUtils + { + private static readonly object[] EmptyObjects = new object[] { }; + + public delegate object GetDelegate(object source); + public delegate void SetDelegate(object source, object value); + public delegate object ConstructorDelegate(params object[] args); + + public delegate TValue ThreadSafeDictionaryValueFactory<TKey, TValue>(TKey key); + +#if SIMPLE_JSON_TYPEINFO + public static TypeInfo GetTypeInfo(Type type) + { + return type.GetTypeInfo(); + } +#else + public static Type GetTypeInfo(Type type) + { + return type; + } +#endif + + public static Attribute GetAttribute(MemberInfo info, Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (info == null || type == null || !info.IsDefined(type)) + return null; + return info.GetCustomAttribute(type); +#else + if (info == null || type == null || !Attribute.IsDefined(info, type)) + return null; + return Attribute.GetCustomAttribute(info, type); +#endif + } + + public static Type GetGenericListElementType(Type type) + { + IEnumerable<Type> interfaces; +#if SIMPLE_JSON_TYPEINFO + interfaces = type.GetTypeInfo().ImplementedInterfaces; +#else + interfaces = type.GetInterfaces(); +#endif + foreach (Type implementedInterface in interfaces) + { + if (IsTypeGeneric(implementedInterface) && + implementedInterface.GetGenericTypeDefinition() == typeof (IList<>)) + { + return GetGenericTypeArguments(implementedInterface)[0]; + } + } + return GetGenericTypeArguments(type)[0]; + } + + public static Attribute GetAttribute(Type objectType, Type attributeType) + { + +#if SIMPLE_JSON_TYPEINFO + if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) + return null; + return objectType.GetTypeInfo().GetCustomAttribute(attributeType); +#else + if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) + return null; + return Attribute.GetCustomAttribute(objectType, attributeType); +#endif + } + + public static Type[] GetGenericTypeArguments(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().GenericTypeArguments; +#else + return type.GetGenericArguments(); +#endif + } + + public static bool IsTypeGeneric(Type type) + { + return GetTypeInfo(type).IsGenericType; + } + + public static bool IsTypeGenericeCollectionInterface(Type type) + { + if (!IsTypeGeneric(type)) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(IList<>) + || genericDefinition == typeof(ICollection<>) + || genericDefinition == typeof(IEnumerable<>) +#if SIMPLE_JSON_READONLY_COLLECTIONS + || genericDefinition == typeof(IReadOnlyCollection<>) + || genericDefinition == typeof(IReadOnlyList<>) +#endif + ); + } + + public static bool IsAssignableFrom(Type type1, Type type2) + { + return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); + } + + public static bool IsTypeDictionary(Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + return true; +#else + if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) + return true; +#endif + if (!GetTypeInfo(type).IsGenericType) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + return genericDefinition == typeof(IDictionary<,>); + } + + public static bool IsNullableType(Type type) + { + return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + public static object ToNullableType(object obj, Type nullableType) + { + return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); + } + + public static bool IsValueType(Type type) + { + return GetTypeInfo(type).IsValueType; + } + + public static IEnumerable<ConstructorInfo> GetConstructors(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().DeclaredConstructors; +#else + return type.GetConstructors(); +#endif + } + + public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) + { + IEnumerable<ConstructorInfo> constructorInfos = GetConstructors(type); + int i; + bool matches; + foreach (ConstructorInfo constructorInfo in constructorInfos) + { + ParameterInfo[] parameters = constructorInfo.GetParameters(); + if (argsType.Length != parameters.Length) + continue; + + i = 0; + matches = true; + foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) + { + if (parameterInfo.ParameterType != argsType[i]) + { + matches = false; + break; + } + } + + if (matches) + return constructorInfo; + } + + return null; + } + + public static IEnumerable<PropertyInfo> GetProperties(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeProperties(); +#else + return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static IEnumerable<FieldInfo> GetFields(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeFields(); +#else + return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.GetMethod; +#else + return propertyInfo.GetGetMethod(true); +#endif + } + + public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.SetMethod; +#else + return propertyInfo.GetSetMethod(true); +#endif + } + + public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(constructorInfo); +#else + return GetConstructorByExpression(constructorInfo); +#endif + } + + public static ConstructorDelegate GetContructor(Type type, params Type[] argsType) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(type, argsType); +#else + return GetConstructorByExpression(type, argsType); +#endif + } + + public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) + { + return delegate(object[] args) { return constructorInfo.Invoke(args); }; + } + + public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + // if it's a value type (i.e., struct), it won't have a default constructor, so use Activator instead + return constructorInfo == null ? (type.IsValueType ? GetConstructorForValueType(type) : null) : GetConstructorByReflection(constructorInfo); + } + + static ConstructorDelegate GetConstructorForValueType(Type type) + { + return delegate (object[] args) { return Activator.CreateInstance(type); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) + { + ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); + ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); + Expression[] argsExp = new Expression[paramsInfo.Length]; + for (int i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + Expression paramAccessorExp = Expression.ArrayIndex(param, index); + Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); + argsExp[i] = paramCastExp; + } + NewExpression newExp = Expression.New(constructorInfo, argsExp); + Expression<Func<object[], object>> lambda = Expression.Lambda<Func<object[], object>>(newExp, param); + Func<object[], object> compiledLambda = lambda.Compile(); + return delegate(object[] args) { return compiledLambda(args); }; + } + + public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + // if it's a value type (i.e., struct), it won't have a default constructor, so use Activator instead + return constructorInfo == null ? (type.IsValueType ? GetConstructorForValueType(type) : null) : GetConstructorByExpression(constructorInfo); + } + +#endif + + public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(propertyInfo); +#else + return GetGetMethodByExpression(propertyInfo); +#endif + } + + public static GetDelegate GetGetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(fieldInfo); +#else + return GetGetMethodByExpression(fieldInfo); +#endif + } + + public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); + return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; + } + + public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source) { return fieldInfo.GetValue(source); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + Func<object, object> compiled = Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + + public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); + GetDelegate compiled = Expression.Lambda<GetDelegate>(Expression.Convert(member, typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + +#endif + + public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(propertyInfo); +#else + // if it's a struct, we want to use reflection, as linq expressions modify copies of the object and not the real thing + if (propertyInfo.DeclaringType.IsValueType) + return GetSetMethodByReflection(propertyInfo); + return GetSetMethodByExpression(propertyInfo); +#endif + } + + public static SetDelegate GetSetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(fieldInfo); +#else + // if it's a struct, we want to use reflection, as linq expressions modify copies of the object and not the real thing + if (fieldInfo.DeclaringType.IsValueType) + return GetSetMethodByReflection(fieldInfo); + return GetSetMethodByExpression(fieldInfo); +#endif + } + + public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); + return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; + } + + public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); + Action<object, object> compiled = Expression.Lambda<Action<object, object>>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + Action<object, object> compiled = Expression.Lambda<Action<object, object>>( + Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static BinaryExpression Assign(Expression left, Expression right) + { +#if SIMPLE_JSON_TYPEINFO + return Expression.Assign(left, right); +#else + MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); + BinaryExpression assignExpr = Expression.Add(left, right, assign); + return assignExpr; +#endif + } + + private static class Assigner<T> + { + public static T Assign(ref T left, T right) + { + return (left = right); + } + } + +#endif + + public sealed class ThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue> + { + private readonly object _lock = new object(); + private readonly ThreadSafeDictionaryValueFactory<TKey, TValue> _valueFactory; + private Dictionary<TKey, TValue> _dictionary; + + public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory<TKey, TValue> valueFactory) + { + _valueFactory = valueFactory; + } + + private TValue Get(TKey key) + { + if (_dictionary == null) + return AddValue(key); + TValue value; + if (!_dictionary.TryGetValue(key, out value)) + return AddValue(key); + return value; + } + + private TValue AddValue(TKey key) + { + TValue value = _valueFactory(key); + lock (_lock) + { + if (_dictionary == null) + { + _dictionary = new Dictionary<TKey, TValue>(); + _dictionary[key] = value; + } + else + { + TValue val; + if (_dictionary.TryGetValue(key, out val)) + return val; + Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(_dictionary); + dict[key] = value; + _dictionary = dict; + } + } + return value; + } + + public void Add(TKey key, TValue value) + { + throw new NotImplementedException(); + } + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public ICollection<TKey> Keys + { + get { return _dictionary.Keys; } + } + + public bool Remove(TKey key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { + value = this[key]; + return true; + } + + public ICollection<TValue> Values + { + get { return _dictionary.Values; } + } + + public TValue this[TKey key] + { + get { return Get(key); } + set { throw new NotImplementedException(); } + } + + public void Add(KeyValuePair<TKey, TValue> item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(KeyValuePair<TKey, TValue> item) + { + throw new NotImplementedException(); + } + + public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public int Count + { + get { return _dictionary.Count; } + } + + public bool IsReadOnly + { + get { throw new NotImplementedException(); } + } + + public bool Remove(KeyValuePair<TKey, TValue> item) + { + throw new NotImplementedException(); + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + } + + } + } +} +// ReSharper restore LoopCanBeConvertedToQuery +// ReSharper restore RedundantExplicitArrayCreation +// ReSharper restore SuggestUseVarKeywordEvident diff --git a/src/GitHub.Exports/UI/IDialogView.cs b/src/GitHub.Exports/UI/IDialogView.cs new file mode 100644 index 0000000000..eebbf85f66 --- /dev/null +++ b/src/GitHub.Exports/UI/IDialogView.cs @@ -0,0 +1,14 @@ +using System; + +namespace GitHub.UI +{ + public interface IDialogView : IView, IHasDone, IHasCancel + { + IObservable<bool> IsBusy { get; } + } + + public interface ICanLoad + { + IObservable<ViewWithData> Load { get; } + } +} diff --git a/src/GitHub.Exports/UI/IUIController.cs b/src/GitHub.Exports/UI/IUIController.cs deleted file mode 100644 index 24e086d5dc..0000000000 --- a/src/GitHub.Exports/UI/IUIController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using GitHub.Models; -using System; -using System.Threading.Tasks; -using System.Windows.Controls; - -namespace GitHub.UI -{ - public interface IUIController - { - IObservable<UserControl> SelectFlow(UIControllerFlow choice); - /// <summary> - /// Allows listening to the completion state of the ui flow - whether - /// it was completed because it was cancelled or whether it succeeded. - /// </summary> - /// <returns>true for success, false for cancel</returns> - IObservable<bool> ListenToCompletionState(); - void Start(IConnection connection); - void Stop(); - bool IsStopped { get; } - } - - public enum UIControllerFlow - { - None = 0, - Authentication = 1, - Create = 2, - Clone = 3, - Publish - } -} diff --git a/src/GitHub.Exports/UI/IView.cs b/src/GitHub.Exports/UI/IView.cs deleted file mode 100644 index 5c7697e755..0000000000 --- a/src/GitHub.Exports/UI/IView.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace GitHub.UI -{ - public interface IView - { - object ViewModel { get; set; } - IObservable<object> Done { get; } - IObservable<object> Cancel { get; } - IObservable<bool> IsBusy { get; } - } -} diff --git a/src/GitHub.Exports/UI/Octicon.cs b/src/GitHub.Exports/UI/Octicon.cs index 3633af118f..0064141067 100644 --- a/src/GitHub.Exports/UI/Octicon.cs +++ b/src/GitHub.Exports/UI/Octicon.cs @@ -5,9 +5,6 @@ namespace GitHub.UI public enum Octicon { alert, - alignment_align, - alignment_aligned_to, - alignment_unalign, arrow_down, arrow_left, arrow_right, @@ -16,7 +13,9 @@ public enum Octicon arrow_small_right, arrow_small_up, arrow_up, - beer, + beaker, + bell, + bold, book, bookmark, briefcase, @@ -37,13 +36,13 @@ public enum Octicon cloud_download, cloud_upload, code, - color_mode, comment_discussion, comment, credit_card, dash, dashboard, database, + desktop_download, device_camera_video, device_camera, device_desktop, @@ -83,18 +82,14 @@ public enum Octicon history, home, horizontal_rule, - hourglass, hubot, inbox, info, issue_closed, issue_opened, issue_reopened, + italic, jersey, - jump_down, - jump_left, - jump_right, - jump_up, key, keyboard, law, @@ -105,6 +100,7 @@ public enum Octicon list_unordered, location, @lock, + logo_gist, logo_github, mail_read, mail_reply, @@ -113,14 +109,9 @@ public enum Octicon markdown, megaphone, mention, - microscope, milestone, mirror, mortar_board, - move_down, - move_left, - move_right, - move_up, mute, no_newline, octoface, @@ -130,17 +121,11 @@ public enum Octicon pencil, person, pin, - playback_fast_forward, - playback_pause, - playback_play, - playback_rewind, plug, plus, - podium, primitive_dot, primitive_square, pulse, - puzzle, question, quote, radio_tower, @@ -153,23 +138,25 @@ public enum Octicon rocket, rss, ruby, - screen_full, - screen_normal, search, server, settings, + shield, sign_in, sign_out, - split, + smiley, squirrel, star, - steps, stop, sync, tag, + tasklist, telescope, terminal, + text_size, three_bars, + thumbsdown, + thumbsup, tools, trashcan, triangle_down, @@ -178,7 +165,9 @@ public enum Octicon triangle_up, unfold, unmute, + verified, versions, + watch, x, zap, } diff --git a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs new file mode 100644 index 0000000000..6e44ca5bf0 --- /dev/null +++ b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs @@ -0,0 +1,111 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// Describes the ways that the display of <see cref="IGitHubPaneViewModel.Content"/> can + /// be overridden. + /// </summary> + public enum ContentOverride + { + /// <summary> + /// No override, display the content. + /// </summary> + None, + + /// <summary> + /// Display a spinner instead of the content. + /// </summary> + Spinner, + + /// <summary> + /// Display an error instead of the content. + /// </summary> + Error + } + + /// <summary> + /// The view model for the GitHub Pane. + /// </summary> + public interface IGitHubPaneViewModel : IViewModel + { + /// <summary> + /// Gets the connection to the current repository. + /// </summary> + IConnection Connection { get; } + + /// <summary> + /// Gets the content to display in the GitHub pane. + /// </summary> + IViewModel Content { get; } + + /// <summary> + /// Gets a value describing whether to display the <see cref="Content"/> or to override + /// it with another view. + /// </summary> + ContentOverride ContentOverride { get; } + + /// <summary> + /// Gets a value indicating whether search is available on the current page. + /// </summary> + bool IsSearchEnabled { get; } + + /// <summary> + /// Gets the local repository. + /// </summary> + ILocalRepositoryModel LocalRepository { get; } + + /// <summary> + /// Gets or sets the search query for the current page. + /// </summary> + string SearchQuery { get; set; } + + /// <summary> + /// Gets the title to display in the GitHub pane header. + /// </summary> + string Title { get; } + + /// <summary> + /// Initializes the view model. + /// </summary> + Task InitializeAsync(IServiceProvider paneServiceProvider); + + /// <summary> + /// Navigates to a GitHub Pane URL. + /// </summary> + /// <param name="uri">The URL.</param> + Task NavigateTo(Uri uri); + + /// <summary> + /// Shows the pull reqest list in the GitHub pane. + /// </summary> + Task ShowPullRequests(); + + /// <summary> + /// Shows the details for a pull request in the GitHub pane. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="repo">The repository name.</param> + /// <param name="number">The pull rqeuest number.</param> + Task ShowPullRequest(string owner, string repo, int number); + + /// <summary> + /// Shows the pull requests reviews authored by a user. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="repo">The repository name.</param> + /// <param name="number">The pull rqeuest number.</param> + /// <param name="login">The user login.</param> + Task ShowPullRequestReviews(string owner, string repo, int number, string login); + + /// <summary> + /// Shows a pane authoring a pull request review. + /// </summary> + /// <param name="owner">The repository owner.</param> + /// <param name="repo">The repository name.</param> + /// <param name="number">The pull rqeuest number.</param> + Task ShowPullRequestReviewAuthoring(string owner, string repo, int number); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubToolWindowManager.cs b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubToolWindowManager.cs new file mode 100644 index 0000000000..cd1c71701b --- /dev/null +++ b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubToolWindowManager.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace GitHub.ViewModels.GitHubPane +{ + /// <summary> + /// The Visual Studio service interface for accessing the GitHub Pane. + /// </summary> + [Guid("FC9EC5B5-C297-4548-A229-F8E16365543C")] + [ComVisible(true)] + public interface IGitHubToolWindowManager + { + /// <summary> + /// Ensure that the GitHub pane is created and visible. + /// </summary> + /// <returns>The view model for the GitHub Pane.</returns> + Task<IGitHubPaneViewModel> ShowGitHubPane(); + } +} diff --git a/src/GitHub.Exports/ViewModels/IConnectionInitializedViewModel.cs b/src/GitHub.Exports/ViewModels/IConnectionInitializedViewModel.cs new file mode 100644 index 0000000000..4889067b7f --- /dev/null +++ b/src/GitHub.Exports/ViewModels/IConnectionInitializedViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.ViewModels +{ + /// <summary> + /// Represents a view model that requires initialization with a connection. + /// </summary> + public interface IConnectionInitializedViewModel : IViewModel + { + /// <summary> + /// Initializes the view model with the specified connection. + /// </summary> + /// <param name="connection">The connection.</param> + /// <returns>A task tracking the initialization.</returns> + Task InitializeAsync(IConnection connection); + } +} diff --git a/src/GitHub.Exports/ViewModels/IGitHubConnectSection.cs b/src/GitHub.Exports/ViewModels/IGitHubConnectSection.cs index 19d0a41711..b07a96f587 100644 --- a/src/GitHub.Exports/ViewModels/IGitHubConnectSection.cs +++ b/src/GitHub.Exports/ViewModels/IGitHubConnectSection.cs @@ -1,15 +1,21 @@ using GitHub.Models; -using System.Collections.ObjectModel; +using System.Windows.Input; namespace GitHub.VisualStudio.TeamExplorer.Connect { public interface IGitHubConnectSection { void DoCreate(); - void DoClone(); void SignOut(); void Login(); + void Retry(); bool OpenRepository(); + string ErrorMessage { get; } IConnection SectionConnection { get; } + bool IsLoggingIn { get; } + bool ShowLogin { get; } + bool ShowLogout { get; } + bool ShowRetry { get; } + ICommand Clone { get; } } } diff --git a/src/GitHub.Exports/ViewModels/IGitHubHomeSection.cs b/src/GitHub.Exports/ViewModels/IGitHubHomeSection.cs index 6b046678f3..5b93aae4c3 100644 --- a/src/GitHub.Exports/ViewModels/IGitHubHomeSection.cs +++ b/src/GitHub.Exports/ViewModels/IGitHubHomeSection.cs @@ -1,4 +1,5 @@ -using GitHub.UI; +using System.Windows.Input; +using GitHub.UI; namespace GitHub.VisualStudio.TeamExplorer.Home { @@ -18,5 +19,20 @@ public interface IGitHubHomeSection /// The icon to show next to a repository name. It indicates whether it's private, public, a fork, etc. /// </summary> Octicon Icon { get; } + + /// <summary> + /// Indicate if the user is Logged in or not. + /// </summary> + bool IsLoggedIn { get; } + + /// <summary> + /// Gets a command which opens the repository on GitHub when executed. + /// </summary> + ICommand OpenOnGitHub { get; } + + /// <summary> + /// Start the login flow. + /// </summary> + void Login(); } } diff --git a/src/GitHub.Exports/ViewModels/IGitHubInvitationSection.cs b/src/GitHub.Exports/ViewModels/IGitHubInvitationSection.cs index d1aa1a466c..80cde72153 100644 --- a/src/GitHub.Exports/ViewModels/IGitHubInvitationSection.cs +++ b/src/GitHub.Exports/ViewModels/IGitHubInvitationSection.cs @@ -1,4 +1,5 @@ -using GitHub.UI; +using System.Threading.Tasks; +using GitHub.UI; namespace GitHub.VisualStudio.TeamExplorer { @@ -9,8 +10,7 @@ public interface IGitHubInvitationSection bool ShowLogin { get; } bool ShowSignup { get; } bool ShowGetStarted { get; } - void Connect(); + Task Connect(); void SignUp(); - void Login(); } } diff --git a/src/GitHub.Exports/ViewModels/IGitHubPaneViewModel.cs b/src/GitHub.Exports/ViewModels/IGitHubPaneViewModel.cs deleted file mode 100644 index 3aabf10394..0000000000 --- a/src/GitHub.Exports/ViewModels/IGitHubPaneViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Windows.Input; - -namespace GitHub.ViewModels -{ - public interface IGitHubPaneViewModel : IViewModel - { - string ActiveRepoName { get; } - } -} \ No newline at end of file diff --git a/src/GitHub.Exports/ViewModels/IInfoPanel.cs b/src/GitHub.Exports/ViewModels/IInfoPanel.cs new file mode 100644 index 0000000000..4b8972df5a --- /dev/null +++ b/src/GitHub.Exports/ViewModels/IInfoPanel.cs @@ -0,0 +1,14 @@ +namespace GitHub.ViewModels +{ + public interface IInfoPanel + { + string Message { get; set; } + MessageType MessageType { get; set; } + } + + public enum MessageType + { + Information, + Warning + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/ViewModels/ILoginViewModel.cs b/src/GitHub.Exports/ViewModels/ILoginViewModel.cs deleted file mode 100644 index a94233d556..0000000000 --- a/src/GitHub.Exports/ViewModels/ILoginViewModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using GitHub.Authentication; - -namespace GitHub.ViewModels -{ - public interface ILoginViewModel : IViewModel - { - /// <summary> - /// Gets an observable sequence which produces an authentication - /// result every time a log in attempt through this control success - /// or fails. - /// </summary> - IObservable<AuthenticationResult> AuthenticationResults { get; } - } -} diff --git a/src/GitHub.Exports/ViewModels/IOpenInBrowser.cs b/src/GitHub.Exports/ViewModels/IOpenInBrowser.cs new file mode 100644 index 0000000000..7eaaeb4828 --- /dev/null +++ b/src/GitHub.Exports/ViewModels/IOpenInBrowser.cs @@ -0,0 +1,15 @@ +using System; + +namespace GitHub.ViewModels +{ + /// <summary> + /// Represents a view model with a URL that can be opened in the system web browser. + /// </summary> + public interface IOpenInBrowser + { + /// <summary> + /// Gets the URL. + /// </summary> + Uri WebUrl { get; } + } +} diff --git a/src/GitHub.Exports/ViewModels/IViewModel.cs b/src/GitHub.Exports/ViewModels/IViewModel.cs index c1edd6aac1..1137370652 100644 --- a/src/GitHub.Exports/ViewModels/IViewModel.cs +++ b/src/GitHub.Exports/ViewModels/IViewModel.cs @@ -1,11 +1,14 @@ -using System.Windows.Input; +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace GitHub.ViewModels { - public interface IViewModel + /// <summary> + /// Base interface for all view models. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces")] + public interface IViewModel : INotifyPropertyChanged { - string Title { get; } - ICommand Cancel { get; } - bool IsShowing { get; } - } -} \ No newline at end of file + } +} diff --git a/src/GitHub.Exports/packages.config b/src/GitHub.Exports/packages.config new file mode 100644 index 0000000000..7be8f695d2 --- /dev/null +++ b/src/GitHub.Exports/packages.config @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Setup.Configuration.Interop" version="1.15.103" targetFramework="net461" developmentDependency="true" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" /> + <package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> + <package id="SimpleJson" version="0.38.0" targetFramework="net461" /> + <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" /> + <package id="VSSDK.IDE.12" version="12.0.4" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.Extensions.Reactive/FodyWeavers.xml b/src/GitHub.Extensions.Reactive/FodyWeavers.xml deleted file mode 100644 index 9321cb912f..0000000000 --- a/src/GitHub.Extensions.Reactive/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard/> -</Weavers> \ No newline at end of file diff --git a/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj b/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj index 991b0776d8..513b10e112 100644 --- a/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj +++ b/src/GitHub.Extensions.Reactive/GitHub.Extensions.Reactive.csproj @@ -9,51 +9,44 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub.Extensions.Reactive</RootNamespace> <AssemblyName>GitHub.Extensions.Reactive</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <NuGetPackageImportStamp>83c06b42</NuGetPackageImportStamp> - <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>true</RunCodeAnalysis> <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <Private>True</Private> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> - </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="Microsoft.CSharp" /> @@ -81,9 +74,6 @@ <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> @@ -97,9 +87,6 @@ <ItemGroup> <None Include="packages.config" /> </ItemGroup> - <ItemGroup> - <Content Include="FodyWeavers.xml" /> - </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> @@ -113,15 +100,12 @@ <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>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}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> - </Target> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/GitHub.Extensions.Reactive/packages.config b/src/GitHub.Extensions.Reactive/packages.config index ee7a943de7..00e9d82f18 100644 --- a/src/GitHub.Extensions.Reactive/packages.config +++ b/src/GitHub.Extensions.Reactive/packages.config @@ -1,7 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Fody" version="1.28.0" targetFramework="net45" developmentDependency="true" userInstalled="true" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net45" developmentDependency="true" userInstalled="true" /> <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> diff --git a/src/GitHub.Extensions/EnumerableExtensions.cs b/src/GitHub.Extensions/EnumerableExtensions.cs index 6a7450c527..1f188193a6 100644 --- a/src/GitHub.Extensions/EnumerableExtensions.cs +++ b/src/GitHub.Extensions/EnumerableExtensions.cs @@ -21,5 +21,14 @@ public static IEnumerable<TSource> Except<TSource>( } + public static class StackExtensions + { + public static T TryPeek<T>(this Stack<T> stack) where T : class + { + if (stack.Count > 0) + return stack.Peek(); + return default(T); + } + } } diff --git a/src/GitHub.Extensions/FodyWeavers.xml b/src/GitHub.Extensions/FodyWeavers.xml deleted file mode 100644 index 9321cb912f..0000000000 --- a/src/GitHub.Extensions/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard/> -</Weavers> \ No newline at end of file diff --git a/src/GitHub.Extensions/GitHub.Extensions.csproj b/src/GitHub.Extensions/GitHub.Extensions.csproj index 51ccd8795e..89d0090a7a 100644 --- a/src/GitHub.Extensions/GitHub.Extensions.csproj +++ b/src/GitHub.Extensions/GitHub.Extensions.csproj @@ -9,55 +9,50 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub.Extensions</RootNamespace> <AssemblyName>GitHub.Extensions</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <NuGetPackageImportStamp>ae382bf5</NuGetPackageImportStamp> - <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>true</RunCodeAnalysis> <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>true</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> - <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Provider, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> - <Private>False</Private> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.Composition" /> @@ -69,11 +64,10 @@ <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> - <Compile Include="GitRepoExtensions.cs" /> + <Compile Include="IReadOnlyObservableCollection.cs" /> <Compile Include="LambdaComparer.cs" /> + <Compile Include="ObservableCollectionEx.cs" /> + <Compile Include="ObservableCollectionExtensions.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> @@ -88,11 +82,6 @@ <Compile Include="TaskExtensions.cs" /> <Compile Include="UriExtensions.cs" /> </ItemGroup> - <ItemGroup> - <Content Include="FodyWeavers.xml"> - <SubType>Designer</SubType> - </Content> - </ItemGroup> <ItemGroup> <None Include="packages.config" /> </ItemGroup> @@ -101,15 +90,12 @@ <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> <Name>Splat-Net45</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>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}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> - </Target> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/GitHub.Extensions/GitRepoExtensions.cs b/src/GitHub.Extensions/GitRepoExtensions.cs deleted file mode 100644 index fb6508645e..0000000000 --- a/src/GitHub.Extensions/GitRepoExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; -using NullGuard; -using System; - -namespace GitHub.Extensions -{ - public static class GitRepoExtensions - { - public static bool Compare([AllowNull] this IGitRepositoryInfo lhs, [AllowNull]IGitRepositoryInfo rhs) - { - if (lhs == null && rhs == null) - return true; - if (lhs != null && rhs != null) - return String.Equals(lhs.RepositoryPath, rhs.RepositoryPath, StringComparison.CurrentCultureIgnoreCase); - return false; - } - } -} diff --git a/src/GitHub.Extensions/Guard.cs b/src/GitHub.Extensions/Guard.cs index cdb385b0ec..240dbd4cbd 100644 --- a/src/GitHub.Extensions/Guard.cs +++ b/src/GitHub.Extensions/Guard.cs @@ -1,5 +1,4 @@ -using NullGuard; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -9,16 +8,10 @@ namespace GitHub.Extensions { public static class Guard { - public static void ArgumentNotNull([AllowNull]object value, string name) + public static void ArgumentNotNull(object value, string name) { if (value != null) return; string message = String.Format(CultureInfo.InvariantCulture, "Failed Null Check on '{0}'", name); -#if DEBUG - if (!InUnitTestRunner()) - { - Debug.Fail(message); - } -#endif throw new ArgumentNullException(name, message); } @@ -27,30 +20,18 @@ public static void ArgumentNonNegative(int value, string name) if (value > -1) return; var message = String.Format(CultureInfo.InvariantCulture, "The value for '{0}' must be non-negative", name); -#if DEBUG - if (!InUnitTestRunner()) - { - Debug.Fail(message); - } -#endif throw new ArgumentException(message, name); } /// <summary> - /// Checks a string argument to ensure it isn't null or empty. + /// Checks a string argument to ensure it isn't null or empty. /// </summary> /// <param name = "value">The argument value to check.</param> /// <param name = "name">The name of the argument.</param> public static void ArgumentNotEmptyString(string value, string name) { - if (value.Length > 0) return; + if (value?.Length > 0) return; string message = String.Format(CultureInfo.InvariantCulture, "The value for '{0}' must not be empty", name); -#if DEBUG - if (!InUnitTestRunner()) - { - Debug.Fail(message); - } -#endif throw new ArgumentException(message, name); } @@ -62,12 +43,6 @@ public static void ArgumentInRange(int value, int minValue, string name) value, name, minValue); -#if DEBUG - if (!InUnitTestRunner()) - { - Debug.Fail(message); - } -#endif throw new ArgumentOutOfRangeException(name, message); } @@ -80,19 +55,11 @@ public static void ArgumentInRange(int value, int minValue, int maxValue, string name, minValue, maxValue); -#if DEBUG - if (!InUnitTestRunner()) - { - Debug.Fail(message); - } -#endif throw new ArgumentOutOfRangeException(name, message); } // Borrowed from Splat. - static bool InUnitTestRunner() - { - return Splat.ModeDetector.InUnitTestRunner(); - } + + public static bool InUnitTestRunner => Splat.ModeDetector.InUnitTestRunner(); } } diff --git a/src/GitHub.Extensions/IReadOnlyObservableCollection.cs b/src/GitHub.Extensions/IReadOnlyObservableCollection.cs new file mode 100644 index 0000000000..847512ce92 --- /dev/null +++ b/src/GitHub.Extensions/IReadOnlyObservableCollection.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace GitHub.Extensions +{ + /// <summary> + /// Represents a read-only interface to an <see cref="ObservableCollection{T}"/> + /// </summary> + /// <typeparam name="T">The type of elements in the collection.</typeparam> + public interface IReadOnlyObservableCollection<T> : IReadOnlyList<T>, + INotifyCollectionChanged, + INotifyPropertyChanged + { + } +} \ No newline at end of file diff --git a/src/GitHub.Extensions/LambdaComparer.cs b/src/GitHub.Extensions/LambdaComparer.cs index aa97072a9c..289ddb2117 100644 --- a/src/GitHub.Extensions/LambdaComparer.cs +++ b/src/GitHub.Extensions/LambdaComparer.cs @@ -1,6 +1,6 @@ -using NullGuard; using System; using System.Collections.Generic; +using GitHub.Extensions; namespace GitHub.Collections { @@ -10,29 +10,33 @@ public class LambdaComparer<T> : IEqualityComparer<T>, IComparer<T> readonly Func<T, int> lambdaHash; public LambdaComparer(Func<T, T, int> lambdaComparer) : - this(lambdaComparer, o => 0) + this(lambdaComparer, null) { } - LambdaComparer(Func<T, T, int> lambdaComparer, Func<T, int> lambdaHash) + public LambdaComparer(Func<T, T, int> lambdaComparer, Func<T, int> lambdaHash) { + Guard.ArgumentNotNull(lambdaComparer, nameof(lambdaComparer)); + this.lambdaComparer = lambdaComparer; this.lambdaHash = lambdaHash; } - public int Compare([AllowNull] T x, [AllowNull] T y) + public int Compare(T x, T y) { return lambdaComparer(x, y); } - public bool Equals([AllowNull] T x, [AllowNull] T y) + public bool Equals(T x, T y) { return lambdaComparer(x, y) == 0; } - public int GetHashCode([AllowNull] T obj) + public int GetHashCode(T obj) { - return lambdaHash(obj); + return lambdaHash != null + ? lambdaHash(obj) + : obj?.GetHashCode() ?? 0; } } } \ No newline at end of file diff --git a/src/GitHub.Extensions/ObservableCollectionEx.cs b/src/GitHub.Extensions/ObservableCollectionEx.cs new file mode 100644 index 0000000000..dc23b14cdb --- /dev/null +++ b/src/GitHub.Extensions/ObservableCollectionEx.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace GitHub.Extensions +{ + /// <summary> + /// A non-braindead way to expose a read-only view on an <see cref="ObservableCollection{T}"/>. + /// </summary> + /// <typeparam name="T">The type of elements in the collection.</typeparam> + /// <remarks> + /// <see cref="ReadOnlyObservableCollection{T}"/> fails in its only purpose by Not. Freaking. + /// Exposing. INotifyCollectionChanged. We define our own <see cref="IReadOnlyObservableCollection{T}"/> + /// type and use this class to expose it. Seriously. + /// </remarks> + public class ObservableCollectionEx<T> : ObservableCollection<T>, IReadOnlyObservableCollection<T> + { + /// <summary> + /// Initializes a new instance of the <see cref="ObservableCollectionEx{T}"/> class. + /// </summary> + public ObservableCollectionEx() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ObservableCollectionEx{T}"/> class that + /// contains elements copied from the specified list. + /// </summary> + public ObservableCollectionEx(List<T> list) + : base(list) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ObservableCollectionEx{T}"/> class that + /// contains elements copied from the specified collection. + /// </summary> + public ObservableCollectionEx(IEnumerable<T> collection) + : base(collection) + { + } + + /// <summary> + /// Adds the elements of the specified collection to the end of the list. + /// </summary> + /// <param name="items">The items to add.</param> + public void AddRange(IEnumerable<T> items) + { + foreach (var item in items) Add(item); + } + } +} \ No newline at end of file diff --git a/src/GitHub.Extensions/ObservableCollectionExtensions.cs b/src/GitHub.Extensions/ObservableCollectionExtensions.cs new file mode 100644 index 0000000000..0a761eb256 --- /dev/null +++ b/src/GitHub.Extensions/ObservableCollectionExtensions.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections; +using System.Collections.Specialized; + +namespace GitHub.Extensions +{ + public static class ObservableCollectionExtensions + { + /// <summary> + /// Invokes an action for each item in a collection and subsequently each item added or + /// removed from the collection. + /// </summary> + /// <typeparam name="T">The type of the collection items.</typeparam> + /// <param name="collection">The collection.</param> + /// <param name="added"> + /// An action called initially for each item in the collection and subsequently for each + /// item added to the collection. + /// </param> + /// <param name="removed"> + /// An action called for each item removed from the collection. + /// </param> + /// <param name="reset"> + /// An action called when the collection is reset. This will be followed by calls to + /// <paramref name="added"/> for each item present in the collection after the reset. + /// </param> + /// <returns>A disposable used to terminate the subscription.</returns> + public static IDisposable ForEachItem<T>( + this IReadOnlyObservableCollection<T> collection, + Action<T> added, + Action<T> removed, + Action reset) + { + Action<IList> add = items => + { + foreach (T item in items) + { + added(item); + } + }; + + Action<IList> remove = items => + { + for (var i = items.Count - 1; i >= 0; --i) + { + removed((T)items[i]); + } + }; + + NotifyCollectionChangedEventHandler handler = (_, e) => + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + add(e.NewItems); + break; + + case NotifyCollectionChangedAction.Move: + case NotifyCollectionChangedAction.Replace: + remove(e.OldItems); + add(e.NewItems); + break; + + case NotifyCollectionChangedAction.Remove: + remove(e.OldItems); + break; + + case NotifyCollectionChangedAction.Reset: + if (reset == null) + { + throw new InvalidOperationException( + "Reset called on collection without reset handler."); + } + + reset(); + add((IList)collection); + break; + } + }; + + add((IList)collection); + collection.CollectionChanged += handler; + + return new ActionDisposable(() => collection.CollectionChanged -= handler); + } + + class ActionDisposable : IDisposable + { + Action dispose; + + public ActionDisposable(Action dispose) + { + this.dispose = dispose; + } + + public void Dispose() + { + dispose(); + } + } + } +} diff --git a/src/GitHub.Extensions/ReflectionExtensions.cs b/src/GitHub.Extensions/ReflectionExtensions.cs index dd72f690bc..e69c65f1d5 100644 --- a/src/GitHub.Extensions/ReflectionExtensions.cs +++ b/src/GitHub.Extensions/ReflectionExtensions.cs @@ -1,5 +1,4 @@ -using NullGuard; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -12,6 +11,8 @@ public static class ReflectionExtensions { public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) { + Guard.ArgumentNotNull(assembly, nameof(assembly)); + try { return assembly.GetTypes(); @@ -24,6 +25,9 @@ public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) public static bool HasInterface(this Type type, Type targetInterface) { + Guard.ArgumentNotNull(type, nameof(type)); + Guard.ArgumentNotNull(targetInterface, nameof(targetInterface)); + if (targetInterface.IsAssignableFrom(type)) return true; return type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == targetInterface); @@ -32,6 +36,9 @@ public static bool HasInterface(this Type type, Type targetInterface) [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] public static string GetCustomAttributeValue<T>(this Assembly assembly, string propertyName) where T : Attribute { + Guard.ArgumentNotNull(assembly, nameof(assembly)); + Guard.ArgumentNotEmptyString(propertyName, nameof(propertyName)); + if (assembly == null || string.IsNullOrEmpty(propertyName)) return string.Empty; object[] attributes = assembly.GetCustomAttributes(typeof(T), false); @@ -46,28 +53,5 @@ public static string GetCustomAttributeValue<T>(this Assembly assembly, string p var value = propertyInfo.GetValue(attribute, null); return value.ToString(); } - - [return: AllowNull] - public static object GetValueForProperty(this Type type, object instance, string propName) - { - var prop = type.GetProperty(propName); - Debug.Assert(prop != null, string.Format(CultureInfo.InvariantCulture, "'{0}' {1} not found in assembly '{2}'. Check if it's been moved or mistyped.", - propName, "property", type.Assembly.GetCustomAttributeValue<AssemblyFileVersionAttribute>("Version"))); - if (prop == null) - return null; - var getm = prop.GetGetMethod(); - Debug.Assert(prop != null, string.Format(CultureInfo.InvariantCulture, "'{0}' {1} not found in assembly '{2}'. Check if it's been moved or mistyped.", - propName, "getter", type.Assembly.GetCustomAttributeValue<AssemblyFileVersionAttribute>("Version"))); - if (getm == null) - return null; - try { - return getm.Invoke(instance, null); - } - catch (Exception ex) - { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' {1} in assembly '{2}' threw an exception. {3}.", - propName, "getter", type.Assembly.GetCustomAttributeValue<AssemblyFileVersionAttribute>("Version"), ex)); - } - } } } diff --git a/src/GitHub.Extensions/StringExtensions.cs b/src/GitHub.Extensions/StringExtensions.cs index 1dd95e3332..9e3c649e25 100644 --- a/src/GitHub.Extensions/StringExtensions.cs +++ b/src/GitHub.Extensions/StringExtensions.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; -using NullGuard; +using System.Text.RegularExpressions; +using GitHub.Logging; +using Splat; namespace GitHub.Extensions { @@ -12,45 +16,40 @@ public static class StringExtensions { public static bool Contains(this string s, string expectedSubstring, StringComparison comparison) { + Guard.ArgumentNotNull(s, nameof(s)); + Guard.ArgumentNotNull(expectedSubstring, nameof(expectedSubstring)); + return s.IndexOf(expectedSubstring, comparison) > -1; } public static bool ContainsAny(this string s, IEnumerable<char> characters) { + Guard.ArgumentNotNull(s, nameof(s)); + return s.IndexOfAny(characters.ToArray()) > -1; } - public static string DebugRepresentation([AllowNull]this string s) + public static string DebugRepresentation(this string s) { s = s ?? "(null)"; return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", s); } - public static bool IsNotNullOrEmpty(this string s) - { - return !string.IsNullOrEmpty(s); - } - - public static bool IsNullOrEmpty([AllowNull]this string s) + public static string ToNullIfEmpty(this string s) { - return string.IsNullOrEmpty(s); + return String.IsNullOrEmpty(s) ? null : s; } - [return: AllowNull] - public static string ToNullIfEmpty([AllowNull]this string s) - { - return s.IsNullOrEmpty() ? null : s; - } - - public static bool StartsWith([AllowNull]this string s, char c) + public static bool StartsWith(this string s, char c) { if (String.IsNullOrEmpty(s)) return false; return s.First() == c; } - [return: AllowNull] - public static string RightAfter([AllowNull]this string s, string search) + public static string RightAfter(this string s, string search) { + Guard.ArgumentNotNull(search, nameof(search)); + if (s == null) return null; int lastIndex = s.IndexOf(search, StringComparison.OrdinalIgnoreCase); if (lastIndex < 0) @@ -59,9 +58,10 @@ public static string RightAfter([AllowNull]this string s, string search) return s.Substring(lastIndex + search.Length); } - [return: AllowNull] public static string RightAfterLast(this string s, string search) { + Guard.ArgumentNotNull(search, nameof(search)); + if (s == null) return null; int lastIndex = s.LastIndexOf(search, StringComparison.OrdinalIgnoreCase); if (lastIndex < 0) @@ -70,9 +70,10 @@ public static string RightAfterLast(this string s, string search) return s.Substring(lastIndex + search.Length); } - [return: AllowNull] - public static string LeftBeforeLast([AllowNull]this string s, string search) + public static string LeftBeforeLast(this string s, string search) { + Guard.ArgumentNotNull(search, nameof(search)); + if (s == null) return null; int lastIndex = s.LastIndexOf(search, StringComparison.OrdinalIgnoreCase); if (lastIndex < 0) @@ -82,47 +83,43 @@ public static string LeftBeforeLast([AllowNull]this string s, string search) } // Returns a file name even if the path is FUBAR. - [return: AllowNull] - public static string ParseFileName([AllowNull]this string path) + public static string ParseFileName(this string path) { if (path == null) return null; return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).RightAfterLast(Path.DirectorySeparatorChar + ""); } // Returns the parent directory even if the path is FUBAR. - [return: AllowNull] - public static string ParseParentDirectory([AllowNull]this string path) + public static string ParseParentDirectory(this string path) { if (path == null) return null; return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).LeftBeforeLast(Path.DirectorySeparatorChar + ""); } - [return: AllowNull] - public static string EnsureStartsWith([AllowNull]this string s, char c) + public static string EnsureStartsWith(this string s, char c) { if (s == null) return null; return c + s.TrimStart(c); } // Ensures the string ends with the specified character. - [return: AllowNull] - public static string EnsureEndsWith([AllowNull]this string s, char c) + public static string EnsureEndsWith(this string s, char c) { if (s == null) return null; return s.TrimEnd(c) + c; } - [return: AllowNull] - public static string NormalizePath([AllowNull]this string path) + public static string NormalizePath(this string path) { if (String.IsNullOrEmpty(path)) return null; return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } - [return: AllowNull] - public static string TrimEnd([AllowNull]this string s, string suffix) + public static string TrimEnd(this string s, string suffix) { + Guard.ArgumentNotNull(suffix, nameof(suffix)); + if (s == null) return null; if (!s.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) return s; @@ -132,6 +129,8 @@ public static string TrimEnd([AllowNull]this string s, string suffix) public static string RemoveSurroundingQuotes(this string s) { + Guard.ArgumentNotNull(s, nameof(s)); + if (s.Length < 2) return s; @@ -148,6 +147,8 @@ public static string RemoveSurroundingQuotes(this string s) public static Int32 ToInt32(this string s) { + Guard.ArgumentNotNull(s, nameof(s)); + Int32 val; return Int32.TryParse(s, out val) ? val : 0; } @@ -160,6 +161,8 @@ public static Int32 ToInt32(this string s) /// <returns>A wrapped string using the platform's default newline character. This string will end in a newline.</returns> public static string Wrap(this string text, int maxLength = 72) { + Guard.ArgumentNotNull(text, nameof(text)); + if (text.Length == 0) return string.Empty; var sb = new StringBuilder(); @@ -192,9 +195,53 @@ public static string Wrap(this string text, int maxLength = 72) public static Uri ToUriSafe(this string url) { + Guard.ArgumentNotNull(url, nameof(url)); + Uri uri; Uri.TryCreate(url, UriKind.Absolute, out uri); return uri; } + + /// <summary> + /// Returns an alphanumeric sentence cased string with dashes and underscores as spaces. + /// </summary> + /// <param name="s">The string to format.</param> + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] + public static string Humanize(this string s) + { + if (String.IsNullOrWhiteSpace(s)) + { + return s; + } + + var matches = Regex.Matches(s, @"[a-zA-Z\d]{1,}", RegexOptions.None); + + if (matches.Count == 0) + { + return s; + } + + var result = matches.Cast<Match>().Select(match => match.Value.ToLower(CultureInfo.InvariantCulture)); + var combined = String.Join(" ", result); + return Char.ToUpper(combined[0], CultureInfo.InvariantCulture) + combined.Substring(1); + } + + /// <summary> + /// Generates a SHA256 hash for a string. + /// </summary> + /// <param name="input">The input string.</param> + /// <returns>The SHA256 hash.</returns> + public static string GetSha256Hash(this string input) + { + Guard.ArgumentNotNull(input, nameof(input)); + + using (var sha256 = SHA256.Create()) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hash = sha256.ComputeHash(bytes); + + return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + } + } } } diff --git a/src/GitHub.Extensions/TaskExtensions.cs b/src/GitHub.Extensions/TaskExtensions.cs index 4e404dc126..a878b8ae28 100644 --- a/src/GitHub.Extensions/TaskExtensions.cs +++ b/src/GitHub.Extensions/TaskExtensions.cs @@ -1,14 +1,18 @@ using System; using System.Threading.Tasks; -using NullGuard; +using GitHub.Logging; +using Serilog; namespace GitHub.Extensions { public static class TaskExtensions { - [return: AllowNull] + static readonly ILogger log = LogManager.ForContext(typeof(TaskExtensions)); + public static async Task<T> Catch<T>(this Task<T> source, Func<Exception, T> handler = null) { + Guard.ArgumentNotNull(source, nameof(source)); + try { return await source; @@ -20,8 +24,53 @@ public static async Task<T> Catch<T>(this Task<T> source, Func<Exception, T> han return default(T); } } - public static void Forget(this Task task) + + public static async Task Catch(this Task source, Action<Exception> handler = null) { + Guard.ArgumentNotNull(source, nameof(source)); + + try + { + await source; + } + catch (Exception ex) + { + if (handler != null) + handler(ex); + } + } + + /// <summary> + /// Allow task to run and log any exceptions. + /// </summary> + /// <param name="task">The <see cref="Task"/> to log exceptions from.</param> + /// <param name="errorMessage">An error message to log if the task throws.</param> + public static void Forget(this Task task, string errorMessage = "") + { + task.ContinueWith(t => + { + if (t.IsFaulted) + { + log.Error(t.Exception, errorMessage); + } + }); + } + + /// <summary> + /// Allow task to run and log any exceptions. + /// </summary> + /// <param name="task">The task to log exceptions from.</param> + /// <param name="log">The logger to use.</param> + /// <param name="errorMessage">The error message to log if the task throws.</param> + public static void Forget(this Task task, ILogger log, string errorMessage = "") + { + task.ContinueWith(t => + { + if (t.IsFaulted) + { + log.Error(t.Exception, errorMessage); + } + }); } } } diff --git a/src/GitHub.Extensions/packages.config b/src/GitHub.Extensions/packages.config index 2a53434606..b8376bd218 100644 --- a/src/GitHub.Extensions/packages.config +++ b/src/GitHub.Extensions/packages.config @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Fody" version="1.28.0" targetFramework="net45" developmentDependency="true" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net45" developmentDependency="true" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> </packages> \ No newline at end of file diff --git a/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs b/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs new file mode 100644 index 0000000000..d86aeb9114 --- /dev/null +++ b/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using GitHub.Commands; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.Tags; +using GitHub.Logging; +using GitHub.Services; +using GitHub.Services.Vssdk.Commands; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.TextManager.Interop; +using Serilog; + +namespace GitHub.InlineReviews.Commands +{ + /// <summary> + /// Base class for commands that navigate between inline comments. + /// </summary> + abstract class InlineCommentNavigationCommand : VsCommand<InlineCommentNavigationParams> + { + static readonly ILogger log = LogManager.ForContext<InlineCommentNavigationCommand>(); + readonly IGitHubServiceProvider serviceProvider; + readonly IViewTagAggregatorFactoryService tagAggregatorFactory; + readonly IInlineCommentPeekService peekService; + + /// <summary> + /// Initializes a new instance of the <see cref="InlineCommentNavigationCommand"/> class. + /// </summary> + /// <param name="serviceProvider"></param> + /// <param name="tagAggregatorFactory">The tag aggregator factory.</param> + /// <param name="peekService">The peek service.</param> + /// <param name="commandSet">The GUID of the group the command belongs to.</param> + /// <param name="commandId">The numeric identifier of the command.</param> + protected InlineCommentNavigationCommand( + IGitHubServiceProvider serviceProvider, + IViewTagAggregatorFactoryService tagAggregatorFactory, + IInlineCommentPeekService peekService, + Guid commandSet, + int commandId) + : base(commandSet, commandId) + { + this.serviceProvider = serviceProvider; + this.tagAggregatorFactory = tagAggregatorFactory; + this.peekService = peekService; + BeforeQueryStatus += QueryStatus; + } + + /// <summary> + /// Gets the text buffer position for the line specified in the parameters or from the + /// cursor point if no line is specified or <paramref name="parameter"/> is null. + /// </summary> + /// <param name="parameter">The parameters.</param> + /// <param name="textView">The text view.</param> + /// <returns></returns> + protected int GetCursorPoint(ITextView textView, InlineCommentNavigationParams parameter) + { + if (parameter?.FromLine != null) + { + return parameter.FromLine > -1 ? GetCursorPoint(textView, parameter.FromLine.Value) : -1; + } + else + { + return textView.Caret.Position.BufferPosition.Position; + } + } + + /// <summary> + /// Gets the text buffer position for the specified line. + /// </summary> + /// <param name="textView">The text view containing the buffer</param> + /// <param name="lineNumber">The 0-based line number.</param> + /// <returns></returns> + protected int GetCursorPoint(ITextView textView, int lineNumber) + { + lineNumber = Math.Max(0, Math.Min(lineNumber, textView.TextSnapshot.LineCount - 1)); + return textView.TextSnapshot.GetLineFromLineNumber(lineNumber).Start.Position; + } + + /// <summary> + /// Gets the currently active text view(s) from Visual Studio. + /// </summary> + /// <returns> + /// Zero, one or two active <see cref="ITextView"/> objects. + /// </returns> + /// <remarks> + /// This method will return a single text view for a normal code window, or a pair of text + /// views if the currently active text view is a difference view in side by side mode, with + /// the first item being the side that currently has focus. If there is no active text view, + /// an empty collection will be returned. + /// </remarks> + protected IEnumerable<ITextView> GetCurrentTextViews() + { + var result = new List<ITextView>(); + + try + { + var monitorSelection = (IVsMonitorSelection)serviceProvider.GetService(typeof(SVsShellMonitorSelection)); + if (monitorSelection == null) + { + return result; + } + + object curDocument; + if (ErrorHandler.Failed(monitorSelection.GetCurrentElementValue((uint)VSConstants.VSSELELEMID.SEID_DocumentFrame, out curDocument))) + { + return result; + } + + IVsWindowFrame frame = curDocument as IVsWindowFrame; + if (frame == null) + { + return result; + } + + object docView = null; + if (ErrorHandler.Failed(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView))) + { + return result; + } + + if (docView is IVsDifferenceCodeWindow) + { + var diffWindow = (IVsDifferenceCodeWindow)docView; + + switch (diffWindow.DifferenceViewer.ViewMode) + { + case DifferenceViewMode.Inline: + result.Add(diffWindow.DifferenceViewer.InlineView); + break; + case DifferenceViewMode.SideBySide: + switch (diffWindow.DifferenceViewer.ActiveViewType) + { + case DifferenceViewType.LeftView: + result.Add(diffWindow.DifferenceViewer.LeftView); + result.Add(diffWindow.DifferenceViewer.RightView); + break; + case DifferenceViewType.RightView: + result.Add(diffWindow.DifferenceViewer.RightView); + result.Add(diffWindow.DifferenceViewer.LeftView); + break; + } + result.Add(diffWindow.DifferenceViewer.LeftView); + break; + case DifferenceViewMode.RightViewOnly: + result.Add(diffWindow.DifferenceViewer.RightView); + break; + } + } + else if (docView is IVsCodeWindow) + { + IVsTextView textView; + if (ErrorHandler.Failed(((IVsCodeWindow)docView).GetPrimaryView(out textView))) + { + return result; + } + + var model = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); + var adapterFactory = model.GetService<IVsEditorAdaptersFactoryService>(); + var wpfTextView = adapterFactory.GetWpfTextView(textView); + result.Add(wpfTextView); + } + } + catch (Exception e) + { + log.Error(e, "Exception in InlineCommentNavigationCommand.GetCurrentTextViews()"); + } + + return result; + } + + /// <summary> + /// Creates a tag aggregator for the specified text view. + /// </summary> + /// <param name="textView">The text view.</param> + /// <returns>The tag aggregator</returns> + protected ITagAggregator<InlineCommentTag> CreateTagAggregator(ITextView textView) + { + return tagAggregatorFactory.CreateTagAggregator<InlineCommentTag>(textView); + } + + /// <summary> + /// Gets the <see cref="ShowInlineCommentTag"/>s for the specified text view. + /// </summary> + /// <param name="textViews">The active text views.</param> + /// <returns>A collection of <see cref="ITagInfo"/> objects, ordered by line.</returns> + protected IReadOnlyList<ITagInfo> GetTags(IEnumerable<ITextView> textViews) + { + var result = new List<ITagInfo>(); + + foreach (var textView in textViews) + { + var tagAggregator = CreateTagAggregator(textView); + var span = new SnapshotSpan(textView.TextSnapshot, 0, textView.TextSnapshot.Length); + var mappingSpan = textView.BufferGraph.CreateMappingSpan(span, SpanTrackingMode.EdgeExclusive); + var tags = tagAggregator.GetTags(mappingSpan) + .Select(x => new TagInfo + { + TextView = textView, + Point = Map(x.Span.Start, textView.TextSnapshot), + Tag = x.Tag as ShowInlineCommentTag, + }) + .Where(x => x.Tag != null && x.Point.HasValue); + result.AddRange(tags); + } + + result.Sort(TagInfoComparer.Instance); + return result; + } + + /// <summary> + /// Shows the inline comments for the specified tag in a peek view. + /// </summary> + /// <param name="textView">The text view containing the tag</param> + /// <param name="tag">The inline comment tag</param> + /// <param name="parameter">The navigation parameter detailing a search from the specified tag</param> + /// <param name="allTextViews">The full list of text views</param> + protected void ShowPeekComments( + InlineCommentNavigationParams parameter, + ITextView textView, + ShowInlineCommentTag tag, + IEnumerable<ITextView> allTextViews) + { + foreach (var other in allTextViews) + { + if (other != textView) + { + peekService.Hide(other); + } + } + + var point = peekService.Show(textView, tag); + + if (parameter?.MoveCursor != false) + { + var caretPoint = textView.BufferGraph.MapUpToSnapshot( + point.GetPoint(point.TextBuffer.CurrentSnapshot), + PointTrackingMode.Negative, + PositionAffinity.Successor, + textView.TextSnapshot); + + if (caretPoint.HasValue) + { + (textView as FrameworkElement)?.Focus(); + textView.Caret.MoveTo(caretPoint.Value); + } + } + } + + SnapshotPoint? Map(IMappingPoint p, ITextSnapshot textSnapshot) + { + return p.GetPoint(textSnapshot.TextBuffer, PositionAffinity.Predecessor); + } + + void QueryStatus(object sender, EventArgs e) + { + var tags = GetTags(GetCurrentTextViews()); + Enabled = tags.Count > 0; + } + + protected interface ITagInfo + { + ITextView TextView { get; } + ShowInlineCommentTag Tag { get; } + SnapshotPoint Point { get; } + } + + class TagInfo : ITagInfo + { + public ITextView TextView { get; set; } + public ShowInlineCommentTag Tag { get; set; } + public SnapshotPoint? Point { get; set; } + + SnapshotPoint ITagInfo.Point => Point.Value; + } + + class TagInfoComparer : IComparer<ITagInfo> + { + public static readonly TagInfoComparer Instance = new TagInfoComparer(); + public int Compare(ITagInfo x, ITagInfo y) => x.Point.Position - y.Point.Position; + } + } +} diff --git a/src/GitHub.InlineReviews/Commands/NextInlineCommentCommand.cs b/src/GitHub.InlineReviews/Commands/NextInlineCommentCommand.cs new file mode 100644 index 0000000000..42ea92d69a --- /dev/null +++ b/src/GitHub.InlineReviews/Commands/NextInlineCommentCommand.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.VisualStudio; +using GitHub.InlineReviews.Services; +using Microsoft.VisualStudio.Text.Tagging; +using GitHub.Commands; +using GitHub.Services; + +namespace GitHub.InlineReviews.Commands +{ + /// <summary> + /// Navigates to and opens the the next inline comment thread in the currently active text view. + /// </summary> + [Export(typeof(INextInlineCommentCommand))] + class NextInlineCommentCommand : InlineCommentNavigationCommand, INextInlineCommentCommand + { + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.CommandSetGuid; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.NextInlineCommentId; + + /// <summary> + /// Initializes a new instance of the <see cref="NextInlineCommentCommand"/> class. + /// </summary> + /// <param name="serviceProvider">The GitHub service provider.</param> + /// <param name="tagAggregatorFactory">The tag aggregator factory.</param> + /// <param name="peekService">The peek service.</param> + [ImportingConstructor] + protected NextInlineCommentCommand( + IGitHubServiceProvider serviceProvider, + IViewTagAggregatorFactoryService tagAggregatorFactory, + IInlineCommentPeekService peekService) + : base(serviceProvider, tagAggregatorFactory, peekService, CommandSet, CommandId) + { + } + + /// <summary> + /// Executes the command. + /// </summary> + /// <returns>A task that tracks the execution of the command.</returns> + public override Task Execute(InlineCommentNavigationParams parameter) + { + var textViews = GetCurrentTextViews().ToList(); + var tags = GetTags(textViews); + + if (tags.Count > 0) + { + var cursorPoint = GetCursorPoint(textViews[0], parameter); + var next = tags.FirstOrDefault(x => x.Point > cursorPoint) ?? tags.First(); + ShowPeekComments(parameter, next.TextView, next.Tag, textViews); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.InlineReviews/Commands/PreviousInlineCommentCommand.cs b/src/GitHub.InlineReviews/Commands/PreviousInlineCommentCommand.cs new file mode 100644 index 0000000000..782d838f00 --- /dev/null +++ b/src/GitHub.InlineReviews/Commands/PreviousInlineCommentCommand.cs @@ -0,0 +1,63 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Text.Tagging; +using GitHub.VisualStudio; +using GitHub.InlineReviews.Services; +using GitHub.Commands; +using GitHub.Services; + +namespace GitHub.InlineReviews.Commands +{ + /// <summary> + /// Navigates to and opens the the previous inline comment thread in the currently active text view. + /// </summary> + [Export(typeof(IPreviousInlineCommentCommand))] + class PreviousInlineCommentCommand : InlineCommentNavigationCommand, IPreviousInlineCommentCommand + { + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.CommandSetGuid; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.PreviousInlineCommentId; + + /// <summary> + /// Initializes a new instance of the <see cref="PreviousInlineCommentCommand"/> class. + /// </summary> + /// <param name="serviceProvider">The GitHub service provider.</param> + /// <param name="tagAggregatorFactory">The tag aggregator factory.</param> + /// <param name="peekService">The peek service.</param> + [ImportingConstructor] + protected PreviousInlineCommentCommand( + IGitHubServiceProvider serviceProvider, + IViewTagAggregatorFactoryService tagAggregatorFactory, + IInlineCommentPeekService peekService) + : base(serviceProvider, tagAggregatorFactory, peekService, CommandSet, CommandId) + { + } + + /// <summary> + /// Executes the command. + /// </summary> + /// <returns>A task that tracks the execution of the command.</returns> + public override Task Execute(InlineCommentNavigationParams parameter) + { + var textViews = GetCurrentTextViews().ToList(); + var tags = GetTags(textViews); + + if (tags.Count > 0) + { + var cursorPoint = GetCursorPoint(textViews[0], parameter); + var next = tags.LastOrDefault(x => x.Point < cursorPoint) ?? tags.Last(); + ShowPeekComments(parameter, next.TextView, next.Tag, textViews); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs b/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs new file mode 100644 index 0000000000..01f817b2ed --- /dev/null +++ b/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using System.ComponentModel.Composition; +using GitHub.Commands; +using GitHub.Services; +using GitHub.VisualStudio; +using GitHub.InlineReviews.Margins; +using GitHub.Services.Vssdk.Commands; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; + +namespace GitHub.InlineReviews.Commands +{ + [Export(typeof(IToggleInlineCommentMarginCommand))] + public class ToggleInlineCommentMarginCommand : VsCommand, IToggleInlineCommentMarginCommand + { + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.CommandSetGuid; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.ToggleInlineCommentMarginId; + + readonly Lazy<IVsTextManager> textManager; + readonly Lazy<IVsEditorAdaptersFactoryService> editorAdapter; + readonly Lazy<IUsageTracker> usageTracker; + + [ImportingConstructor] + public ToggleInlineCommentMarginCommand( + IGitHubServiceProvider serviceProvider, + Lazy<IVsEditorAdaptersFactoryService> editorAdapter, + Lazy<IUsageTracker> usageTracker) : base(CommandSet, CommandId) + { + textManager = new Lazy<IVsTextManager>(() => serviceProvider.GetService<SVsTextManager, IVsTextManager>()); + this.editorAdapter = editorAdapter; + this.usageTracker = usageTracker; + } + + public override Task Execute() + { + usageTracker.Value.IncrementCounter(x => x.ExecuteToggleInlineCommentMarginCommand); + + IVsTextView activeView = null; + if (textManager.Value.GetActiveView(1, null, out activeView) == VSConstants.S_OK) + { + var wpfTextView = editorAdapter.Value.GetWpfTextView(activeView); + var options = wpfTextView.Options; + var enabled = options.GetOptionValue(InlineCommentTextViewOptions.MarginEnabledId); + options.SetOptionValue(InlineCommentTextViewOptions.MarginEnabledId, !enabled); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj new file mode 100644 index 0000000000..36dd35ed3b --- /dev/null +++ b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj @@ -0,0 +1,501 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> + <Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props" Condition="Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props')" /> + <PropertyGroup> + <!-- This is added to prevent forced migrations in Visual Studio 2012 and newer --> + <MinimumVisualStudioVersion Condition="'$(VisualStudioVersion)' != ''">$(VisualStudioVersion)</MinimumVisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + <UseCodebase>true</UseCodebase> + <TargetFrameworkProfile /> + </PropertyGroup> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <SchemaVersion>2.0</SchemaVersion> + <ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <ProjectGuid>{7F5ED78B-74A3-4406-A299-70CFB5885B8B}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.InlineReviews</RootNamespace> + <AssemblyName>GitHub.InlineReviews</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <GeneratePkgDefFile>true</GeneratePkgDefFile> + <IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer> + <IncludeDebugSymbolsInVSIXContainer>true</IncludeDebugSymbolsInVSIXContainer> + <IncludeDebugSymbolsInLocalVSIXDeployment>true</IncludeDebugSymbolsInLocalVSIXDeployment> + <CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory> + <CopyOutputSymbolsToOutputDirectory>true</CopyOutputSymbolsToOutputDirectory> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <CreateVsixContainer>False</CreateVsixContainer> + <DeployExtension>False</DeployExtension> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>TRACE;DEBUG</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>TRACE;DEBUG;CODE_ANALYSIS</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> + </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <ItemGroup> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Commands\ToggleInlineCommentMarginCommand.cs" /> + <Compile Include="Commands\InlineCommentNavigationCommand.cs" /> + <Compile Include="Commands\PreviousInlineCommentCommand.cs" /> + <Compile Include="Commands\NextInlineCommentCommand.cs" /> + <Compile Include="Margins\InlineCommentTextViewOptions.cs" /> + <Compile Include="Margins\PullRequestFileMargin.cs" /> + <Compile Include="Margins\PullRequestFileMarginProvider.cs" /> + <Compile Include="Glyph\GlyphData.cs" /> + <Compile Include="Glyph\GlyphMargin.cs" /> + <Compile Include="Glyph\GlyphMarginVisualManager.cs" /> + <Compile Include="Glyph\IGlyphFactory.cs" /> + <Compile Include="Margins\InlineCommentMargin.cs" /> + <Compile Include="Margins\InlineCommentMarginVisible.cs" /> + <Compile Include="Margins\InlineCommentMarginEnabled.cs" /> + <Compile Include="Services\CommentService.cs" /> + <Compile Include="Services\ICommentService.cs" /> + <Compile Include="PullRequestStatusBarPackage.cs" /> + <Compile Include="InlineReviewsPackage.cs" /> + <Compile Include="Models\InlineCommentThreadModel.cs" /> + <Compile Include="Models\PullRequestSessionLiveFile.cs" /> + <Compile Include="Models\PullRequestSessionFile.cs" /> + <Compile Include="Services\PullRequestStatusBarManager.cs" /> + <Compile Include="Tags\MouseEnterAndLeaveEventRouter.cs" /> + <Compile Include="Peek\InlineCommentPeekableItem.cs" /> + <Compile Include="Peek\InlineCommentPeekableItemSource.cs" /> + <Compile Include="Peek\InlineCommentPeekableItemSourceProvider.cs" /> + <Compile Include="Peek\InlineCommentPeekableResultSource.cs" /> + <Compile Include="Peek\InlineCommentPeekRelationship.cs" /> + <Compile Include="Peek\InlineCommentPeekResult.cs" /> + <Compile Include="Peek\InlineCommentPeekResultPresentation.cs" /> + <Compile Include="Peek\InlineCommentPeekResultPresenter.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SampleData\CommentThreadViewModelDesigner.cs" /> + <Compile Include="Services\IInlineCommentPeekService.cs" /> + <Compile Include="Services\IPullRequestSessionService.cs" /> + <Compile Include="Services\InlineCommentPeekService.cs" /> + <Compile Include="Services\PullRequestSession.cs" /> + <Compile Include="Services\PullRequestSessionManager.cs" /> + <Compile Include="Margins\InlineCommentMarginProvider.cs" /> + <Compile Include="Services\PullRequestSessionService.cs" /> + <Compile Include="ViewModels\PullRequestFileMarginViewModel.cs" /> + <Compile Include="ViewModels\CommentViewModel.cs" /> + <Compile Include="ViewModels\ICommentThreadViewModel.cs" /> + <Compile Include="ViewModels\CommentThreadViewModel.cs" /> + <Compile Include="ViewModels\InlineCommentPeekViewModel.cs" /> + <Compile Include="ViewModels\IPullRequestReviewCommentViewModel.cs" /> + <Compile Include="ViewModels\NewInlineCommentThreadViewModel.cs" /> + <Compile Include="ViewModels\PullRequestReviewCommentViewModel.cs" /> + <Compile Include="ViewModels\PullRequestStatusViewModel.cs" /> + <Compile Include="Views\PullRequestFileMarginView.xaml.cs"> + <DependentUpon>PullRequestFileMarginView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GlyphMarginGrid.xaml.cs"> + <DependentUpon>GlyphMarginGrid.xaml</DependentUpon> + </Compile> + <Compile Include="Views\InlineCommentPeekView.xaml.cs"> + <DependentUpon>InlineCommentPeekView.xaml</DependentUpon> + </Compile> + <Compile Include="SampleData\CommentViewModelDesigner.cs" /> + <Compile Include="Services\DiffService.cs" /> + <Compile Include="Services\IDiffService.cs" /> + <Compile Include="Tags\AddInlineCommentTag.cs" /> + <Compile Include="Tags\AddInlineCommentGlyph.xaml.cs"> + <DependentUpon>AddInlineCommentGlyph.xaml</DependentUpon> + </Compile> + <Compile Include="Tags\ShowInlineCommentGlyph.xaml.cs"> + <DependentUpon>ShowInlineCommentGlyph.xaml</DependentUpon> + </Compile> + <Compile Include="Tags\InlineCommentGlyphFactory.cs" /> + <Compile Include="Tags\InlineCommentTag.cs" /> + <Compile Include="Tags\ShowInlineCommentTag.cs" /> + <Compile Include="Tags\InlineCommentTagger.cs" /> + <Compile Include="Tags\InlineCommentTaggerProvider.cs" /> + <Compile Include="ViewModels\InlineCommentThreadViewModel.cs" /> + <Compile Include="ViewModels\ICommentViewModel.cs" /> + <Compile Include="Views\CommentThreadView.xaml.cs"> + <DependentUpon>CommentThreadView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\CommentView.xaml.cs"> + <DependentUpon>CommentView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\PullRequestStatusView.xaml.cs"> + <DependentUpon>PullRequestStatusView.xaml</DependentUpon> + </Compile> + <Compile Include="VisualStudioExtensions.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + <None Include="source.extension.vsixmanifest"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1CE2D235-8072-4649-BA5A-CFB1AF8776E0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions.Reactive\GitHub.Extensions.Reactive.csproj"> + <Project>{6559E128-8B40-49A5-85A8-05565ED0C7E3}</Project> + <Name>GitHub.Extensions.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Services.Vssdk\GitHub.Services.Vssdk.csproj"> + <Project>{2d3d2834-33be-45ca-b3cc-12f853557d7b}</Project> + <Name>GitHub.Services.Vssdk</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj"> + <Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project> + <Name>GitHub.UI.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> + <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> + <Name>GitHub.UI</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj"> + <Project>{d1dfbb0c-b570-4302-8f1e-2e3a19c41961}</Project> + <Name>GitHub.VisualStudio.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Reference Include="EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> + <Reference Include="EnvDTE100, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> + <Reference Include="EnvDTE80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> + <Reference Include="EnvDTE90, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Markdig, Version=0.13.0.0, Culture=neutral, PublicKeyToken=870da25a133885f8, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Markdig.Signed.0.13.0\lib\net40\Markdig.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Markdig.Wpf, Version=0.2.1.0, Culture=neutral, PublicKeyToken=a0d0cdbebd8d164b, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Markdig.Wpf.Signed.0.2.1\lib\net452\Markdig.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="Microsoft.VisualStudio.CommandBars, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Data" /> + <Reference Include="System.Design" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Numerics" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> + </Reference> + <Reference Include="System.Windows.Forms" /> + <Reference Include="System.Xaml" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <VSCTCompile Include="InlineReviewsPackage.vsct"> + <ResourceName>Menus.ctmenu</ResourceName> + <SubType>Designer</SubType> + </VSCTCompile> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="VSPackage.resx"> + <MergeWithCTO>true</MergeWithCTO> + <ManifestResourceName>VSPackage</ManifestResourceName> + <SubType>Designer</SubType> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <Page Include="Properties\DesignTimeResources.xaml" Condition="'$(DesignTime)'=='true' OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)') AND '$(BuildingInsideVisualStudio)'!='true' AND '$(BuildingInsideExpressionBlend)'!='true')"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + <ContainsDesignTimeResources>true</ContainsDesignTimeResources> + </Page> + <Page Include="Views\PullRequestFileMarginView.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\GlyphMarginGrid.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\InlineCommentPeekView.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Tags\AddInlineCommentGlyph.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Tags\ShowInlineCommentGlyph.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\CommentThreadView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\CommentView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\PullRequestStatusView.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> + <ItemGroup> + <Content Include="Resources\logo_32x32%402x.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + </ItemGroup> + <ItemGroup /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != '' And '$(NCrunch)' != '1'" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets'))" /> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> + </Target> + <Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets" Condition="Exists('..\..\packages\Microsoft.VSSDK.BuildTools.14.3.25407\build\Microsoft.VSSDK.BuildTools.targets')" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/src/GitHub.InlineReviews/Glyph/GlyphData.cs b/src/GitHub.InlineReviews/Glyph/GlyphData.cs new file mode 100644 index 0000000000..8364b320e9 --- /dev/null +++ b/src/GitHub.InlineReviews/Glyph/GlyphData.cs @@ -0,0 +1,46 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using Microsoft.VisualStudio.Text; + +namespace GitHub.InlineReviews.Glyph.Implementation +{ + /// <summary> + /// Information about the position of a glyph. + /// </summary> + /// <typeparam name="TGlyphTag">The type of glyph tag we're dealing with.</typeparam> + internal class GlyphData<TGlyphTag> + { + double deltaY; + + public GlyphData(SnapshotSpan visualSpan, TGlyphTag tag, UIElement element) + { + VisualSpan = visualSpan; + GlyphType = tag.GetType(); + Glyph = element; + + deltaY = Canvas.GetTop(element); + if (double.IsNaN(deltaY)) + { + deltaY = 0.0; + } + } + + public void SetSnapshot(ITextSnapshot snapshot) + { + VisualSpan = VisualSpan.Value.TranslateTo(snapshot, SpanTrackingMode.EdgeInclusive); + } + + public void SetTop(double top) + { + Canvas.SetTop(Glyph, top + deltaY); + } + + public UIElement Glyph { get; } + + public Type GlyphType { get; } + + public SnapshotSpan? VisualSpan { get; private set; } + } +} + diff --git a/src/GitHub.InlineReviews/Glyph/GlyphMargin.cs b/src/GitHub.InlineReviews/Glyph/GlyphMargin.cs new file mode 100644 index 0000000000..7f9df1c80b --- /dev/null +++ b/src/GitHub.InlineReviews/Glyph/GlyphMargin.cs @@ -0,0 +1,154 @@ +using System; +using System.Reactive.Linq; +using System.Windows.Media; +using System.Windows.Controls; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using GitHub.InlineReviews.Glyph.Implementation; +using ReactiveUI; + +namespace GitHub.InlineReviews.Glyph +{ + /// <summary> + /// Responsibe for updating the margin when tags change. + /// </summary> + /// <typeparam name="TGlyphTag">The type of glyph tag we're managing.</typeparam> + public sealed class GlyphMargin<TGlyphTag> : IDisposable where TGlyphTag : ITag + { + readonly IWpfTextView textView; + readonly Grid marginGrid; + readonly IViewTagAggregatorFactoryService tagAggregatorFactory; + readonly GlyphMarginVisualManager<TGlyphTag> visualManager; + + IDisposable visibleSubscription; + bool refreshAllGlyphs; + ITagAggregator<TGlyphTag> tagAggregator; + bool disposed; + + public GlyphMargin( + IWpfTextView textView, + IGlyphFactory<TGlyphTag> glyphFactory, + Grid marginGrid, + IViewTagAggregatorFactoryService tagAggregatorFactory, + IEditorFormatMap editorFormatMap, + string marginPropertiesName) + { + this.textView = textView; + this.marginGrid = marginGrid; + this.tagAggregatorFactory = tagAggregatorFactory; + visualManager = new GlyphMarginVisualManager<TGlyphTag>(textView, glyphFactory, marginGrid, editorFormatMap, marginPropertiesName); + + // Initialize when first visible + visibleSubscription = marginGrid.WhenAnyValue(x => x.IsVisible).Distinct().Where(x => x).Subscribe(_ => Initialize()); + } + + public void Dispose() + { + if (!disposed) + { + disposed = true; + + textView.LayoutChanged -= OnLayoutChanged; + textView.ZoomLevelChanged -= OnZoomLevelChanged; + + tagAggregator?.Dispose(); + tagAggregator = null; + + visibleSubscription?.Dispose(); + visibleSubscription = null; + } + } + + void Initialize() + { + tagAggregator = tagAggregatorFactory.CreateTagAggregator<TGlyphTag>(textView); + tagAggregator.BatchedTagsChanged += OnBatchedTagsChanged; + textView.LayoutChanged += OnLayoutChanged; + textView.ZoomLevelChanged += OnZoomLevelChanged; + + if (textView.InLayout) + { + refreshAllGlyphs = true; + } + else + { + foreach (var line in textView.TextViewLines) + { + RefreshGlyphsOver(line); + } + } + + marginGrid.LayoutTransform = new ScaleTransform(textView.ZoomLevel / 100.0, textView.ZoomLevel / 100.0); + marginGrid.LayoutTransform.Freeze(); + } + + void OnBatchedTagsChanged(object sender, BatchedTagsChangedEventArgs e) + { + if (!textView.IsClosed) + { + var list = new List<SnapshotSpan>(); + foreach (var span in e.Spans) + { + list.AddRange(span.GetSpans(textView.TextSnapshot)); + } + + if (list.Count > 0) + { + var span = list[0]; + int start = span.Start; + int end = span.End; + for (int i = 1; i < list.Count; i++) + { + span = list[i]; + start = Math.Min(start, span.Start); + end = Math.Max(end, span.End); + } + + var rangeSpan = new SnapshotSpan(textView.TextSnapshot, start, end - start); + visualManager.RemoveGlyphsByVisualSpan(rangeSpan); + foreach (var line in textView.TextViewLines.GetTextViewLinesIntersectingSpan(rangeSpan)) + { + RefreshGlyphsOver(line); + } + } + } + } + + void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) + { + visualManager.SetSnapshotAndUpdate(textView.TextSnapshot, e.NewOrReformattedLines, e.VerticalTranslation ? (IList<ITextViewLine>)textView.TextViewLines : e.TranslatedLines); + + var lines = refreshAllGlyphs ? (IList<ITextViewLine>)textView.TextViewLines : e.NewOrReformattedLines; + foreach (var line in lines) + { + visualManager.RemoveGlyphsByVisualSpan(line.Extent); + RefreshGlyphsOver(line); + } + + refreshAllGlyphs = false; + } + + void OnZoomLevelChanged(object sender, ZoomLevelChangedEventArgs e) + { + refreshAllGlyphs = true; + marginGrid.LayoutTransform = e.ZoomTransform; + } + + void RefreshGlyphsOver(ITextViewLine textViewLine) + { + foreach (IMappingTagSpan<TGlyphTag> span in tagAggregator.GetTags(textViewLine.ExtentAsMappingSpan)) + { + NormalizedSnapshotSpanCollection spans; + if (span.Span.Start.GetPoint(textView.VisualSnapshot.TextBuffer, PositionAffinity.Predecessor).HasValue && + ((spans = span.Span.GetSpans(textView.TextSnapshot)).Count > 0)) + { + visualManager.AddGlyph(span.Tag, spans[0]); + } + } + } + } +} diff --git a/src/GitHub.InlineReviews/Glyph/GlyphMarginVisualManager.cs b/src/GitHub.InlineReviews/Glyph/GlyphMarginVisualManager.cs new file mode 100644 index 0000000000..bc5444919b --- /dev/null +++ b/src/GitHub.InlineReviews/Glyph/GlyphMarginVisualManager.cs @@ -0,0 +1,199 @@ +using System; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls; +using System.Collections.Generic; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Classification; + +namespace GitHub.InlineReviews.Glyph.Implementation +{ + /// <summary> + /// Manage the MarginVisual element. + /// </summary> + /// <typeparam name="TGlyphTag">The type of tag we're managing.</typeparam> + internal class GlyphMarginVisualManager<TGlyphTag> where TGlyphTag : ITag + { + readonly IEditorFormatMap editorFormatMap; + readonly IGlyphFactory<TGlyphTag> glyphFactory; + readonly Grid glyphMarginGrid; + readonly string marginPropertiesName; + readonly IWpfTextView textView; + readonly Dictionary<Type, Canvas> visuals; + + Dictionary<UIElement, GlyphData<TGlyphTag>> glyphs; + + public GlyphMarginVisualManager(IWpfTextView textView, IGlyphFactory<TGlyphTag> glyphFactory, Grid glyphMarginGrid, + IEditorFormatMap editorFormatMap, string marginPropertiesName) + { + this.textView = textView; + this.marginPropertiesName = marginPropertiesName; + this.editorFormatMap = editorFormatMap; + this.editorFormatMap.FormatMappingChanged += OnFormatMappingChanged; + this.textView.Closed += new EventHandler(OnTextViewClosed); + this.glyphFactory = glyphFactory; + this.glyphMarginGrid = glyphMarginGrid; + UpdateBackgroundColor(); + + glyphs = new Dictionary<UIElement, GlyphData<TGlyphTag>>(); + visuals = new Dictionary<Type, Canvas>(); + + foreach (Type type in glyphFactory.GetTagTypes()) + { + if (!visuals.ContainsKey(type)) + { + var element = new Canvas(); + element.ClipToBounds = true; + glyphMarginGrid.Children.Add(element); + visuals[type] = element; + } + } + } + + public void AddGlyph(TGlyphTag tag, SnapshotSpan span) + { + var textViewLines = textView.TextViewLines; + var glyphType = tag.GetType(); + if (textView.TextViewLines.IntersectsBufferSpan(span)) + { + var startingLine = GetStartingLine(textViewLines, span) as IWpfTextViewLine; + if (startingLine != null) + { + var element = (FrameworkElement)glyphFactory.GenerateGlyph(startingLine, tag); + if (element != null) + { + var data = new GlyphData<TGlyphTag>(span, tag, element); + element.Width = glyphMarginGrid.Width; + + // draw where text is + element.Height = startingLine.TextHeight + 1; // HACK: +1 to fill gaps + data.SetTop(startingLine.TextTop - textView.ViewportTop); + + glyphs[element] = data; + visuals[glyphType].Children.Add(element); + } + } + } + } + + public void RemoveGlyphsByVisualSpan(SnapshotSpan span) + { + var list = new List<UIElement>(); + foreach (var pair in glyphs) + { + var data = pair.Value; + if (data.VisualSpan.HasValue && span.IntersectsWith(data.VisualSpan.Value)) + { + list.Add(pair.Key); + visuals[data.GlyphType].Children.Remove(data.Glyph); + } + } + + foreach (var element in list) + { + glyphs.Remove(element); + } + } + + public void SetSnapshotAndUpdate(ITextSnapshot snapshot, IList<ITextViewLine> newOrReformattedLines, IList<ITextViewLine> translatedLines) + { + if (glyphs.Count > 0) + { + var dictionary = new Dictionary<UIElement, GlyphData<TGlyphTag>>(glyphs.Count); + foreach (var pair in glyphs) + { + var data = pair.Value; + if (!data.VisualSpan.HasValue) + { + dictionary[pair.Key] = data; + continue; + } + + data.SetSnapshot(snapshot); + SnapshotSpan bufferSpan = data.VisualSpan.Value; + if (!textView.TextViewLines.IntersectsBufferSpan(bufferSpan) || GetStartingLine(newOrReformattedLines, bufferSpan) != null) + { + visuals[data.GlyphType].Children.Remove(data.Glyph); + continue; + } + + dictionary[data.Glyph] = data; + var startingLine = GetStartingLine(translatedLines, bufferSpan); + if (startingLine != null) + { + data.SetTop(startingLine.TextTop - textView.ViewportTop); + } + } + + glyphs = dictionary; + } + } + + static ITextViewLine GetStartingLine(IList<ITextViewLine> lines, Span span) + { + if (lines.Count > 0) + { + int num = 0; + int count = lines.Count; + while (num < count) + { + int middle = (num + count) / 2; + var middleLine = lines[middle]; + if (span.Start < middleLine.Start) + { + count = middle; + } + else + { + if (span.Start >= middleLine.EndIncludingLineBreak) + { + num = middle + 1; + continue; + } + + return middleLine; + } + } + + var line = lines[lines.Count - 1]; + if (line.EndIncludingLineBreak == line.Snapshot.Length && span.Start == line.EndIncludingLineBreak) + { + return line; + } + } + + return null; + } + + void OnFormatMappingChanged(object sender, FormatItemsEventArgs e) + { + if (e.ChangedItems.Contains(marginPropertiesName)) + { + UpdateBackgroundColor(); + } + } + + void OnTextViewClosed(object sender, EventArgs e) + { + editorFormatMap.FormatMappingChanged -= OnFormatMappingChanged; + } + + void UpdateBackgroundColor() + { + // set background color for children + var properties = editorFormatMap.GetProperties(marginPropertiesName); + if (properties.Contains("BackgroundColor")) + { + var backgroundColor = (Color)properties["BackgroundColor"]; + ImageThemingUtilities.SetImageBackgroundColor(glyphMarginGrid, backgroundColor); + } + } + + public FrameworkElement MarginVisual => glyphMarginGrid; + } +} + diff --git a/src/GitHub.InlineReviews/Glyph/IGlyphFactory.cs b/src/GitHub.InlineReviews/Glyph/IGlyphFactory.cs new file mode 100644 index 0000000000..6ec57355c4 --- /dev/null +++ b/src/GitHub.InlineReviews/Glyph/IGlyphFactory.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text.Formatting; + +namespace GitHub.InlineReviews.Glyph +{ + /// <summary> + /// A factory for a type of tag (or multiple subtypes). + /// </summary> + /// <typeparam name="TGlyphTag"></typeparam> + public interface IGlyphFactory<TGlyphTag> where TGlyphTag : ITag + { + /// <summary> + /// Create a glyph element for a particular line and tag. + /// </summary> + /// <param name="line">The line.</param> + /// <param name="tag">The tag.</param> + /// <returns></returns> + UIElement GenerateGlyph(IWpfTextViewLine line, TGlyphTag tag); + + /// <summary> + /// A list of tag subtypes this is a factory for. + /// </summary> + /// <returns></returns> + IEnumerable<Type> GetTagTypes(); + } +} diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.cs b/src/GitHub.InlineReviews/InlineReviewsPackage.cs new file mode 100644 index 0000000000..2531cc6788 --- /dev/null +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel.Design; +using System.Runtime.InteropServices; +using System.Threading; +using GitHub.Exports; +using GitHub.Logging; +using GitHub.Commands; +using GitHub.Services.Vssdk.Commands; +using GitHub.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Serilog; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.InlineReviews +{ + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [Guid(Guids.InlineReviewsPackageId)] + [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] + [ProvideMenuResource("Menus.ctmenu", 1)] + public class InlineReviewsPackage : AsyncPackage + { + static readonly ILogger log = LogManager.ForContext<InlineReviewsPackage>(); + + protected override async Task InitializeAsync( + CancellationToken cancellationToken, + IProgress<ServiceProgressData> progress) + { + var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); + var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); + var exports = componentModel.DefaultExportProvider; + + // Avoid delays when there is ongoing UI activity. + // See: https://github.com/github/VisualStudio/issues/1537 + await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeMenus); + } + + async Task InitializeMenus() + { + if (!ExportForVisualStudioProcessAttribute.IsVisualStudioProcess()) + { + log.Warning("Don't initialize menus for non-Visual Studio process"); + return; + } + + var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); + var exports = componentModel.DefaultExportProvider; + var commands = new IVsCommandBase[] + { + exports.GetExportedValue<INextInlineCommentCommand>(), + exports.GetExportedValue<IPreviousInlineCommentCommand>(), + exports.GetExportedValue<IToggleInlineCommentMarginCommand>() + }; + + await JoinableTaskFactory.SwitchToMainThreadAsync(); + var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); + menuService.AddCommands(commands); + } + } +} diff --git a/src/GitHub.InlineReviews/InlineReviewsPackage.vsct b/src/GitHub.InlineReviews/InlineReviewsPackage.vsct new file mode 100644 index 0000000000..d9e99ccb84 --- /dev/null +++ b/src/GitHub.InlineReviews/InlineReviewsPackage.vsct @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="utf-8"?> +<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + <!-- This is the file that defines the actual layout and type of the commands. + It is divided in different sections (e.g. command definition, command + placement, ...), with each defining a specific set of properties. + See the comment before each section for more details about how to + use it. --> + + <!-- The VSCT compiler (the tool that translates this file into the binary + format that VisualStudio will consume) has the ability to run a preprocessor + on the vsct file; this preprocessor is (usually) the C++ preprocessor, so + it is possible to define includes and macros with the same syntax used + in C++ files. Using this ability of the compiler here, we include some files + defining some of the constants that we will use inside the file. --> + + <!--This is the file that defines the IDs for all the commands exposed by VisualStudio. --> + <Extern href="stdidcmd.h"/> + + <!--This header contains the command ids for the menus provided by the shell. --> + <Extern href="vsshlids.h"/> + + <!--The Commands section is where commands, menus, and menu groups are defined. + This section uses a Guid to identify the package that provides the command defined inside it. --> + <Commands package="guidInlineReviewsPackage"> + <!-- Inside this section we have different sub-sections: one for the menus, another + for the menu groups, one for the buttons (the actual commands), one for the combos + and the last one for the bitmaps used. Each element is identified by a command id that + is a unique pair of guid and numeric identifier; the guid part of the identifier is usually + called "command set" and is used to group different command inside a logically related + group; your package should define its own command set in order to avoid collisions + with command ids defined by other packages. --> + + <!--Buttons section. --> + <!--This section defines the elements the user can interact with, like a menu command or a button + or combo box in a toolbar. --> + <Buttons> + <!--To define a menu group you have to specify its ID, the parent menu and its display priority. + The command is visible and enabled by default. If you need to change the visibility, status, etc, you can use + the CommandFlag node. + You can add more than one CommandFlag node e.g.: + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + If you do not want an image next to your command, remove the Icon node /> --> + <Button guid="guidGitHubCommandSet" id="NextInlineCommentId" priority="0x0100" type="Button"> + <Parent guid="guidSHLMainMenu" id="IDG_VS_EDIT_GOTO" /> + <CommandFlag>DefaultDisabled</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <CommandName>GitHub.InlineReviews.NextInlineComment</CommandName> + <ButtonText>Next Comment</ButtonText> + <CanonicalName>.GitHub.NextComment</CanonicalName> + <LocCanonicalName>.GitHub.NextComment</LocCanonicalName> + </Strings> + </Button> + <Button guid="guidGitHubCommandSet" id="PreviousInlineCommentId" priority="0x0100" type="Button"> + <Parent guid="guidSHLMainMenu" id="IDG_VS_EDIT_GOTO" /> + <CommandFlag>DefaultDisabled</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <CommandName>GitHub.InlineReviews.PreviousInlineComment</CommandName> + <ButtonText>Previous Comment</ButtonText> + <CanonicalName>.GitHub.PreviousComment</CanonicalName> + <LocCanonicalName>.GitHub.PreviousComment</LocCanonicalName> + </Strings> + </Button> + <Button guid="guidGitHubCommandSet" id="ToggleInlineCommentMarginId" type="Button"> + <CommandFlag>DefaultDisabled</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <ButtonText>Toggle Comment Margin</ButtonText> + <CanonicalName>.GitHub.ToggleInlineCommentMargin</CanonicalName> + <LocCanonicalName>.GitHub.ToggleInlineCommentMargin</LocCanonicalName> + </Strings> + </Button> + </Buttons> + </Commands> + <KeyBindings> + <KeyBinding guid="guidGitHubCommandSet" id="NextInlineCommentId" editor="guidVSStd97" key1="VK_OEM_6" mod1="Alt"/> + <KeyBinding guid="guidGitHubCommandSet" id="PreviousInlineCommentId" editor="guidVSStd97" key1="VK_OEM_4" mod1="Alt"/> + </KeyBindings> + <Symbols> + <!-- This is the package guid. --> + <GuidSymbol name="guidInlineReviewsPackage" value="{248325be-4a2d-4111-b122-e7d59bf73a35}" /> + + <!-- This is the guid used to group the menu commands together --> + <GuidSymbol name="guidGitHubCommandSet" value="{C5F1193E-F300-41B3-B4C4-5A703DD3C1C6}"> + <IDSymbol name="PullRequestCommentsToolWindowCommandId" value="0x1000" /> + <IDSymbol name="NextInlineCommentId" value="0x1001" /> + <IDSymbol name="PreviousInlineCommentId" value="0x1002" /> + <IDSymbol name="ToggleInlineCommentMarginId" value="0x1003" /> + </GuidSymbol> + + <GuidSymbol name="guidImages" value="{775aa523-6c52-4c11-9c28-823c99d15613}" > + <IDSymbol name="bmpPic1" value="1" /> + <IDSymbol name="bmpPic2" value="2" /> + <IDSymbol name="bmpPicSearch" value="3" /> + <IDSymbol name="bmpPicX" value="4" /> + <IDSymbol name="bmpPicArrows" value="5" /> + <IDSymbol name="bmpPicStrikethrough" value="6" /> + </GuidSymbol> + </Symbols> +</CommandTable> diff --git a/src/GitHub.InlineReviews/Margins/InlineCommentMargin.cs b/src/GitHub.InlineReviews/Margins/InlineCommentMargin.cs new file mode 100644 index 0000000000..6736d3f855 --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/InlineCommentMargin.cs @@ -0,0 +1,141 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.Extensions; +using GitHub.InlineReviews.Tags; +using GitHub.InlineReviews.Views; +using GitHub.InlineReviews.Glyph; +using GitHub.InlineReviews.Services; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text.Classification; +using ReactiveUI; + +namespace GitHub.InlineReviews.Margins +{ + public sealed class InlineCommentMargin : IWpfTextViewMargin + { + public const string MarginName = "InlineComment"; + const string MarginPropertiesName = "Indicator Margin"; // Same background color as Glyph margin + + readonly IWpfTextView textView; + readonly IPullRequestSessionManager sessionManager; + readonly Grid marginGrid; + + GlyphMargin<InlineCommentTag> glyphMargin; + IDisposable currentSessionSubscription; + IDisposable visibleSubscription; + bool hasChanges; + bool hasInfo; + + public InlineCommentMargin( + IWpfTextViewHost wpfTextViewHost, + IInlineCommentPeekService peekService, + IEditorFormatMapService editorFormatMapService, + IViewTagAggregatorFactoryService tagAggregatorFactory, + Lazy<IPullRequestSessionManager> sessionManager) + { + textView = wpfTextViewHost.TextView; + this.sessionManager = sessionManager.Value; + + // Default to not show comment margin + textView.Options.SetOptionValue(InlineCommentTextViewOptions.MarginEnabledId, false); + + marginGrid = new GlyphMarginGrid { Width = 17.0 }; + var glyphFactory = new InlineCommentGlyphFactory(peekService, textView); + var editorFormatMap = editorFormatMapService.GetEditorFormatMap(textView); + + glyphMargin = new GlyphMargin<InlineCommentTag>(textView, glyphFactory, marginGrid, tagAggregatorFactory, + editorFormatMap, MarginPropertiesName); + + if (IsDiffView()) + { + TrackCommentGlyph(wpfTextViewHost, marginGrid); + } + + currentSessionSubscription = this.sessionManager.WhenAnyValue(x => x.CurrentSession) + .Subscribe(x => RefreshCurrentSession().Forget()); + + visibleSubscription = marginGrid.WhenAnyValue(x => x.IsVisible) + .Subscribe(x => textView.Options.SetOptionValue(InlineCommentTextViewOptions.MarginVisibleId, x)); + + textView.Options.OptionChanged += (s, e) => RefreshMarginVisibility(); + } + + async Task RefreshCurrentSession() + { + var sessionFile = await FindSessionFile(); + hasChanges = sessionFile?.Diff != null && sessionFile.Diff.Count > 0; + + await Task.Yield(); // HACK: Give diff view a chance to initialize. + var info = sessionManager.GetTextBufferInfo(textView.TextBuffer); + hasInfo = info != null; + + RefreshMarginVisibility(); + } + + public ITextViewMargin GetTextViewMargin(string name) + { + return (name == MarginName) ? this : null; + } + + public void Dispose() + { + visibleSubscription?.Dispose(); + visibleSubscription = null; + + currentSessionSubscription?.Dispose(); + currentSessionSubscription = null; + + glyphMargin?.Dispose(); + glyphMargin = null; + } + + public FrameworkElement VisualElement => marginGrid; + + public double MarginSize => marginGrid.Width; + + public bool Enabled => IsMarginVisible(); + + async Task<IPullRequestSessionFile> FindSessionFile() + { + await sessionManager.EnsureInitialized(); + + var session = sessionManager.CurrentSession; + if (session == null) + { + return null; + } + + var relativePath = sessionManager.GetRelativePath(textView.TextBuffer); + if (relativePath == null) + { + return null; + } + + return await session.GetFile(relativePath); + } + + bool IsDiffView() => textView.Roles.Contains("DIFF"); + + void TrackCommentGlyph(IWpfTextViewHost host, UIElement marginElement) + { + var router = new MouseEnterAndLeaveEventRouter<AddInlineCommentGlyph>(); + router.Add(host.HostControl, marginElement); + } + + void RefreshMarginVisibility() + { + marginGrid.Visibility = IsMarginVisible() ? Visibility.Visible : Visibility.Collapsed; + } + + bool IsMarginVisible() + { + var enabled = textView.Options.GetOptionValue(InlineCommentTextViewOptions.MarginEnabledId); + return hasInfo || (enabled && hasChanges); + } + } +} diff --git a/src/GitHub.InlineReviews/Margins/InlineCommentMarginEnabled.cs b/src/GitHub.InlineReviews/Margins/InlineCommentMarginEnabled.cs new file mode 100644 index 0000000000..3249371eff --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/InlineCommentMarginEnabled.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Editor; + +namespace GitHub.InlineReviews.Margins +{ + [Export(typeof(EditorOptionDefinition))] + public class InlineCommentMarginEnabled : ViewOptionDefinition<bool> + { + public override bool Default => false; + + public override EditorOptionKey<bool> Key => InlineCommentTextViewOptions.MarginEnabledId; + } +} diff --git a/src/GitHub.InlineReviews/Margins/InlineCommentMarginProvider.cs b/src/GitHub.InlineReviews/Margins/InlineCommentMarginProvider.cs new file mode 100644 index 0000000000..36cbda2c6f --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/InlineCommentMarginProvider.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Text.Classification; +using GitHub.Services; +using GitHub.VisualStudio; +using GitHub.InlineReviews.Services; + +namespace GitHub.InlineReviews.Margins +{ + [Export(typeof(IWpfTextViewMarginProvider))] + [Name(InlineCommentMargin.MarginName)] + [Order(After = PredefinedMarginNames.Glyph)] + [MarginContainer(PredefinedMarginNames.Left)] + [ContentType("text")] + [TextViewRole(PredefinedTextViewRoles.Interactive)] + internal sealed class InlineCommentMarginProvider : IWpfTextViewMarginProvider + { + readonly Lazy<IEditorFormatMapService> editorFormatMapService; + readonly Lazy<IViewTagAggregatorFactoryService> tagAggregatorFactory; + readonly Lazy<IInlineCommentPeekService> peekService; + readonly Lazy<IPullRequestSessionManager> sessionManager; + readonly UIContext uiContext; + + [ImportingConstructor] + public InlineCommentMarginProvider( + Lazy<IPullRequestSessionManager> sessionManager, + Lazy<IEditorFormatMapService> editorFormatMapService, + Lazy<IViewTagAggregatorFactoryService> tagAggregatorFactory, + Lazy<IInlineCommentPeekService> peekService) + { + this.sessionManager = sessionManager; + this.editorFormatMapService = editorFormatMapService; + this.tagAggregatorFactory = tagAggregatorFactory; + this.peekService = peekService; + + uiContext = UIContext.FromUIContextGuid(new Guid(Guids.UIContext_Git)); + } + + public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin parent) + { + if (!uiContext.IsActive) + { + // Only create margin when in the context of a Git repository + return null; + } + + return new InlineCommentMargin( + wpfTextViewHost, + peekService.Value, + editorFormatMapService.Value, + tagAggregatorFactory.Value, + sessionManager); + } + } +} diff --git a/src/GitHub.InlineReviews/Margins/InlineCommentMarginVisible.cs b/src/GitHub.InlineReviews/Margins/InlineCommentMarginVisible.cs new file mode 100644 index 0000000000..967a9654f8 --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/InlineCommentMarginVisible.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Editor; + +namespace GitHub.InlineReviews.Margins +{ + [Export(typeof(EditorOptionDefinition))] + public class InlineCommentMarginVisible : ViewOptionDefinition<bool> + { + public override bool Default => false; + + public override EditorOptionKey<bool> Key => InlineCommentTextViewOptions.MarginVisibleId; + } +} diff --git a/src/GitHub.InlineReviews/Margins/InlineCommentTextViewOptions.cs b/src/GitHub.InlineReviews/Margins/InlineCommentTextViewOptions.cs new file mode 100644 index 0000000000..f2dc24da51 --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/InlineCommentTextViewOptions.cs @@ -0,0 +1,11 @@ +using Microsoft.VisualStudio.Text.Editor; + +namespace GitHub.InlineReviews.Margins +{ + public static class InlineCommentTextViewOptions + { + public static EditorOptionKey<bool> MarginVisibleId = new EditorOptionKey<bool>("TextViewHost/InlineCommentMarginVisible"); + + public static EditorOptionKey<bool> MarginEnabledId = new EditorOptionKey<bool>("TextViewHost/InlineCommentMarginEnabled"); + } +} diff --git a/src/GitHub.InlineReviews/Margins/PullRequestFileMargin.cs b/src/GitHub.InlineReviews/Margins/PullRequestFileMargin.cs new file mode 100644 index 0000000000..c46311bc0a --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/PullRequestFileMargin.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Windows; +using System.Threading.Tasks; +using System.Reactive.Linq; +using GitHub.Models; +using GitHub.Commands; +using GitHub.Services; +using GitHub.Extensions; +using GitHub.InlineReviews.Views; +using GitHub.InlineReviews.ViewModels; +using Microsoft.VisualStudio.Text.Editor; +using ReactiveUI; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.InlineReviews.Margins +{ + /// <summary> + /// This margin appears on solution files that have a corresponding PR file (file with changes in current PR). + /// </summary> + internal class PullRequestFileMargin : IWpfTextViewMargin + { + public const string MarginName = "PullRequestFileMargin"; + + readonly IWpfTextView textView; + readonly PullRequestFileMarginViewModel viewModel; + readonly PullRequestFileMarginView visualElement; + readonly IPullRequestSessionManager sessionManager; + + bool isDisposed; + + IDisposable currentSessionSubscription; + IDisposable optionChangedSubscription; + IDisposable visibilitySubscription; + + public PullRequestFileMargin( + IWpfTextView textView, + IToggleInlineCommentMarginCommand toggleInlineCommentMarginCommand, + IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand, + IPullRequestSessionManager sessionManager, + Lazy<IUsageTracker> usageTracker) + { + this.textView = textView; + this.sessionManager = sessionManager; + + viewModel = new PullRequestFileMarginViewModel(toggleInlineCommentMarginCommand, goToSolutionOrPullRequestFileCommand, usageTracker); + visualElement = new PullRequestFileMarginView { DataContext = viewModel, ClipToBounds = true }; + + visibilitySubscription = viewModel.WhenAnyValue(x => x.Enabled).Subscribe(enabled => + { + visualElement.Visibility = enabled ? Visibility.Visible : Visibility.Collapsed; + }); + + optionChangedSubscription = Observable.FromEventPattern(textView.Options, nameof(textView.Options.OptionChanged)).Subscribe(_ => + { + viewModel.MarginEnabled = textView.Options.GetOptionValue(InlineCommentTextViewOptions.MarginEnabledId); + }); + + currentSessionSubscription = sessionManager.WhenAnyValue(x => x.CurrentSession) + .Subscribe(x => RefreshCurrentSession().Forget()); + } + + public void Dispose() + { + if (!isDisposed) + { + GC.SuppressFinalize(this); + isDisposed = true; + + currentSessionSubscription.Dispose(); + optionChangedSubscription.Dispose(); + visibilitySubscription.Dispose(); + } + } + + async Task RefreshCurrentSession() + { + var sessionFile = await FindSessionFile(); + if (sessionFile != null) + { + viewModel.FileName = Path.GetFileName(sessionFile.RelativePath); + viewModel.CommentsInFile = sessionFile.InlineCommentThreads?.Count ?? -1; + viewModel.Enabled = sessionFile.Diff.Count > 0; + } + else + { + viewModel.CommentsInFile = 0; + viewModel.Enabled = false; + } + } + + async Task<IPullRequestSessionFile> FindSessionFile() + { + await sessionManager.EnsureInitialized(); + + var session = sessionManager.CurrentSession; + if (session == null) + { + return null; + } + + var relativePath = sessionManager.GetRelativePath(textView.TextBuffer); + if (relativePath == null) + { + return null; + } + + return await session.GetFile(relativePath); + } + + public FrameworkElement VisualElement => visualElement; + + public double MarginSize => visualElement.ActualHeight; + + public bool Enabled => viewModel.Enabled; + + public ITextViewMargin GetTextViewMargin(string marginName) + { + return string.Equals(marginName, MarginName, StringComparison.OrdinalIgnoreCase) ? this : null; + } + } +} diff --git a/src/GitHub.InlineReviews/Margins/PullRequestFileMarginProvider.cs b/src/GitHub.InlineReviews/Margins/PullRequestFileMarginProvider.cs new file mode 100644 index 0000000000..7dfafdfbc5 --- /dev/null +++ b/src/GitHub.InlineReviews/Margins/PullRequestFileMarginProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Commands; +using GitHub.Services; +using GitHub.Settings; +using GitHub.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Text.Editor; + +namespace GitHub.InlineReviews.Margins +{ + /// <summary> + /// Export a <see cref="IWpfTextViewMarginProvider"/>, which returns an instance of the margin for the editor to use. + /// </summary> + [Export(typeof(IWpfTextViewMarginProvider))] + [Name(PullRequestFileMargin.MarginName)] + [Order(After = PredefinedMarginNames.ZoomControl)] + [MarginContainer(PredefinedMarginNames.BottomControl)] // Set the container to the bottom of the editor window + [ContentType("text")] // Show this margin for all text-based types + [TextViewRole(PredefinedTextViewRoles.Editable)] + internal sealed class PullRequestFileMarginProvider : IWpfTextViewMarginProvider + { + readonly Lazy<IPullRequestSessionManager> sessionManager; + readonly Lazy<IToggleInlineCommentMarginCommand> enableInlineCommentsCommand; + readonly Lazy<IGoToSolutionOrPullRequestFileCommand> goToSolutionOrPullRequestFileCommand; + readonly Lazy<IPackageSettings> packageSettings; + readonly Lazy<IUsageTracker> usageTracker; + readonly UIContext uiContext; + + [ImportingConstructor] + public PullRequestFileMarginProvider( + Lazy<IToggleInlineCommentMarginCommand> enableInlineCommentsCommand, + Lazy<IGoToSolutionOrPullRequestFileCommand> goToSolutionOrPullRequestFileCommand, + Lazy<IPullRequestSessionManager> sessionManager, + Lazy<IPackageSettings> packageSettings, + Lazy<IUsageTracker> usageTracker) + { + this.enableInlineCommentsCommand = enableInlineCommentsCommand; + this.goToSolutionOrPullRequestFileCommand = goToSolutionOrPullRequestFileCommand; + this.sessionManager = sessionManager; + this.packageSettings = packageSettings; + this.usageTracker = usageTracker; + + uiContext = UIContext.FromUIContextGuid(new Guid(Guids.UIContext_Git)); + } + + /// <summary> + /// Creates an <see cref="IWpfTextViewMargin"/> for the given <see cref="IWpfTextViewHost"/>. + /// </summary> + /// <param name="wpfTextViewHost">The <see cref="IWpfTextViewHost"/> for which to create the <see cref="IWpfTextViewMargin"/>.</param> + /// <param name="marginContainer">The margin that will contain the newly-created margin.</param> + /// <returns>The <see cref="IWpfTextViewMargin"/>. + /// The value may be null if this <see cref="IWpfTextViewMarginProvider"/> does not participate for this context. + /// </returns> + public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin marginContainer) + { + if (!uiContext.IsActive) + { + // Only create margin when in the context of a Git repository + return null; + } + + // Comments in the editor feature flag + if (!packageSettings.Value.EditorComments) + { + return null; + } + + // Never show on diff views + if (IsDiffView(wpfTextViewHost.TextView)) + { + return null; + } + + return new PullRequestFileMargin( + wpfTextViewHost.TextView, + enableInlineCommentsCommand.Value, + goToSolutionOrPullRequestFileCommand.Value, + sessionManager.Value, + usageTracker); + } + + bool IsDiffView(ITextView textView) => textView.Roles.Contains("DIFF"); + } +} diff --git a/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs b/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs new file mode 100644 index 0000000000..18fdf202b1 --- /dev/null +++ b/src/GitHub.InlineReviews/Models/InlineCommentThreadModel.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GitHub.Extensions; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.InlineReviews.Models +{ + /// <summary> + /// Represents a thread of inline comments on an <see cref="IPullRequestSessionFile"/>. + /// </summary> + class InlineCommentThreadModel : ReactiveObject, IInlineCommentThreadModel + { + bool isStale; + int lineNumber = -1; + + /// <summary> + /// Initializes a new instance of the <see cref="InlineCommentThreadModel"/> class. + /// </summary> + /// <param name="relativePath">The relative path to the file that the thread is on.</param> + /// <param name="commitSha">The SHA of the commit that the thread appears on.</param> + /// <param name="diffMatch"> + /// The last five lines of the thread's diff hunk, in reverse order. + /// </param> + /// <param name="comments">The comments in the thread</param> + public InlineCommentThreadModel( + string relativePath, + string commitSha, + IList<DiffLine> diffMatch, + IEnumerable<InlineCommentModel> comments) + { + Guard.ArgumentNotNull(relativePath, nameof(relativePath)); + Guard.ArgumentNotNull(commitSha, nameof(commitSha)); + Guard.ArgumentNotNull(diffMatch, nameof(diffMatch)); + + Comments = comments.ToList(); + DiffMatch = diffMatch; + DiffLineType = diffMatch[0].Type; + CommitSha = commitSha; + RelativePath = relativePath; + + foreach (var comment in comments) + { + comment.Thread = this; + } + } + + /// <inheritdoc/> + public IReadOnlyList<InlineCommentModel> Comments { get; } + + /// <inheritdoc/> + public IList<DiffLine> DiffMatch { get; } + + /// <inheritdoc/> + public DiffChangeType DiffLineType { get; } + + /// <inheritdoc/> + public bool IsStale + { + get { return isStale; } + set { this.RaiseAndSetIfChanged(ref isStale, value); } + } + + /// <inheritdoc/> + public int LineNumber + { + get { return lineNumber; } + set { this.RaiseAndSetIfChanged(ref lineNumber, value); } + } + + /// <inheritdoc/> + public string CommitSha { get; } + + /// <inheritdoc/> + public string RelativePath { get; } + } +} diff --git a/src/GitHub.InlineReviews/Models/PullRequestSessionFile.cs b/src/GitHub.InlineReviews/Models/PullRequestSessionFile.cs new file mode 100644 index 0000000000..a38b0d22c7 --- /dev/null +++ b/src/GitHub.InlineReviews/Models/PullRequestSessionFile.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Subjects; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.InlineReviews.Models +{ + /// <summary> + /// A file in a pull request session. + /// </summary> + /// <remarks> + /// A <see cref="PullRequestSessionFile"/> holds the review comments for a file in a pull + /// request together with associated information such as the commit SHA of the file and the + /// diff with the file's merge base. + /// </remarks> + /// <seealso cref="PullRequestSession"/> + /// <seealso cref="PullRequestSessionManager"/> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", + Justification = "linesChanged is shared and shouldn't be disposed")] + public class PullRequestSessionFile : ReactiveObject, IPullRequestSessionFile + { + readonly Subject<IReadOnlyList<Tuple<int, DiffSide>>> linesChanged = new Subject<IReadOnlyList<Tuple<int, DiffSide>>>(); + IReadOnlyList<DiffChunk> diff; + string commitSha; + IReadOnlyList<IInlineCommentThreadModel> inlineCommentThreads; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestSessionFile"/> class. + /// </summary> + /// <param name="relativePath"> + /// The relative path to the file in the repository. + /// </param> + /// <param name="commitSha"> + /// The commit to pin the file to, or "HEAD" to follow the pull request head. + /// </param> + public PullRequestSessionFile(string relativePath, string commitSha = "HEAD") + { + RelativePath = relativePath; + this.commitSha = commitSha; + IsTrackingHead = commitSha == "HEAD"; + } + + /// <inheritdoc/> + public string RelativePath { get; } + + /// <inheritdoc/> + public IReadOnlyList<DiffChunk> Diff + { + get { return diff; } + internal set { this.RaiseAndSetIfChanged(ref diff, value); } + } + + /// <inheritdoc/> + public string BaseSha { get; internal set; } + + /// <inheritdoc/> + public string CommitSha + { + get { return commitSha; } + internal set + { + if (value != commitSha) + { + if (!IsTrackingHead) + { + throw new GitHubLogicException( + "Cannot change the CommitSha of a PullRequestSessionFile that is not tracking HEAD."); + } + + this.RaiseAndSetIfChanged(ref commitSha, value); + } + } + } + + /// <inheritdoc/> + public bool IsTrackingHead { get; } + + /// <inheritdoc/> + public IReadOnlyList<IInlineCommentThreadModel> InlineCommentThreads + { + get { return inlineCommentThreads; } + set + { + var lines = (inlineCommentThreads ?? Enumerable.Empty<IInlineCommentThreadModel>())? + .Concat(value ?? Enumerable.Empty<IInlineCommentThreadModel>()) + .Select(x => Tuple.Create(x.LineNumber, x.DiffLineType == DiffChangeType.Delete ? DiffSide.Left : DiffSide.Right)) + .Where(x => x.Item1 >= 0) + .Distinct() + .ToList(); + this.RaisePropertyChanging(); + inlineCommentThreads = value; + this.RaisePropertyChanged(); + NotifyLinesChanged(lines); + } + } + + /// <inheritdoc/> + public IObservable<IReadOnlyList<Tuple<int, DiffSide>>> LinesChanged => linesChanged; + + /// <summary> + /// Raises the <see cref="LinesChanged"/> signal. + /// </summary> + /// <param name="lines">The lines that have changed.</param> + public void NotifyLinesChanged(IReadOnlyList<Tuple<int, DiffSide>> lines) => linesChanged.OnNext(lines); + } +} diff --git a/src/GitHub.InlineReviews/Models/PullRequestSessionLiveFile.cs b/src/GitHub.InlineReviews/Models/PullRequestSessionLiveFile.cs new file mode 100644 index 0000000000..f9df506ae7 --- /dev/null +++ b/src/GitHub.InlineReviews/Models/PullRequestSessionLiveFile.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Subjects; +using GitHub.Models; +using Microsoft.VisualStudio.Text; + +namespace GitHub.InlineReviews.Models +{ + /// <summary> + /// A file in a pull request session that tracks editor content. + /// </summary> + /// <remarks> + /// A live session file extends <see cref="PullRequestSessionFile"/> to update the file's + /// review comments in real time, based on the contents of an editor and + /// <see cref="IPullRequestSessionManager.CurrentSession"/>. + /// </remarks> + public sealed class PullRequestSessionLiveFile : PullRequestSessionFile, IDisposable + { + bool disposed = false; + + public PullRequestSessionLiveFile( + string relativePath, + ITextBuffer textBuffer, + ISubject<ITextSnapshot, ITextSnapshot> rebuild) + : base(relativePath) + { + TextBuffer = textBuffer; + Rebuild = rebuild; + } + + /// <summary> + /// Gets the VS text buffer that the file is associated with. + /// </summary> + public ITextBuffer TextBuffer { get; } + + /// <summary> + /// Gets a resource to dispose. + /// </summary> + public IDisposable ToDispose { get; internal set; } + + /// <summary> + /// Gets a dictionary mapping review comments to tracking points in the <see cref="TextBuffer"/>. + /// </summary> + public IDictionary<IInlineCommentThreadModel, ITrackingPoint> TrackingPoints { get; internal set; } + + /// <summary> + /// Gets an observable raised when the review comments for the file should be rebuilt. + /// </summary> + public ISubject<ITextSnapshot, ITextSnapshot> Rebuild { get; } + + public void Dispose() + { + Dispose(true); + } + + /// <summary> + /// Disposes of the resources in <see cref="ToDispose"/>. + /// </summary> + void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + + if (disposing) + { + ToDispose?.Dispose(); + } + } + } + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekRelationship.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekRelationship.cs new file mode 100644 index 0000000000..826b0002aa --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekRelationship.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.VisualStudio.Language.Intellisense; + +namespace GitHub.InlineReviews.Peek +{ + class InlineCommentPeekRelationship : IPeekRelationship + { + static InlineCommentPeekRelationship instance; + + private InlineCommentPeekRelationship() + { + } + + public static InlineCommentPeekRelationship Instance + { + get + { + if (instance == null) + { + instance = new InlineCommentPeekRelationship(); + } + + return instance; + } + } + + public string DisplayName => "GitHub Code Review"; + public string Name => "GitHubCodeReview"; + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekResult.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekResult.cs new file mode 100644 index 0000000000..ac9394b78b --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekResult.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.VisualStudio.Language.Intellisense; +using GitHub.Extensions; +using GitHub.InlineReviews.ViewModels; + +namespace GitHub.InlineReviews.Peek +{ + sealed class InlineCommentPeekResult : IPeekResult + { + public InlineCommentPeekResult(InlineCommentPeekViewModel viewModel) + { + Guard.ArgumentNotNull(viewModel, nameof(viewModel)); + + this.ViewModel = viewModel; + } + + public bool CanNavigateTo => true; + public InlineCommentPeekViewModel ViewModel { get; } + + public IPeekResultDisplayInfo DisplayInfo { get; } + = new PeekResultDisplayInfo("Review", null, "GitHub Review", "GitHub Review"); + + public Action<IPeekResult, object, object> PostNavigationCallback => null; + + public event EventHandler Disposed; + + public void Dispose() + { + Disposed?.Invoke(this, EventArgs.Empty); + } + + public void NavigateTo(object data) + { + } + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekResultPresentation.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekResultPresentation.cs new file mode 100644 index 0000000000..3aefe50256 --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekResultPresentation.cs @@ -0,0 +1,105 @@ +using System; +using System.Windows; +using Microsoft.VisualStudio.Language.Intellisense; +using GitHub.InlineReviews.ViewModels; +using GitHub.InlineReviews.Views; + +namespace GitHub.InlineReviews.Peek +{ + class InlineCommentPeekResultPresentation : IPeekResultPresentation, IDesiredHeightProvider + { + const double PeekBorders = 28.0; + readonly InlineCommentPeekViewModel viewModel; + InlineCommentPeekView view; + double desiredHeight; + + public bool IsDirty => false; + public bool IsReadOnly => true; + + public InlineCommentPeekResultPresentation(InlineCommentPeekViewModel viewModel) + { + this.viewModel = viewModel; + } + + public double ZoomLevel + { + get { return 1.0; } + set { } + } + + public double DesiredHeight + { + get { return desiredHeight; } + private set + { + if (desiredHeight != value && DesiredHeightChanged != null) + { + desiredHeight = value; + DesiredHeightChanged(this, EventArgs.Empty); + } + } + } + + public event EventHandler IsDirtyChanged + { + add { } + remove { } + } + + public event EventHandler IsReadOnlyChanged + { + add { } + remove { } + } + + public event EventHandler<RecreateContentEventArgs> RecreateContent = delegate { }; + public event EventHandler<EventArgs> DesiredHeightChanged; + + public bool CanSave(out string defaultPath) + { + defaultPath = null; + return false; + } + + public IPeekResultScrollState CaptureScrollState() + { + return null; + } + + public void Close() + { + } + + public UIElement Create(IPeekSession session, IPeekResultScrollState scrollState) + { + view = new InlineCommentPeekView(); + view.DataContext = viewModel; + + // Report the desired size back to the peek view. Unfortunately the peek view + // helpfully assigns this desired size to the control that also contains the tab at + // the top of the peek view, so we need to put in a fudge factor. Using a const + // value for the moment, as there's no easy way to get the size of the control. + view.DesiredHeight.Subscribe(x => DesiredHeight = x + PeekBorders); + + return view; + } + + public void Dispose() + { + } + + public void ScrollIntoView(IPeekResultScrollState scrollState) + { + } + + public void SetKeyboardFocus() + { + } + + public bool TryOpen(IPeekResult otherResult) => false; + + public bool TryPrepareToClose() => true; + + public bool TrySave(bool saveAs) => true; + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekResultPresenter.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekResultPresenter.cs new file mode 100644 index 0000000000..2c0ee81087 --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekResultPresenter.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Utilities; + +namespace GitHub.InlineReviews.Peek +{ + [Export(typeof(IPeekResultPresenter))] + [Name("GitHub Inline Comments Peek Presenter")] + class InlineCommentPeekResultPresenter : IPeekResultPresenter + { + public IPeekResultPresentation TryCreatePeekResultPresentation(IPeekResult result) + { + var review = result as InlineCommentPeekResult; + return review != null ? + new InlineCommentPeekResultPresentation(review.ViewModel) : + null; + } + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItem.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItem.cs new file mode 100644 index 0000000000..2f07f939f2 --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItem.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Language.Intellisense; +using GitHub.InlineReviews.ViewModels; + +namespace GitHub.InlineReviews.Peek +{ + class InlineCommentPeekableItem : IPeekableItem + { + public InlineCommentPeekableItem(InlineCommentPeekViewModel viewModel) + { + ViewModel = viewModel; + } + + public string DisplayName => "GitHub Code Review"; + public InlineCommentPeekViewModel ViewModel { get; } + + public IEnumerable<IPeekRelationship> Relationships => new[] { InlineCommentPeekRelationship.Instance }; + + public IPeekResultSource GetOrCreateResultSource(string relationshipName) + { + return new InlineCommentPeekableResultSource(ViewModel); + } + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItemSource.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItemSource.cs new file mode 100644 index 0000000000..21e63fbddd --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItemSource.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.InlineReviews.Commands; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.ViewModels; +using GitHub.Services; +using Microsoft.VisualStudio.Language.Intellisense; + +namespace GitHub.InlineReviews.Peek +{ + class InlineCommentPeekableItemSource : IPeekableItemSource + { + readonly IInlineCommentPeekService peekService; + readonly IPullRequestSessionManager sessionManager; + readonly INextInlineCommentCommand nextCommentCommand; + readonly IPreviousInlineCommentCommand previousCommentCommand; + readonly ICommentService commentService; + + public InlineCommentPeekableItemSource(IInlineCommentPeekService peekService, + IPullRequestSessionManager sessionManager, + INextInlineCommentCommand nextCommentCommand, + IPreviousInlineCommentCommand previousCommentCommand, + ICommentService commentService) + { + this.peekService = peekService; + this.sessionManager = sessionManager; + this.nextCommentCommand = nextCommentCommand; + this.previousCommentCommand = previousCommentCommand; + this.commentService = commentService; + } + + public void AugmentPeekSession(IPeekSession session, IList<IPeekableItem> peekableItems) + { + if (session.RelationshipName == InlineCommentPeekRelationship.Instance.Name) + { + var viewModel = new InlineCommentPeekViewModel( + peekService, + session, + sessionManager, + nextCommentCommand, + previousCommentCommand, + commentService); + viewModel.Initialize().Forget(); + peekableItems.Add(new InlineCommentPeekableItem(viewModel)); + } + } + + public void Dispose() + { + } + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItemSourceProvider.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItemSourceProvider.cs new file mode 100644 index 0000000000..65558b3d83 --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableItemSourceProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Commands; +using GitHub.Factories; +using GitHub.InlineReviews.Commands; +using GitHub.InlineReviews.Services; +using GitHub.Services; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; + +namespace GitHub.InlineReviews.Peek +{ + [Export(typeof(IPeekableItemSourceProvider))] + [ContentType("text")] + [Name("GitHub Inline Comments Peekable Item Source")] + class InlineCommentPeekableItemSourceProvider : IPeekableItemSourceProvider + { + readonly IInlineCommentPeekService peekService; + readonly IPullRequestSessionManager sessionManager; + readonly INextInlineCommentCommand nextCommentCommand; + readonly IPreviousInlineCommentCommand previousCommentCommand; + readonly ICommentService commentService; + + [ImportingConstructor] + public InlineCommentPeekableItemSourceProvider( + IInlineCommentPeekService peekService, + IPullRequestSessionManager sessionManager, + INextInlineCommentCommand nextCommentCommand, + IPreviousInlineCommentCommand previousCommentCommand, + ICommentService commentService) + { + this.peekService = peekService; + this.sessionManager = sessionManager; + this.nextCommentCommand = nextCommentCommand; + this.previousCommentCommand = previousCommentCommand; + this.commentService = commentService; + } + + public IPeekableItemSource TryCreatePeekableItemSource(ITextBuffer textBuffer) + { + return new InlineCommentPeekableItemSource( + peekService, + sessionManager, + nextCommentCommand, + previousCommentCommand, + commentService); + } + } +} diff --git a/src/GitHub.InlineReviews/Peek/InlineCommentPeekableResultSource.cs b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableResultSource.cs new file mode 100644 index 0000000000..b15dddbb60 --- /dev/null +++ b/src/GitHub.InlineReviews/Peek/InlineCommentPeekableResultSource.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; +using Microsoft.VisualStudio.Language.Intellisense; +using GitHub.InlineReviews.ViewModels; + +namespace GitHub.InlineReviews.Peek +{ + class InlineCommentPeekableResultSource : IPeekResultSource + { + readonly InlineCommentPeekViewModel viewModel; + + public InlineCommentPeekableResultSource(InlineCommentPeekViewModel viewModel) + { + this.viewModel = viewModel; + } + + public void FindResults(string relationshipName, IPeekResultCollection resultCollection, CancellationToken cancellationToken, IFindPeekResultsCallback callback) + { + resultCollection.Add(new InlineCommentPeekResult(viewModel)); + } + } +} diff --git a/src/GitHub.InlineReviews/Properties/AssemblyInfo.cs b/src/GitHub.InlineReviews/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f4657e8520 --- /dev/null +++ b/src/GitHub.InlineReviews/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("GitHub.InlineReviews")] +[assembly: AssemblyDescription("Provides inline viewing of PR review comments")] +[assembly: Guid("3bf91177-3d16-425d-9c62-50a86cf26298")] diff --git a/src/GitHub.InlineReviews/Properties/DesignTimeResources.xaml b/src/GitHub.InlineReviews/Properties/DesignTimeResources.xaml new file mode 100644 index 0000000000..389ecf3081 --- /dev/null +++ b/src/GitHub.InlineReviews/Properties/DesignTimeResources.xaml @@ -0,0 +1,11 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsColorsBlue.xaml" /> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsBrushesBlue.xaml" /> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/ThemeBlue.xaml" /> + </ResourceDictionary.MergedDictionaries> + +</ResourceDictionary> diff --git a/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs b/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs new file mode 100644 index 0000000000..55eeb756e6 --- /dev/null +++ b/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading; +using System.Runtime.InteropServices; +using GitHub.VisualStudio; +using GitHub.InlineReviews.Services; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.ComponentModelHost; + +namespace GitHub.InlineReviews +{ + [Guid(Guids.PullRequestStatusPackageId)] + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] + public class PullRequestStatusBarPackage : AsyncPackage + { + /// <summary> + /// Initialize the PR status UI on Visual Studio's status bar. + /// </summary> + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) + { + // Avoid delays when there is ongoing UI activity. + // See: https://github.com/github/VisualStudio/issues/1537 + await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeStatusBar); + } + + async Task InitializeStatusBar() + { + var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); + var exports = componentModel.DefaultExportProvider; + var barManager = exports.GetExportedValue<PullRequestStatusBarManager>(); + + await JoinableTaskFactory.SwitchToMainThreadAsync(); + barManager.StartShowingStatus(); + } + } +} diff --git a/src/GitHub.InlineReviews/Resources/logo_32x32@2x.png b/src/GitHub.InlineReviews/Resources/logo_32x32@2x.png new file mode 100644 index 0000000000..1fd18c1c7a Binary files /dev/null and b/src/GitHub.InlineReviews/Resources/logo_32x32@2x.png differ diff --git a/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs new file mode 100644 index 0000000000..eac1dca542 --- /dev/null +++ b/src/GitHub.InlineReviews/SampleData/CommentThreadViewModelDesigner.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using GitHub.InlineReviews.ViewModels; +using GitHub.Models; +using GitHub.ViewModels; +using ReactiveUI; + +namespace GitHub.InlineReviews.SampleData +{ + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + class CommentThreadViewModelDesigner : ICommentThreadViewModel + { + public ObservableCollection<ICommentViewModel> Comments { get; } + = new ObservableCollection<ICommentViewModel>(); + + public IActorViewModel CurrentUser { get; set; } + = new ActorViewModel { Login = "shana" }; + + public ReactiveCommand<Unit> PostComment { get; } + public ReactiveCommand<Unit> EditComment { get; } + public ReactiveCommand<Unit> DeleteComment { get; } + } +} diff --git a/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs b/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs new file mode 100644 index 0000000000..ddd907015c --- /dev/null +++ b/src/GitHub.InlineReviews/SampleData/CommentViewModelDesigner.cs @@ -0,0 +1,38 @@ +using System; +using System.Reactive; +using System.Diagnostics.CodeAnalysis; +using GitHub.InlineReviews.ViewModels; +using ReactiveUI; +using GitHub.ViewModels; + +namespace GitHub.InlineReviews.SampleData +{ + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + class CommentViewModelDesigner : ReactiveObject, ICommentViewModel + { + public CommentViewModelDesigner() + { + Author = new ActorViewModel { Login = "shana" }; + } + + public string Id { get; set; } + public int PullRequestId { get; set; } + public int DatabaseId { get; set; } + public string Body { get; set; } + public string ErrorMessage { get; set; } + public CommentEditState EditState { get; set; } + public bool IsReadOnly { get; set; } + public bool IsSubmitting { get; set; } + public bool CanDelete { get; } = true; + public ICommentThreadViewModel Thread { get; } + public DateTimeOffset UpdatedAt => DateTime.Now.Subtract(TimeSpan.FromDays(3)); + public IActorViewModel Author { get; set; } + public Uri WebUrl { get; } + + public ReactiveCommand<object> BeginEdit { get; } + public ReactiveCommand<object> CancelEdit { get; } + public ReactiveCommand<Unit> CommitEdit { get; } + public ReactiveCommand<object> OpenOnGitHub { get; } + public ReactiveCommand<Unit> Delete { get; } + } +} diff --git a/src/GitHub.InlineReviews/Services/CommentService.cs b/src/GitHub.InlineReviews/Services/CommentService.cs new file mode 100644 index 0000000000..566c4b764e --- /dev/null +++ b/src/GitHub.InlineReviews/Services/CommentService.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.Composition; +using System.Windows.Forms; + +namespace GitHub.InlineReviews.Services +{ + [Export(typeof(ICommentService))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class CommentService:ICommentService + { + public bool ConfirmCommentDelete() + { + return MessageBox.Show( + VisualStudio.UI.Resources.DeleteCommentConfirmation, + VisualStudio.UI.Resources.DeleteCommentConfirmationCaption, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question) == DialogResult.Yes; + } + } +} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/Services/DiffService.cs b/src/GitHub.InlineReviews/Services/DiffService.cs new file mode 100644 index 0000000000..625bf7492e --- /dev/null +++ b/src/GitHub.InlineReviews/Services/DiffService.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Service for generating parsed diffs. + /// </summary> + [Export(typeof(IDiffService))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class DiffService : IDiffService + { + readonly IGitClient gitClient; + + [ImportingConstructor] + public DiffService(IGitClient gitClient) + { + this.gitClient = gitClient; + } + + /// <inheritdoc/> + public async Task<IReadOnlyList<DiffChunk>> Diff( + IRepository repo, + string baseSha, + string headSha, + string path) + { + var patch = await gitClient.Compare(repo, baseSha, headSha, path); + + if (patch != null) + { + return DiffUtilities.ParseFragment(patch).ToList(); + } + else + { + return new DiffChunk[0]; + } + } + + /// <inheritdoc/> + public async Task<IReadOnlyList<DiffChunk>> Diff( + IRepository repo, + string baseSha, + string headSha, + string path, + byte[] contents) + { + var changes = await gitClient.CompareWith(repo, baseSha, headSha, path, contents); + + if (changes?.Patch != null) + { + return DiffUtilities.ParseFragment(changes.Patch).ToList(); + } + else + { + return new DiffChunk[0]; + } + } + } +} diff --git a/src/GitHub.InlineReviews/Services/ICommentService.cs b/src/GitHub.InlineReviews/Services/ICommentService.cs new file mode 100644 index 0000000000..a206eb2ac4 --- /dev/null +++ b/src/GitHub.InlineReviews/Services/ICommentService.cs @@ -0,0 +1,14 @@ +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// This service allows for functionality to be injected into the chain of different peek Comment ViewModel types. + /// </summary> + public interface ICommentService + { + /// <summary> + /// This function uses MessageBox.Show to display a confirmation if a comment should be deleted. + /// </summary> + /// <returns></returns> + bool ConfirmCommentDelete(); + } +} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/Services/IDiffService.cs b/src/GitHub.InlineReviews/Services/IDiffService.cs new file mode 100644 index 0000000000..c8c76aeeed --- /dev/null +++ b/src/GitHub.InlineReviews/Services/IDiffService.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Models; +using LibGit2Sharp; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Service for generating parsed diffs. + /// </summary> + public interface IDiffService + { + /// <summary> + /// Calculates the diff of a file in a repository between two commits. + /// </summary> + /// <param name="repo">The repository</param> + /// <param name="baseSha">The base commit SHA.</param> + /// <param name="headSha">The head commit SHA.</param> + /// <param name="relativePath">The path to the file in the repository.</param> + /// <returns> + /// A collection of <see cref="DiffChunk"/>s containing the parsed diff. + /// </returns> + Task<IReadOnlyList<DiffChunk>> Diff(IRepository repo, string baseSha, string headSha, string relativePath); + + /// <summary> + /// Calculates the diff of a file in a repository between a base commit and a byte arrat. + /// </summary> + /// <param name="repo">The repository</param> + /// <param name="baseSha">The base commit SHA.</param> + /// <param name="headSha">The head commit SHA.</param> + /// <param name="relativePath">The path to the file in the repository.</param> + /// <param name="contents">The byte array to compare with the base SHA.</param> + /// <returns> + /// A collection of <see cref="DiffChunk"/>s containing the parsed diff. + /// </returns> + /// <remarks> + /// Note that even though the comparison is done between <paramref name="baseSha"/> and + /// <paramref name="contents"/>, the <paramref name="headSha"/> still needs to be provided in order + /// to track renames. + /// </remarks> + Task<IReadOnlyList<DiffChunk>> Diff(IRepository repo, string baseSha, string headSha, string relativePath, byte[] contents); + } +} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/Services/IInlineCommentPeekService.cs b/src/GitHub.InlineReviews/Services/IInlineCommentPeekService.cs new file mode 100644 index 0000000000..5351d64a2d --- /dev/null +++ b/src/GitHub.InlineReviews/Services/IInlineCommentPeekService.cs @@ -0,0 +1,45 @@ +using System; +using GitHub.InlineReviews.Tags; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Shows inline comments in a peek view. + /// </summary> + public interface IInlineCommentPeekService + { + /// <summary> + /// Gets the line number for a peek session tracking point. + /// </summary> + /// <param name="session">The peek session.</param> + /// <param name="point">The peek session tracking point</param> + /// <returns> + /// A tuple containing the line number and whether the line number represents a line in the + /// left hand side of a diff view. + /// </returns> + Tuple<int, bool> GetLineNumber(IPeekSession session, ITrackingPoint point); + + /// <summary> + /// Hides the inline comment peek view for a text view. + /// </summary> + /// <param name="textView">The text view.</param> + void Hide(ITextView textView); + + /// <summary> + /// Shows the peek view for a <see cref="ShowInlineCommentTag"/>. + /// </summary> + /// <param name="textView">The text view.</param> + /// <param name="tag">The tag.</param> + ITrackingPoint Show(ITextView textView, ShowInlineCommentTag tag); + + /// <summary> + /// Shows the peek view for an <see cref="AddInlineCommentTag"/>. + /// </summary> + /// <param name="textView">The text view.</param> + /// <param name="tag">The tag.</param> + ITrackingPoint Show(ITextView textView, AddInlineCommentTag tag); + } +} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs new file mode 100644 index 0000000000..a867a4393a --- /dev/null +++ b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs @@ -0,0 +1,347 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using Microsoft.VisualStudio.Text; +using Octokit; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Provides a common interface for services required by <see cref="PullRequestSession"/>. + /// </summary> + public interface IPullRequestSessionService + { + /// <summary> + /// Carries out a diff of a file between two commits. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="baseSha">The commit to use as the base.</param> + /// <param name="headSha">The commit to use as the head.</param> + /// <param name="relativePath">The relative path to the file.</param> + /// <returns></returns> + Task<IReadOnlyList<DiffChunk>> Diff( + ILocalRepositoryModel repository, + string baseSha, + string headSha, + string relativePath); + + /// <summary> + /// Carries out a diff between a file at a commit and the current file contents. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="baseSha">The commit to use as the base.</param> + /// <param name="headSha">The commit to use as the head.</param> + /// <param name="relativePath">The relative path to the file.</param> + /// <param name="contents">The contents of the file.</param> + /// <returns></returns> + Task<IReadOnlyList<DiffChunk>> Diff( + ILocalRepositoryModel repository, + string baseSha, + string headSha, + string relativePath, + byte[] contents); + + /// <summary> + /// Builds a set of comment thread models for a file based on a pull request model and a diff. + /// </summary> + /// <param name="pullRequest">The pull request session.</param> + /// <param name="relativePath">The relative path to the file.</param> + /// <param name="diff">The diff.</param> + /// <param name="headSha">The SHA of the <paramref name="diff"/> HEAD.</param> + /// <returns> + /// A collection of <see cref="IInlineCommentThreadModel"/> objects with updated line numbers. + /// </returns> + IReadOnlyList<IInlineCommentThreadModel> BuildCommentThreads( + PullRequestDetailModel pullRequest, + string relativePath, + IReadOnlyList<DiffChunk> diff, + string headSha); + + /// <summary> + /// Updates a set of comment thread models for a file based on a new diff. + /// </summary> + /// <param name="threads">The theads to update.</param> + /// <param name="diff">The diff.</param> + /// <returns> + /// A collection of updated line numbers. + /// </returns> + IReadOnlyList<Tuple<int, DiffSide>> UpdateCommentThreads( + IReadOnlyList<IInlineCommentThreadModel> threads, + IReadOnlyList<DiffChunk> diff); + + /// <summary> + /// Tests whether the contents of a file represent a commit that is pushed to origin. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="relativePath">The relative path to the file.</param> + /// <param name="contents">The contents of the file.</param> + /// <returns> + /// A task returning true if the file is unmodified with respect to the latest commit + /// pushed to origin; otherwise false. + /// </returns> + Task<bool> IsUnmodifiedAndPushed( + ILocalRepositoryModel repository, + string relativePath, + byte[] contents); + + /// <summary> + /// Extracts a file at a specified commit from the repository. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequestNumber">The pull request number</param> + /// <param name="sha">The SHA of the commit.</param> + /// <param name="relativePath">The path to the file, relative to the repository.</param> + /// <returns> + /// The contents of the file, or null if the file was not found at the specified commit. + /// </returns> + Task<byte[]> ExtractFileFromGit( + ILocalRepositoryModel repository, + int pullRequestNumber, + string sha, + string relativePath); + + /// <summary> + /// Gets the associated <see cref="ITextDocument"/> for an <see cref="ITextBuffer"/>. + /// </summary> + /// <param name="buffer">The buffer.</param> + /// <returns> + /// The associated document, or null if not found. + /// </returns> + ITextDocument GetDocument(ITextBuffer buffer); + + /// <summary> + /// Gets the contents of an <see cref="ITextBuffer"/> using the buffer's current encoding. + /// </summary> + /// <param name="buffer">The buffer.</param> + /// <returns>The contents of the buffer.</returns> + byte[] GetContents(ITextBuffer buffer); + + /// <summary> + /// Gets the SHA of the tip of the current branch. + /// </summary> + /// <param name="repository">The repository.</param> + /// <returns>The tip SHA.</returns> + Task<string> GetTipSha(ILocalRepositoryModel repository); + + /// <summary> + /// Asynchronously reads the contents of a file. + /// </summary> + /// <param name="path">The full path to the file.</param> + /// <returns> + /// A task returning the contents of the file, or null if the file was not found. + /// </returns> + Task<byte[]> ReadFileAsync(string path); + + /// <summary> + /// Reads a <see cref="PullRequestDetailModel"/> for a specified pull request. + /// </summary> + /// <param name="address">The host address.</param> + /// <param name="owner">The repository owner.</param> + /// <param name="name">The repository name.</param> + /// <param name="number">The pull request number.</param> + /// <returns>A task returning the pull request model.</returns> + Task<PullRequestDetailModel> ReadPullRequestDetail(HostAddress address, string owner, string name, int number); + + /// <summary> + /// Reads the current viewer for the specified address.. + /// </summary> + /// <param name="address">The host address.</param> + /// <returns>A task returning the viewer.</returns> + /// <remarks> + /// A "Viewer" is the GraphQL term for the currently authenticated user. + /// </remarks> + Task<ActorModel> ReadViewer(HostAddress address); + + /// <summary> + /// Find the merge base for a pull request. + /// </summary> + /// <param name="repository">The repository.</param> + /// <param name="pullRequest">The pull request.</param> + /// <returns> + /// The merge base SHA for the PR. + /// </returns> + Task<string> GetPullRequestMergeBase(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest); + + /// <summary> + /// Gets the GraphQL ID for a pull request. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="repositoryOwner">The owner of the remote fork.</param> + /// <param name="number">The pull request number.</param> + /// <returns></returns> + Task<string> GetGraphQLPullRequestId( + ILocalRepositoryModel localRepository, + string repositoryOwner, + int number); + + /// <summary> + /// Creates a rebuild signal subject for a <see cref="IPullRequestSessionLiveFile"/>. + /// </summary> + /// <returns> + /// A subject which is used to signal a rebuild of a live file. + /// </returns> + /// <remarks> + /// The creation of the rebuild signal for a <see cref="IPullRequestSessionLiveFile"/> is + /// abstracted out into this service for unit testing. The default behavior of this subject + /// is to throttle the signal for 500ms so that the live file is not updated continuously + /// while the user is typing. + /// </remarks> + ISubject<ITextSnapshot, ITextSnapshot> CreateRebuildSignal(); + + /// <summary> + /// Creates a new pending review on the server. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="user">The user posting the review.</param> + /// <param name="pullRequestId">The GraphQL ID of the pull request.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> CreatePendingReview( + ILocalRepositoryModel localRepository, + string pullRequestId); + + /// <summary> + /// Cancels a pending review on the server. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="reviewId">The GraphQL ID of the review.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> CancelPendingReview( + ILocalRepositoryModel localRepository, + string reviewId); + + /// <summary> + /// Posts PR review with no comments. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="pullRequestId">The GraphQL ID of the pull request.</param> + /// <param name="commitId">The SHA of the commit being reviewed.</param> + /// <param name="body">The review body.</param> + /// <param name="e">The review event.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> PostReview( + ILocalRepositoryModel localRepository, + string pullRequestId, + string commitId, + string body, + PullRequestReviewEvent e); + + /// <summary> + /// Submits a pending PR review. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="pendingReviewId">The GraphQL ID of the pending review.</param> + /// <param name="body">The review body.</param> + /// <param name="e">The review event.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> SubmitPendingReview( + ILocalRepositoryModel localRepository, + string pendingReviewId, + string body, + PullRequestReviewEvent e); + + /// <summary> + /// Posts a new pending PR review comment. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="pendingReviewId">The GraphQL ID of the pending review.</param> + /// <param name="body">The comment body.</param> + /// <param name="commitId">THe SHA of the commit to comment on.</param> + /// <param name="path">The relative path of the file to comment on.</param> + /// <param name="position">The line index in the diff to comment on.</param> + /// <returns>The updated state of the pull request.</returns> + /// <remarks> + /// This method posts a new pull request comment to a pending review started by + /// <see cref="CreatePendingReview(ILocalRepositoryModel, string)"/>. + /// </remarks> + Task<PullRequestDetailModel> PostPendingReviewComment( + ILocalRepositoryModel localRepository, + string pendingReviewId, + string body, + string commitId, + string path, + int position); + + /// <summary> + /// Posts a new pending PR review comment reply. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="pendingReviewId">The GraphQL ID of the pending review.</param> + /// <param name="body">The comment body.</param> + /// <param name="inReplyTo">The GraphQL ID of the comment to reply to.</param> + /// <returns>The updated state of the pull request.</returns> + /// <remarks> + /// The method posts a new pull request comment to a pending review started by + /// <see cref="CreatePendingReview(ILocalRepositoryModel, string)"/>. + /// </remarks> + Task<PullRequestDetailModel> PostPendingReviewCommentReply( + ILocalRepositoryModel localRepository, + string pendingReviewId, + string body, + string inReplyTo); + + /// <summary> + /// Posts a new standalone PR review comment. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="pullRequestId">The GraphQL ID of the pull request.</param> + /// <param name="body">The comment body.</param> + /// <param name="commitId">THe SHA of the commit to comment on.</param> + /// <param name="path">The relative path of the file to comment on.</param> + /// <param name="position">The line index in the diff to comment on.</param> + /// <returns>The updated state of the pull request.</returns> + /// <remarks> + /// The method posts a new standalone pull request comment that is not attached to a pending + /// pull request review. + /// </remarks> + Task<PullRequestDetailModel> PostStandaloneReviewComment( + ILocalRepositoryModel localRepository, + string pullRequestId, + string body, + string commitId, + string path, + int position); + + /// <summary> + /// Posts a PR review comment reply. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="pullRequestId">The GraphQL ID of the pull request.</param> + /// <param name="body">The comment body.</param> + /// <param name="inReplyTo">The GraphQL ID of the comment to reply to.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> PostStandaloneReviewCommentReply( + ILocalRepositoryModel localRepository, + string pullRequestId, + string body, + string inReplyTo); + + /// <summary> + /// Delete a PR review comment. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="remoteRepositoryOwner">The owner of the repository.</param> + /// <param name="pullRequestId">The pull request id of the comment</param> + /// <param name="commentDatabaseId">The pull request comment number.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> DeleteComment(ILocalRepositoryModel localRepository, + string remoteRepositoryOwner, + int pullRequestId, + int commentDatabaseId); + + /// <summary> + /// Edit a PR review comment. + /// </summary> + /// <param name="localRepository">The local repository.</param> + /// <param name="remoteRepositoryOwner">The owner of the repository.</param> + /// <param name="commentNodeId">The pull request comment node id.</param> + /// <param name="body">The replacement comment body.</param> + /// <returns>The updated state of the pull request.</returns> + Task<PullRequestDetailModel> EditComment(ILocalRepositoryModel localRepository, + string remoteRepositoryOwner, + string commentNodeId, + string body); + } +} diff --git a/src/GitHub.InlineReviews/Services/InlineCommentPeekService.cs b/src/GitHub.InlineReviews/Services/InlineCommentPeekService.cs new file mode 100644 index 0000000000..892a7c47b1 --- /dev/null +++ b/src/GitHub.InlineReviews/Services/InlineCommentPeekService.cs @@ -0,0 +1,175 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.InlineReviews.Peek; +using GitHub.InlineReviews.Tags; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Outlining; +using Microsoft.VisualStudio.Text.Projection; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Shows inline comments in a peek view. + /// </summary> + [Export(typeof(IInlineCommentPeekService))] + class InlineCommentPeekService : IInlineCommentPeekService + { + readonly IOutliningManagerService outliningService; + readonly IPeekBroker peekBroker; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public InlineCommentPeekService( + IOutliningManagerService outliningManager, + IPeekBroker peekBroker, + IUsageTracker usageTracker) + { + this.outliningService = outliningManager; + this.peekBroker = peekBroker; + this.usageTracker = usageTracker; + } + + /// <inheritdoc/> + public Tuple<int, bool> GetLineNumber(IPeekSession session, ITrackingPoint point) + { + var diffModel = (session.TextView as IWpfTextView)?.TextViewModel as IDifferenceTextViewModel; + var leftBuffer = false; + ITextSnapshotLine line = null; + + if (diffModel != null) + { + if (diffModel.ViewType == DifferenceViewType.InlineView) + { + // If we're displaying a diff in inline mode, then we need to map the point down + // to the left or right buffer. + var snapshotPoint = point.GetPoint(point.TextBuffer.CurrentSnapshot); + var mappedPoint = session.TextView.BufferGraph.MapDownToFirstMatch( + snapshotPoint, + PointTrackingMode.Negative, + x => !(x is IProjectionSnapshot), + PositionAffinity.Successor); + + if (mappedPoint != null) + { + leftBuffer = mappedPoint.Value.Snapshot == diffModel.Viewer.DifferenceBuffer.LeftBuffer.CurrentSnapshot; + line = mappedPoint.Value.GetContainingLine(); + } + } + else + { + // If we're displaying a diff in any other mode than inline, then we're in the + // left buffer if the session's text view is the diff's left view. + leftBuffer = session.TextView == diffModel.Viewer.LeftView; + } + } + + if (line == null) + { + line = point.GetPoint(point.TextBuffer.CurrentSnapshot).GetContainingLine(); + } + + return Tuple.Create(line.LineNumber, leftBuffer); + } + + /// <inheritdoc/> + public void Hide(ITextView textView) + { + peekBroker.DismissPeekSession(textView); + } + + /// <inheritdoc/> + public ITrackingPoint Show(ITextView textView, AddInlineCommentTag tag) + { + Guard.ArgumentNotNull(tag, nameof(tag)); + + var lineAndtrackingPoint = GetLineAndTrackingPoint(textView, tag); + var line = lineAndtrackingPoint.Item1; + var trackingPoint = lineAndtrackingPoint.Item2; + var options = new PeekSessionCreationOptions( + textView, + InlineCommentPeekRelationship.Instance.Name, + trackingPoint, + defaultHeight: 0); + + ExpandCollapsedRegions(textView, line.Extent); + + var session = peekBroker.TriggerPeekSession(options); + var item = session.PeekableItems.OfType<InlineCommentPeekableItem>().FirstOrDefault(); + item?.ViewModel.Close.Take(1).Subscribe(_ => session.Dismiss()); + + return trackingPoint; + } + + /// <inheritdoc/> + public ITrackingPoint Show(ITextView textView, ShowInlineCommentTag tag) + { + Guard.ArgumentNotNull(textView, nameof(textView)); + Guard.ArgumentNotNull(tag, nameof(tag)); + + var lineAndtrackingPoint = GetLineAndTrackingPoint(textView, tag); + var line = lineAndtrackingPoint.Item1; + var trackingPoint = lineAndtrackingPoint.Item2; + var options = new PeekSessionCreationOptions( + textView, + InlineCommentPeekRelationship.Instance.Name, + trackingPoint, + defaultHeight: 0); + + ExpandCollapsedRegions(textView, line.Extent); + + var session = peekBroker.TriggerPeekSession(options); + var item = session.PeekableItems.OfType<InlineCommentPeekableItem>().FirstOrDefault(); + item?.ViewModel.Close.Take(1).Subscribe(_ => session.Dismiss()); + + return trackingPoint; + } + + Tuple<ITextSnapshotLine, ITrackingPoint> GetLineAndTrackingPoint(ITextView textView, InlineCommentTag tag) + { + var diffModel = (textView as IWpfTextView)?.TextViewModel as IDifferenceTextViewModel; + var snapshot = textView.TextSnapshot; + + if (diffModel?.ViewType == DifferenceViewType.InlineView) + { + snapshot = tag.DiffChangeType == DiffChangeType.Delete ? + diffModel.Viewer.DifferenceBuffer.LeftBuffer.CurrentSnapshot : + diffModel.Viewer.DifferenceBuffer.RightBuffer.CurrentSnapshot; + } + + var line = snapshot.GetLineFromLineNumber(tag.LineNumber); + var trackingPoint = snapshot.CreateTrackingPoint(line.Start.Position, PointTrackingMode.Positive); + + ExpandCollapsedRegions(textView, line.Extent); + peekBroker.TriggerPeekSession(textView, trackingPoint, InlineCommentPeekRelationship.Instance.Name); + + usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentOpen).Forget(); + + return Tuple.Create(line, trackingPoint); + } + + void ExpandCollapsedRegions(ITextView textView, SnapshotSpan span) + { + var outlining = outliningService.GetOutliningManager(textView); + + if (outlining != null) + { + foreach (var collapsed in outlining.GetCollapsedRegions(span)) + { + outlining.Expand(collapsed); + } + } + } + } +} diff --git a/src/GitHub.InlineReviews/Services/PullRequestSession.cs b/src/GitHub.InlineReviews/Services/PullRequestSession.cs new file mode 100644 index 0000000000..0759b996d2 --- /dev/null +++ b/src/GitHub.InlineReviews/Services/PullRequestSession.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.InlineReviews.Models; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; +using System.Threading; +using System.Reactive.Subjects; +using static System.FormattableString; +using GitHub.Primitives; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// A pull request session used to display inline reviews. + /// </summary> + /// <remarks> + /// A pull request session represents the real-time state of a pull request in the IDE. + /// It takes the pull request model and updates according to the current state of the + /// repository on disk and in the editor. + /// </remarks> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", + Justification = "PullRequestSession is shared and shouldn't be disposed")] + public class PullRequestSession : ReactiveObject, IPullRequestSession + { + readonly IPullRequestSessionService service; + readonly Dictionary<string, PullRequestSessionFile> fileIndex = new Dictionary<string, PullRequestSessionFile>(); + readonly SemaphoreSlim getFilesLock = new SemaphoreSlim(1); + bool isCheckedOut; + string mergeBase; + IReadOnlyList<IPullRequestSessionFile> files; + PullRequestDetailModel pullRequest; + string pullRequestNodeId; + Subject<PullRequestDetailModel> pullRequestChanged = new Subject<PullRequestDetailModel>(); + bool hasPendingReview; + + public PullRequestSession( + IPullRequestSessionService service, + ActorModel user, + PullRequestDetailModel pullRequest, + ILocalRepositoryModel localRepository, + string repositoryOwner, + bool isCheckedOut) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(user, nameof(user)); + Guard.ArgumentNotNull(pullRequest, nameof(pullRequest)); + Guard.ArgumentNotNull(localRepository, nameof(localRepository)); + + this.service = service; + this.isCheckedOut = isCheckedOut; + this.pullRequest = pullRequest; + User = user; + LocalRepository = localRepository; + RepositoryOwner = repositoryOwner; + UpdatePendingReview(); + } + + /// <inheritdoc/> + public async Task<IReadOnlyList<IPullRequestSessionFile>> GetAllFiles() + { + if (files == null) + { + files = await CreateAllFiles(); + } + + return files; + } + + /// <inheritdoc/> + public async Task<IPullRequestSessionFile> GetFile( + string relativePath, + string commitSha = "HEAD") + { + await getFilesLock.WaitAsync(); + + try + { + PullRequestSessionFile file; + var normalizedPath = relativePath.Replace("\\", "/"); + var key = normalizedPath + '@' + commitSha; + + if (!fileIndex.TryGetValue(key, out file)) + { + file = new PullRequestSessionFile(normalizedPath, commitSha); + await UpdateFile(file); + fileIndex.Add(key, file); + } + + return file; + } + finally + { + getFilesLock.Release(); + } + } + + /// <inheritdoc/> + public async Task<string> GetMergeBase() + { + if (mergeBase == null) + { + mergeBase = await service.GetPullRequestMergeBase(LocalRepository, PullRequest); + } + + return mergeBase; + } + + /// <inheritdoc/> + 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; + } + + /// <inheritdoc/> + public async Task PostReviewComment( + string body, + string commitId, + string path, + IReadOnlyList<DiffChunk> diff, + int position) + { + if (!HasPendingReview) + { + var model = await service.PostStandaloneReviewComment( + LocalRepository, + PullRequest.Id, + body, + commitId, + path, + position); + await Update(model); + } + else + { + var model = await service.PostPendingReviewComment( + LocalRepository, + PendingReviewId, + body, + commitId, + path, + position); + await Update(model); + } + } + + /// <inheritdoc/> + public async Task DeleteComment(int pullRequestId, int commentDatabaseId) + { + var model = await service.DeleteComment( + LocalRepository, + RepositoryOwner, + pullRequestId, + commentDatabaseId); + + await Update(model); + } + + /// <inheritdoc/> + public async Task EditComment(string commentNodeId, string body) + { + var model = await service.EditComment( + LocalRepository, + RepositoryOwner, + commentNodeId, + body); + + await Update(model); + } + + /// <inheritdoc/> + public async Task PostReviewComment( + string body, + string inReplyTo) + { + if (!HasPendingReview) + { + var model = await service.PostStandaloneReviewCommentReply( + LocalRepository, + PullRequest.Id, + body, + inReplyTo); + await Update(model); + } + else + { + var model = await service.PostPendingReviewCommentReply( + LocalRepository, + PendingReviewId, + body, + inReplyTo); + await Update(model); + } + } + + /// <inheritdoc/> + public async Task StartReview() + { + if (HasPendingReview) + { + throw new InvalidOperationException("A pending review is already underway."); + } + + var model = await service.CreatePendingReview( + LocalRepository, + await GetPullRequestNodeId()); + + await Update(model); + } + + /// <inheritdoc/> + public async Task CancelReview() + { + if (!HasPendingReview) + { + throw new InvalidOperationException("There is no pending review to cancel."); + } + + var pullRequest = await service.CancelPendingReview(LocalRepository, PendingReviewId); + await Update(pullRequest); + } + + /// <inheritdoc/> + public async Task PostReview(string body, Octokit.PullRequestReviewEvent e) + { + PullRequestDetailModel model; + + if (PendingReviewId == null) + { + model = await service.PostReview( + LocalRepository, + PullRequest.Id, + PullRequest.HeadRefSha, + body, + e); + } + else + { + model = await service.SubmitPendingReview( + LocalRepository, + PendingReviewId, + body, + e); + } + + await Update(model); + } + + /// <inheritdoc/> + public async Task Refresh() + { + var address = HostAddress.Create(LocalRepository.CloneUrl); + var model = await service.ReadPullRequestDetail( + address, + RepositoryOwner, + LocalRepository.Name, + PullRequest.Number); + await Update(model); + } + + /// <inheritdoc/> + async Task Update(PullRequestDetailModel pullRequestModel) + { + PullRequest = pullRequestModel; + mergeBase = null; + + foreach (var file in this.fileIndex.Values.ToList()) + { + await UpdateFile(file); + } + + UpdatePendingReview(); + pullRequestChanged.OnNext(pullRequestModel); + } + + async Task AddComment(PullRequestReviewCommentModel comment) + { + var review = PullRequest.Reviews.FirstOrDefault(x => x.Id == PendingReviewId); + + if (review == null) + { + throw new KeyNotFoundException("Could not find pending review."); + } + + review.Comments = review.Comments + .Concat(new[] { comment }) + .ToList(); + await Update(PullRequest); + } + + 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); + } + + void UpdatePendingReview() + { + var pendingReview = PullRequest.Reviews + .FirstOrDefault(x => x.State == PullRequestReviewState.Pending && x.Author.Login == User.Login); + + if (pendingReview != null) + { + HasPendingReview = true; + PendingReviewId = pendingReview.Id; + } + else + { + HasPendingReview = false; + PendingReviewId = null; + } + } + + async Task<IReadOnlyList<IPullRequestSessionFile>> CreateAllFiles() + { + var result = new List<IPullRequestSessionFile>(); + + foreach (var path in FilePaths) + { + var file = await GetFile(path); + result.Add(file); + } + + return result; + } + + string GetFullPath(string relativePath) + { + return Path.Combine(LocalRepository.LocalPath, relativePath); + } + + async Task<string> GetPullRequestNodeId() + { + if (pullRequestNodeId == null) + { + pullRequestNodeId = await service.GetGraphQLPullRequestId( + LocalRepository, + RepositoryOwner, + PullRequest.Number); + } + + return pullRequestNodeId; + } + + static string BuildDiffHunk(IReadOnlyList<DiffChunk> diff, int position) + { + var lines = diff.SelectMany(x => x.Lines).Reverse(); + var context = lines.SkipWhile(x => x.DiffLineNumber != position).Take(5).Reverse().ToList(); + var oldLineNumber = context.Select(x => x.OldLineNumber).Where(x => x != -1).FirstOrDefault(); + var newLineNumber = context.Select(x => x.NewLineNumber).Where(x => x != -1).FirstOrDefault(); + var header = Invariant($"@@ -{oldLineNumber},5 +{newLineNumber},5 @@"); + return header + '\n' + string.Join("\n", context); + } + + /// <inheritdoc/> + public bool IsCheckedOut + { + get { return isCheckedOut; } + internal set { this.RaiseAndSetIfChanged(ref isCheckedOut, value); } + } + + /// <inheritdoc/> + public ActorModel User { get; } + + /// <inheritdoc/> + public PullRequestDetailModel PullRequest + { + get { return pullRequest; } + private set + { + // PullRequestModel overrides Equals such that two PRs with the same number are + // considered equal. This was causing the PullRequest not to be updated on refresh: + // we need to use ReferenceEquals. + if (!ReferenceEquals(pullRequest, value)) + { + this.RaisePropertyChanging(nameof(PullRequest)); + pullRequest = value; + this.RaisePropertyChanged(nameof(PullRequest)); + } + } + } + + /// <inheritdoc/> + public IObservable<PullRequestDetailModel> PullRequestChanged => pullRequestChanged; + + /// <inheritdoc/> + public ILocalRepositoryModel LocalRepository { get; } + + /// <inheritdoc/> + public string RepositoryOwner { get; } + + /// <inheritdoc/> + public bool HasPendingReview + { + get { return hasPendingReview; } + private set { this.RaiseAndSetIfChanged(ref hasPendingReview, value); } + } + + /// <inheritdoc/> + public string PendingReviewId { get; private set; } + + IEnumerable<string> FilePaths + { + get { return PullRequest.ChangedFiles.Select(x => x.FileName); } + } + } +} diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs new file mode 100644 index 0000000000..b0d69c0266 --- /dev/null +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.InlineReviews.Models; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Projection; +using ReactiveUI; +using Serilog; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Manages pull request sessions. + /// </summary> + [Export(typeof(IPullRequestSessionManager))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class PullRequestSessionManager : ReactiveObject, IPullRequestSessionManager + { + static readonly ILogger log = LogManager.ForContext<PullRequestSessionManager>(); + readonly IPullRequestService service; + readonly IPullRequestSessionService sessionService; + readonly Dictionary<Tuple<string, int>, WeakReference<PullRequestSession>> sessions = + new Dictionary<Tuple<string, int>, WeakReference<PullRequestSession>>(); + TaskCompletionSource<object> initialized; + IPullRequestSession currentSession; + ILocalRepositoryModel repository; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestSessionManager"/> class. + /// </summary> + /// <param name="service">The PR service to use.</param> + /// <param name="sessionService">The PR session service to use.</param> + /// <param name="teamExplorerContext">The team explorer context to use.</param> + [ImportingConstructor] + public PullRequestSessionManager( + IPullRequestService service, + IPullRequestSessionService sessionService, + ITeamExplorerContext teamExplorerContext) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(sessionService, nameof(sessionService)); + Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); + + this.service = service; + this.sessionService = sessionService; + initialized = new TaskCompletionSource<object>(null); + + Observable.FromEventPattern(teamExplorerContext, nameof(teamExplorerContext.StatusChanged)) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(_ => StatusChanged().Forget(log)); + + teamExplorerContext.WhenAnyValue(x => x.ActiveRepository) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => RepoChanged(x).Forget(log)); + } + + /// <inheritdoc/> + public IPullRequestSession CurrentSession + { + get { return currentSession; } + private set { this.RaiseAndSetIfChanged(ref currentSession, value); } + } + + /// <inheritdoc/> + public Task EnsureInitialized() => initialized.Task; + + /// <inheritdoc/> + public async Task<IPullRequestSessionFile> GetLiveFile( + string relativePath, + ITextView textView, + ITextBuffer textBuffer) + { + PullRequestSessionLiveFile result; + + if (!textBuffer.Properties.TryGetProperty( + typeof(IPullRequestSessionFile), + out result)) + { + var dispose = new CompositeDisposable(); + + result = new PullRequestSessionLiveFile( + relativePath, + textBuffer, + sessionService.CreateRebuildSignal()); + + textBuffer.Properties.AddProperty( + typeof(IPullRequestSessionFile), + result); + + await UpdateLiveFile(result, true); + + textBuffer.Changed += TextBufferChanged; + textView.Closed += TextViewClosed; + + dispose.Add(Disposable.Create(() => + { + textView.TextBuffer.Changed -= TextBufferChanged; + textView.Closed -= TextViewClosed; + })); + + dispose.Add(result.Rebuild.Subscribe(x => UpdateLiveFile(result, x).Forget())); + + dispose.Add(this.WhenAnyValue(x => x.CurrentSession) + .Skip(1) + .Subscribe(_ => UpdateLiveFile(result, true).Forget())); + dispose.Add(this.WhenAnyObservable(x => x.CurrentSession.PullRequestChanged) + .Subscribe(_ => UpdateLiveFile(result, true).Forget())); + + result.ToDispose = dispose; + } + + return result; + } + + /// <inheritdoc/> + public string GetRelativePath(ITextBuffer buffer) + { + var document = sessionService.GetDocument(buffer); + var path = document?.FilePath; + + if (!string.IsNullOrWhiteSpace(path) && Path.IsPathRooted(path) && repository != null) + { + var basePath = repository.LocalPath; + + if (path.StartsWith(basePath, StringComparison.OrdinalIgnoreCase) && path.Length > basePath.Length + 1) + { + return path.Substring(basePath.Length + 1); + } + } + + return null; + } + + /// <inheritdoc/> + public async Task<IPullRequestSession> GetSession(string owner, string name, int number) + { + var session = await GetSessionInternal(owner, name, number); + + if (await service.EnsureLocalBranchesAreMarkedAsPullRequests(repository, session.PullRequest)) + { + // The branch for the PR was not previously marked with the PR number in the git + // config so we didn't pick up that the current branch is a PR branch. That has + // now been corrected, so call StatusChanged to make sure everything is up-to-date. + await StatusChanged(); + } + + return session; + } + + /// <inheritdoc/> + public PullRequestTextBufferInfo GetTextBufferInfo(ITextBuffer buffer) + { + var projectionBuffer = buffer as IProjectionBuffer; + PullRequestTextBufferInfo result; + + if (buffer.Properties.TryGetProperty(typeof(PullRequestTextBufferInfo), out result)) + { + return result; + } + + if (projectionBuffer != null) + { + foreach (var sourceBuffer in projectionBuffer.SourceBuffers) + { + var sourceBufferInfo = GetTextBufferInfo(sourceBuffer); + if (sourceBufferInfo != null) return sourceBufferInfo; + } + } + + return null; + } + + async Task RepoChanged(ILocalRepositoryModel localRepositoryModel) + { + repository = localRepositoryModel; + CurrentSession = null; + sessions.Clear(); + + if (localRepositoryModel != null) + { + await StatusChanged(); + } + } + + async Task StatusChanged() + { + var session = CurrentSession; + + var pr = await service.GetPullRequestForCurrentBranch(repository).FirstOrDefaultAsync(); + if (pr != null) + { + var changePR = + pr.Item1 != (session?.PullRequest.BaseRepositoryOwner) || + pr.Item2 != (session?.PullRequest.Number); + + if (changePR) + { + var newSession = await GetSessionInternal(pr.Item1, repository.Name, pr.Item2); + if (newSession != null) newSession.IsCheckedOut = true; + session = newSession; + } + } + else + { + session = null; + } + + CurrentSession = session; + initialized.TrySetResult(null); + } + + async Task<PullRequestSession> GetSessionInternal(string owner, string name, int number) + { + PullRequestSession session = null; + WeakReference<PullRequestSession> weakSession; + var key = Tuple.Create(owner.ToLowerInvariant(), number); + + if (sessions.TryGetValue(key, out weakSession)) + { + weakSession.TryGetTarget(out session); + } + + if (session == null) + { + var address = HostAddress.Create(repository.CloneUrl); + var pullRequest = await sessionService.ReadPullRequestDetail(address, owner, name, number); + + session = new PullRequestSession( + sessionService, + await sessionService.ReadViewer(address), + pullRequest, + repository, + key.Item1, + false); + sessions[key] = new WeakReference<PullRequestSession>(session); + } + + return session; + } + + async Task UpdateLiveFile(PullRequestSessionLiveFile file, bool rebuildThreads) + { + var session = CurrentSession; + + if (session != null) + { + var mergeBase = await session.GetMergeBase(); + var contents = sessionService.GetContents(file.TextBuffer); + file.BaseSha = session.PullRequest.BaseRefSha; + file.CommitSha = await CalculateCommitSha(session, file, contents); + file.Diff = await sessionService.Diff( + session.LocalRepository, + mergeBase, + session.PullRequest.HeadRefSha, + file.RelativePath, + contents); + + if (rebuildThreads) + { + file.InlineCommentThreads = sessionService.BuildCommentThreads( + session.PullRequest, + file.RelativePath, + file.Diff, + session.PullRequest.HeadRefSha); + } + else + { + var changedLines = sessionService.UpdateCommentThreads( + file.InlineCommentThreads, + file.Diff); + + if (changedLines.Count > 0) + { + file.NotifyLinesChanged(changedLines); + } + } + + file.TrackingPoints = BuildTrackingPoints( + file.TextBuffer.CurrentSnapshot, + file.InlineCommentThreads); + } + else + { + file.BaseSha = null; + file.CommitSha = null; + file.Diff = null; + file.InlineCommentThreads = null; + file.TrackingPoints = null; + } + } + + async Task UpdateLiveFile(PullRequestSessionLiveFile file, ITextSnapshot snapshot) + { + if (file.TextBuffer.CurrentSnapshot == snapshot) + { + await UpdateLiveFile(file, false); + } + } + + void InvalidateLiveThreads(PullRequestSessionLiveFile file, ITextSnapshot snapshot) + { + if (file.TrackingPoints != null) + { + var linesChanged = new List<Tuple<int, DiffSide>>(); + + foreach (var thread in file.InlineCommentThreads) + { + ITrackingPoint trackingPoint; + + if (file.TrackingPoints.TryGetValue(thread, out trackingPoint)) + { + var position = trackingPoint.GetPosition(snapshot); + var lineNumber = snapshot.GetLineNumberFromPosition(position); + + if (thread.DiffLineType != DiffChangeType.Delete && lineNumber != thread.LineNumber) + { + linesChanged.Add(Tuple.Create(lineNumber, DiffSide.Right)); + linesChanged.Add(Tuple.Create(thread.LineNumber, DiffSide.Right)); + thread.LineNumber = lineNumber; + thread.IsStale = true; + } + } + } + + linesChanged = linesChanged + .Where(x => x.Item1 >= 0) + .Distinct() + .ToList(); + + if (linesChanged.Count > 0) + { + file.NotifyLinesChanged(linesChanged); + } + } + } + + private IDictionary<IInlineCommentThreadModel, ITrackingPoint> BuildTrackingPoints( + ITextSnapshot snapshot, + IReadOnlyList<IInlineCommentThreadModel> threads) + { + var result = new Dictionary<IInlineCommentThreadModel, ITrackingPoint>(); + + foreach (var thread in threads) + { + if (thread.LineNumber >= 0 && thread.DiffLineType != DiffChangeType.Delete) + { + var line = snapshot.GetLineFromLineNumber(thread.LineNumber); + var p = snapshot.CreateTrackingPoint(line.Start, PointTrackingMode.Positive); + result.Add(thread, p); + } + } + + return result; + } + + async Task<string> CalculateCommitSha( + IPullRequestSession session, + IPullRequestSessionFile file, + byte[] content) + { + var repo = session.LocalRepository; + return await sessionService.IsUnmodifiedAndPushed(repo, file.RelativePath, content) ? + await sessionService.GetTipSha(repo) : null; + } + + private void CloseLiveFiles(ITextBuffer textBuffer) + { + PullRequestSessionLiveFile file; + + if (textBuffer.Properties.TryGetProperty( + typeof(IPullRequestSessionFile), + out file)) + { + file.Dispose(); + } + + var projection = textBuffer as IProjectionBuffer; + + if (projection != null) + { + foreach (var source in projection.SourceBuffers) + { + CloseLiveFiles(source); + } + } + } + + void TextBufferChanged(object sender, TextContentChangedEventArgs e) + { + var textBuffer = (ITextBuffer)sender; + var file = textBuffer.Properties.GetProperty<PullRequestSessionLiveFile>(typeof(IPullRequestSessionFile)); + InvalidateLiveThreads(file, e.After); + file.Rebuild.OnNext(textBuffer.CurrentSnapshot); + } + + void TextViewClosed(object sender, EventArgs e) + { + var textView = (ITextView)sender; + CloseLiveFiles(textView.TextBuffer); + } + } +} diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs new file mode 100644 index 0000000000..c4ed289942 --- /dev/null +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs @@ -0,0 +1,926 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.App.Services; +using GitHub.Factories; +using GitHub.InlineReviews.Models; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Logging; +using GitHub.Services; +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 + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Provides a common interface for services required by <see cref="PullRequestSession"/>. + /// </summary> + [Export(typeof(IPullRequestSessionService))] + public class PullRequestSessionService : IPullRequestSessionService + { + static readonly ILogger log = LogManager.ForContext<PullRequestSessionService>(); + static ICompiledQuery<PullRequestDetailModel> readPullRequest; + static ICompiledQuery<IEnumerable<LastCommitAdapter>> readCommitStatuses; + static ICompiledQuery<IEnumerable<LastCommitAdapter>> readCommitStatusesEnterprise; + static ICompiledQuery<ActorModel> readViewer; + + readonly IGitService gitService; + readonly IGitClient gitClient; + readonly IDiffService diffService; + readonly IApiClientFactory apiClientFactory; + readonly IGraphQLClientFactory graphqlFactory; + readonly IUsageTracker usageTracker; + readonly IDictionary<Tuple<string, string>, string> mergeBaseCache; + + [ImportingConstructor] + public PullRequestSessionService( + IGitService gitService, + IGitClient gitClient, + IDiffService diffService, + IApiClientFactory apiClientFactory, + IGraphQLClientFactory graphqlFactory, + IUsageTracker usageTracker) + { + this.gitService = gitService; + this.gitClient = gitClient; + this.diffService = diffService; + this.apiClientFactory = apiClientFactory; + this.graphqlFactory = graphqlFactory; + this.usageTracker = usageTracker; + + mergeBaseCache = new Dictionary<Tuple<string, string>, string>(); + } + + /// <inheritdoc/> + public virtual async Task<IReadOnlyList<DiffChunk>> Diff(ILocalRepositoryModel repository, string baseSha, string headSha, string relativePath) + { + using (var repo = await GetRepository(repository)) + { + return await diffService.Diff(repo, baseSha, headSha, relativePath); + } + } + + /// <inheritdoc/> + public virtual async Task<IReadOnlyList<DiffChunk>> Diff(ILocalRepositoryModel repository, string baseSha, string headSha, string relativePath, byte[] contents) + { + using (var repo = await GetRepository(repository)) + { + return await diffService.Diff(repo, baseSha, headSha, relativePath, contents); + } + } + + /// <inheritdoc/> + public IReadOnlyList<IInlineCommentThreadModel> BuildCommentThreads( + PullRequestDetailModel pullRequest, + string relativePath, + IReadOnlyList<DiffChunk> diff, + string headSha) + { + relativePath = relativePath.Replace("\\", "/"); + + var threadsByPosition = pullRequest.Threads + .Where(x => x.Path == relativePath && !x.IsOutdated) + .OrderBy(x => x.Id) + .GroupBy(x => Tuple.Create(x.OriginalCommitSha, x.OriginalPosition)); + var threads = new List<IInlineCommentThreadModel>(); + + foreach (var thread in threadsByPosition) + { + var hunk = thread.First().DiffHunk; + var chunks = DiffUtilities.ParseFragment(hunk); + var chunk = chunks.Last(); + var diffLines = chunk.Lines.Reverse().Take(5).ToList(); + var firstLine = diffLines.FirstOrDefault(); + if (firstLine == null) + { + log.Warning("Ignoring in-line comment in {RelativePath} with no diff line context", relativePath); + continue; + } + + var inlineThread = new InlineCommentThreadModel( + relativePath, + headSha, + diffLines, + thread.SelectMany(t => t.Comments.Select(c => new InlineCommentModel + { + Comment = c, + Review = pullRequest.Reviews.FirstOrDefault(x => x.Comments.Contains(c)), + }))); + threads.Add(inlineThread); + } + + UpdateCommentThreads(threads, diff); + return threads; + } + + /// <inheritdoc/> + public IReadOnlyList<Tuple<int, GitHub.Models.DiffSide>> UpdateCommentThreads( + IReadOnlyList<IInlineCommentThreadModel> threads, + IReadOnlyList<DiffChunk> diff) + { + var changedLines = new List<Tuple<int, GitHub.Models.DiffSide>>(); + + foreach (var thread in threads) + { + var oldLineNumber = thread.LineNumber; + var newLineNumber = GetUpdatedLineNumber(thread, diff); + var changed = false; + + if (thread.IsStale) + { + thread.IsStale = false; + changed = true; + } + + if (newLineNumber != thread.LineNumber) + { + thread.LineNumber = newLineNumber; + thread.IsStale = false; + changed = true; + } + + if (changed) + { + var side = thread.DiffLineType == DiffChangeType.Delete ? GitHub.Models.DiffSide.Left : GitHub.Models.DiffSide.Right; + if (oldLineNumber != -1) changedLines.Add(Tuple.Create(oldLineNumber, side)); + if (newLineNumber != -1 && newLineNumber != oldLineNumber) changedLines.Add(Tuple.Create(newLineNumber, side)); + } + } + + return changedLines; + } + + /// <inheritdoc/> + public byte[] GetContents(ITextBuffer buffer) + { + var encoding = GetDocument(buffer)?.Encoding ?? Encoding.Default; + var content = encoding.GetBytes(buffer.CurrentSnapshot.GetText()); + + var preamble = encoding.GetPreamble(); + if (preamble.Length == 0) return content; + + var completeContent = new byte[preamble.Length + content.Length]; + Buffer.BlockCopy(preamble, 0, completeContent, 0, preamble.Length); + Buffer.BlockCopy(content, 0, completeContent, preamble.Length, content.Length); + + return completeContent; + } + + /// <inheritdoc/> + public ITextDocument GetDocument(ITextBuffer buffer) + { + ITextDocument result; + + if (buffer.Properties.TryGetProperty(typeof(ITextDocument), out result)) + return result; + + var projection = buffer as IProjectionBuffer; + + if (projection != null) + { + foreach (var source in projection.SourceBuffers) + { + if ((result = GetDocument(source)) != null) + return result; + } + } + + return null; + } + + /// <inheritdoc/> + public virtual async Task<string> GetTipSha(ILocalRepositoryModel repository) + { + using (var repo = await GetRepository(repository)) + { + return repo.Head.Tip.Sha; + } + } + + /// <inheritdoc/> + public async Task<bool> IsUnmodifiedAndPushed(ILocalRepositoryModel repository, string relativePath, byte[] contents) + { + using (var repo = await GetRepository(repository)) + { + var modified = await gitClient.IsModified(repo, relativePath, contents); + var pushed = await gitClient.IsHeadPushed(repo); + + return !modified && pushed; + } + } + + public async Task<byte[]> ExtractFileFromGit( + ILocalRepositoryModel repository, + int pullRequestNumber, + string sha, + string relativePath) + { + using (var repo = await GetRepository(repository)) + { + try + { + return await gitClient.ExtractFileBinary(repo, sha, relativePath); + } + catch (FileNotFoundException) + { + var pullHeadRef = $"refs/pull/{pullRequestNumber}/head"; + await gitClient.Fetch(repo, "origin", sha, pullHeadRef); + return await gitClient.ExtractFileBinary(repo, sha, relativePath); + } + } + } + + /// <inheritdoc/> + public async Task<byte[]> ReadFileAsync(string path) + { + if (File.Exists(path)) + { + try + { + using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) + { + var buffer = new MemoryStream(); + await file.CopyToAsync(buffer); + return buffer.ToArray(); + } + } + catch { } + } + + return null; + } + + public virtual async Task<PullRequestDetailModel> ReadPullRequestDetail(HostAddress address, string owner, string name, int number) + { + if (readPullRequest == null) + { + readPullRequest = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .PullRequest(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, + 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), + }, + Comments = review.Comments(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, + CreatedAt = comment.CreatedAt, + Url = comment.Url, + }).ToList(), + }).ToList(), + }).Compile(); + } + + var vars = new Dictionary<string, object> + { + { nameof(owner), owner }, + { nameof(name), name }, + { nameof(number), number }, + }; + + var connection = await graphqlFactory.CreateConnection(address); + var result = await connection.Run(readPullRequest, vars); + + 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; + + result.ChangedFiles = files.Select(file => new PullRequestFileModel + { + FileName = file.FileName, + Sha = file.Sha, + Status = (PullRequestFileStatus)Enum.Parse(typeof(PullRequestFileStatus), file.Status, true), + }).ToList(); + + BuildPullRequestThreads(result); + return result; + } + + public virtual async Task<ActorModel> ReadViewer(HostAddress address) + { + if (readViewer == null) + { + readViewer = new Query() + .Viewer + .Select(x => new ActorModel + { + Login = x.Login, + AvatarUrl = x.AvatarUrl(null), + }).Compile(); + } + + var connection = await graphqlFactory.CreateConnection(address); + return await connection.Run(readViewer); + } + + public async Task<string> GetGraphQLPullRequestId( + ILocalRepositoryModel localRepository, + string repositoryOwner, + int number) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var query = new Query() + .Repository(repositoryOwner, localRepository.Name) + .PullRequest(number) + .Select(x => x.Id); + + return (await graphql.Run(query)).Value; + } + + /// <inheritdoc/> + public virtual async Task<string> GetPullRequestMergeBase(ILocalRepositoryModel repository, PullRequestDetailModel pullRequest) + { + var baseSha = pullRequest.BaseRefSha; + var headSha = pullRequest.HeadRefSha; + var key = new Tuple<string, string>(baseSha, headSha); + + string mergeBase; + if (mergeBaseCache.TryGetValue(key, out mergeBase)) + { + return mergeBase; + } + + using (var repo = await GetRepository(repository)) + { + var targetUrl = repository.CloneUrl.WithOwner(pullRequest.BaseRepositoryOwner); + var headUrl = repository.CloneUrl.WithOwner(pullRequest.HeadRepositoryOwner); + var baseRef = pullRequest.BaseRefName; + var pullNumber = pullRequest.Number; + try + { + mergeBase = await gitClient.GetPullRequestMergeBase(repo, targetUrl, baseSha, headSha, baseRef, pullNumber); + } + catch (NotFoundException ex) + { + throw new NotFoundException("The Pull Request failed to load. Please check your network connection and click refresh to try again. If this issue persists, please let us know at support@github.com", ex); + } + + return mergeBaseCache[key] = mergeBase; + } + } + + /// <inheritdoc/> + public virtual ISubject<ITextSnapshot, ITextSnapshot> CreateRebuildSignal() + { + var input = new Subject<ITextSnapshot>(); + var output = Observable.Create<ITextSnapshot>(x => input + .Throttle(TimeSpan.FromMilliseconds(500)) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x)); + return Subject.Create(input, output); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> CreatePendingReview( + ILocalRepositoryModel localRepository, + string pullRequestId) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + var (_, owner, number) = await CreatePendingReviewCore(localRepository, pullRequestId); + var detail = await ReadPullRequestDetail(address, owner, localRepository.Name, number); + + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentStartReview); + + return detail; + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> CancelPendingReview( + ILocalRepositoryModel localRepository, + string reviewId) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var delete = new DeletePullRequestReviewInput + { + PullRequestReviewId = new ID(reviewId), + }; + + var mutation = new Mutation() + .DeletePullRequestReview(delete) + .Select(x => new + { + x.PullRequestReview.Repository.Owner.Login, + x.PullRequestReview.PullRequest.Number + }); + + var result = await graphql.Run(mutation); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> PostReview( + ILocalRepositoryModel localRepository, + string pullRequestId, + string commitId, + string body, + PullRequestReviewEvent e) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var addReview = new AddPullRequestReviewInput + { + Body = body, + CommitOID = commitId, + Event = ToGraphQl(e), + PullRequestId = new ID(pullRequestId), + }; + + var mutation = new Mutation() + .AddPullRequestReview(addReview) + .Select(x => new + { + x.PullRequestReview.Repository.Owner.Login, + x.PullRequestReview.PullRequest.Number + }); + + var result = await graphql.Run(mutation); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + public async Task<PullRequestDetailModel> SubmitPendingReview( + ILocalRepositoryModel localRepository, + string pendingReviewId, + string body, + PullRequestReviewEvent e) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var submit = new SubmitPullRequestReviewInput + { + Body = body, + Event = ToGraphQl(e), + PullRequestReviewId = new ID(pendingReviewId), + }; + + var mutation = new Mutation() + .SubmitPullRequestReview(submit) + .Select(x => new + { + x.PullRequestReview.Repository.Owner.Login, + x.PullRequestReview.PullRequest.Number + }); + + var result = await graphql.Run(mutation); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> PostPendingReviewComment( + ILocalRepositoryModel localRepository, + string pendingReviewId, + string body, + string commitId, + string path, + int position) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var comment = new AddPullRequestReviewCommentInput + { + Body = body, + CommitOID = commitId, + Path = path, + Position = position, + PullRequestReviewId = new ID(pendingReviewId), + }; + + var addComment = new Mutation() + .AddPullRequestReviewComment(comment) + .Select(x => new + { + x.Comment.Repository.Owner.Login, + x.Comment.PullRequest.Number + }); + + var result = await graphql.Run(addComment); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> PostPendingReviewCommentReply( + ILocalRepositoryModel localRepository, + string pendingReviewId, + string body, + string inReplyTo) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var comment = new AddPullRequestReviewCommentInput + { + Body = body, + InReplyTo = new ID(inReplyTo), + PullRequestReviewId = new ID(pendingReviewId), + }; + + var addComment = new Mutation() + .AddPullRequestReviewComment(comment) + .Select(x => new + { + x.Comment.Repository.Owner.Login, + x.Comment.PullRequest.Number + }); + + var result = await graphql.Run(addComment); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> PostStandaloneReviewComment( + ILocalRepositoryModel localRepository, + string pullRequestId, + string body, + string commitId, + string path, + int position) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var addReview = new AddPullRequestReviewInput + { + Body = body, + CommitOID = commitId, + Event = Octokit.GraphQL.Model.PullRequestReviewEvent.Comment, + PullRequestId = new ID(pullRequestId), + Comments = new[] + { + new DraftPullRequestReviewComment + { + Body = body, + Path = path, + Position = position, + }, + }, + }; + + var mutation = new Mutation() + .AddPullRequestReview(addReview) + .Select(x => new + { + x.PullRequestReview.Repository.Owner.Login, + x.PullRequestReview.PullRequest.Number + }); + + var result = await graphql.Run(mutation); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> PostStandaloneReviewCommentReply( + ILocalRepositoryModel localRepository, + string pullRequestId, + string body, + string inReplyTo) + { + var (id, _, _) = await CreatePendingReviewCore(localRepository, pullRequestId); + var comment = await PostPendingReviewCommentReply(localRepository, id, body, inReplyTo); + return await SubmitPendingReview(localRepository, id, null, PullRequestReviewEvent.Comment); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> DeleteComment( + ILocalRepositoryModel localRepository, + string remoteRepositoryOwner, + int pullRequestId, + int commentDatabaseId) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var apiClient = await apiClientFactory.Create(address); + + await apiClient.DeletePullRequestReviewComment( + remoteRepositoryOwner, + localRepository.Name, + commentDatabaseId); + + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentDelete); + return await ReadPullRequestDetail(address, remoteRepositoryOwner, localRepository.Name, pullRequestId); + } + + /// <inheritdoc/> + public async Task<PullRequestDetailModel> EditComment(ILocalRepositoryModel localRepository, + string remoteRepositoryOwner, + string commentNodeId, + string body) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var updatePullRequestReviewCommentInput = new UpdatePullRequestReviewCommentInput + { + Body = body, + PullRequestReviewCommentId = new ID(commentNodeId), + }; + + var editComment = new Mutation().UpdatePullRequestReviewComment(updatePullRequestReviewCommentInput) + .Select(x => new + { + x.PullRequestReviewComment.Repository.Owner.Login, + x.PullRequestReviewComment.PullRequest.Number + }); + + var result = await graphql.Run(editComment); + await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + } + + async Task<(string id, string owner, int number)> CreatePendingReviewCore(ILocalRepositoryModel localRepository, string pullRequestId) + { + var address = HostAddress.Create(localRepository.CloneUrl.Host); + var graphql = await graphqlFactory.CreateConnection(address); + + var input = new AddPullRequestReviewInput + { + PullRequestId = new ID(pullRequestId), + }; + + var mutation = new Mutation() + .AddPullRequestReview(input) + .Select(x => new + { + Id = x.PullRequestReview.Id.Value, + Owner = x.PullRequestReview.Repository.Owner.Login, + x.PullRequestReview.PullRequest.Number + }); + + var result = await graphql.Run(mutation); + return (result.Id, result.Owner, result.Number); + } + + int GetUpdatedLineNumber(IInlineCommentThreadModel thread, IEnumerable<DiffChunk> diff) + { + var line = DiffUtilities.Match(diff, thread.DiffMatch); + + if (line != null) + { + return (thread.DiffLineType == DiffChangeType.Delete) ? + line.OldLineNumber - 1 : + line.NewLineNumber - 1; + } + + return -1; + } + + Task<IRepository> GetRepository(ILocalRepositoryModel repository) + { + return Task.Factory.StartNew(() => gitService.GetRepository(repository.LocalPath)); + } + + async Task<LastCommitAdapter> GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, int number) + { + ICompiledQuery<IEnumerable<LastCommitAdapter>> query; + if (address.IsGitHubDotCom()) + { + if (readCommitStatuses == null) + { + readCommitStatuses = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .PullRequest(Var(nameof(number))).Commits(last: 1).Nodes.Select( + commit => new LastCommitAdapter + { + 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 + { + Conclusion = run.Conclusion.FromGraphQl(), + Status = run.Status.FromGraphQl(), + Name = run.Name, + DetailsUrl = run.Permalink, + Summary = run.Summary, + }).ToList() + }).ToList(), + 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() + } + ).Compile(); + } + + query = readCommitStatuses; + } + else + { + if (readCommitStatusesEnterprise == null) + { + readCommitStatusesEnterprise = new Query() + .Repository(Var(nameof(owner)), Var(nameof(name))) + .PullRequest(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() + } + ).Compile(); + } + + query = readCommitStatusesEnterprise; + } + + var vars = new Dictionary<string, object> + { + { nameof(owner), owner }, + { nameof(name), name }, + { nameof(number), number }, + }; + + var connection = await graphqlFactory.CreateConnection(address); + var result = await connection.Run(query, vars); + return result.First(); + } + + static void BuildPullRequestThreads(PullRequestDetailModel model) + { + var commentsByReplyId = new Dictionary<string, List<CommentAdapter>>(); + + // 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<CommentAdapter> { comment }); + } + } + + // Get the comments that are replies and place them into the relevant list. + foreach (CommentAdapter comment in model.Reviews.SelectMany(x => x.Comments)) + { + if (comment.ReplyTo != null) + { + List<CommentAdapter> 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<PullRequestReviewThreadModel>(); + + 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) + { + case Octokit.PullRequestReviewEvent.Approve: + return Octokit.GraphQL.Model.PullRequestReviewEvent.Approve; + case Octokit.PullRequestReviewEvent.Comment: + return Octokit.GraphQL.Model.PullRequestReviewEvent.Comment; + case Octokit.PullRequestReviewEvent.RequestChanges: + return Octokit.GraphQL.Model.PullRequestReviewEvent.RequestChanges; + default: + throw new NotSupportedException(); + } + } + + class CommentAdapter : PullRequestReviewCommentModel + { + public string Path { get; set; } + public string CommitSha { get; set; } + public string DiffHunk { get; set; } + public int? Position { get; set; } + public int OriginalPosition { get; set; } + public string OriginalCommitId { get; set; } + public string ReplyTo { get; set; } + } + + class LastCommitAdapter + { + public List<CheckSuiteModel> CheckSuites { get; set; } + + public List<StatusModel> Statuses { get; set; } + } + } +} diff --git a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs new file mode 100644 index 0000000000..c6672b2847 --- /dev/null +++ b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs @@ -0,0 +1,182 @@ +using System; +using System.Windows; +using System.Windows.Input; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Primitives; +using GitHub.InlineReviews.Views; +using GitHub.InlineReviews.ViewModels; +using GitHub.Services; +using GitHub.Models; +using GitHub.Logging; +using Serilog; +using ReactiveUI; + +namespace GitHub.InlineReviews.Services +{ + /// <summary> + /// Manage the UI that shows the PR for the current branch. + /// </summary> + [Export(typeof(PullRequestStatusBarManager))] + public class PullRequestStatusBarManager + { + static readonly ILogger log = LogManager.ForContext<PullRequestStatusBarManager>(); + const string StatusBarPartName = "PART_SccStatusBarHost"; + + readonly ICommand openPullRequestsCommand; + readonly ICommand showCurrentPullRequestCommand; + + // At the moment these must be constructed on the main thread. + // TeamExplorerContext needs to retrieve DTE using GetService. + readonly Lazy<IPullRequestSessionManager> pullRequestSessionManager; + readonly Lazy<ITeamExplorerContext> teamExplorerContext; + readonly Lazy<IConnectionManager> connectionManager; + + IDisposable currentSessionSubscription; + + [ImportingConstructor] + public PullRequestStatusBarManager( + Lazy<IUsageTracker> usageTracker, + IOpenPullRequestsCommand openPullRequestsCommand, + IShowCurrentPullRequestCommand showCurrentPullRequestCommand, + Lazy<IPullRequestSessionManager> pullRequestSessionManager, + Lazy<ITeamExplorerContext> teamExplorerContext, + Lazy<IConnectionManager> connectionManager) + { + this.openPullRequestsCommand = new UsageTrackingCommand(usageTracker, + x => x.NumberOfStatusBarOpenPullRequestList, openPullRequestsCommand); + this.showCurrentPullRequestCommand = new UsageTrackingCommand(usageTracker, + x => x.NumberOfShowCurrentPullRequest, showCurrentPullRequestCommand); + + this.pullRequestSessionManager = pullRequestSessionManager; + this.teamExplorerContext = teamExplorerContext; + this.connectionManager = connectionManager; + } + + /// <summary> + /// Start showing the PR for the active branch on the status bar. + /// </summary> + /// <remarks> + /// This must be called from the Main thread. + /// </remarks> + public void StartShowingStatus() + { + try + { + teamExplorerContext.Value.WhenAnyValue(x => x.ActiveRepository) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => RefreshActiveRepository(x)); + } + catch (Exception e) + { + log.Error(e, "Error initializing"); + } + } + + void RefreshActiveRepository(ILocalRepositoryModel repository) + { + currentSessionSubscription?.Dispose(); + currentSessionSubscription = pullRequestSessionManager.Value.WhenAnyValue(x => x.CurrentSession) + .Subscribe(x => RefreshCurrentSession(repository, x).Forget()); + } + + async Task RefreshCurrentSession(ILocalRepositoryModel repository, IPullRequestSession session) + { + try + { + var showStatus = await IsDotComOrEnterpriseRepository(repository); + if (!showStatus) + { + ShowStatus(null); + return; + } + + var viewModel = CreatePullRequestStatusViewModel(session); + ShowStatus(viewModel); + } + catch (Exception e) + { + log.Error(e, nameof(RefreshCurrentSession)); + } + } + + async Task<bool> IsDotComOrEnterpriseRepository(ILocalRepositoryModel repository) + { + var cloneUrl = repository?.CloneUrl; + if (cloneUrl == null) + { + // No active repository or remote + return false; + } + + var isDotCom = HostAddress.IsGitHubDotComUri(cloneUrl.ToRepositoryUrl()); + if (isDotCom) + { + // This is a github.com repository + return true; + } + + var connection = await connectionManager.Value.GetConnection(repository); + if (connection != null) + { + // This is an enterprise repository + return true; + } + + return false; + } + + PullRequestStatusViewModel CreatePullRequestStatusViewModel(IPullRequestSession session) + { + var pullRequestStatusViewModel = new PullRequestStatusViewModel(openPullRequestsCommand, showCurrentPullRequestCommand); + var pullRequest = session?.PullRequest; + pullRequestStatusViewModel.Number = pullRequest?.Number; + pullRequestStatusViewModel.Title = pullRequest?.Title; + return pullRequestStatusViewModel; + } + + void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) + { + var statusBar = FindSccStatusBar(Application.Current.MainWindow); + if (statusBar != null) + { + var githubStatusBar = Find<PullRequestStatusView>(statusBar); + if (githubStatusBar != null) + { + // Replace to ensure status shows up. + statusBar.Items.Remove(githubStatusBar); + } + + if (pullRequestStatusViewModel != null) + { + githubStatusBar = new PullRequestStatusView { DataContext = pullRequestStatusViewModel }; + statusBar.Items.Insert(0, githubStatusBar); + } + } + } + + static T Find<T>(StatusBar statusBar) + { + foreach (var item in statusBar.Items) + { + if (item is T) + { + return (T)item; + } + } + + return default(T); + } + + StatusBar FindSccStatusBar(Window mainWindow) + { + var contentControl = mainWindow?.Template?.FindName(StatusBarPartName, mainWindow) as ContentControl; + return contentControl?.Content as StatusBar; + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/AddInlineCommentGlyph.xaml b/src/GitHub.InlineReviews/Tags/AddInlineCommentGlyph.xaml new file mode 100644 index 0000000000..bc2a7cff7b --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/AddInlineCommentGlyph.xaml @@ -0,0 +1,19 @@ +<UserControl x:Class="GitHub.InlineReviews.Tags.AddInlineCommentGlyph" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + mc:Ignorable="d"> + + <Grid> + <Border Background="{DynamicResource GitHubGlyphMarginCommentableBackground}" BorderThickness="0,0,1,0" /> + <Viewbox x:Name="AddViewbox" Margin="0,1,0,0"> + <Canvas Width="14" Height="14"> + <Rectangle Width="13" Height="13" Fill="{DynamicResource GitHubDiffGlyphFill.None}"/> + <Rectangle Stroke="#313131" Fill="Transparent" Opacity="0.163" Width="13" Height="13" /> + <Path Canvas.Top="-0.5" Canvas.Left="-0.5" Fill="#FFF" Data="M11 8H8v3H6V8H3V6h3V3h2v3h3z"/> + </Canvas> + </Viewbox> + </Grid> +</UserControl> diff --git a/src/GitHub.InlineReviews/Tags/AddInlineCommentGlyph.xaml.cs b/src/GitHub.InlineReviews/Tags/AddInlineCommentGlyph.xaml.cs new file mode 100644 index 0000000000..f3d7bc33cc --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/AddInlineCommentGlyph.xaml.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Tags +{ + public partial class AddInlineCommentGlyph : UserControl + { + public AddInlineCommentGlyph() + { + InitializeComponent(); + + AddViewbox.Visibility = Visibility.Hidden; + MouseEnter += (s, e) => AddViewbox.Visibility = Visibility.Visible; + MouseLeave += (s, e) => AddViewbox.Visibility = Visibility.Hidden; + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/AddInlineCommentTag.cs b/src/GitHub.InlineReviews/Tags/AddInlineCommentTag.cs new file mode 100644 index 0000000000..2e64f6ebf8 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/AddInlineCommentTag.cs @@ -0,0 +1,57 @@ +using System; +using GitHub.Services; +using GitHub.Models; + +namespace GitHub.InlineReviews.Tags +{ + /// <summary> + /// A tag which marks a line in an editor where a new review comment can be added. + /// </summary> + public class AddInlineCommentTag : InlineCommentTag + { + /// <summary> + /// Initializes a new instance of the <see cref="AddInlineCommentTag"/> class. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="commitSha"> + /// The SHA of the commit to which a new comment should be added. May be null if the tag + /// represents trying to add a comment to a line that hasn't yet been pushed. + /// </param> + /// <param name="filePath">The path to the file.</param> + /// <param name="diffLine">The line in the diff that the line relates to.</param> + /// <param name="lineNumber">The line in the file.</param> + /// <param name="diffChangeType">The type of represented by the diff line.</param> + public AddInlineCommentTag( + IPullRequestSession session, + string commitSha, + string filePath, + int diffLine, + int lineNumber, + DiffChangeType diffChangeType) + : base(session, lineNumber, diffChangeType) + { + CommitSha = commitSha; + DiffLine = diffLine; + FilePath = filePath; + } + + /// <summary> + /// Gets the SHA of the commit to which a new comment should be added. + /// </summary> + /// <remarks> + /// May be null if the tag represents trying to add a comment to a line that hasn't yet been + /// pushed. + /// </remarks> + public string CommitSha { get; } + + /// <summary> + /// Gets the line in the diff that the line relates to. + /// </summary> + public int DiffLine { get; } + + /// <summary> + /// Gets the path to the file. + /// </summary> + public string FilePath { get; } + } +} diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs b/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs new file mode 100644 index 0000000000..38b62be7d3 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs @@ -0,0 +1,87 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using GitHub.InlineReviews.Glyph; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using GitHub.InlineReviews.Services; + +namespace GitHub.InlineReviews.Tags +{ + class InlineCommentGlyphFactory : IGlyphFactory<InlineCommentTag> + { + readonly IInlineCommentPeekService peekService; + readonly ITextView textView; + + public InlineCommentGlyphFactory( + IInlineCommentPeekService peekService, + ITextView textView) + { + this.peekService = peekService; + this.textView = textView; + } + + public UIElement GenerateGlyph(IWpfTextViewLine line, InlineCommentTag tag) + { + var glyph = CreateGlyph(tag); + glyph.DataContext = tag; + glyph.MouseLeftButtonUp += (s, e) => + { + if (OpenThreadView(tag)) e.Handled = true; + }; + + return glyph; + } + + public IEnumerable<Type> GetTagTypes() + { + return new[] + { + typeof(AddInlineCommentTag), + typeof(ShowInlineCommentTag) + }; + } + + [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)")] + static UserControl CreateGlyph(InlineCommentTag tag) + { + var addTag = tag as AddInlineCommentTag; + var showTag = tag as ShowInlineCommentTag; + + if (addTag != null) + { + return new AddInlineCommentGlyph(); + } + else if (showTag != null) + { + return new ShowInlineCommentGlyph() + { + Opacity = showTag.Thread.IsStale ? 0.5 : 1, + }; + } + + throw new ArgumentException($"Unknown 'InlineCommentTag' type '{tag}'"); + } + + bool OpenThreadView(InlineCommentTag tag) + { + var addTag = tag as AddInlineCommentTag; + var showTag = tag as ShowInlineCommentTag; + + if (addTag != null) + { + peekService.Show(textView, addTag); + return true; + } + else if (showTag != null) + { + peekService.Show(textView, showTag); + return true; + } + + return false; + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentTag.cs b/src/GitHub.InlineReviews/Tags/InlineCommentTag.cs new file mode 100644 index 0000000000..bdf46a062e --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/InlineCommentTag.cs @@ -0,0 +1,31 @@ +using GitHub.Extensions; +using GitHub.Services; +using GitHub.Models; +using Microsoft.VisualStudio.Text.Tagging; + +namespace GitHub.InlineReviews.Tags +{ + /// <summary> + /// Base class for inline comment tags. + /// </summary> + /// <seealso cref="AddInlineCommentTag"/> + /// <seealso cref="ShowInlineCommentTag"/> + public abstract class InlineCommentTag : ITag + { + protected InlineCommentTag( + IPullRequestSession session, + int lineNumber, + DiffChangeType diffChangeType) + { + Guard.ArgumentNotNull(session, nameof(session)); + + LineNumber = lineNumber; + Session = session; + DiffChangeType = diffChangeType; + } + + public int LineNumber { get; } + public IPullRequestSession Session { get; } + public DiffChangeType DiffChangeType { get; } + } +} diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs b/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs new file mode 100644 index 0000000000..3891003d63 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using GitHub.InlineReviews.Margins; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using ReactiveUI; +using Serilog; + +namespace GitHub.InlineReviews.Tags +{ + /// <summary> + /// Creates tags in an <see cref="ITextBuffer"/> for inline comment threads. + /// </summary> + public sealed class InlineCommentTagger : ITagger<InlineCommentTag>, IDisposable + { + static readonly ILogger log = LogManager.ForContext<InlineCommentTagger>(); + static readonly IReadOnlyList<ITagSpan<InlineCommentTag>> EmptyTags = new ITagSpan<InlineCommentTag>[0]; + readonly ITextBuffer buffer; + readonly ITextView view; + readonly IPullRequestSessionManager sessionManager; + bool needsInitialize = true; + string relativePath; + DiffSide side; + IPullRequestSession session; + IPullRequestSessionFile file; + IDisposable fileSubscription; + IDisposable sessionManagerSubscription; + IDisposable visibleSubscription; + + public InlineCommentTagger( + ITextView view, + ITextBuffer buffer, + IPullRequestSessionManager sessionManager) + { + Guard.ArgumentNotNull(buffer, nameof(buffer)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + + this.buffer = buffer; + this.view = view; + this.sessionManager = sessionManager; + } + + public bool ShowMargin => file?.Diff?.Count > 0; + + public event EventHandler<SnapshotSpanEventArgs> TagsChanged; + + public void Dispose() + { + sessionManagerSubscription?.Dispose(); + sessionManagerSubscription = null; + fileSubscription?.Dispose(); + fileSubscription = null; + visibleSubscription?.Dispose(); + visibleSubscription = null; + } + + public IEnumerable<ITagSpan<InlineCommentTag>> GetTags(NormalizedSnapshotSpanCollection spans) + { + if (needsInitialize) + { + // Sucessful initialization will call NotifyTagsChanged, causing this method to be re-called. + ForgetWithLogging(Initialize()); + return EmptyTags; + } + else if (file?.InlineCommentThreads != null) + { + var result = new List<ITagSpan<InlineCommentTag>>(); + var currentSession = session ?? sessionManager.CurrentSession; + + if (currentSession == null) + return EmptyTags; + + foreach (var span in spans) + { + var startLine = span.Start.GetContainingLine().LineNumber; + var endLine = span.End.GetContainingLine().LineNumber; + var linesWithComments = new BitArray((endLine - startLine) + 1); + var spanThreads = file.InlineCommentThreads.Where(x => + x.LineNumber >= startLine && + x.LineNumber <= endLine); + + foreach (var thread in spanThreads) + { + var snapshot = span.Snapshot; + var line = snapshot.GetLineFromLineNumber(thread.LineNumber); + + if ((side == DiffSide.Left && thread.DiffLineType == DiffChangeType.Delete) || + (side == DiffSide.Right && thread.DiffLineType != DiffChangeType.Delete)) + { + linesWithComments[thread.LineNumber - startLine] = true; + + result.Add(new TagSpan<ShowInlineCommentTag>( + new SnapshotSpan(line.Start, line.End), + new ShowInlineCommentTag(currentSession, thread))); + } + } + + foreach (var chunk in file.Diff) + { + foreach (var line in chunk.Lines) + { + var lineNumber = (side == DiffSide.Left ? line.OldLineNumber : line.NewLineNumber) - 1; + + if (lineNumber >= startLine && + lineNumber <= endLine && + !linesWithComments[lineNumber - startLine] + && (side == DiffSide.Right || line.Type == DiffChangeType.Delete)) + { + var snapshotLine = span.Snapshot.GetLineFromLineNumber(lineNumber); + result.Add(new TagSpan<InlineCommentTag>( + new SnapshotSpan(snapshotLine.Start, snapshotLine.End), + new AddInlineCommentTag(currentSession, file.CommitSha, relativePath, line.DiffLineNumber, lineNumber, line.Type))); + } + } + } + } + + return result; + } + else + { + return EmptyTags; + } + } + + async Task Initialize() + { + needsInitialize = false; + + var bufferInfo = sessionManager.GetTextBufferInfo(buffer); + + if (bufferInfo != null) + { + var commitSha = bufferInfo.Side == DiffSide.Left ? "HEAD" : bufferInfo.CommitSha; + session = bufferInfo.Session; + relativePath = bufferInfo.RelativePath; + file = await session.GetFile(relativePath, commitSha); + fileSubscription = file.LinesChanged.Subscribe(LinesChanged); + side = bufferInfo.Side ?? DiffSide.Right; + NotifyTagsChanged(); + } + else + { + side = DiffSide.Right; + await InitializeLiveFile(); + sessionManagerSubscription = sessionManager + .WhenAnyValue(x => x.CurrentSession) + .Skip(1) + .Subscribe(_ => ForgetWithLogging(InitializeLiveFile())); + } + } + + async Task InitializeLiveFile() + { + fileSubscription?.Dispose(); + fileSubscription = null; + + relativePath = sessionManager.GetRelativePath(buffer); + + if (relativePath != null) + { + file = await sessionManager.GetLiveFile(relativePath, view, buffer); + + var options = view.Options; + visibleSubscription = + Observable.FromEventPattern<EditorOptionChangedEventArgs>(options, nameof(options.OptionChanged)) + .Select(_ => Unit.Default) + .StartWith(Unit.Default) + .Select(x => options.GetOptionValue(InlineCommentTextViewOptions.MarginVisibleId)) + .DistinctUntilChanged() + .Subscribe(VisibleChanged); + } + else + { + file = null; + } + + NotifyTagsChanged(); + } + + void VisibleChanged(bool enabled) + { + if (enabled) + { + fileSubscription = fileSubscription ?? file.LinesChanged.Subscribe(LinesChanged); + } + else + { + fileSubscription?.Dispose(); + fileSubscription = null; + } + } + + static void ForgetWithLogging(Task task) + { + task.Catch(e => log.Error(e, "Exception caught while executing background task")).Forget(); + } + + void LinesChanged(IReadOnlyList<Tuple<int, DiffSide>> lines) + { + NotifyTagsChanged(lines.Where(x => x.Item2 == side).Select(x => x.Item1)); + } + + void NotifyTagsChanged() + { + var entireFile = new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length); + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireFile)); + } + + void NotifyTagsChanged(int lineNumber) + { + var line = buffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber); + var span = new SnapshotSpan(buffer.CurrentSnapshot, line.Start, line.Length); + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(span)); + } + + void NotifyTagsChanged(IEnumerable<int> lineNumbers) + { + foreach (var lineNumber in lineNumbers) + { + NotifyTagsChanged(lineNumber); + } + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs b/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs new file mode 100644 index 0000000000..fbdb02ab7d --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Extensions; +using GitHub.InlineReviews.Services; +using GitHub.Services; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; + +namespace GitHub.InlineReviews.Tags +{ + /// <summary> + /// Factory class for <see cref="InlineCommentTagger"/>s. + /// </summary> + [Export(typeof(IViewTaggerProvider))] + [ContentType("text")] + [TagType(typeof(ShowInlineCommentTag))] + class InlineCommentTaggerProvider : IViewTaggerProvider + { + readonly IPullRequestSessionManager sessionManager; + + [ImportingConstructor] + public InlineCommentTaggerProvider( + IPullRequestSessionManager sessionManager) + { + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + + this.sessionManager = sessionManager; + } + + public ITagger<T> CreateTagger<T>(ITextView view, ITextBuffer buffer) where T : ITag + { + return buffer.Properties.GetOrCreateSingletonProperty(() => + new InlineCommentTagger( + view, + buffer, + sessionManager)) as ITagger<T>; + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/MouseEnterAndLeaveEventRouter.cs b/src/GitHub.InlineReviews/Tags/MouseEnterAndLeaveEventRouter.cs new file mode 100644 index 0000000000..103b399115 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/MouseEnterAndLeaveEventRouter.cs @@ -0,0 +1,105 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace GitHub.InlineReviews.Tags +{ + class MouseEnterAndLeaveEventRouter<T> where T : FrameworkElement + { + T previousMouseOverElement; + + public void Add(UIElement sourceElement, UIElement targetElement) + { + sourceElement.MouseMove += (t, e) => MouseMove(targetElement, e); + sourceElement.MouseLeave += (t, e) => MouseLeave(targetElement, e); + } + + void MouseMove(object target, MouseEventArgs e) + { + T mouseOverElement = null; + Action<T> visitAction = element => + { + mouseOverElement = element; + }; + + var visitor = new Visitor(e, visitAction); + visitor.Visit(target); + + if (mouseOverElement != previousMouseOverElement) + { + MouseLeave(previousMouseOverElement, e); + MouseEnter(mouseOverElement, e); + } + } + + void MouseLeave(object target, MouseEventArgs e) + { + MouseLeave(previousMouseOverElement, e); + } + + void MouseEnter(T element, MouseEventArgs e) + { + element?.RaiseEvent(new MouseEventArgs(e.MouseDevice, e.Timestamp) + { + RoutedEvent = Mouse.MouseEnterEvent, + }); + + previousMouseOverElement = element; + } + + void MouseLeave(T element, MouseEventArgs e) + { + element?.RaiseEvent(new MouseEventArgs(e.MouseDevice, e.Timestamp) + { + RoutedEvent = Mouse.MouseLeaveEvent, + }); + + previousMouseOverElement = null; + } + + class Visitor + { + MouseEventArgs mouseEventArgs; + Action<T> action; + + internal Visitor(MouseEventArgs mouseEventArgs, Action<T> action) + { + this.mouseEventArgs = mouseEventArgs; + this.action = action; + } + + internal void Visit(object obj) + { + if (obj is Panel) + { + Visit((Panel)obj); + return; + } + + if (obj is T) + { + Visit((T)obj); + return; + } + } + + internal void Visit(Panel panel) + { + foreach (var child in panel.Children) + { + Visit(child); + } + } + + internal void Visit(T element) + { + var point = mouseEventArgs.GetPosition(element); + if (point.Y >= 0 && point.Y < element.ActualHeight) + { + action(element); + } + } + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml new file mode 100644 index 0000000000..77e7386777 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml @@ -0,0 +1,25 @@ +<UserControl x:Class="GitHub.InlineReviews.Tags.ShowInlineCommentGlyph" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + mc:Ignorable="d"> + + <Grid> + <Border Background="{DynamicResource GitHubGlyphMarginCommentableBackground}" BorderThickness="0,0,1,0" /> + <Viewbox HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,1,0,0"> + <Canvas Width="16" Height="15"> + <Path Canvas.Top="1" Canvas.Left="0.85" + Data="M1,0 C0.45,0 0,0.45 0,1 L0,9 C0,9.55 0.45,10 1,10 L3,10 L3,13.5 L6.5,10 L13,10 C13.55,10 14,9.55 14,9 L14,1 C14,0.45 13.55,0 13,0 L1,0 Z" /> + <Path Canvas.Top="1" Canvas.Left="0.85" Opacity="0.163" Stroke="#313131" + Data="M3.5,12.2928932 L6.29289322,9.5 L13,9.5 C13.2738576,9.5 13.5,9.27385763 13.5,9 L13.5,1 C13.5,0.726142375 13.2738576,0.5 13,0.5 L1,0.5 C0.726142375,0.5 0.5,0.726142375 0.5,1 L0.5,9 C0.5,9.27385763 0.726142375,9.5 1,9.5 L3.5,9.5 L3.5,12.2928932 Z" /> + <Canvas.Resources> + <Style TargetType="Path"> + <Setter Property="Fill" Value="{DynamicResource GitHubDiffGlyphFill.None}" /> + </Style> + </Canvas.Resources> + </Canvas> + </Viewbox> + </Grid> +</UserControl> diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs new file mode 100644 index 0000000000..50dd329d61 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs @@ -0,0 +1,14 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Tags +{ + public partial class ShowInlineCommentGlyph : UserControl + { + public ShowInlineCommentGlyph() + { + InitializeComponent(); + } + + } +} diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs b/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs new file mode 100644 index 0000000000..b1071754cf --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs @@ -0,0 +1,33 @@ +using System; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; + +namespace GitHub.InlineReviews.Tags +{ + /// <summary> + /// A tag which marks a line where inline review comments are present. + /// </summary> + public class ShowInlineCommentTag : InlineCommentTag + { + /// <summary> + /// Initializes a new instance of the <see cref="ShowInlineCommentTag"/> class. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="thread">A model holding the details of the thread.</param> + public ShowInlineCommentTag( + IPullRequestSession session, + IInlineCommentThreadModel thread) + : base(session, thread.LineNumber, thread.DiffLineType) + { + Guard.ArgumentNotNull(thread, nameof(thread)); + + Thread = thread; + } + + /// <summary> + /// Gets a model holding details of the thread at the tagged line. + /// </summary> + public IInlineCommentThreadModel Thread { get; } + } +} diff --git a/src/GitHub.InlineReviews/VSPackage.resx b/src/GitHub.InlineReviews/VSPackage.resx new file mode 100644 index 0000000000..b534be634e --- /dev/null +++ b/src/GitHub.InlineReviews/VSPackage.resx @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="110" xml:space="preserve"> + <value>GitHub.InlineReviews</value> + </data> + <data name="112" xml:space="preserve"> + <value>A Visual Studio Extension that brings the GitHub Flow into Visual Studio.</value> + </data> + <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <data name="400" type="System.Resources.ResXFileRef, System.Windows.Forms"> + <value>resources\logo_32x32@2x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> + </data> +</root> \ No newline at end of file diff --git a/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs new file mode 100644 index 0000000000..84f35c4695 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/CommentThreadViewModel.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.ObjectModel; +using System.Reactive; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.ViewModels; +using ReactiveUI; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// Base view model for a thread of comments. + /// </summary> + public abstract class CommentThreadViewModel : ReactiveObject, ICommentThreadViewModel + { + ReactiveCommand<Unit> postComment; + ReactiveCommand<Unit> editComment; + ReactiveCommand<Unit> deleteComment; + + /// <summary> + /// Intializes a new instance of the <see cref="CommentThreadViewModel"/> class. + /// </summary> + /// <param name="currentUser">The current user.</param> + protected CommentThreadViewModel(ActorModel currentUser) + { + Guard.ArgumentNotNull(currentUser, nameof(currentUser)); + + Comments = new ObservableCollection<ICommentViewModel>(); + CurrentUser = new ActorViewModel(currentUser); + } + + /// <inheritdoc/> + public ObservableCollection<ICommentViewModel> Comments { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> PostComment + { + get { return postComment; } + protected set + { + Guard.ArgumentNotNull(value, nameof(value)); + postComment = value; + + // We want to ignore thrown exceptions from PostComment - the error should be handled + // by the CommentViewModel that trigged PostComment.Execute(); + value.ThrownExceptions.Subscribe(_ => { }); + } + } + + public ReactiveCommand<Unit> EditComment + { + get { return editComment; } + protected set + { + Guard.ArgumentNotNull(value, nameof(value)); + editComment = value; + + value.ThrownExceptions.Subscribe(_ => { }); + } + } + + public ReactiveCommand<Unit> DeleteComment + { + get { return deleteComment; } + protected set + { + Guard.ArgumentNotNull(value, nameof(value)); + deleteComment = value; + + value.ThrownExceptions.Subscribe(_ => { }); + } + } + + /// <inheritdoc/> + public IActorViewModel CurrentUser { get; } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs new file mode 100644 index 0000000000..3565682991 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/CommentViewModel.cs @@ -0,0 +1,296 @@ +using System; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Windows; +using GitHub.Extensions; +using GitHub.InlineReviews.Services; +using GitHub.Logging; +using GitHub.Models; +using GitHub.ViewModels; +using ReactiveUI; +using Serilog; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// View model for an issue or pull request comment. + /// </summary> + public class CommentViewModel : ReactiveObject, ICommentViewModel + { + static readonly ILogger log = LogManager.ForContext<CommentViewModel>(); + ICommentService commentService; + string body; + string errorMessage; + bool isReadOnly; + bool isSubmitting; + CommentEditState state; + DateTimeOffset updatedAt; + string undoBody; + ObservableAsPropertyHelper<bool> canDelete; + + /// <summary> + /// Initializes a new instance of the <see cref="CommentViewModel"/> class. + /// </summary> + /// <param name="commentService">The comment service</param> + /// <param name="thread">The thread that the comment is a part of.</param> + /// <param name="currentUser">The current user.</param> + /// <param name="pullRequestId">The pull request id of the comment.</param> + /// <param name="commentId">The GraphQL ID of the comment.</param> + /// <param name="databaseId">The database id of the comment.</param> + /// <param name="body">The comment body.</param> + /// <param name="state">The comment edit state.</param> + /// <param name="author">The author of the comment.</param> + /// <param name="updatedAt">The modified date of the comment.</param> + /// <param name="webUrl"></param> + protected CommentViewModel( + ICommentService commentService, + ICommentThreadViewModel thread, + IActorViewModel currentUser, + int pullRequestId, + string commentId, + int databaseId, + string body, + CommentEditState state, + IActorViewModel author, + DateTimeOffset updatedAt, + Uri webUrl) + { + this.commentService = commentService; + Guard.ArgumentNotNull(thread, nameof(thread)); + Guard.ArgumentNotNull(currentUser, nameof(currentUser)); + Guard.ArgumentNotNull(author, nameof(author)); + + Thread = thread; + CurrentUser = currentUser; + Id = commentId; + DatabaseId = databaseId; + PullRequestId = pullRequestId; + Body = body; + EditState = state; + Author = author; + UpdatedAt = updatedAt; + WebUrl = webUrl; + + var canDeleteObservable = this.WhenAnyValue( + x => x.EditState, + x => x == CommentEditState.None && author.Login == currentUser.Login); + + canDelete = canDeleteObservable.ToProperty(this, x => x.CanDelete); + + Delete = ReactiveCommand.CreateAsyncTask(canDeleteObservable, DoDelete); + + var canEdit = this.WhenAnyValue( + x => x.EditState, + x => x == CommentEditState.Placeholder || (x == CommentEditState.None && author.Login == currentUser.Login)); + + BeginEdit = ReactiveCommand.Create(canEdit); + BeginEdit.Subscribe(DoBeginEdit); + AddErrorHandler(BeginEdit); + + CommitEdit = ReactiveCommand.CreateAsyncTask( + Observable.CombineLatest( + this.WhenAnyValue(x => x.IsReadOnly), + this.WhenAnyValue(x => x.Body, x => !string.IsNullOrWhiteSpace(x)), + this.WhenAnyObservable(x => x.Thread.PostComment.CanExecuteObservable), + (readOnly, hasBody, canPost) => !readOnly && hasBody && canPost), + DoCommitEdit); + AddErrorHandler(CommitEdit); + + CancelEdit = ReactiveCommand.Create(CommitEdit.IsExecuting.Select(x => !x)); + CancelEdit.Subscribe(DoCancelEdit); + AddErrorHandler(CancelEdit); + + OpenOnGitHub = ReactiveCommand.Create(this.WhenAnyValue(x => x.Id).Select(x => x != null)); + } + + /// <summary> + /// Initializes a new instance of the <see cref="CommentViewModel"/> class. + /// </summary> + /// <param name="commentService">Comment Service</param> + /// <param name="thread">The thread that the comment is a part of.</param> + /// <param name="currentUser">The current user.</param> + /// <param name="model">The comment model.</param> + protected CommentViewModel( + ICommentService commentService, + ICommentThreadViewModel thread, + ActorModel currentUser, + CommentModel model) + : this( + commentService, + thread, + new ActorViewModel(currentUser), + model.PullRequestId, + model.Id, + model.DatabaseId, + model.Body, + CommentEditState.None, + new ActorViewModel(model.Author), + model.CreatedAt, + new Uri(model.Url)) + { + } + + protected void AddErrorHandler<T>(ReactiveCommand<T> command) + { + command.ThrownExceptions.Subscribe(x => ErrorMessage = x.Message); + } + + async Task DoDelete(object unused) + { + if (commentService.ConfirmCommentDelete()) + { + try + { + ErrorMessage = null; + IsSubmitting = true; + + await Thread.DeleteComment.ExecuteAsyncTask(new Tuple<int, int>(PullRequestId, DatabaseId)); + } + catch (Exception e) + { + var message = e.Message; + ErrorMessage = message; + log.Error(e, "Error Deleting comment"); + } + finally + { + IsSubmitting = false; + } + } + } + + void DoBeginEdit(object unused) + { + if (state != CommentEditState.Editing) + { + ErrorMessage = null; + undoBody = Body; + EditState = CommentEditState.Editing; + } + } + + void DoCancelEdit(object unused) + { + if (EditState == CommentEditState.Editing) + { + EditState = string.IsNullOrWhiteSpace(undoBody) ? CommentEditState.Placeholder : CommentEditState.None; + Body = undoBody; + ErrorMessage = null; + undoBody = null; + } + } + + async Task DoCommitEdit(object unused) + { + try + { + ErrorMessage = null; + IsSubmitting = true; + + if (Id == null) + { + await Thread.PostComment.ExecuteAsyncTask(Body); + } + else + { + await Thread.EditComment.ExecuteAsyncTask(new Tuple<string, string>(Id, Body)); + } + } + catch (Exception e) + { + var message = e.Message; + ErrorMessage = message; + log.Error(e, "Error posting comment"); + } + finally + { + IsSubmitting = false; + } + } + + /// <inheritdoc/> + public string Id { get; private set; } + + /// <inheritdoc/> + public int DatabaseId { get; private set; } + + /// <inheritdoc/> + public int PullRequestId { get; private set; } + + /// <inheritdoc/> + public string Body + { + get { return body; } + set { this.RaiseAndSetIfChanged(ref body, value); } + } + + /// <inheritdoc/> + public string ErrorMessage + { + get { return this.errorMessage; } + private set { this.RaiseAndSetIfChanged(ref errorMessage, value); } + } + + /// <inheritdoc/> + public CommentEditState EditState + { + get { return state; } + private set { this.RaiseAndSetIfChanged(ref state, value); } + } + + /// <inheritdoc/> + public bool IsReadOnly + { + get { return isReadOnly; } + set { this.RaiseAndSetIfChanged(ref isReadOnly, value); } + } + + /// <inheritdoc/> + public bool IsSubmitting + { + get { return isSubmitting; } + protected set { this.RaiseAndSetIfChanged(ref isSubmitting, value); } + } + + public bool CanDelete + { + get { return canDelete.Value; } + } + + /// <inheritdoc/> + public DateTimeOffset UpdatedAt + { + get { return updatedAt; } + private set { this.RaiseAndSetIfChanged(ref updatedAt, value); } + } + + /// <inheritdoc/> + public IActorViewModel CurrentUser { get; } + + /// <inheritdoc/> + public ICommentThreadViewModel Thread { get; } + + /// <inheritdoc/> + public IActorViewModel Author { get; } + + /// <inheritdoc/> + public Uri WebUrl { get; } + + /// <inheritdoc/> + public ReactiveCommand<object> BeginEdit { get; } + + /// <inheritdoc/> + public ReactiveCommand<object> CancelEdit { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> CommitEdit { get; } + + /// <inheritdoc/> + public ReactiveCommand<object> OpenOnGitHub { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> Delete { get; } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs new file mode 100644 index 0000000000..b17290de59 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/ICommentThreadViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.ObjectModel; +using System.Reactive; +using GitHub.Models; +using GitHub.ViewModels; +using ReactiveUI; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// A comment thread. + /// </summary> + public interface ICommentThreadViewModel + { + /// <summary> + /// Gets the comments in the thread. + /// </summary> + ObservableCollection<ICommentViewModel> Comments { get; } + + /// <summary> + /// Gets the current user under whos account new comments will be created. + /// </summary> + IActorViewModel CurrentUser { get; } + + /// <summary> + /// Called by a comment in the thread to post itself as a new comment to the API. + /// </summary> + ReactiveCommand<Unit> PostComment { get; } + + /// <summary> + /// Called by a comment in the thread to post itself as an edit to a comment to the API. + /// </summary> + ReactiveCommand<Unit> EditComment { get; } + + /// <summary> + /// Called by a comment in the thread to send a delete of the comment to the API. + /// </summary> + ReactiveCommand<Unit> DeleteComment { get; } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs new file mode 100644 index 0000000000..9bb64b0bfb --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/ICommentViewModel.cs @@ -0,0 +1,112 @@ +using System; +using System.Reactive; +using GitHub.Models; +using GitHub.ViewModels; +using ReactiveUI; + +namespace GitHub.InlineReviews.ViewModels +{ + public enum CommentEditState + { + None, + Editing, + Placeholder, + } + + /// <summary> + /// View model for an issue or pull request comment. + /// </summary> + public interface ICommentViewModel : IViewModel + { + /// <summary> + /// Gets the GraphQL ID of the comment. + /// </summary> + string Id { get; } + + /// <summary> + /// Gets the Database ID of the comment. + /// </summary> + int DatabaseId { get; } + + /// <summary> + /// The pull request id of the comment + /// </summary> + int PullRequestId { get; } + + /// <summary> + /// Gets or sets the body of the comment. + /// </summary> + string Body { get; set; } + + /// <summary> + /// Gets any error message encountered posting or updating the comment. + /// </summary> + string ErrorMessage { get; } + + /// <summary> + /// Gets the current edit state of the comment. + /// </summary> + CommentEditState EditState { get; } + + /// <summary> + /// Gets or sets a value indicating whether the comment is read-only. + /// </summary> + bool IsReadOnly { get; set; } + + /// <summary> + /// Gets a value indicating whether the comment is currently in the process of being + /// submitted. + /// </summary> + bool IsSubmitting { get; } + + /// <summary> + /// Gets a value indicating whether the comment can be edited or deleted by the current user + /// </summary> + bool CanDelete { get; } + + /// <summary> + /// Gets the modified date of the comment. + /// </summary> + DateTimeOffset UpdatedAt { get; } + + /// <summary> + /// Gets the author of the comment. + /// </summary> + IActorViewModel Author { get; } + + /// <summary> + /// Gets the thread that the comment is a part of. + /// </summary> + ICommentThreadViewModel Thread { get; } + + /// <summary> + /// Gets the URL of the comment on the web. + /// </summary> + Uri WebUrl { get; } + + /// <summary> + /// Gets a command which will begin editing of the comment. + /// </summary> + ReactiveCommand<object> BeginEdit { get; } + + /// <summary> + /// Gets a command which will cancel editing of the comment. + /// </summary> + ReactiveCommand<object> CancelEdit { get; } + + /// <summary> + /// Gets a command which will commit edits to the comment. + /// </summary> + ReactiveCommand<Unit> CommitEdit { get; } + + /// <summary> + /// Gets a command to open the comment in a browser. + /// </summary> + ReactiveCommand<object> OpenOnGitHub { get; } + + /// <summary> + /// Deletes a comment. + /// </summary> + ReactiveCommand<Unit> Delete { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/ViewModels/IPullRequestReviewCommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/IPullRequestReviewCommentViewModel.cs new file mode 100644 index 0000000000..63bb86d96d --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/IPullRequestReviewCommentViewModel.cs @@ -0,0 +1,36 @@ +using System; +using System.Reactive; +using ReactiveUI; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// View model for a pull request review comment. + /// </summary> + public interface IPullRequestReviewCommentViewModel : ICommentViewModel + { + /// <summary> + /// Gets a value indicating whether the user can start a new review with this comment. + /// </summary> + bool CanStartReview { get; } + + /// <summary> + /// Gets the caption for the "Commit" button. + /// </summary> + /// <remarks> + /// This will be "Add a single comment" when not in review mode and "Add review comment" + /// when in review mode. + /// </remarks> + string CommitCaption { get; } + + /// <summary> + /// Gets a value indicating whether this comment is part of a pending pull request review. + /// </summary> + bool IsPending { get; } + + /// <summary> + /// Gets a command which will commit a new comment and start a review. + /// </summary> + ReactiveCommand<Unit> StartReview { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs new file mode 100644 index 0000000000..91bef39588 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.Factories; +using GitHub.InlineReviews.Commands; +using GitHub.InlineReviews.Peek; +using GitHub.InlineReviews.Services; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using ReactiveUI; +using Serilog; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// Represents the contents of an inline comment peek view displayed in an editor. + /// </summary> + public sealed class InlineCommentPeekViewModel : ReactiveObject, IDisposable + { + static readonly ILogger log = LogManager.ForContext<InlineCommentPeekViewModel>(); + readonly IInlineCommentPeekService peekService; + readonly IPeekSession peekSession; + readonly IPullRequestSessionManager sessionManager; + readonly ICommentService commentService; + IPullRequestSession session; + IPullRequestSessionFile file; + ICommentThreadViewModel thread; + IDisposable fileSubscription; + IDisposable sessionSubscription; + IDisposable threadSubscription; + ITrackingPoint triggerPoint; + string relativePath; + DiffSide side; + + /// <summary> + /// Initializes a new instance of the <see cref="InlineCommentPeekViewModel"/> class. + /// </summary> + public InlineCommentPeekViewModel(IInlineCommentPeekService peekService, + IPeekSession peekSession, + IPullRequestSessionManager sessionManager, + INextInlineCommentCommand nextCommentCommand, + IPreviousInlineCommentCommand previousCommentCommand, + ICommentService commentService) + { + Guard.ArgumentNotNull(peekService, nameof(peekService)); + Guard.ArgumentNotNull(peekSession, nameof(peekSession)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + Guard.ArgumentNotNull(nextCommentCommand, nameof(nextCommentCommand)); + Guard.ArgumentNotNull(previousCommentCommand, nameof(previousCommentCommand)); + + this.peekService = peekService; + this.peekSession = peekSession; + this.sessionManager = sessionManager; + this.commentService = commentService; + triggerPoint = peekSession.GetTriggerPoint(peekSession.TextView.TextBuffer); + + peekSession.Dismissed += (s, e) => Dispose(); + + Close = this.WhenAnyValue(x => x.Thread) + .SelectMany(x => x is NewInlineCommentThreadViewModel + ? x.Comments.Single().CancelEdit.SelectUnit() + : Observable.Never<Unit>()); + + NextComment = ReactiveCommand.CreateAsyncTask( + Observable.Return(nextCommentCommand.Enabled), + _ => nextCommentCommand.Execute(new InlineCommentNavigationParams + { + FromLine = peekService.GetLineNumber(peekSession, triggerPoint).Item1, + })); + + PreviousComment = ReactiveCommand.CreateAsyncTask( + Observable.Return(previousCommentCommand.Enabled), + _ => previousCommentCommand.Execute(new InlineCommentNavigationParams + { + FromLine = peekService.GetLineNumber(peekSession, triggerPoint).Item1, + })); + } + + /// <summary> + /// Gets the thread of comments to display. + /// </summary> + public ICommentThreadViewModel Thread + { + get { return thread; } + private set { this.RaiseAndSetIfChanged(ref thread, value); } + } + + /// <summary> + /// Gets a command which moves to the next inline comment in the file. + /// </summary> + public ReactiveCommand<Unit> NextComment { get; } + + /// <summary> + /// Gets a command which moves to the previous inline comment in the file. + /// </summary> + public ReactiveCommand<Unit> PreviousComment { get; } + + public IObservable<Unit> Close { get; } + + public void Dispose() + { + threadSubscription?.Dispose(); + threadSubscription = null; + sessionSubscription?.Dispose(); + sessionSubscription = null; + fileSubscription?.Dispose(); + fileSubscription = null; + } + + public async Task Initialize() + { + var buffer = peekSession.TextView.TextBuffer; + var info = sessionManager.GetTextBufferInfo(buffer); + + if (info != null) + { + var commitSha = info.Side == DiffSide.Left ? "HEAD" : info.CommitSha; + relativePath = info.RelativePath; + side = info.Side ?? DiffSide.Right; + file = await info.Session.GetFile(relativePath, commitSha); + session = info.Session; + await UpdateThread(); + } + else + { + relativePath = sessionManager.GetRelativePath(buffer); + side = DiffSide.Right; + file = await sessionManager.GetLiveFile(relativePath, peekSession.TextView, buffer); + await SessionChanged(sessionManager.CurrentSession); + sessionSubscription = sessionManager.WhenAnyValue(x => x.CurrentSession) + .Skip(1) + .Subscribe(x => SessionChanged(x).Forget()); + } + + fileSubscription?.Dispose(); + fileSubscription = file.LinesChanged.Subscribe(LinesChanged); + } + + async void LinesChanged(IReadOnlyList<Tuple<int, DiffSide>> lines) + { + try + { + var lineNumber = peekService.GetLineNumber(peekSession, triggerPoint).Item1; + + if (lines.Contains(Tuple.Create(lineNumber, side))) + { + await UpdateThread(); + } + } + catch (Exception e) + { + log.Error(e, "Error updating InlineCommentViewModel"); + } + } + + async Task UpdateThread() + { + var placeholderBody = GetPlaceholderBodyToPreserve(); + + Thread = null; + threadSubscription?.Dispose(); + + if (file == null) + return; + + var lineAndLeftBuffer = peekService.GetLineNumber(peekSession, triggerPoint); + var lineNumber = lineAndLeftBuffer.Item1; + var leftBuffer = lineAndLeftBuffer.Item2; + var thread = file.InlineCommentThreads?.FirstOrDefault(x => + x.LineNumber == lineNumber && + ((leftBuffer && x.DiffLineType == DiffChangeType.Delete) || (!leftBuffer && x.DiffLineType != DiffChangeType.Delete))); + + if (thread != null) + { + Thread = new InlineCommentThreadViewModel(commentService, session, thread.Comments); + } + else + { + Thread = new NewInlineCommentThreadViewModel(commentService, session, file, lineNumber, leftBuffer); + } + + if (!string.IsNullOrWhiteSpace(placeholderBody)) + { + var placeholder = Thread.Comments.LastOrDefault(); + + if (placeholder?.EditState == CommentEditState.Placeholder) + { + await placeholder.BeginEdit.ExecuteAsync(null); + placeholder.Body = placeholderBody; + } + } + } + + async Task SessionChanged(IPullRequestSession pullRequestSession) + { + this.session = pullRequestSession; + + if (pullRequestSession == null) + { + Thread = null; + threadSubscription?.Dispose(); + threadSubscription = null; + return; + } + else + { + await UpdateThread(); + } + } + + string GetPlaceholderBodyToPreserve() + { + var lastComment = Thread?.Comments.LastOrDefault(); + + if (lastComment?.EditState == CommentEditState.Editing) + { + if (!lastComment.IsSubmitting) return lastComment.Body; + } + + return null; + } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs new file mode 100644 index 0000000000..8c2d1567fd --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/InlineCommentThreadViewModel.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.InlineReviews.Services; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// A thread of inline comments (aka Pull Request Review Comments). + /// </summary> + public class InlineCommentThreadViewModel : CommentThreadViewModel + { + /// <summary> + /// Initializes a new instance of the <see cref="InlineCommentThreadViewModel"/> class. + /// </summary> + /// <param name="commentService">The comment service</param> + /// <param name="session">The current PR review session.</param> + /// <param name="comments">The comments to display in this inline review.</param> + public InlineCommentThreadViewModel(ICommentService commentService, IPullRequestSession session, + IEnumerable<InlineCommentModel> comments) + : base(session.User) + { + Guard.ArgumentNotNull(session, nameof(session)); + + Session = session; + + PostComment = ReactiveCommand.CreateAsyncTask( + Observable.Return(true), + DoPostComment); + + EditComment = ReactiveCommand.CreateAsyncTask( + Observable.Return(true), + DoEditComment); + + DeleteComment = ReactiveCommand.CreateAsyncTask( + Observable.Return(true), + DoDeleteComment); + + foreach (var comment in comments) + { + Comments.Add(new PullRequestReviewCommentViewModel( + session, + commentService, + this, + CurrentUser, + comment.Review, + comment.Comment)); + } + + Comments.Add(PullRequestReviewCommentViewModel.CreatePlaceholder(session, commentService, this, CurrentUser)); + } + + /// <summary> + /// Gets the current pull request review session. + /// </summary> + public IPullRequestSession Session { get; } + + async Task DoPostComment(object parameter) + { + Guard.ArgumentNotNull(parameter, nameof(parameter)); + + var body = (string)parameter; + var replyId = Comments[0].Id; + await Session.PostReviewComment(body, replyId); + } + + async Task DoEditComment(object parameter) + { + Guard.ArgumentNotNull(parameter, nameof(parameter)); + + var item = (Tuple<string, string>)parameter; + await Session.EditComment(item.Item1, item.Item2); + } + + async Task DoDeleteComment(object parameter) + { + Guard.ArgumentNotNull(parameter, nameof(parameter)); + + var item = (Tuple<int, int>)parameter; + await Session.DeleteComment(item.Item1, item.Item2); + } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs b/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs new file mode 100644 index 0000000000..a9753a4816 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/NewInlineCommentThreadViewModel.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.InlineReviews.Services; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// A new inline comment thread that is being authored. + /// </summary> + public class NewInlineCommentThreadViewModel : CommentThreadViewModel + { + bool needsPush; + + /// <summary> + /// Initializes a new instance of the <see cref="InlineCommentThreadViewModel"/> class. + /// </summary> + /// <param name="commentService">The comment service</param> + /// <param name="session">The current PR review session.</param> + /// <param name="file">The file being commented on.</param> + /// <param name="lineNumber">The 0-based line number in the file.</param> + /// <param name="leftComparisonBuffer"> + /// True if the comment is being left on the left-hand-side of a diff; otherwise false. + /// </param> + public NewInlineCommentThreadViewModel(ICommentService commentService, + IPullRequestSession session, + IPullRequestSessionFile file, + int lineNumber, + bool leftComparisonBuffer) + : base(session.User) + { + Guard.ArgumentNotNull(session, nameof(session)); + Guard.ArgumentNotNull(file, nameof(file)); + + Session = session; + File = file; + LineNumber = lineNumber; + LeftComparisonBuffer = leftComparisonBuffer; + + PostComment = ReactiveCommand.CreateAsyncTask( + this.WhenAnyValue(x => x.NeedsPush, x => !x), + DoPostComment); + + EditComment = ReactiveCommand.CreateAsyncTask<Unit>( + Observable.Return(false), + o => null); + + DeleteComment = ReactiveCommand.CreateAsyncTask<Unit>( + Observable.Return(false), + o => null); + + var placeholder = PullRequestReviewCommentViewModel.CreatePlaceholder(session, commentService, this, CurrentUser); + placeholder.BeginEdit.Execute(null); + this.WhenAnyValue(x => x.NeedsPush).Subscribe(x => placeholder.IsReadOnly = x); + Comments.Add(placeholder); + + file.WhenAnyValue(x => x.CommitSha).Subscribe(x => NeedsPush = x == null); + } + + /// <summary> + /// Gets the file that the comment will be left on. + /// </summary> + public IPullRequestSessionFile File { get; } + + /// <summary> + /// Gets the 0-based line number in the file that the comment will be left on. + /// </summary> + public int LineNumber { get; } + + /// <summary> + /// Gets a value indicating whether comment is being left on the left-hand-side of a diff. + /// </summary> + public bool LeftComparisonBuffer { get; } + + /// <summary> + /// Gets the current pull request review session. + /// </summary> + public IPullRequestSession Session { get; } + + /// <summary> + /// Gets a value indicating whether the user must commit and push their changes before + /// leaving a comment on the requested line. + /// </summary> + public bool NeedsPush + { + get { return needsPush; } + private set { this.RaiseAndSetIfChanged(ref needsPush, value); } + } + + async Task DoPostComment(object parameter) + { + Guard.ArgumentNotNull(parameter, nameof(parameter)); + + var diffPosition = File.Diff + .SelectMany(x => x.Lines) + .FirstOrDefault(x => + { + var line = LeftComparisonBuffer ? x.OldLineNumber : x.NewLineNumber; + return line == LineNumber + 1; + }); + + if (diffPosition == null) + { + throw new InvalidOperationException("Unable to locate line in diff."); + } + + var body = (string)parameter; + await Session.PostReviewComment( + body, + File.CommitSha, + File.RelativePath.Replace("\\", "/"), + File.Diff, + diffPosition.DiffLineNumber); + } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestFileMarginViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestFileMarginViewModel.cs new file mode 100644 index 0000000000..84968d9d85 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/PullRequestFileMarginViewModel.cs @@ -0,0 +1,53 @@ +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<IUsageTracker> 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/PullRequestReviewCommentViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs new file mode 100644 index 0000000000..047000f734 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/PullRequestReviewCommentViewModel.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.InlineReviews.Services; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels; +using GitHub.VisualStudio.UI; +using ReactiveUI; +using Serilog; + +namespace GitHub.InlineReviews.ViewModels +{ + /// <summary> + /// View model for a pull request review comment. + /// </summary> + public class PullRequestReviewCommentViewModel : CommentViewModel, IPullRequestReviewCommentViewModel + { + readonly IPullRequestSession session; + ObservableAsPropertyHelper<bool> canStartReview; + ObservableAsPropertyHelper<string> commitCaption; + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestReviewCommentViewModel"/> class. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="commentService">The comment service</param> + /// <param name="thread">The thread that the comment is a part of.</param> + /// <param name="currentUser">The current user.</param> + /// <param name="pullRequestId">The pull request id of the comment.</param> + /// <param name="commentId">The GraphQL ID of the comment.</param> + /// <param name="databaseId">The database id of the comment.</param> + /// <param name="body">The comment body.</param> + /// <param name="state">The comment edit state.</param> + /// <param name="author">The author of the comment.</param> + /// <param name="updatedAt">The modified date of the comment.</param> + /// <param name="isPending">Whether this is a pending comment.</param> + /// <param name="webUrl"></param> + public PullRequestReviewCommentViewModel( + IPullRequestSession session, + ICommentService commentService, + ICommentThreadViewModel thread, + IActorViewModel currentUser, + int pullRequestId, + string commentId, + int databaseId, + string body, + CommentEditState state, + IActorViewModel author, + DateTimeOffset updatedAt, + bool isPending, + Uri webUrl) + : base(commentService, thread, currentUser, pullRequestId, commentId, databaseId, body, state, author, updatedAt, webUrl) + { + Guard.ArgumentNotNull(session, nameof(session)); + + this.session = session; + IsPending = isPending; + + var pendingReviewAndIdObservable = Observable.CombineLatest( + session.WhenAnyValue(x => x.HasPendingReview, x => !x), + this.WhenAnyValue(model => model.Id).Select(i => i == null), + (hasPendingReview, isNewComment) => new { hasPendingReview, isNewComment }); + + canStartReview = pendingReviewAndIdObservable + .Select(arg => arg.hasPendingReview && arg.isNewComment) + .ToProperty(this, x => x.CanStartReview); + + commitCaption = pendingReviewAndIdObservable + .Select(arg => !arg.isNewComment ? Resources.UpdateComment : arg.hasPendingReview ? Resources.AddSingleComment : Resources.AddReviewComment) + .ToProperty(this, x => x.CommitCaption); + + StartReview = ReactiveCommand.CreateAsyncTask( + CommitEdit.CanExecuteObservable, + DoStartReview); + AddErrorHandler(StartReview); + } + + /// <summary> + /// Initializes a new instance of the <see cref="PullRequestReviewCommentViewModel"/> class. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="commentService">Comment Service</param> + /// <param name="thread">The thread that the comment is a part of.</param> + /// <param name="currentUser">The current user.</param> + /// <param name="review">The associated pull request review.</param> + /// <param name="model">The comment model.</param> + public PullRequestReviewCommentViewModel( + IPullRequestSession session, + ICommentService commentService, + ICommentThreadViewModel thread, + IActorViewModel currentUser, + PullRequestReviewModel review, + PullRequestReviewCommentModel model) + : this( + session, + commentService, + thread, + currentUser, + model.PullRequestId, + model.Id, + model.DatabaseId, + model.Body, + CommentEditState.None, + new ActorViewModel(model.Author), + model.CreatedAt, + review.State == PullRequestReviewState.Pending, + model.Url != null ? new Uri(model.Url) : null) + { + } + + /// <summary> + /// Creates a placeholder comment which can be used to add a new comment to a thread. + /// </summary> + /// <param name="session">The pull request session.</param> + /// <param name="commentService">Comment Service</param> + /// <param name="thread">The comment thread.</param> + /// <param name="currentUser">The current user.</param> + /// <returns>THe placeholder comment.</returns> + public static CommentViewModel CreatePlaceholder( + IPullRequestSession session, + ICommentService commentService, + ICommentThreadViewModel thread, + IActorViewModel currentUser) + { + return new PullRequestReviewCommentViewModel( + session, + commentService, + thread, + currentUser, + 0, + null, + 0, + string.Empty, + CommentEditState.Placeholder, + currentUser, + DateTimeOffset.MinValue, + false, + null); + } + + /// <inheritdoc/> + public bool CanStartReview => canStartReview.Value; + + /// <inheritdoc/> + public string CommitCaption => commitCaption.Value; + + /// <inheritdoc/> + public bool IsPending { get; } + + /// <inheritdoc/> + public ReactiveCommand<Unit> StartReview { get; } + + async Task DoStartReview(object unused) + { + IsSubmitting = true; + + try + { + await session.StartReview(); + await CommitEdit.ExecuteAsync(null); + } + finally + { + IsSubmitting = false; + } + } + } +} diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs new file mode 100644 index 0000000000..9c59ad2379 --- /dev/null +++ b/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs @@ -0,0 +1,49 @@ +using System; +using System.Windows.Input; +using System.ComponentModel; + +namespace GitHub.InlineReviews.ViewModels +{ + public class PullRequestStatusViewModel : INotifyPropertyChanged + { + int? number; + string title; + + public PullRequestStatusViewModel(ICommand openPullRequestsCommand, ICommand showCurrentPullRequestCommand) + { + OpenPullRequestsCommand = openPullRequestsCommand; + ShowCurrentPullRequestCommand = showCurrentPullRequestCommand; + } + + public int? Number + { + get { return number; } + set + { + if (number != value) + { + number = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Number))); + } + } + } + + public string Title + { + get { return title; } + set + { + if (title != value) + { + title = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title))); + } + } + } + + public ICommand OpenPullRequestsCommand { get; } + public ICommand ShowCurrentPullRequestCommand { get; } + + public event PropertyChangedEventHandler PropertyChanged; + } +} diff --git a/src/GitHub.InlineReviews/Views/CommentThreadView.xaml b/src/GitHub.InlineReviews/Views/CommentThreadView.xaml new file mode 100644 index 0000000000..6d8c904166 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/CommentThreadView.xaml @@ -0,0 +1,40 @@ +<UserControl x:Class="GitHub.InlineReviews.Views.CommentThreadView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.InlineReviews.Views" + xmlns:sample="clr-namespace:GitHub.InlineReviews.SampleData" + mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> + <d:DesignProperties.DataContext> + <x:Array Type="{x:Type sample:CommentThreadViewModelDesigner}"> + <sample:CommentThreadViewModelDesigner> + <!-- <sample:CommentThreadViewModelDesigner.Comments> + <sample:CommentViewModelDesigner> + <sample:CommentViewModelDesigner.Body> + I assume this doesn't do anything if our message isn't showing? + </sample:CommentViewModelDesigner.Body> + </sample:CommentViewModelDesigner> + <sample:CommentViewModelDesigner> + <sample:CommentViewModelDesigner.Body> + Nope, does nothing! Also checked the logs. + </sample:CommentViewModelDesigner.Body> + </sample:CommentViewModelDesigner> + <sample:CommentViewModelDesigner EditState="Placeholder"> + <sample:CommentViewModelDesigner.Body> + Reply... + </sample:CommentViewModelDesigner.Body> + </sample:CommentViewModelDesigner> + </sample:CommentThreadViewModelDesigner.Comments> --> + </sample:CommentThreadViewModelDesigner> + </x:Array> + </d:DesignProperties.DataContext> + + <ItemsControl ItemsSource="{Binding Comments}"> + <ItemsControl.ItemTemplate> + <DataTemplate> + <local:CommentView Margin="0 4"/> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> +</UserControl> diff --git a/src/GitHub.InlineReviews/Views/CommentThreadView.xaml.cs b/src/GitHub.InlineReviews/Views/CommentThreadView.xaml.cs new file mode 100644 index 0000000000..3143ef3e42 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/CommentThreadView.xaml.cs @@ -0,0 +1,15 @@ +using System; +using System.Windows.Controls; +using GitHub.VisualStudio.UI.Helpers; + +namespace GitHub.InlineReviews.Views +{ + public partial class CommentThreadView : UserControl + { + public CommentThreadView() + { + InitializeComponent(); + PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; + } + } +} diff --git a/src/GitHub.InlineReviews/Views/CommentView.xaml b/src/GitHub.InlineReviews/Views/CommentView.xaml new file mode 100644 index 0000000000..7e49028622 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/CommentView.xaml @@ -0,0 +1,226 @@ +<views:GenericCommentView x:Class="GitHub.InlineReviews.Views.CommentView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:controls="clr-namespace:GitHub.VisualStudio.UI.Controls;assembly=GitHub.VisualStudio.UI" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + xmlns:sample="clr-namespace:GitHub.InlineReviews.SampleData" + xmlns:views="clr-namespace:GitHub.InlineReviews.Views" + mc:Ignorable="d" d:DesignWidth="300"> + <d:DesignProperties.DataContext> + <sample:CommentViewModelDesigner EditState="None"> + <sample:CommentViewModelDesigner.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). + </sample:CommentViewModelDesigner.Body> + </sample:CommentViewModelDesigner> + </d:DesignProperties.DataContext> + + <FrameworkElement.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Style TargetType="Button" BasedOn="{StaticResource GitHubVsButton}"/> + </ResourceDictionary> + </FrameworkElement.Resources> + + <FrameworkElement.CommandBindings> + <CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" /> + </FrameworkElement.CommandBindings> + + <Grid> + <!-- Displays an existing comment--> + <StackPanel Orientation="Vertical" Margin="4"> + <StackPanel.Style> + <Style TargetType="FrameworkElement"> + <Setter Property="Visibility" Value="Collapsed"/> + <Style.Triggers> + <DataTrigger Binding="{Binding EditState}" Value="None"> + <Setter Property="Visibility" Value="Visible"/> + </DataTrigger> + </Style.Triggers> + </Style> + </StackPanel.Style> + + <DockPanel> + <StackPanel Orientation="Horizontal" DockPanel.Dock="Left" > + <controls:AccountAvatar Width="16" + Height="16" + Account="{Binding Author}"/> + + <TextBlock Foreground="{DynamicResource GitHubVsToolWindowText}" FontWeight="Bold" Text="{Binding Author.Login}" Margin="4 0"/> + <ui:GitHubActionLink Content="{Binding UpdatedAt, Converter={ui:DurationToStringConverter}}" + Command="{Binding OpenOnGitHub}" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Opacity="0.75" /> + <Border Background="{DynamicResource VsBrush.InfoBackground}" + BorderBrush="{DynamicResource VsBrush.AccentPale}" + BorderThickness="1" + CornerRadius="3" + Padding="2 1" + Visibility="{Binding IsPending, Converter={ui:BooleanToVisibilityConverter}, FallbackValue=Collapsed}"> + <TextBlock FontSize="10">Pending</TextBlock> + </Border> + </StackPanel> + + <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" DockPanel.Dock="Right" + Visibility="{Binding CanDelete, Converter={ui:BooleanToVisibilityConverter}}"> + <ui:OcticonButton Command="{Binding BeginEdit}" + Height="16" + Width="20" + Margin="0 0 4 0" + Background="Transparent" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Icon="pencil"/> + <ui:OcticonButton Command="{Binding Delete}" + Width="16" + Height="16" + Margin="0" + Background="Transparent" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Icon="x"/> + </StackPanel> + </DockPanel> + + <markdig:MarkdownViewer Grid.Column="1" Grid.Row="1" + Margin="0 2 0 0" + Foreground="{DynamicResource VsBrush.WindowText}" + Markdown="{Binding Body}"/> + + <DockPanel Grid.Column="1" Grid.Row="2" + Margin="0 4" + HorizontalAlignment="Left" + TextBlock.Foreground="Red"> + <DockPanel.Style> + <Style TargetType="FrameworkElement"> + <Style.Triggers> + <DataTrigger Binding="{Binding ErrorMessage}" Value="{x:Null}"> + <Setter Property="Visibility" Value="Collapsed"/> + </DataTrigger> + </Style.Triggers> + </Style> + </DockPanel.Style> + <ui:OcticonImage DockPanel.Dock="Left" Icon="alert" Margin="0 0 4 0"/> + <TextBlock Text="{Binding ErrorMessage}" TextWrapping="Wrap"/> + </DockPanel> + </StackPanel> + + <!-- Displays edit view or a reply placeholder--> + <Grid> + <Grid.Style> + <Style TargetType="FrameworkElement"> + <Setter Property="Visibility" Value="Collapsed"/> + <Style.Triggers> + <DataTrigger Binding="{Binding EditState}" Value="Placeholder"> + <Setter Property="Visibility" Value="Visible"/> + </DataTrigger> + <DataTrigger Binding="{Binding EditState}" Value="Editing"> + <Setter Property="Visibility" Value="Visible"/> + </DataTrigger> + </Style.Triggers> + </Style> + </Grid.Style> + + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <Separator Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Margin="0 0 0 4" + Background="{DynamicResource GitHubButtonBorderBrush}"/> + + <ui:PromptTextBox Name="body" + Grid.Column="1" + Grid.Row="1" + AcceptsReturn="True" + AcceptsTab="True" + IsReadOnly="{Binding IsReadOnly}" + Margin="4 0" + Text="{Binding Body, UpdateSourceTrigger=PropertyChanged}" + TextWrapping="Wrap" + VerticalAlignment="Center" + GotFocus="ReplyPlaceholder_GotFocus" + SpellCheck.IsEnabled="True"> + <ui:PromptTextBox.Style> + <Style TargetType="ui:PromptTextBox" BasedOn="{StaticResource RoundedPromptTextBox}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + <Setter Property="Background" Value="{DynamicResource VsBrush.SearchBoxBackground}" /> + <Setter Property="Height" Value="28"/> + <Setter Property="PromptText" Value="Reply..."/> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubVsBrandedUIBorder}" /> + + <Style.Triggers> + <DataTrigger Binding="{Binding EditState}" Value="Editing"> + <Setter Property="MinHeight" Value="100"/> + <Setter Property="PromptText" Value="Leave a comment"/> + </DataTrigger> + </Style.Triggers> + + <Style.Resources> + <Style TargetType="TextBlock"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + </Style> + </Style.Resources> + </Style> + </ui:PromptTextBox.Style> + </ui:PromptTextBox> + + <DockPanel Grid.Column="1" Grid.Row="2" + Margin="0 4" + HorizontalAlignment="Left" + TextBlock.Foreground="Red"> + <DockPanel.Style> + <Style TargetType="FrameworkElement"> + <Style.Triggers> + <DataTrigger Binding="{Binding ErrorMessage}" Value="{x:Null}"> + <Setter Property="Visibility" Value="Collapsed"/> + </DataTrigger> + </Style.Triggers> + </Style> + </DockPanel.Style> + <ui:OcticonImage DockPanel.Dock="Left" Icon="alert" Margin="0 0 4 0"/> + <TextBlock Text="{Binding ErrorMessage}" TextWrapping="Wrap"/> + </DockPanel> + + <StackPanel Name="buttonPanel" + Grid.Column="1" Grid.Row="3" + Margin="4 8" + HorizontalAlignment="Left" + Orientation="Horizontal" + IsVisibleChanged="buttonPanel_IsVisibleChanged"> + <StackPanel.Style> + <Style TargetType="FrameworkElement"> + <Setter Property="Visibility" Value="Collapsed"/> + <Style.Triggers> + <DataTrigger Binding="{Binding EditState}" Value="Editing"> + <Setter Property="Visibility" Value="Visible"/> + </DataTrigger> + </Style.Triggers> + </Style> + </StackPanel.Style> + <Button Command="{Binding CommitEdit}" Content="{Binding CommitCaption}"/> + <Button Margin="4 0 0 0" + Command="{Binding StartReview}" + Visibility="{Binding CanStartReview, Converter={ui:BooleanToVisibilityConverter}}"> + Start a review + </Button> + <Button Margin="4 0 0 0" Command="{Binding CancelEdit}">Cancel</Button> + </StackPanel> + </Grid> + </Grid> +</views:GenericCommentView> diff --git a/src/GitHub.InlineReviews/Views/CommentView.xaml.cs b/src/GitHub.InlineReviews/Views/CommentView.xaml.cs new file mode 100644 index 0000000000..271fac0502 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/CommentView.xaml.cs @@ -0,0 +1,74 @@ +using System; +using System.Windows.Input; +using GitHub.InlineReviews.ViewModels; +using GitHub.Services; +using GitHub.UI; +using Microsoft.VisualStudio.Shell; +using ReactiveUI; + +namespace GitHub.InlineReviews.Views +{ + public class GenericCommentView : ViewBase<ICommentViewModel, GenericCommentView> { } + + public partial class CommentView : GenericCommentView + { + public CommentView() + { + InitializeComponent(); + this.Loaded += CommentView_Loaded; + + this.WhenActivated(d => + { + d(ViewModel.OpenOnGitHub.Subscribe(_ => DoOpenOnGitHub())); + }); + } + + IVisualStudioBrowser GetBrowser() + { + var serviceProvider = (IGitHubServiceProvider)Package.GetGlobalService(typeof(IGitHubServiceProvider)); + return serviceProvider.GetService<IVisualStudioBrowser>(); + } + + void DoOpenOnGitHub() + { + GetBrowser().OpenUrl(ViewModel.WebUrl); + } + + private void CommentView_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + if (buttonPanel.IsVisible) + { + BringIntoView(); + body.Focus(); + } + } + + private void ReplyPlaceholder_GotFocus(object sender, System.Windows.RoutedEventArgs e) + { + var command = ((ICommentViewModel)DataContext)?.BeginEdit; + + if (command?.CanExecute(null) == true) + { + command.Execute(null); + } + } + + private void buttonPanel_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) + { + if (buttonPanel.IsVisible) + { + BringIntoView(); + } + } + + void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) + { + Uri uri; + + if (Uri.TryCreate(e.Parameter?.ToString(), UriKind.Absolute, out uri)) + { + GetBrowser().OpenUrl(uri); + } + } + } +} diff --git a/src/GitHub.InlineReviews/Views/GlyphMarginGrid.xaml b/src/GitHub.InlineReviews/Views/GlyphMarginGrid.xaml new file mode 100644 index 0000000000..5c8097f256 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/GlyphMarginGrid.xaml @@ -0,0 +1,20 @@ +<Grid x:Class="GitHub.InlineReviews.Views.GlyphMarginGrid" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.InlineReviews.Views" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + mc:Ignorable="d" + Background="{DynamicResource VsBrush.Window}" + d:DesignHeight="100" Width="17"> + <Grid.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Grid.Resources> + + <Border Background="{DynamicResource GitHubPeekViewBackground}" BorderBrush="{DynamicResource GitHubPeekViewBackground}" BorderThickness="0,0,1,0"/> +</Grid> diff --git a/src/GitHub.InlineReviews/Views/GlyphMarginGrid.xaml.cs b/src/GitHub.InlineReviews/Views/GlyphMarginGrid.xaml.cs new file mode 100644 index 0000000000..251d92ef27 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/GlyphMarginGrid.xaml.cs @@ -0,0 +1,16 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Views +{ + /// <summary> + /// Interaction logic for GlyphMarginGrid.xaml + /// </summary> + public partial class GlyphMarginGrid : Grid + { + public GlyphMarginGrid() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml new file mode 100644 index 0000000000..c27c40447b --- /dev/null +++ b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml @@ -0,0 +1,111 @@ +<UserControl x:Class="GitHub.InlineReviews.Views.InlineCommentPeekView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.InlineReviews.Views" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + mc:Ignorable="d" + d:DesignHeight="200" d:DesignWidth="500"> + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Style x:Key="GitHubToolbarButton" TargetType="{x:Type Button}"> + <Setter Property="Background" Value="Transparent" /> + + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Button}"> + <Border Background="{TemplateBinding Background}" BorderThickness="1"> + <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + + <Style.Triggers> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.25" /> + </Trigger> + + <Trigger Property="IsMouseOver" Value="True"> + <Setter Property="Background" Value="{DynamicResource VsBrush.CommandBarHover}" /> + </Trigger> + </Style.Triggers> + </Style> + </ResourceDictionary> + </UserControl.Resources> + + <DockPanel> + <Border DockPanel.Dock="Top" + Background="{DynamicResource {x:Static SystemColors.InfoBrushKey}}" + Padding="8"> + <Border.Style> + <Style TargetType="Border"> + <Setter Property="Visibility" Value="Collapsed"/> + <Style.Triggers> + <DataTrigger Binding="{Binding Thread.NeedsPush}" Value="True"> + <Setter Property="Visibility" Value="Visible"/> + </DataTrigger> + </Style.Triggers> + </Style> + </Border.Style> + <DockPanel> + <ui:OcticonImage DockPanel.Dock="Left" Icon="alert" Margin="0 0 8 0"/> + <TextBlock TextWrapping="Wrap">You must commit and push your changes to add a comment here.</TextBlock> + </DockPanel> + </Border> + + <Border DockPanel.Dock="Top" + BorderThickness="0 0 0 1" + Background="{DynamicResource VsBrush.CommandBarOptionsBackground}" + BorderBrush="{DynamicResource GitHubVsBrandedUIBorder}"> + <StackPanel Orientation="Horizontal" + HorizontalAlignment="Left" > + <Button Margin="2 0" Padding="2" Command="{Binding PreviousComment}" + Style="{StaticResource GitHubToolbarButton}"> + <Button.ToolTip> + <TextBlock FontSize="11">Previous Comment</TextBlock> + </Button.ToolTip> + <Canvas Width="16" Height="16"> + <!-- TODO: Double check these brushes since they don't seem like the right fill colors --> + <Polygon Points="10 2 5 8 8 8 8 12 12 12 12 8 15 8" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + <Rectangle Canvas.Left="1.25" Canvas.Top="9" Width="6" Height="1" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + <Rectangle Canvas.Left="1.25" Canvas.Top="10.75" Width="6" Height="1" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + <Rectangle Canvas.Left="1.25" Canvas.Top="12.75" Width="6" Height="1" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + </Canvas> + </Button> + + <Button Margin="2 0" Padding="2" Command="{Binding NextComment}" + Style="{StaticResource GitHubToolbarButton}"> + <Button.ToolTip> + <TextBlock FontSize="11">Next Comment</TextBlock> + </Button.ToolTip> + + <Canvas Width="16" Height="16"> + <!-- TODO: Double check these brushes since they don't seem like the right fill colors --> + <Polygon Points="8 8 8 4 4 4 4 8 1 8 6 14 11 8" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + <Rectangle Canvas.Left="9" Canvas.Top="2" Width="6" Height="1" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + <Rectangle Canvas.Left="9" Canvas.Top="4" Width="6" Height="1" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + <Rectangle Canvas.Left="9" Canvas.Top="6" Width="6" Height="1" Fill="{DynamicResource VsBrush.CommandBarOptionsGlyph}" StrokeThickness="1"/> + </Canvas> + </Button> + <Separator Background="{DynamicResource GitHubButtonBorderBrush}" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" /> + </StackPanel> + </Border> + + <ScrollViewer Name="threadScroller" + VerticalScrollBarVisibility="Auto" + Background="{DynamicResource GitHubPeekViewBackground}"> + <Grid> + <local:CommentThreadView x:Name="threadView" DataContext="{Binding Thread}"/> + </Grid> + </ScrollViewer> + </DockPanel> +</UserControl> diff --git a/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs new file mode 100644 index 0000000000..77e1511a6d --- /dev/null +++ b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs @@ -0,0 +1,31 @@ +using System; +using System.Reactive.Subjects; +using System.Windows.Controls; +using GitHub.VisualStudio.UI.Helpers; + +namespace GitHub.InlineReviews.Views +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] + public partial class InlineCommentPeekView : UserControl + { + readonly Subject<double> desiredHeight; + + public InlineCommentPeekView() + { + InitializeComponent(); + + desiredHeight = new Subject<double>(); + threadView.LayoutUpdated += ThreadViewLayoutUpdated; + threadScroller.PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; + } + + public IObservable<double> DesiredHeight => desiredHeight; + + void ThreadViewLayoutUpdated(object sender, EventArgs e) + { + var otherControlsHeight = ActualHeight - threadScroller.ActualHeight; + var threadViewHeight = threadView.DesiredSize.Height + threadView.Margin.Top + threadView.Margin.Bottom; + desiredHeight.OnNext(threadViewHeight + otherControlsHeight); + } + } +} diff --git a/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml b/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml new file mode 100644 index 0000000000..c6a0543ea9 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml @@ -0,0 +1,46 @@ +<UserControl x:Class="GitHub.InlineReviews.Views.PullRequestFileMarginView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.InlineReviews.Views" + xmlns:ghfvs="https://github.com/github/VisualStudio" + mc:Ignorable="d" + d:DesignHeight="30" d:DesignWidth="300"> + + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Controls/Octicons/OcticonImage.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </UserControl.Resources> + + <StackPanel Orientation="Horizontal"> + <CheckBox Command="{Binding ToggleInlineCommentMarginCommand}" IsChecked="{Binding MarginEnabled, Mode=OneWay}" Margin="4 0"> + <TextBlock VerticalAlignment="Center"> + <Run Text="{Binding FileName}"/> + <ghfvs:OcticonImage Icon="comment_discussion" Height="10" Margin="-2 0" /> + </TextBlock> + </CheckBox> + <TextBlock VerticalAlignment="Center" Margin="4 0"> + <Hyperlink Command="{Binding ViewChangesCommand}"> + <Run> + <Run.Style> + <Style> + <Setter Property="Run.Text" Value="{Binding CommentsInFile, StringFormat=view {0} comments}"/> + <Style.Triggers> + <DataTrigger Binding="{Binding CommentsInFile}" Value="0"> + <Setter Property="Run.Text" Value="view changes"/> + </DataTrigger> + <DataTrigger Binding="{Binding CommentsInFile}" Value="1"> + <Setter Property="TextBlock.Text" Value="view comment"/> + </DataTrigger> + </Style.Triggers> + </Style> + </Run.Style> + </Run> + </Hyperlink> + </TextBlock> + </StackPanel> +</UserControl> diff --git a/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml.cs b/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml.cs new file mode 100644 index 0000000000..9dda80ff8b --- /dev/null +++ b/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml.cs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000000..180ecd8532 --- /dev/null +++ b/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml @@ -0,0 +1,86 @@ +<UserControl x:Class="GitHub.InlineReviews.Views.PullRequestStatusView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.10.0" + xmlns:local="clr-namespace:GitHub.InlineReviews.Views" + mc:Ignorable="d" + d:DesignHeight="20" d:DesignWidth="60"> + + <UserControl.Resources> + <Style TargetType="Button"> + <Setter Property="Background" Value="Transparent" /> + + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="Button"> + <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> + <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> + </Border> + + <ControlTemplate.Triggers> + <Trigger Property="IsMouseOver" Value="True"> + <Setter TargetName="border" Property="Background"> + <Setter.Value> + <SolidColorBrush Color="White" Opacity="0.2" /> + </Setter.Value> + </Setter> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </UserControl.Resources> + + <StackPanel Orientation="Horizontal"> + <Grid Visibility="{Binding Number, Converter={ui:EqualsToVisibilityConverter {x:Null}}}"> + <Button + Foreground="{DynamicResource VsBrush.StatusBarText}" + BorderThickness="0" + Padding="4 0" + Command="{Binding OpenPullRequestsCommand}"> + + <StackPanel Orientation="Horizontal"> + <ui:OcticonPath + Fill="White" + VerticalAlignment="Bottom" + Margin="0 0 4 0 " + Icon="git_pull_request" /> + </StackPanel> + </Button> + <Grid.ToolTip> + <TextBlock VerticalAlignment="Center"> + View, Checkout or Create a Pull request + </TextBlock> + </Grid.ToolTip> + </Grid> + + <Grid Visibility="{Binding Number, Converter={ui:NotEqualsToVisibilityConverter {x:Null}}}"> + <Button + Foreground="{DynamicResource VsBrush.StatusBarText}" + BorderThickness="0" + Padding="4 0" + Command="{Binding ShowCurrentPullRequestCommand}"> + + <StackPanel Orientation="Horizontal"> + <ui:OcticonPath + Fill="White" + VerticalAlignment="Bottom" + Margin="0 0 4 0 " + Icon="git_pull_request" /> + <TextBlock VerticalAlignment="Center"> + #<Run Text="{Binding Number}" /> + </TextBlock> + </StackPanel> + </Button> + <Grid.ToolTip> + <TextBlock VerticalAlignment="Center"> + #<Run Text="{Binding Number}" /> - <Run Text="{Binding Title}" /> + </TextBlock> + </Grid.ToolTip> + </Grid> + </StackPanel> +</UserControl> diff --git a/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml.cs b/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml.cs new file mode 100644 index 0000000000..535830924a --- /dev/null +++ b/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml.cs @@ -0,0 +1,13 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Views +{ + public partial class PullRequestStatusView : UserControl + { + public PullRequestStatusView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.InlineReviews/VisualStudioExtensions.cs b/src/GitHub.InlineReviews/VisualStudioExtensions.cs new file mode 100644 index 0000000000..e2d1b9f5c5 --- /dev/null +++ b/src/GitHub.InlineReviews/VisualStudioExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace GitHub.InlineReviews +{ + static class VisualStudioExtensions + { + public static T GetOptionValue<T>(this IEditorOptions options, string optionId, T defaultValue) + { + return options.IsOptionDefined(optionId, false) ? + options.GetOptionValue<T>(optionId) : defaultValue; + } + + public static T GetProperty<T>(this PropertyCollection properties, object key, T defaultValue) + { + T value; + return properties.TryGetProperty(key, out value) ? value : defaultValue; + } + } +} diff --git a/src/GitHub.InlineReviews/packages.config b/src/GitHub.InlineReviews/packages.config new file mode 100644 index 0000000000..44d71eb082 --- /dev/null +++ b/src/GitHub.InlineReviews/packages.config @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Markdig.Signed" version="0.13.0" targetFramework="net461" /> + <package id="Markdig.Wpf.Signed" version="0.2.1" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Imaging" version="14.3.25407" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.111" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net452" /> + <package id="Microsoft.VSSDK.BuildTools" version="14.3.25407" targetFramework="net452" developmentDependency="true" /> + <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> + <package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> + <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" /> + <package id="VSSDK.ComponentModelHost" version="12.0.4" targetFramework="net461" /> + <package id="VSSDK.IDE.12" version="12.0.4" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.InlineReviews/source.extension.vsixmanifest b/src/GitHub.InlineReviews/source.extension.vsixmanifest new file mode 100644 index 0000000000..72e62bba15 --- /dev/null +++ b/src/GitHub.InlineReviews/source.extension.vsixmanifest @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> + <Metadata> + <Identity Id="GitHub.InlineReviews.56b6fdce-e5b1-4ec3-9837-af4545ba933e" Version="2.0" Language="en-US" Publisher="GitHub, Inc" /> + <DisplayName>GitHub Inline Reviews</DisplayName> + <Description xml:space="preserve">Inline reviews for GitHub pull requests</Description> + </Metadata> + <Installation> + <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[14.0,15.0]" /> + </Installation> + <Dependencies> + <Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" /> + <Dependency Id="Microsoft.VisualStudio.MPF.14.0" DisplayName="Visual Studio MPF 14.0" d:Source="Installed" Version="[14.0]" /> + </Dependencies> + <Assets> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" /> + <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" /> + </Assets> +</PackageManifest> diff --git a/src/GitHub.Logging/GitHub.Logging.csproj b/src/GitHub.Logging/GitHub.Logging.csproj new file mode 100644 index 0000000000..952eef5f75 --- /dev/null +++ b/src/GitHub.Logging/GitHub.Logging.csproj @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{8D73575A-A89F-47CC-B153-B47DD06837F0}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub</RootNamespace> + <AssemblyName>GitHub.Logging</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <TargetFrameworkProfile /> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <ItemGroup> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog.Enrichers.Process, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.Enrichers.Process.2.0.1\lib\net45\Serilog.Enrichers.Process.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog.Enrichers.Thread, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.Enrichers.Thread.3.0.0\lib\net45\Serilog.Enrichers.Thread.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog.Sinks.File, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.Sinks.File.3.2.0\lib\net45\Serilog.Sinks.File.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Info\ApplicationInfo.cs" /> + <Compile Include="Logging\ILoggerExtensions.cs" /> + <Compile Include="Logging\Log.cs" /> + <Compile Include="Logging\LogManager.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/src/GitHub.Logging/Info/ApplicationInfo.cs b/src/GitHub.Logging/Info/ApplicationInfo.cs new file mode 100644 index 0000000000..7134aad155 --- /dev/null +++ b/src/GitHub.Logging/Info/ApplicationInfo.cs @@ -0,0 +1,40 @@ +using System; +using System.Diagnostics; + +namespace GitHub.Info +{ + public static class ApplicationInfo + { +#if DEBUG + public const string ApplicationName = "GìtHūbVisualStudio"; + public const string ApplicationProvider = "GitHub"; +#else + public const string ApplicationName = "GitHubVisualStudio"; + public const string ApplicationProvider = "GitHub"; +#endif + public const string ApplicationSafeName = "GitHubVisualStudio"; + public const string ApplicationDescription = "GitHub Extension for Visual Studio"; + + /// <summary> + /// Gets the version information for the host process. + /// </summary> + /// <returns>The version of the host process.</returns> + public static FileVersionInfo GetHostVersionInfo() + { + return Process.GetCurrentProcess().MainModule.FileVersionInfo; + } + + /// <summary> + /// Gets the version of a Visual Studio package. + /// </summary> + /// <param name="package"> + /// The VS Package object. This is untyped here as this assembly does not depend on + /// any VS assemblies. + /// </param> + /// <returns>The version of the package.</returns> + public static Version GetPackageVersion(object package) + { + return package.GetType().Assembly.GetName().Version; + } + } +} diff --git a/src/GitHub.Logging/Logging/ILoggerExtensions.cs b/src/GitHub.Logging/Logging/ILoggerExtensions.cs new file mode 100644 index 0000000000..3d61316ab2 --- /dev/null +++ b/src/GitHub.Logging/Logging/ILoggerExtensions.cs @@ -0,0 +1,29 @@ +using Serilog; + +namespace GitHub.Logging +{ + public static class ILoggerExtensions + { + public static void Assert(this ILogger logger, bool condition, string messageTemplate) + { + if (!condition) + { + messageTemplate = "Assertion Failed: " + messageTemplate; +#pragma warning disable Serilog004 // propertyValues might not be strings + logger.Warning(messageTemplate); +#pragma warning restore Serilog004 + } + } + + public static void Assert(this ILogger logger, bool condition, string messageTemplate, params object[] propertyValues) + { + if (!condition) + { + messageTemplate = "Assertion Failed: " + messageTemplate; +#pragma warning disable Serilog004 // propertyValues might not be strings + logger.Warning(messageTemplate, propertyValues); +#pragma warning restore Serilog004 + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.Logging/Logging/Log.cs b/src/GitHub.Logging/Logging/Log.cs new file mode 100644 index 0000000000..721f528391 --- /dev/null +++ b/src/GitHub.Logging/Logging/Log.cs @@ -0,0 +1,13 @@ +using System; +using Serilog; + +namespace GitHub.Logging +{ + public static class Log + { + private static Lazy<ILogger> Logger { get; } = new Lazy<ILogger>(() => LogManager.ForContext(typeof(Log))); + + public static void Assert(bool condition, string messageTemplate) + => Logger.Value.Assert(condition, messageTemplate); + } +} \ No newline at end of file diff --git a/src/GitHub.Logging/Logging/LogManager.cs b/src/GitHub.Logging/Logging/LogManager.cs new file mode 100644 index 0000000000..f6212e5490 --- /dev/null +++ b/src/GitHub.Logging/Logging/LogManager.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Diagnostics.CodeAnalysis; +using GitHub.Info; +using Serilog; +using Serilog.Core; +using Serilog.Events; + +namespace GitHub.Logging +{ + public static class LogManager + { +#if DEBUG + private static LogEventLevel DefaultLoggingLevel = LogEventLevel.Debug; +#else + private static LogEventLevel DefaultLoggingLevel = LogEventLevel.Information; +#endif + + private static LoggingLevelSwitch LoggingLevelSwitch = new LoggingLevelSwitch(DefaultLoggingLevel); + + static Logger CreateLogger() + { + var logPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + ApplicationInfo.ApplicationName, + "extension.log"); + + const string outputTemplate = + "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{ProcessId:00000}] {Level:u4} [{ThreadId:00}] {ShortSourceContext,-25} {Message:lj}{NewLine}{Exception}"; + + return new LoggerConfiguration() + .Enrich.WithProcessId() + .Enrich.WithThreadId() + .MinimumLevel.ControlledBy(LoggingLevelSwitch) + .WriteTo.File(logPath, + fileSizeLimitBytes: null, + outputTemplate: outputTemplate, + shared: true) + .CreateLogger(); + } + + public static void EnableTraceLogging(bool enable) + { + var logEventLevel = enable ? LogEventLevel.Verbose : DefaultLoggingLevel; + if(LoggingLevelSwitch.MinimumLevel != logEventLevel) + { + ForContext(typeof(LogManager)).Information("Set Logging Level: {LogEventLevel}", logEventLevel); + LoggingLevelSwitch.MinimumLevel = logEventLevel; + } + } + + static Lazy<Logger> Logger { get; } = new Lazy<Logger>(CreateLogger); + + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] + public static ILogger ForContext<T>() => ForContext(typeof(T)); + + public static ILogger ForContext(Type type) => Logger.Value.ForContext(type).ForContext("ShortSourceContext", type.Name); + } +} \ No newline at end of file diff --git a/src/GitHub.Logging/Properties/AssemblyInfo.cs b/src/GitHub.Logging/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..748cab9da4 --- /dev/null +++ b/src/GitHub.Logging/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Reflection; + +[assembly: AssemblyTitle("GitHub.Logging")] +[assembly: AssemblyDescription("")] \ No newline at end of file diff --git a/src/GitHub.Logging/packages.config b/src/GitHub.Logging/packages.config new file mode 100644 index 0000000000..faa0a313a0 --- /dev/null +++ b/src/GitHub.Logging/packages.config @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="Serilog.Enrichers.Process" version="2.0.1" targetFramework="net461" /> + <package id="Serilog.Enrichers.Thread" version="3.0.0" targetFramework="net452" /> + <package id="Serilog.Sinks.File" version="3.2.0" targetFramework="net452" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.Services.Vssdk/Commands/MenuCommandServiceExtensions.cs b/src/GitHub.Services.Vssdk/Commands/MenuCommandServiceExtensions.cs new file mode 100644 index 0000000000..f06dcc7d0f --- /dev/null +++ b/src/GitHub.Services.Vssdk/Commands/MenuCommandServiceExtensions.cs @@ -0,0 +1,100 @@ +using System; +using System.ComponentModel.Design; +using System.Windows.Input; +using GitHub.Commands; +using GitHub.Extensions; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.Services.Vssdk.Commands +{ + /// <summary> + /// Extension methods for <see cref="IMenuCommandService"/>. + /// </summary> + public static class MenuCommandServiceExtensions + { + /// <summary> + /// Adds <see cref="IVsCommand"/>s or <see cref="IVsCommand{TParam}"/>s to a menu. + /// </summary> + /// <param name="service">The menu command service.</param> + /// <param name="commands">The commands to add.</param> + public static void AddCommands( + this IMenuCommandService service, + params IVsCommandBase[] commands) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(commands, nameof(commands)); + + foreach (MenuCommand command in commands) + { + service.AddCommand(command); + } + } + + /// <summary> + /// Binds an <see cref="ICommand"/> to a Visual Studio command. + /// </summary> + /// <param name="service">The menu command service.</param> + /// <param name="id">The ID of the visual studio command.</param> + /// <param name="command">The <see cref="ICommand"/> to bind</param> + /// <param name="hideWhenDisabled"> + /// If true, the visual studio command will be hidden when disabled. + /// </param> + /// <remarks> + /// This method wires up the <paramref name="command"/> to be executed when the Visual Studio + /// command is invoked, and for the <paramref name="command"/>'s + /// <see cref="ICommand.CanExecute(object)"/> state to control the enabled/visible state of + /// the Visual Studio command. + /// </remarks> + /// <returns> + /// The created <see cref="OleMenuCommand"/>. + /// </returns> + public static OleMenuCommand BindCommand( + this IMenuCommandService service, + CommandID id, + ICommand command, + bool hideWhenDisabled = false) + { + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(id, nameof(id)); + Guard.ArgumentNotNull(command, nameof(command)); + + var bound = new BoundCommand(id, command, hideWhenDisabled); + service.AddCommand(bound); + return bound; + } + + class BoundCommand : OleMenuCommand + { + readonly ICommand inner; + readonly bool hideWhenDisabled; + + public BoundCommand(CommandID id, ICommand command, bool hideWhenDisabled) + : base(InvokeHandler, delegate { }, HandleBeforeQueryStatus, id) + { + Guard.ArgumentNotNull(id, nameof(id)); + Guard.ArgumentNotNull(command, nameof(command)); + + inner = command; + this.hideWhenDisabled = hideWhenDisabled; + inner.CanExecuteChanged += (s, e) => HandleBeforeQueryStatus(this, e); + } + + static void InvokeHandler(object sender, EventArgs e) + { + var command = sender as BoundCommand; + command?.inner.Execute((e as OleMenuCmdEventArgs)?.InValue); + } + + static void HandleBeforeQueryStatus(object sender, EventArgs e) + { + var command = sender as BoundCommand; + + if (command != null) + { + command.Enabled = command.inner.CanExecute(null); + command.Visible = command.hideWhenDisabled ? command.Enabled : true; + } + } + } + } +} diff --git a/src/GitHub.Services.Vssdk/Commands/VsCommand.cs b/src/GitHub.Services.Vssdk/Commands/VsCommand.cs new file mode 100644 index 0000000000..0c3e3da031 --- /dev/null +++ b/src/GitHub.Services.Vssdk/Commands/VsCommand.cs @@ -0,0 +1,85 @@ +using System; +using System.Windows.Input; +using GitHub.Commands; +using GitHub.Extensions; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services.Vssdk.Commands +{ + /// <summary> + /// Implements <see cref="ICommand"/> for <see cref="OleMenuCommand"/>s that don't accept a + /// parameter. + /// </summary> + /// <remarks> + /// <para> + /// This class derives from <see cref="OleMenuCommand"/> and implements <see cref="ICommand"/> + /// so that the command can be bound in the UI. + /// </para> + /// <para> + /// To implement a new command, inherit from this class and override the <see cref="Execute"/> + /// method to provide the implementation of the command. + /// </para> + /// </remarks> + public abstract class VsCommand : VsCommandBase, IVsCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="VsCommand"/> class. + /// </summary> + /// <param name="commandSet">The GUID of the group the command belongs to.</param> + /// <param name="commandId">The numeric identifier of the command.</param> + protected VsCommand(Guid commandSet, int commandId) + : base(commandSet, commandId) + { + } + + /// <summary> + /// Overridden by derived classes with the implementation of the command. + /// </summary> + /// <returns>A task that tracks the execution of the command.</returns> + public abstract Task Execute(); + + /// <inheritdoc/> + protected sealed override void ExecuteUntyped(object parameter) + { + Execute().Forget(); + } + } + + /// <summary> + /// Implements <see cref="ICommand"/> for <see cref="OleMenuCommand"/>s that accept a parameter. + /// </summary> + /// <typeparam name="TParam">The type of the parameter accepted by the command.</typeparam> + /// <para> + /// This class derives from <see cref="OleMenuCommand"/> and implements <see cref="ICommand"/> + /// so that the command can be bound in the UI. + /// </para> + /// <para> + /// To implement a new command, inherit from this class and override the <see cref="Execute"/> + /// method to provide the implementation of the command. + /// </para> + public abstract class VsCommand<TParam> : VsCommandBase, IVsCommand<TParam>, ICommand + { + /// <summary> + /// Initializes a new instance of the <see cref="VsCommand"/> class. + /// </summary> + /// <param name="commandSet">The GUID of the group the command belongs to.</param> + /// <param name="commandId">The numeric identifier of the command.</param> + protected VsCommand(Guid commandSet, int commandId) + : base(commandSet, commandId) + { + } + + /// <summary> + /// Overridden by derived classes with the implementation of the command. + /// </summary> + /// /// <param name="parameter">The command parameter.</param> + /// <returns>A task that tracks the execution of the command.</returns> + public abstract Task Execute(TParam parameter); + + /// <inheritdoc/> + protected sealed override void ExecuteUntyped(object parameter) + { + Execute((TParam)parameter).Forget(); + } + } +} diff --git a/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs b/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs new file mode 100644 index 0000000000..cca02d90e8 --- /dev/null +++ b/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs @@ -0,0 +1,76 @@ +using System; +using System.ComponentModel.Design; +using System.Windows.Input; +using GitHub.Commands; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.Services.Vssdk.Commands +{ + /// <summary> + /// Base class for <see cref="VsCommand"/> and <see cref="VsCommand{TParam}"/>. + /// </summary> + public abstract class VsCommandBase : OleMenuCommand, IVsCommandBase + { + EventHandler canExecuteChanged; + + /// <summary> + /// Initializes a new instance of the <see cref="VsCommandBase"/> class. + /// </summary> + /// <param name="commandSet">The GUID of the group the command belongs to.</param> + /// <param name="commandId">The numeric identifier of the command.</param> + protected VsCommandBase(Guid commandSet, int commandId) + : base(ExecHandler, delegate { }, QueryStatusHandler, new CommandID(commandSet, commandId)) + { + } + + /// <inheritdoc/> + event EventHandler ICommand.CanExecuteChanged + { + add { canExecuteChanged += value; } + remove { canExecuteChanged -= value; } + } + + /// <inheritdoc/> + bool ICommand.CanExecute(object parameter) + { + QueryStatus(); + return Enabled && Visible; + } + + /// <inheritdoc/> + void ICommand.Execute(object parameter) + { + ExecuteUntyped(parameter); + } + + /// <summary> + /// When overridden in a derived class, executes the command after casting the passed + /// parameter to the correct type. + /// </summary> + /// <param name="parameter">The parameter</param> + protected abstract void ExecuteUntyped(object parameter); + + protected override void OnCommandChanged(EventArgs e) + { + base.OnCommandChanged(e); + canExecuteChanged?.Invoke(this, e); + } + + protected virtual void QueryStatus() + { + } + + static void ExecHandler(object sender, EventArgs e) + { + var args = (OleMenuCmdEventArgs)e; + var command = sender as VsCommandBase; + command?.ExecuteUntyped(args.InValue); + } + + static void QueryStatusHandler(object sender, EventArgs e) + { + var command = sender as VsCommandBase; + command?.QueryStatus(); + } + } +} diff --git a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj new file mode 100644 index 0000000000..b580a56a0a --- /dev/null +++ b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{2D3D2834-33BE-45CA-B3CC-12F853557D7B}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.Services.Vssdk</RootNamespace> + <AssemblyName>GitHub.Services.Vssdk</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Commands\MenuCommandServiceExtensions.cs" /> + <Compile Include="Commands\VsCommand.cs" /> + <Compile Include="Commands\VsCommandBase.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup /> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project> \ No newline at end of file diff --git a/src/GitHub.Services.Vssdk/Properties/AssemblyInfo.cs b/src/GitHub.Services.Vssdk/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..08f1d22c43 --- /dev/null +++ b/src/GitHub.Services.Vssdk/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Reflection; + +[assembly: AssemblyTitle("GitHub.Services.Vssdk")] +[assembly: AssemblyDescription("Abstractions for the VSSDK")] \ No newline at end of file diff --git a/src/GitHub.Services.Vssdk/packages.config b/src/GitHub.Services.Vssdk/packages.config new file mode 100644 index 0000000000..8ec99b891b --- /dev/null +++ b/src/GitHub.Services.Vssdk/packages.config @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Microsoft.VisualStudio.Imaging" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.111" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.StartPage/GitHub.StartPage.csproj b/src/GitHub.StartPage/GitHub.StartPage.csproj new file mode 100644 index 0000000000..74a2246009 --- /dev/null +++ b/src/GitHub.StartPage/GitHub.StartPage.csproj @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props" Condition="'$(VisualStudioVersion)' == '14.0' And Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props')" /> + <Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props" Condition="'$(VisualStudioVersion)' == '15.0' And Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props')" /> + <PropertyGroup> + <!-- This is added to prevent forced migrations in Visual Studio 2012 and newer --> + <MinimumVisualStudioVersion Condition="'$(VisualStudioVersion)' != ''">$(VisualStudioVersion)</MinimumVisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <UseCodeBase>true</UseCodeBase> + </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <SchemaVersion>2.0</SchemaVersion> + <ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <ProjectGuid>{50E277B8-8580-487A-8F8E-5C3B9FBF0F77}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.StartPage</RootNamespace> + <AssemblyName>GitHub.StartPage</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <GeneratePkgDefFile>true</GeneratePkgDefFile> + <IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer> + <IncludeDebugSymbolsInVSIXContainer>true</IncludeDebugSymbolsInVSIXContainer> + <IncludeDebugSymbolsInLocalVSIXDeployment>true</IncludeDebugSymbolsInLocalVSIXDeployment> + <CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory> + <CopyOutputSymbolsToOutputDirectory>true</CopyOutputSymbolsToOutputDirectory> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <CreateVsixContainer>False</CreateVsixContainer> + <DeployExtension>False</DeployExtension> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>TRACE;DEBUG</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>TRACE;DEBUG;CODE_ANALYSIS</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> + </PropertyGroup> + <ItemGroup> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="StartPagePackage.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + <None Include="source.extension.vsixmanifest"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> + <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> + <Name>GitHub.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Reference Include="Microsoft.TeamFoundation.Controls"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Controls.dll</HintPath> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Git.Controls"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.15.0, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.15.0.15.0.25901-RC\lib\Microsoft.VisualStudio.Shell.15.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Framework, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Framework.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.Shell.Framework.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.15.0.20-pre\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.15.0.11-pre\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Resources.resx" /> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != '' And '$(NCrunch)' != '1'" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets'))" /> + </Target> + <Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets" Condition="'$(VisualStudioVersion)' == '15.0' And Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets')" /> + <Import Project="..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets" Condition="'$(VisualStudioVersion)' == '14.0' And Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets')" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/src/GitHub.StartPage/Properties/AssemblyInfo.cs b/src/GitHub.StartPage/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ba001558de --- /dev/null +++ b/src/GitHub.StartPage/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Reflection; + +[assembly: AssemblyTitle("GitHub.StartPage")] +[assembly: AssemblyDescription("GitHub Start Page for Visual Studio")] diff --git a/src/GitHub.StartPage/Resources.resx b/src/GitHub.StartPage/Resources.resx new file mode 100644 index 0000000000..c10fe53dd0 --- /dev/null +++ b/src/GitHub.StartPage/Resources.resx @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="110" xml:space="preserve"> + <value>GitHub</value> + </data> + <data name="111" xml:space="preserve"> + <value>Powerful collaboration, code review, and code management for open source and private projects.</value> + </data> +</root> \ No newline at end of file diff --git a/src/GitHub.StartPage/StartPagePackage.cs b/src/GitHub.StartPage/StartPagePackage.cs new file mode 100644 index 0000000000..5635052a08 --- /dev/null +++ b/src/GitHub.StartPage/StartPagePackage.cs @@ -0,0 +1,163 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.VisualStudio; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.CodeContainerManagement; +using Microsoft.VisualStudio.Threading; +using Serilog; +using CodeContainer = Microsoft.VisualStudio.Shell.CodeContainerManagement.CodeContainer; +using ICodeContainerProvider = Microsoft.VisualStudio.Shell.CodeContainerManagement.ICodeContainerProvider; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.StartPage +{ + [PackageRegistration(UseManagedResourcesOnly = true)] + [Guid(Guids.StartPagePackageId)] + [ProvideCodeContainerProvider("GitHub Container", Guids.StartPagePackageId, Guids.ImagesId, 1, "#110", "#111", typeof(GitHubContainerProvider))] + public sealed class StartPagePackage : ExtensionPointPackage + { + static IServiceProvider serviceProvider; + internal static IServiceProvider ServiceProvider { get { return serviceProvider; } } + + public StartPagePackage() + { + serviceProvider = this; + } + } + + [Guid(Guids.CodeContainerProviderId)] + public class GitHubContainerProvider : ICodeContainerProvider + { + static readonly ILogger log = LogManager.ForContext<GitHubContainerProvider>(); + + public async Task<CodeContainer> AcquireCodeContainerAsync(IProgress<ServiceProgressData> downloadProgress, CancellationToken cancellationToken) + { + + return await RunAcquisition(downloadProgress, cancellationToken, null); + } + + public async Task<CodeContainer> AcquireCodeContainerAsync(RemoteCodeContainer onlineCodeContainer, IProgress<ServiceProgressData> downloadProgress, CancellationToken cancellationToken) + { + var repository = new RepositoryModel(onlineCodeContainer.Name, UriString.ToUriString(onlineCodeContainer.DisplayUrl)); + return await RunAcquisition(downloadProgress, cancellationToken, repository); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "cancellationToken")] + async Task<CodeContainer> RunAcquisition(IProgress<ServiceProgressData> downloadProgress, CancellationToken cancellationToken, IRepositoryModel repository) + { + CloneDialogResult request = null; + + try + { + var uiProvider = await Task.Run(() => Package.GetGlobalService(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider); + await ShowTeamExplorerPage(uiProvider); + request = await ShowCloneDialog(uiProvider, downloadProgress, repository); + } + catch (Exception e) + { + log.Error(e, "Error showing Start Page clone dialog"); + } + + if (request == null) + return null; + + var path = Path.Combine(request.BasePath, request.Repository.Name); + var uri = request.Repository.CloneUrl.ToRepositoryUrl(); + return new CodeContainer( + localProperties: new CodeContainerLocalProperties(path, CodeContainerType.Folder, + new CodeContainerSourceControlProperties(request.Repository.Name, path, new Guid(Guids.GitSccProviderId))), + remote: new RemoteCodeContainer(request.Repository.Name, + new Guid(Guids.CodeContainerProviderId), + uri, + new Uri(uri.ToString().TrimSuffix(".git")), + DateTimeOffset.UtcNow), + isFavorite: false, + lastAccessed: DateTimeOffset.UtcNow); + } + + async Task ShowTeamExplorerPage(IGitHubServiceProvider gitHubServiceProvider) + { + var te = gitHubServiceProvider?.GetService(typeof(ITeamExplorer)) as ITeamExplorer; + + if (te != null) + { + var page = te.NavigateToPage(new Guid(TeamExplorerPageIds.Connect), null); + + if (page == null) + { + var tcs = new TaskCompletionSource<ITeamExplorerPage>(); + PropertyChangedEventHandler handler = null; + + handler = new PropertyChangedEventHandler((s, e) => + { + if (e.PropertyName == "CurrentPage") + { + tcs.SetResult(te.CurrentPage); + te.PropertyChanged -= handler; + } + }); + + te.PropertyChanged += handler; + + page = await tcs.Task; + } + } + } + + async Task<CloneDialogResult> ShowCloneDialog( + IGitHubServiceProvider gitHubServiceProvider, + IProgress<ServiceProgressData> progress, + IRepositoryModel repository = null) + { + var dialogService = gitHubServiceProvider.GetService<IDialogService>(); + var cloneService = gitHubServiceProvider.GetService<IRepositoryCloneService>(); + var usageTracker = gitHubServiceProvider.GetService<IUsageTracker>(); + CloneDialogResult result = null; + + if (repository == null) + { + result = await dialogService.ShowCloneDialog(null); + } + else + { + var basePath = await dialogService.ShowReCloneDialog(repository); + + if (basePath != null) + { + result = new CloneDialogResult(basePath, repository); + } + } + + if (result != null) + { + try + { + await cloneService.CloneRepository( + result.Repository.CloneUrl, + result.Repository.Name, + result.BasePath, + progress); + + usageTracker.IncrementCounter(x => x.NumberOfStartPageClones).Forget(); + } + catch + { + var teServices = gitHubServiceProvider.TryGetService<ITeamExplorerServices>(); + teServices.ShowError($"Failed to clone the repository '{result.Repository.Name}'"); + result = null; + } + } + + return result; + } + } +} diff --git a/src/GitHub.StartPage/packages.config b/src/GitHub.StartPage/packages.config new file mode 100644 index 0000000000..4f783e3a26 --- /dev/null +++ b/src/GitHub.StartPage/packages.config @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Microsoft.VisualStudio.CoreUtility" version="15.0.25901-RC" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Imaging" version="15.0.25901-RC" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Sdk.BuildTasks.14.0" version="14.0.215" targetFramework="net461" developmentDependency="true" /> + <package id="Microsoft.VisualStudio.Shell.15.0" version="15.0.25901-RC" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Framework" version="15.0.25901-RC" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Threading" version="15.0.20-pre" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Utilities" version="15.0.25901-RC" targetFramework="net452" /> + <package id="Microsoft.VisualStudio.Validation" version="15.0.11-pre" targetFramework="net452" /> + <package id="Microsoft.VSSDK.BuildTools" version="15.0.26201" targetFramework="net461" developmentDependency="true" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.StartPage/source.extension.vsixmanifest b/src/GitHub.StartPage/source.extension.vsixmanifest new file mode 100644 index 0000000000..ab6b44c127 --- /dev/null +++ b/src/GitHub.StartPage/source.extension.vsixmanifest @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> + <Metadata> + <Identity Id="GitHub.StartPage.760cdaee-08cf-4807-8e49-227d5a3fdaf6" Version="0.2.0.0" Language="en-US" Publisher="GitHub, Inc" /> + <DisplayName>GitHub Start Page</DisplayName> + <Description xml:space="preserve">GitHub on your Start Page, helping you clone!</Description> + </Metadata> + <Installation> + <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0]" /> + </Installation> + <Dependencies> + <Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" /> + <Dependency Id="Microsoft.VisualStudio.MPF.15.0" DisplayName="Visual Studio MPF 15.0" d:Source="Installed" Version="[15.0]" /> + </Dependencies> + <Assets> + <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" /> + </Assets> + <Prerequisites> + <Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0.25824.0,16.0)" DisplayName="Visual Studio core editor" /> + </Prerequisites> +</PackageManifest> diff --git a/src/GitHub.TeamFoundation.14/Base/EnsureLoggedInSection.cs b/src/GitHub.TeamFoundation.14/Base/EnsureLoggedInSection.cs new file mode 100644 index 0000000000..4a8322cca2 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Base/EnsureLoggedInSection.cs @@ -0,0 +1,65 @@ +using System; +using System.Globalization; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.UI; + +namespace GitHub.VisualStudio.TeamExplorer.Sync +{ + public class EnsureLoggedInSection : TeamExplorerSectionBase + { + readonly ITeamExplorerServices teServices; + readonly IDialogService dialogService; + + public EnsureLoggedInSection(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, + IConnectionManager cm, ITeamExplorerServices teServices, + IDialogService dialogService) + : base(serviceProvider, apiFactory, holder, cm) + { + IsVisible = false; + this.teServices = teServices; + this.dialogService = dialogService; + } + + public override void Initialize(IServiceProvider serviceProvider) + { + base.Initialize(serviceProvider); + CheckLogin().Forget(); + } + + protected override void RepoChanged(bool changed) + { + base.RepoChanged(changed); + CheckLogin().Forget(); + } + + async Task CheckLogin() + { + // this is not a github repo, or it hasn't been published yet + if (ActiveRepo == null || ActiveRepoUri == null) + return; + + var isgithub = await IsAGitHubRepo(); + if (!isgithub) + return; + + teServices.ClearNotifications(); + var add = HostAddress.Create(ActiveRepoUri); + bool loggedIn = await connectionManager.IsLoggedIn(add); + if (!loggedIn) + { + var msg = string.Format(CultureInfo.CurrentUICulture, Resources.NotLoggedInMessage, add.Title, add.Title); + teServices.ShowMessage( + msg, + new Primitives.RelayCommand(_ => dialogService.ShowLoginDialog()) + ); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItemBase.cs b/src/GitHub.TeamFoundation.14/Base/TeamExplorerGitAwareItemBase.cs similarity index 100% rename from src/GitHub.VisualStudio/Base/TeamExplorerGitAwareItemBase.cs rename to src/GitHub.TeamFoundation.14/Base/TeamExplorerGitAwareItemBase.cs diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerInvitationBase.cs b/src/GitHub.TeamFoundation.14/Base/TeamExplorerInvitationBase.cs similarity index 84% rename from src/GitHub.VisualStudio/Base/TeamExplorerInvitationBase.cs rename to src/GitHub.TeamFoundation.14/Base/TeamExplorerInvitationBase.cs index 9457d043f1..424e29ca1e 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerInvitationBase.cs +++ b/src/GitHub.TeamFoundation.14/Base/TeamExplorerInvitationBase.cs @@ -1,7 +1,8 @@ -using GitHub.VisualStudio.Helpers; +using GitHub.Services; +using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Controls; -using NullGuard; using System; +using GitHub.Extensions; namespace GitHub.VisualStudio.Base { @@ -9,9 +10,14 @@ public class TeamExplorerInvitationBase : TeamExplorerBase, ITeamExplorerService { public static readonly Guid TeamExplorerInvitationSectionGuid = new Guid("8914ac06-d960-4537-8345-cb13c00378d8"); + protected TeamExplorerInvitationBase(IGitHubServiceProvider serviceProvider) : base(serviceProvider) + {} + public virtual void Initialize(IServiceProvider serviceProvider) { - ServiceProvider = serviceProvider; + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + + TEServiceProvider = serviceProvider; } /// <summary> @@ -40,28 +46,22 @@ public bool CanSignUp } string connectLabel; - [AllowNull] public string ConnectLabel { - [return: AllowNull] get { return connectLabel; } set { connectLabel = value; this.RaisePropertyChange(); } } string description; - [AllowNull] public string Description { - [return: AllowNull] get { return description; } set { description = value; this.RaisePropertyChange(); } } object icon; - [AllowNull] public object Icon { - [return: AllowNull] get { return icon; } set { icon = value; this.RaisePropertyChange(); } } @@ -74,28 +74,22 @@ public bool IsVisible } string name; - [AllowNull] public string Name { - [return: AllowNull] get { return name; } set { name = value; this.RaisePropertyChange(); } } string provider; - [AllowNull] public string Provider { - [return: AllowNull] get { return provider; } set { provider = value; this.RaisePropertyChange(); } } string signUpLabel; - [AllowNull] public string SignUpLabel { - [return: AllowNull] get { return signUpLabel; } set { signUpLabel = value; this.RaisePropertyChange(); } } diff --git a/src/GitHub.TeamFoundation.14/Base/TeamExplorerNavigationItemBase.cs b/src/GitHub.TeamFoundation.14/Base/TeamExplorerNavigationItemBase.cs new file mode 100644 index 0000000000..f8b73819db --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Base/TeamExplorerNavigationItemBase.cs @@ -0,0 +1,116 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI; +using GitHub.VisualStudio.Helpers; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio.PlatformUI; +using GitHub.Models; + +namespace GitHub.VisualStudio.Base +{ + public class TeamExplorerNavigationItemBase : TeamExplorerItemBase, ITeamExplorerNavigationItem2 + { + readonly Octicon octicon; + + public TeamExplorerNavigationItemBase(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, Octicon octicon) + : base(serviceProvider, apiFactory, holder) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(apiFactory, nameof(apiFactory)); + Guard.ArgumentNotNull(holder, nameof(holder)); + + this.octicon = octicon; + + IsVisible = false; + IsEnabled = true; + + OnThemeChanged(); + VSColorTheme.ThemeChanged += _ => + { + OnThemeChanged(); + Invalidate(); + }; + + holder.Subscribe(this, UpdateRepo); + } + + public override async void Invalidate() + { + IsVisible = false; + IsVisible = await IsAGitHubRepo(); + } + + void OnThemeChanged() + { + var theme = Colors.DetectTheme(); + var dark = theme == "Dark"; + Icon = SharedResources.GetDrawingForIcon(octicon, dark ? Colors.DarkThemeNavigationItem : Colors.LightThemeNavigationItem, theme); + } + + void UpdateRepo(ILocalRepositoryModel repo) + { + var changed = ActiveRepo != repo; + ActiveRepo = repo; + RepoChanged(changed); + Invalidate(); + } + + protected void OpenInBrowser(Lazy<IVisualStudioBrowser> browser, string endpoint) + { + var uri = ActiveRepoUri; + Debug.Assert(uri != null, "OpenInBrowser: uri should never be null"); +#if !DEBUG + if (uri == null) + return; +#endif + var browseUrl = uri.ToRepositoryUrl().Append(endpoint); + + OpenInBrowser(browser, browseUrl); + } + + void Unsubscribe() + { + holder.Unsubscribe(this); + } + + bool disposed; + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!disposed) + { + Unsubscribe(); + disposed = true; + } + } + base.Dispose(disposing); + } + + int argbColor; + public int ArgbColor + { + get { return argbColor; } + set { argbColor = value; this.RaisePropertyChange(); } + } + + object icon; + public object Icon + { + get { return icon; } + set { icon = value; this.RaisePropertyChange(); } + } + + Image image; + public Image Image + { + get{ return image; } + set { image = value; this.RaisePropertyChange(); } + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Base/TeamExplorerSectionBase.cs b/src/GitHub.TeamFoundation.14/Base/TeamExplorerSectionBase.cs new file mode 100644 index 0000000000..7d53af6001 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Base/TeamExplorerSectionBase.cs @@ -0,0 +1,125 @@ +using System; +using GitHub.VisualStudio.Helpers; +using Microsoft.TeamFoundation.Controls; +using GitHub.Services; +using System.Diagnostics; +using GitHub.Api; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.Extensions; + +namespace GitHub.VisualStudio.Base +{ + public class TeamExplorerSectionBase : TeamExplorerItemBase, ITeamExplorerSection, IServiceProviderAware + { + protected IConnectionManager connectionManager; + + bool isBusy; + public bool IsBusy + { + get { return isBusy; } + set { isBusy = value; this.RaisePropertyChange(); } + } + + bool isExpanded; + public bool IsExpanded + { + get { return isExpanded; } + set { isExpanded = value; this.RaisePropertyChange(); } + } + + object sectionContent; + public object SectionContent + { + get { return sectionContent; } + set { sectionContent = value; this.RaisePropertyChange(); } + } + + string title; + public string Title + { + get { return title; } + set { title = value; this.RaisePropertyChange(); } + } + + public virtual object GetExtensibilityService(Type serviceType) + { + return null; + } + + public TeamExplorerSectionBase(IGitHubServiceProvider serviceProvider, ITeamExplorerServiceHolder holder) + : base(serviceProvider, holder) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(holder, nameof(holder)); + + IsVisible = false; + IsEnabled = true; + IsExpanded = true; + } + + public TeamExplorerSectionBase(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder) + : base(serviceProvider, apiFactory, holder) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(apiFactory, nameof(apiFactory)); + Guard.ArgumentNotNull(holder, nameof(holder)); + + IsVisible = false; + IsEnabled = true; + IsExpanded = true; + } + + public TeamExplorerSectionBase(IGitHubServiceProvider serviceProvider, + ITeamExplorerServiceHolder holder, IConnectionManager cm) : this(serviceProvider, holder) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(holder, nameof(holder)); + Guard.ArgumentNotNull(cm, nameof(cm)); + + connectionManager = cm; + } + + public TeamExplorerSectionBase(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, + IConnectionManager cm) : this(serviceProvider, apiFactory, holder) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(apiFactory, nameof(apiFactory)); + Guard.ArgumentNotNull(holder, nameof(holder)); + Guard.ArgumentNotNull(cm, nameof(cm)); + + connectionManager = cm; + } + + void ITeamExplorerSection.Cancel() + { + } + + void ITeamExplorerSection.Initialize(object sender, SectionInitializeEventArgs e) + { + Guard.ArgumentNotNull(e, nameof(e)); + + Initialize(e.ServiceProvider); + } + + public virtual void Loaded(object sender, SectionLoadedEventArgs e) + { + } + + public virtual void Refresh() + { + } + + public virtual void SaveContext(object sender, SectionSaveContextEventArgs e) + { + } + + protected ITeamExplorerSection GetSection(Guid section) + { + var tep = (ITeamExplorerPage)TEServiceProvider.GetServiceSafe(typeof(ITeamExplorerPage)); + return tep?.GetSection(section); + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs b/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs new file mode 100644 index 0000000000..20879c8fbe --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using Serilog; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using System.Windows; + +namespace GitHub.VisualStudio.Base +{ + [Export(typeof(ITeamExplorerServiceHolder))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class TeamExplorerServiceHolder : ITeamExplorerServiceHolder + { + static readonly ILogger log = LogManager.ForContext<TeamExplorerServiceHolder>(); + + readonly Dictionary<object, Action<ILocalRepositoryModel>> activeRepoHandlers = new Dictionary<object, Action<ILocalRepositoryModel>>(); + ILocalRepositoryModel activeRepo; + bool activeRepoNotified = false; + + IServiceProvider serviceProvider; + readonly IVSGitExt gitService; + + /// <summary> + /// This class relies on IVSGitExt that provides information when VS switches repositories. + /// </summary> + /// <param name="gitService">Used for monitoring the active repository.</param> + [ImportingConstructor] + TeamExplorerServiceHolder(IVSGitExt gitService) : this(gitService, ThreadHelper.JoinableTaskContext) + { + } + + /// <summary> + /// This constructor can be used for unit testing. + /// </summary> + /// <param name="gitService">Used for monitoring the active repository.</param> + /// <param name="joinableTaskContext">Used for switching to the Main thread.</param> + public TeamExplorerServiceHolder(IVSGitExt gitService, JoinableTaskContext joinableTaskContext) + { + JoinableTaskCollection = joinableTaskContext.CreateCollection(); + JoinableTaskCollection.DisplayName = nameof(TeamExplorerServiceHolder); + JoinableTaskFactory = joinableTaskContext.CreateFactory(JoinableTaskCollection); + + // This might be null in Blend or SafeMode + if (gitService != null) + { + this.gitService = gitService; + UpdateActiveRepo(); + gitService.ActiveRepositoriesChanged += UpdateActiveRepo; + } + } + + // set by the sections when they get initialized + public IServiceProvider ServiceProvider + { + get { return serviceProvider; } + set + { + if (serviceProvider == value) + return; + + serviceProvider = value; + if (serviceProvider == null) + return; + } + } + + public ILocalRepositoryModel ActiveRepo + { + get { return activeRepo; } + private set + { + if (activeRepo == value) + return; + if (activeRepo != null) + activeRepo.PropertyChanged -= ActiveRepoPropertyChanged; + activeRepo = value; + if (activeRepo != null) + activeRepo.PropertyChanged += ActiveRepoPropertyChanged; + NotifyActiveRepo(); + } + } + + public void Subscribe(object who, Action<ILocalRepositoryModel> handler) + { + Guard.ArgumentNotNull(who, nameof(who)); + Guard.ArgumentNotNull(handler, nameof(handler)); + + bool notificationsExist; + ILocalRepositoryModel repo; + lock (activeRepoHandlers) + { + repo = ActiveRepo; + notificationsExist = activeRepoNotified; + if (!activeRepoHandlers.ContainsKey(who)) + activeRepoHandlers.Add(who, handler); + else + activeRepoHandlers[who] = handler; + } + + // the repo url might have changed and we don't get notifications + // for that, so this is a good place to refresh it in case that happened + repo?.Refresh(); + + // if the active repo hasn't changed and there's notifications queued up, + // notify the subscriber. If the repo has changed, the set above will trigger + // notifications so we don't have to do it here. + if (repo == ActiveRepo && notificationsExist) + handler(repo); + } + + public void Unsubscribe(object who) + { + Guard.ArgumentNotNull(who, nameof(who)); + + if (activeRepoHandlers.ContainsKey(who)) + activeRepoHandlers.Remove(who); + } + + /// <summary> + /// Clears the current ServiceProvider if it matches the one that is passed in. + /// This is usually called on Dispose, which might happen after another section + /// has changed the ServiceProvider to something else, which is why we require + /// the parameter to match. + /// </summary> + /// <param name="provider">If the current ServiceProvider matches this, clear it</param> + public void ClearServiceProvider(IServiceProvider provider) + { + Guard.ArgumentNotNull(provider, nameof(provider)); + + if (serviceProvider != provider) + return; + + ServiceProvider = null; + } + + void NotifyActiveRepo() + { + lock (activeRepoHandlers) + { + activeRepoNotified = true; + foreach (var handler in activeRepoHandlers.Values) + handler(activeRepo); + } + } + + void UpdateActiveRepo() + { + var repo = gitService.ActiveRepositories.FirstOrDefault(); + + if (!Equals(repo, ActiveRepo)) + { + // Fire property change events on Main thread + JoinableTaskFactory.RunAsync(async () => + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + ActiveRepo = repo; + }).Task.Forget(log); + } + } + + void ActiveRepoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + Guard.ArgumentNotNull(e, nameof(e)); + + if (e.PropertyName == "CloneUrl") + ActiveRepo = sender as ILocalRepositoryModel; + } + + public IGitAwareItem HomeSection + { + get + { + if (ServiceProvider == null) + return null; + var page = PageService; + if (page == null) + return null; + return page.GetSection(new Guid(TeamExplorer.Home.GitHubHomeSection.GitHubHomeSectionId)) as IGitAwareItem; + } + } + + ITeamExplorerPage PageService + { + get { return ServiceProvider.GetServiceSafe<ITeamExplorerPage>(); } + } + + public JoinableTaskCollection JoinableTaskCollection { get; } + JoinableTaskFactory JoinableTaskFactory { get; } + } +} diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs new file mode 100644 index 0000000000..451fd141b9 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs @@ -0,0 +1,576 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using System.Windows.Input; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Settings; +using GitHub.UI; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; +using GitHub.VisualStudio.UI; +using GitHub.VisualStudio.UI.Views; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio; +using ReactiveUI; +using Serilog; + +namespace GitHub.VisualStudio.TeamExplorer.Connect +{ + public class GitHubConnectSection : TeamExplorerSectionBase, IGitHubConnectSection + { + static readonly ILogger log = LogManager.ForContext<GitHubConnectSection>(); + readonly IPackageSettings packageSettings; + readonly IVSServices vsServices; + readonly int sectionIndex; + readonly ILocalRepositories localRepositories; + readonly IUsageTracker usageTracker; + + ITeamExplorerSection invitationSection; + string errorMessage; + bool isCloning; + bool isCreating; + GitHubConnectSectionState settings; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] + SectionStateTracker sectionTracker; + + protected GitHubConnectContent View + { + get { return SectionContent as GitHubConnectContent; } + private set { SectionContent = value; } + } + + public string ErrorMessage + { + get { return errorMessage; } + private set { errorMessage = value; this.RaisePropertyChange(); } + } + + public IConnection SectionConnection { get; set; } + + bool isLoggingIn; + public bool IsLoggingIn + { + get { return isLoggingIn; } + private set { isLoggingIn = value; this.RaisePropertyChange(); } + } + + bool showLogin; + public bool ShowLogin + { + get { return showLogin; } + private set { showLogin = value; this.RaisePropertyChange(); } + } + + bool showLogout; + public bool ShowLogout + { + get { return showLogout; } + private set { showLogout = value; this.RaisePropertyChange(); } + } + + bool showRetry; + public bool ShowRetry + { + get { return showRetry; } + private set { showRetry = value; this.RaisePropertyChange(); } + } + + IReactiveDerivedList<ILocalRepositoryModel> repositories; + public IReactiveDerivedList<ILocalRepositoryModel> Repositories + { + get { return repositories; } + private set { repositories = value; this.RaisePropertyChange(); } + } + + ILocalRepositoryModel selectedRepository; + public ILocalRepositoryModel SelectedRepository + { + get { return selectedRepository; } + set { selectedRepository = value; this.RaisePropertyChange(); } + } + + public ICommand Clone { get; } + + internal ITeamExplorerServiceHolder Holder => holder; + + public GitHubConnectSection(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + ITeamExplorerServiceHolder holder, + IConnectionManager manager, + IPackageSettings packageSettings, + IVSServices vsServices, + ILocalRepositories localRepositories, + IUsageTracker usageTracker, + int index) + : base(serviceProvider, apiFactory, holder, manager) + { + Guard.ArgumentNotNull(apiFactory, nameof(apiFactory)); + Guard.ArgumentNotNull(holder, nameof(holder)); + Guard.ArgumentNotNull(manager, nameof(manager)); + Guard.ArgumentNotNull(packageSettings, nameof(packageSettings)); + Guard.ArgumentNotNull(vsServices, nameof(vsServices)); + Guard.ArgumentNotNull(localRepositories, nameof(localRepositories)); + Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); + + Title = "GitHub"; + IsEnabled = true; + IsVisible = false; + sectionIndex = index; + + this.packageSettings = packageSettings; + this.vsServices = vsServices; + this.localRepositories = localRepositories; + this.usageTracker = usageTracker; + + Clone = CreateAsyncCommandHack(DoClone); + + connectionManager.Connections.CollectionChanged += RefreshConnections; + PropertyChanged += OnPropertyChange; + UpdateConnection(); + } + + async Task DoClone() + { + var dialogService = ServiceProvider.GetService<IDialogService>(); + var result = await dialogService.ShowCloneDialog(SectionConnection); + + if (result != null) + { + try + { + ServiceProvider.GitServiceProvider = TEServiceProvider; + var cloneService = ServiceProvider.GetService<IRepositoryCloneService>(); + await cloneService.CloneRepository( + result.Repository.CloneUrl, + result.Repository.Name, + result.BasePath); + + usageTracker.IncrementCounter(x => x.NumberOfGitHubConnectSectionClones).Forget(); + } + catch (Exception e) + { + var teServices = ServiceProvider.TryGetService<ITeamExplorerServices>(); + teServices.ShowError(e.GetUserFriendlyErrorMessage(ErrorType.ClonedFailed, result.Repository.Name)); + } + } + } + + void RefreshConnections(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (connectionManager.Connections.Count > sectionIndex) + Refresh(connectionManager.Connections[sectionIndex]); + break; + case NotifyCollectionChangedAction.Remove: + Refresh(connectionManager.Connections.Count <= sectionIndex + ? null + : connectionManager.Connections[sectionIndex]); + break; + } + } + + protected void Refresh(IConnection connection) + { + InitializeInvitationSection(); + + ErrorMessage = connection?.ConnectionError?.GetUserFriendlyErrorMessage(ErrorType.LoginFailed); + IsLoggingIn = connection?.IsLoggingIn ?? false; + IsVisible = connection != null || (invitationSection?.IsVisible == false); + + if (connection == null || !connection.IsLoggedIn) + { + if (Repositories != null) + Repositories.CollectionChanged -= UpdateRepositoryList; + Repositories = null; + settings = null; + + if (connection?.ConnectionError != null) + { + ShowLogin = false; + ShowLogout = true; + ShowRetry = !(connection.ConnectionError is Octokit.AuthorizationException); + } + else + { + ShowLogin = true; + ShowLogout = false; + ShowRetry = false; + } + } + else if (connection != SectionConnection || Repositories == null) + { + Repositories?.Dispose(); + Repositories = localRepositories.GetRepositoriesForAddress(connection.HostAddress); + Repositories.CollectionChanged += UpdateRepositoryList; + settings = packageSettings.UIState.GetOrCreateConnectSection(Title); + ShowLogin = false; + ShowLogout = true; + Title = connection.HostAddress.Title; + } + + if (connection != null && TEServiceProvider != null) + { + RefreshRepositories().Forget(); + } + + if (SectionConnection != connection) + { + if (SectionConnection != null) + { + SectionConnection.PropertyChanged -= ConnectionPropertyChanged; + } + + SectionConnection = connection; + + if (SectionConnection != null) + { + SectionConnection.PropertyChanged += ConnectionPropertyChanged; + } + } + } + + public override void Refresh() + { + UpdateConnection(); + base.Refresh(); + } + + public override void Initialize(IServiceProvider serviceProvider) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + + base.Initialize(serviceProvider); + UpdateConnection(); + + // watch for new repos added to the local repo list + var section = GetSection(TeamExplorerConnectionsSectionId); + if (section != null) + sectionTracker = new SectionStateTracker(section, RefreshRepositories); + } + + void InitializeInvitationSection() + { + // We're only interested in the invitation section if sectionIndex == 0. Don't want to show + // two "Log In" options. + if (sectionIndex == 0 && invitationSection == null) + { + invitationSection = GetSection(TeamExplorerInvitationBase.TeamExplorerInvitationSectionGuid); + + if (invitationSection != null) + { + invitationSection.PropertyChanged += InvitationSectionPropertyChanged; + } + } + } + + void UpdateConnection() + { + Refresh(connectionManager.Connections.Count > sectionIndex + ? connectionManager.Connections[sectionIndex] + : SectionConnection); + } + + void OnPropertyChange(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(IsVisible) && IsVisible && View == null) + View = new GitHubConnectContent { DataContext = this }; + else if (e.PropertyName == nameof(IsExpanded) && settings != null) + settings.IsExpanded = IsExpanded; + } + + void InvitationSectionPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ITeamExplorerSection.IsVisible)) + { + Refresh(SectionConnection); + } + } + + private void ConnectionPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(IConnection.IsLoggedIn) || + e.PropertyName == nameof(IConnection.IsLoggingIn)) + { + Refresh(SectionConnection); + } + } + + async void UpdateRepositoryList(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + // if we're cloning or creating, only one repo will be added to the list + // so we can handle just one new entry separately + if (isCloning || isCreating) + { + var newrepo = e.NewItems.Cast<ILocalRepositoryModel>().First(); + + SelectedRepository = newrepo; + if (isCreating) + HandleCreatedRepo(newrepo); + else + HandleClonedRepo(newrepo); + + isCreating = isCloning = false; + + try + { + // TODO: Cache the icon state. + var api = await ApiFactory.Create(newrepo.CloneUrl); + var repo = await api.GetRepository(); + newrepo.SetIcon(repo.Private, repo.Fork); + } + catch (Exception ex) + { + // GetRepository() may throw if the user doesn't have permissions to access the repo + // (because the repo no longer exists, or because the user has logged in on a different + // profile, or their permissions have changed remotely) + log.Error(ex, "Error updating repository list"); + } + } + // looks like it's just a refresh with new stuff on the list, update the icons + else + { + e.NewItems + .Cast<ILocalRepositoryModel>() + .ForEach(async r => + { + if (Equals(Holder.ActiveRepo, r)) + SelectedRepository = r; + + try + { + // TODO: Cache the icon state. + var api = await ApiFactory.Create(r.CloneUrl); + var repo = await api.GetRepository(); + r.SetIcon(repo.Private, repo.Fork); + } + catch (Exception ex) + { + // GetRepository() may throw if the user doesn't have permissions to access the repo + // (because the repo no longer exists, or because the user has logged in on a different + // profile, or their permissions have changed remotely) + log.Error(ex, "Error updating repository list"); + } + }); + } + } + } + + void HandleCreatedRepo(ILocalRepositoryModel newrepo) + { + Guard.ArgumentNotNull(newrepo, nameof(newrepo)); + + var msg = string.Format(CultureInfo.CurrentCulture, Constants.Notification_RepoCreated, newrepo.Name, newrepo.CloneUrl); + msg += " " + string.Format(CultureInfo.CurrentCulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); + ShowNotification(newrepo, msg); + } + + void HandleClonedRepo(ILocalRepositoryModel newrepo) + { + Guard.ArgumentNotNull(newrepo, nameof(newrepo)); + + var msg = string.Format(CultureInfo.CurrentCulture, Constants.Notification_RepoCloned, newrepo.Name, newrepo.CloneUrl); + if (newrepo.HasCommits() && newrepo.MightContainSolution()) + msg += " " + string.Format(CultureInfo.CurrentCulture, Constants.Notification_OpenProject, newrepo.LocalPath); + else + msg += " " + string.Format(CultureInfo.CurrentCulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); + ShowNotification(newrepo, msg); + } + + void ShowNotification(ILocalRepositoryModel newrepo, string msg) + { + Guard.ArgumentNotNull(newrepo, nameof(newrepo)); + + var teServices = ServiceProvider.TryGetService<ITeamExplorerServices>(); + + teServices.ClearNotifications(); + teServices.ShowMessage( + msg, + new RelayCommand(o => + { + var str = o.ToString(); + /* the prefix is the action to perform: + * u: launch browser with url + * c: launch create new project dialog + * o: launch open existing project dialog + */ + var prefix = str.Substring(0, 2); + if (prefix == "u:") + OpenInBrowser(ServiceProvider.TryGetService<IVisualStudioBrowser>(), new Uri(str.Substring(2))); + else if (prefix == "o:") + { + if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(str.Substring(2), 1))) + ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + } + else if (prefix == "c:") + { + var vsGitServices = ServiceProvider.TryGetService<IVSGitServices>(); + vsGitServices.SetDefaultProjectPath(newrepo.LocalPath); + if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().CreateNewProjectViaDlg(null, null, 0))) + ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + } + }) + ); + log.Debug("Notification"); + } + + async Task RefreshRepositories() + { + // TODO: This is wasteful as we can be calling it multiple times for a single changed + // signal, once from each section. Needs refactoring. + await localRepositories.Refresh(); + RaisePropertyChanged("Repositories"); // trigger a re-check of the visibility of the listview based on item count + } + + public void DoCreate() + { + ServiceProvider.GitServiceProvider = TEServiceProvider; + var dialogService = ServiceProvider.GetService<IDialogService>(); + dialogService.ShowCreateRepositoryDialog(SectionConnection); + } + + public void SignOut() + { + connectionManager.LogOut(SectionConnection.HostAddress); + } + + public void Login() + { + var dialogService = ServiceProvider.GetService<IDialogService>(); + dialogService.ShowLoginDialog(); + } + + public void Retry() + { + connectionManager.Retry(SectionConnection); + } + + public bool OpenRepository() + { + var old = Repositories.FirstOrDefault(x => x.Equals(Holder.ActiveRepo)); + if (!Equals(SelectedRepository, old)) + { + var opened = vsServices.TryOpenRepository(SelectedRepository.LocalPath); + if (!opened) + { + // TryOpenRepository might fail because dir no longer exists. Let user find solution themselves. + opened = ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(SelectedRepository.LocalPath, 1)); + if (!opened) + { + return false; + } + } + } + + // Navigate away when we're on the correct source control contexts. + ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + return true; + } + + bool disposed; + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!disposed) + { + connectionManager.Connections.CollectionChanged -= RefreshConnections; + if (Repositories != null) + Repositories.CollectionChanged -= UpdateRepositoryList; + disposed = true; + packageSettings.Save(); + } + } + base.Dispose(disposing); + } + + /// <summary> + /// Creates a ReactiveCommand that works like a command created via + /// <see cref="ReactiveCommand.CreateAsyncTask"/> but that does not hang when the async + /// task shows a modal dialog. + /// </summary> + /// <param name="executeAsync">Method that creates the task to run.</param> + /// <returns>A reactive command.</returns> + /// <remarks> + /// The <see cref="Clone"/> command needs to be disabled while a clone operation is in + /// progress but also needs to display a modal dialog. For some reason using + /// <see cref="ReactiveCommand.CreateAsyncTask"/> causes a weird UI hang in this situation + /// where the UI runs but WhenAny no longer responds to property changed notifications. + /// </remarks> + static ReactiveCommand<object> CreateAsyncCommandHack(Func<Task> executeAsync) + { + Guard.ArgumentNotNull(executeAsync, nameof(executeAsync)); + + var enabled = new BehaviorSubject<bool>(true); + var command = ReactiveCommand.Create(enabled); + command.Subscribe(async _ => + { + enabled.OnNext(false); + try { await executeAsync(); } + finally { enabled.OnNext(true); } + }); + return command; + } + + class SectionStateTracker + { + enum SectionState + { + Idle, + Busy, + Refreshing + } + + readonly Stateless.StateMachine<SectionState, string> machine; + readonly ITeamExplorerSection section; + + public SectionStateTracker(ITeamExplorerSection section, Func<Task> onRefreshed) + { + this.section = section; + machine = new Stateless.StateMachine<SectionState, string>(SectionState.Idle); + machine.Configure(SectionState.Idle) + .PermitIf("IsBusy", SectionState.Busy, () => this.section.IsBusy) + .IgnoreIf("IsBusy", () => !this.section.IsBusy); + machine.Configure(SectionState.Busy) + .Permit("Title", SectionState.Refreshing) + .PermitIf("IsBusy", SectionState.Idle, () => !this.section.IsBusy) + .IgnoreIf("IsBusy", () => this.section.IsBusy); + machine.Configure(SectionState.Refreshing) + .Ignore("Title") + .PermitIf("IsBusy", SectionState.Idle, () => !this.section.IsBusy) + .IgnoreIf("IsBusy", () => this.section.IsBusy) + .OnExit(() => onRefreshed()); + + section.PropertyChanged += TrackState; + } +#if DEBUG + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] +#endif + void TrackState(object sender, PropertyChangedEventArgs e) + { + if (machine.PermittedTriggers.Contains(e.PropertyName)) + { + log.Debug("{PropertyName} title:{Title} busy:{IsBusy}", + e.PropertyName, + ((ITeamExplorerSection)sender).Title, + ((ITeamExplorerSection)sender).IsBusy); + machine.Fire(e.PropertyName); + } + } + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs new file mode 100644 index 0000000000..3822195cc3 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs @@ -0,0 +1,28 @@ +using GitHub.Api; +using GitHub.Services; +using GitHub.Settings; +using Microsoft.TeamFoundation.Controls; +using System.ComponentModel.Composition; + +namespace GitHub.VisualStudio.TeamExplorer.Connect +{ + [TeamExplorerSection(GitHubConnectSection0Id, TeamExplorerPageIds.Connect, 10)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GitHubConnectSection0 : GitHubConnectSection + { + public const string GitHubConnectSection0Id = "519B47D3-F2A9-4E19-8491-8C9FA25ABE90"; + + [ImportingConstructor] + public GitHubConnectSection0(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + ITeamExplorerServiceHolder holder, + IConnectionManager manager, + IPackageSettings settings, + IVSServices vsServices, + ILocalRepositories localRepositories, + IUsageTracker usageTracker) + : base(serviceProvider, apiFactory, holder, manager, settings, vsServices, localRepositories, usageTracker, 0) + { + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs new file mode 100644 index 0000000000..4534940642 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs @@ -0,0 +1,28 @@ +using GitHub.Api; +using GitHub.Services; +using GitHub.Settings; +using Microsoft.TeamFoundation.Controls; +using System.ComponentModel.Composition; + +namespace GitHub.VisualStudio.TeamExplorer.Connect +{ + [TeamExplorerSection(GitHubConnectSection1Id, TeamExplorerPageIds.Connect, 10)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GitHubConnectSection1 : GitHubConnectSection + { + public const string GitHubConnectSection1Id = "519B47D3-F2A9-4E19-8491-8C9FA25ABE91"; + + [ImportingConstructor] + public GitHubConnectSection1(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + ITeamExplorerServiceHolder holder, + IConnectionManager manager, + IPackageSettings settings, + IVSServices vsServices, + ILocalRepositories localRepositories, + IUsageTracker usageTracker) + : base(serviceProvider, apiFactory, holder, manager, settings, vsServices, localRepositories, usageTracker, 1) + { + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubInvitationSection.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubInvitationSection.cs new file mode 100644 index 0000000000..de7d12d43b --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubInvitationSection.cs @@ -0,0 +1,72 @@ +using GitHub.Info; +using GitHub.Models; +using GitHub.Services; +using GitHub.UI; +using GitHub.VisualStudio.Base; +using GitHub.Extensions; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio.PlatformUI; +using System; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Media; +using GitHub.VisualStudio.UI; +using System.Linq; + +namespace GitHub.VisualStudio.TeamExplorer.Connect +{ + [TeamExplorerServiceInvitation(GitHubInvitationSectionId, GitHubInvitationSectionPriority)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GitHubInvitationSection : TeamExplorerInvitationBase + { + public const string GitHubInvitationSectionId = "C2443FCC-6D62-4D31-B08A-C4DE70109C7F"; + public const int GitHubInvitationSectionPriority = 100; + readonly IDialogService dialogService; + readonly Lazy<IVisualStudioBrowser> lazyBrowser; + + [ImportingConstructor] + public GitHubInvitationSection( + IGitHubServiceProvider serviceProvider, + IDialogService dialogService, + IConnectionManager cm, + Lazy<IVisualStudioBrowser> browser) + : base(serviceProvider) + { + this.dialogService = dialogService; + lazyBrowser = browser; + CanConnect = true; + CanSignUp = true; + ConnectLabel = Resources.GitHubInvitationSectionConnectLabel; + SignUpLabel = Resources.SignUpLink; + Name = "GitHub"; + Provider = "GitHub, Inc."; + Description = Resources.BlurbText; + OnThemeChanged(); + VSColorTheme.ThemeChanged += _ => + { + OnThemeChanged(); + }; + + IsVisible = cm.Connections.Count == 0; + + cm.Connections.CollectionChanged += (s, e) => IsVisible = cm.Connections.Count == 0; + } + + public override void Connect() + { + dialogService.ShowLoginDialog(); + } + + public override void SignUp() + { + OpenInBrowser(lazyBrowser, GitHubUrls.Plans); + } + + void OnThemeChanged() + { + var theme = Helpers.Colors.DetectTheme(); + var dark = theme == "Dark"; + Icon = SharedResources.GetDrawingForIcon(Octicon.mark_github, dark ? Colors.White : Helpers.Colors.LightThemeNavigationItem, theme); + } + } +} diff --git a/src/GitHub.TeamFoundation.14/GitHub.TeamFoundation.14.csproj b/src/GitHub.TeamFoundation.14/GitHub.TeamFoundation.14.csproj new file mode 100644 index 0000000000..5a493885fe --- /dev/null +++ b/src/GitHub.TeamFoundation.14/GitHub.TeamFoundation.14.csproj @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <MinimumVisualStudioVersion>$(MSBuildToolsVersion)</MinimumVisualStudioVersion> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">$(MSBuildToolsVersion)</VisualStudioVersion> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{161DBF01-1DBF-4B00-8551-C5C00F26720D}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.TeamFoundation</RootNamespace> + <AssemblyName>GitHub.TeamFoundation.14</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>DEBUG;TRACE;TEAMEXPLORER14</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE;TEAMEXPLORER14</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE;TEAMEXPLORER14</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> + </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Common"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Common.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Client"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Client.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Controls"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Controls.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Git.Controls"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Git.Provider"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + <Aliases>global</Aliases> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Stateless, Version=2.5.56.0, Culture=neutral, PublicKeyToken=93038f0927583c9a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Stateless.2.5.56.0\lib\portable-net40+sl50+win+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Stateless.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Core" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Services\VSGitExt.cs" /> + <Compile Include="Home\ForkNavigationItem.cs" /> + <Compile Include="RegistryHelper.cs" /> + <Compile Include="Services\VSGitServices.cs" /> + <Compile Include="Services\LocalRepositoryModelFactory.cs" /> + <Compile Include="Services\VSUIContextFactory.cs" /> + <Compile Include="Settings.cs" /> + <Compile Include="Base\EnsureLoggedInSection.cs" /> + <Compile Include="Base\TeamExplorerInvitationBase.cs" /> + <Compile Include="Base\TeamExplorerNavigationItemBase.cs" /> + <Compile Include="Base\TeamExplorerSectionBase.cs" /> + <Compile Include="Base\TeamExplorerServiceHolder.cs" /> + <Compile Include="Connect\GitHubConnectSection.cs" /> + <Compile Include="Connect\GitHubConnectSection0.cs" /> + <Compile Include="Connect\GitHubConnectSection1.cs" /> + <Compile Include="Connect\GitHubInvitationSection.cs" /> + <Compile Include="Home\GitHubHomeSection.cs" /> + <Compile Include="Home\GraphsNavigationItem.cs" /> + <Compile Include="Home\IssuesNavigationItem.cs" /> + <Compile Include="Home\PullRequestsNavigationItem.cs" /> + <Compile Include="Home\PulseNavigationItem.cs" /> + <Compile Include="Home\WikiNavigationItem.cs" /> + <Compile Include="Sync\EnsureLoggedInSectionSync.cs" /> + <Compile Include="Sync\GitHubPublishSection.cs" /> + <Compile Include="Services\TeamExplorerServices.cs" /> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj"> + <Project>{d1dfbb0c-b570-4302-8f1e-2e3a19c41961}</Project> + <Name>GitHub.VisualStudio.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> + </Target> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/src/GitHub.TeamFoundation.14/Home/ForkNavigationItem.cs b/src/GitHub.TeamFoundation.14/Home/ForkNavigationItem.cs new file mode 100644 index 0000000000..12e8424b40 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Home/ForkNavigationItem.cs @@ -0,0 +1,117 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; +using Microsoft.TeamFoundation.Controls; +using GitHub.UI; +using GitHub.VisualStudio.UI; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Extensions; +using GitHub.Logging; +using Serilog; +using System.Collections.Specialized; +using GitHub.Settings; + +namespace GitHub.VisualStudio.TeamExplorer.Home +{ + [TeamExplorerNavigationItem(ForkNavigationItemId, NavigationItemPriority.Fork)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ForkNavigationItem : TeamExplorerNavigationItemBase + { + static readonly ILogger log = LogManager.ForContext<ForkNavigationItem>(); + + public const string ForkNavigationItemId = "5245767A-B657-4F8E-BFEE-F04159F1DDA6"; + + readonly IDialogService dialogService; + readonly IPackageSettings packageSettings; + readonly IUsageTracker usageTracker; + + IConnectionManager connectionManager; + + [ImportingConstructor] + public ForkNavigationItem(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + ITeamExplorerServiceHolder holder, + IDialogService dialogService, + IPackageSettings packageSettings, + IUsageTracker usageTracker) + : base(serviceProvider, apiFactory, holder, Octicon.repo_forked) + { + this.dialogService = dialogService; + this.packageSettings = packageSettings; + this.usageTracker = usageTracker; + + Text = Resources.ForkNavigationItemText; + ArgbColor = Colors.PurpleNavigationItem.ToInt32(); + ConnectionManager.Connections.CollectionChanged += ConnectionsChanged; + + packageSettings.PropertyChanged += (sender, args) => + { + if (args.PropertyName == nameof(packageSettings.ForkButton)) + { + IsVisible = packageSettings.ForkButton; + } + }; + } + + IConnectionManager ConnectionManager + { + get + { + // We can't receive IConnectionManager in the constructor because Invalidate() + // is called from the base constructor and so we don't have chance to save it + // to a field. + if (connectionManager == null) + { + connectionManager = ServiceProvider.GetService<IConnectionManager>(); + } + + return connectionManager; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + ConnectionManager.Connections.CollectionChanged -= ConnectionsChanged; + } + + public override async void Execute() + { + var connection = await connectionManager.GetConnection(ActiveRepo); + + if (connection?.IsLoggedIn == true) + { + usageTracker.IncrementCounter(model => model.NumberOfShowRepoForkDialogClicks).Forget(); + await dialogService.ShowForkDialog(ActiveRepo, connection); + } + } + + public override async void Invalidate() + { + try + { + IsVisible = false; + + if ((packageSettings?.ForkButton ?? false) && await IsAGitHubDotComRepo()) + { + var connection = await ConnectionManager.GetConnection(ActiveRepo); + IsVisible = connection?.IsLoggedIn ?? false; + } + } + catch (Exception ex) + { + log.Error(ex, "Error updating ForkNavigationItem visibility"); + } + } + + private void ConnectionsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Invalidate(); + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Home/GitHubHomeSection.cs b/src/GitHub.TeamFoundation.14/Home/GitHubHomeSection.cs new file mode 100644 index 0000000000..c93e77091d --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Home/GitHubHomeSection.cs @@ -0,0 +1,188 @@ +using System; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Windows.Input; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Info; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Settings; +using GitHub.UI; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; +using GitHub.VisualStudio.UI; +using GitHub.VisualStudio.UI.Views; +using Microsoft.TeamFoundation.Controls; + +namespace GitHub.VisualStudio.TeamExplorer.Home +{ + [TeamExplorerSection(GitHubHomeSectionId, TeamExplorerPageIds.Home, 10)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GitHubHomeSection : TeamExplorerSectionBase, IGitHubHomeSection + { + public const string GitHubHomeSectionId = "72008232-2104-4FA0-A189-61B0C6F91198"; + const string TrainingUrl = "https://services.github.com/on-demand/windows/visual-studio"; + readonly static Guid welcomeMessageGuid = new Guid(Guids.TeamExplorerWelcomeMessage); + + readonly IVisualStudioBrowser visualStudioBrowser; + readonly ITeamExplorerServices teamExplorerServices; + readonly IPackageSettings settings; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public GitHubHomeSection(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + ITeamExplorerServiceHolder holder, + IVisualStudioBrowser visualStudioBrowser, + ITeamExplorerServices teamExplorerServices, + IPackageSettings settings, + IUsageTracker usageTracker) + : base(serviceProvider, apiFactory, holder) + { + Title = "GitHub"; + View = new GitHubHomeContent(); + View.DataContext = this; + this.visualStudioBrowser = visualStudioBrowser; + this.teamExplorerServices = teamExplorerServices; + this.settings = settings; + this.usageTracker = usageTracker; + + var openOnGitHub = new RelayCommand(_ => DoOpenOnGitHub()); + OpenOnGitHub = openOnGitHub; + } + + bool IsGitToolsMessageVisible() + { + return teamExplorerServices.IsNotificationVisible(new Guid(Guids.TeamExplorerInstall3rdPartyGitTools)); + } + + protected async override void RepoChanged(bool changed) + { + IsLoggedIn = true; + IsVisible = false; + + base.RepoChanged(changed); + + IsVisible = await IsAGitHubRepo(); + + if (IsVisible) + { + RepoName = ActiveRepoName; + RepoUrl = ActiveRepoUri.ToString(); + Icon = GetIcon(false, true, false); + + // We want to display a welcome message but only if Team Explorer isn't + // already displaying the "Install 3rd Party Tools" message and the current repo is hosted on GitHub. + if (!settings.HideTeamExplorerWelcomeMessage && !IsGitToolsMessageVisible()) + { + ShowWelcomeMessage(); + } + + Debug.Assert(SimpleApiClient != null, + "If we're in this block, simpleApiClient cannot be null. It was created by UpdateStatus"); + var repo = await SimpleApiClient.GetRepository(); + Icon = GetIcon(repo.Private, true, repo.Fork); + IsLoggedIn = await IsUserAuthenticated(); + } + else + { + teamExplorerServices.HideNotification(welcomeMessageGuid); + } + } + + public override async void Refresh() + { + IsVisible = await IsAGitHubRepo(); + if (IsVisible) + { + IsLoggedIn = await IsUserAuthenticated(); + } + + base.Refresh(); + } + + static Octicon GetIcon(bool isPrivate, bool isHosted, bool isFork) + { + return !isHosted ? Octicon.device_desktop + : isPrivate ? Octicon.@lock + : isFork ? Octicon.repo_forked : Octicon.repo; + } + + public void Login() + { + var dialogService = ServiceProvider.GetService<IDialogService>(); + dialogService.ShowLoginDialog(); + } + + void DoOpenOnGitHub() + { + visualStudioBrowser?.OpenUrl(ActiveRepo.CloneUrl.ToRepositoryUrl()); + } + + void ShowWelcomeMessage() + { + teamExplorerServices.ShowMessage( + Resources.TeamExplorerWelcomeMessage, + new RelayCommand(o => + { + var str = o.ToString(); + + switch (str) + { + case "show-training": + visualStudioBrowser.OpenUrl(new Uri(TrainingUrl)); + usageTracker.IncrementCounter(x => x.NumberOfWelcomeTrainingClicks).Forget(); + break; + case "show-docs": + visualStudioBrowser.OpenUrl(new Uri(GitHubUrls.Documentation)); + usageTracker.IncrementCounter(x => x.NumberOfWelcomeDocsClicks).Forget(); + break; + case "dont-show-again": + teamExplorerServices.HideNotification(welcomeMessageGuid); + settings.HideTeamExplorerWelcomeMessage = true; + settings.Save(); + break; + } + }), + false, + welcomeMessageGuid); + } + + protected GitHubHomeContent View + { + get { return SectionContent as GitHubHomeContent; } + set { SectionContent = value; } + } + + string repoName = String.Empty; + public string RepoName + { + get { return repoName; } + set { repoName = value; this.RaisePropertyChange(); } + } + + string repoUrl = String.Empty; + public string RepoUrl + { + get { return repoUrl; } + set { repoUrl = value; this.RaisePropertyChange(); } + } + + Octicon icon; + public Octicon Icon + { + get { return icon; } + set { icon = value; this.RaisePropertyChange(); } + } + + bool isLoggedIn; + public bool IsLoggedIn + { + get { return isLoggedIn; } + set { isLoggedIn = value; this.RaisePropertyChange(); } + } + + public ICommand OpenOnGitHub { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/TeamExplorer/Home/GraphsNavigationItem.cs b/src/GitHub.TeamFoundation.14/Home/GraphsNavigationItem.cs similarity index 75% rename from src/GitHub.VisualStudio/TeamExplorer/Home/GraphsNavigationItem.cs rename to src/GitHub.TeamFoundation.14/Home/GraphsNavigationItem.cs index 91076a56d1..e59fdc9f43 100644 --- a/src/GitHub.VisualStudio/TeamExplorer/Home/GraphsNavigationItem.cs +++ b/src/GitHub.TeamFoundation.14/Home/GraphsNavigationItem.cs @@ -6,6 +6,7 @@ using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Controls; using GitHub.UI; +using GitHub.VisualStudio.UI; namespace GitHub.VisualStudio.TeamExplorer.Home { @@ -18,9 +19,11 @@ public class GraphsNavigationItem : TeamExplorerNavigationItemBase readonly Lazy<IVisualStudioBrowser> browser; [ImportingConstructor] - public GraphsNavigationItem(ISimpleApiClientFactory apiFactory, Lazy<IVisualStudioBrowser> browser, - ITeamExplorerServiceHolder holder) - : base(apiFactory, holder, Octicon.graph) + public GraphsNavigationItem(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + Lazy<IVisualStudioBrowser> browser, + ITeamExplorerServiceHolder holder) + : base(serviceProvider, apiFactory, holder, Octicon.graph) { this.browser = browser; Text = Resources.GraphsNavigationItemText; diff --git a/src/GitHub.VisualStudio/TeamExplorer/Home/IssuesNavigationItem.cs b/src/GitHub.TeamFoundation.14/Home/IssuesNavigationItem.cs similarity index 80% rename from src/GitHub.VisualStudio/TeamExplorer/Home/IssuesNavigationItem.cs rename to src/GitHub.TeamFoundation.14/Home/IssuesNavigationItem.cs index 8f425b1c00..0ca6e99b98 100644 --- a/src/GitHub.VisualStudio/TeamExplorer/Home/IssuesNavigationItem.cs +++ b/src/GitHub.TeamFoundation.14/Home/IssuesNavigationItem.cs @@ -6,6 +6,7 @@ using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Controls; using GitHub.UI; +using GitHub.VisualStudio.UI; namespace GitHub.VisualStudio.TeamExplorer.Home { @@ -18,9 +19,11 @@ public class IssuesNavigationItem : TeamExplorerNavigationItemBase readonly Lazy<IVisualStudioBrowser> browser; [ImportingConstructor] - public IssuesNavigationItem(ISimpleApiClientFactory apiFactory, Lazy<IVisualStudioBrowser> browser, - ITeamExplorerServiceHolder holder) - : base(apiFactory, holder, Octicon.issue_opened) + public IssuesNavigationItem(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + Lazy<IVisualStudioBrowser> browser, + ITeamExplorerServiceHolder holder) + : base(serviceProvider, apiFactory, holder, Octicon.issue_opened) { this.browser = browser; Text = Resources.IssuesNavigationItemText; diff --git a/src/GitHub.TeamFoundation.14/Home/PullRequestsNavigationItem.cs b/src/GitHub.TeamFoundation.14/Home/PullRequestsNavigationItem.cs new file mode 100644 index 0000000000..c5a8da413f --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Home/PullRequestsNavigationItem.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Commands; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; +using GitHub.VisualStudio.UI; +using Microsoft.TeamFoundation.Controls; + +namespace GitHub.VisualStudio.TeamExplorer.Home +{ + [TeamExplorerNavigationItem(PullRequestsNavigationItemId, NavigationItemPriority.PullRequests)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestsNavigationItem : TeamExplorerNavigationItemBase + { + public const string PullRequestsNavigationItemId = "5245767A-B657-4F8E-BFEE-F04159F1DDA3"; + + readonly IOpenPullRequestsCommand openPullRequests; + readonly IUsageTracker usageTracker; + + [ImportingConstructor] + public PullRequestsNavigationItem(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + ITeamExplorerServiceHolder holder, + IOpenPullRequestsCommand openPullRequests, + IUsageTracker usageTracker) + : base(serviceProvider, apiFactory, holder, Octicon.git_pull_request) + { + this.openPullRequests = openPullRequests; + this.usageTracker = usageTracker; + Text = Resources.PullRequestsNavigationItemText; + ArgbColor = Colors.RedNavigationItem.ToInt32(); + } + + public override void Execute() + { + openPullRequests.Execute(); + usageTracker.IncrementCounter(x => x.NumberOfTeamExplorerHomeOpenPullRequestList).Forget(); + } + } +} diff --git a/src/GitHub.VisualStudio/TeamExplorer/Home/PulseNavigationItem.cs b/src/GitHub.TeamFoundation.14/Home/PulseNavigationItem.cs similarity index 75% rename from src/GitHub.VisualStudio/TeamExplorer/Home/PulseNavigationItem.cs rename to src/GitHub.TeamFoundation.14/Home/PulseNavigationItem.cs index 8d308278db..45636ba925 100644 --- a/src/GitHub.VisualStudio/TeamExplorer/Home/PulseNavigationItem.cs +++ b/src/GitHub.TeamFoundation.14/Home/PulseNavigationItem.cs @@ -6,6 +6,7 @@ using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Controls; using GitHub.UI; +using GitHub.VisualStudio.UI; namespace GitHub.VisualStudio.TeamExplorer.Home { @@ -18,9 +19,11 @@ public class PulseNavigationItem : TeamExplorerNavigationItemBase readonly Lazy<IVisualStudioBrowser> browser; [ImportingConstructor] - public PulseNavigationItem(ISimpleApiClientFactory apiFactory, Lazy<IVisualStudioBrowser> browser, - ITeamExplorerServiceHolder holder) - : base(apiFactory, holder, Octicon.pulse) + public PulseNavigationItem(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + Lazy<IVisualStudioBrowser> browser, + ITeamExplorerServiceHolder holder) + : base(serviceProvider, apiFactory, holder, Octicon.pulse) { this.browser = browser; Text = Resources.PulseNavigationItemText; diff --git a/src/GitHub.VisualStudio/TeamExplorer/Home/WikiNavigationItem.cs b/src/GitHub.TeamFoundation.14/Home/WikiNavigationItem.cs similarity index 80% rename from src/GitHub.VisualStudio/TeamExplorer/Home/WikiNavigationItem.cs rename to src/GitHub.TeamFoundation.14/Home/WikiNavigationItem.cs index 5b18490965..ddac4fa924 100644 --- a/src/GitHub.VisualStudio/TeamExplorer/Home/WikiNavigationItem.cs +++ b/src/GitHub.TeamFoundation.14/Home/WikiNavigationItem.cs @@ -6,6 +6,7 @@ using GitHub.VisualStudio.Helpers; using Microsoft.TeamFoundation.Controls; using GitHub.UI; +using GitHub.VisualStudio.UI; namespace GitHub.VisualStudio.TeamExplorer.Home { @@ -18,9 +19,11 @@ public class WikiNavigationItem : TeamExplorerNavigationItemBase readonly Lazy<IVisualStudioBrowser> browser; [ImportingConstructor] - public WikiNavigationItem(ISimpleApiClientFactory apiFactory, Lazy<IVisualStudioBrowser> browser, - ITeamExplorerServiceHolder holder) - : base(apiFactory, holder, Octicon.book) + public WikiNavigationItem(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, + Lazy<IVisualStudioBrowser> browser, + ITeamExplorerServiceHolder holder) + : base(serviceProvider, apiFactory, holder, Octicon.book) { this.browser = browser; Text = Resources.WikiNavigationItemText; diff --git a/src/GitHub.TeamFoundation.14/Properties/AssemblyInfo.cs b/src/GitHub.TeamFoundation.14/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6dc9797bbf --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("GitHub.TeamFoundation.14")] +[assembly: AssemblyDescription("GitHub TeamFoundation")] +[assembly: Guid("b389adaf-62cc-486e-85b4-2d8b078df763")] diff --git a/src/GitHub.TeamFoundation.14/RegistryHelper.cs b/src/GitHub.TeamFoundation.14/RegistryHelper.cs new file mode 100644 index 0000000000..e8e2906042 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/RegistryHelper.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using Microsoft.Win32; +using Serilog; + +namespace GitHub.TeamFoundation +{ + internal class RegistryHelper + { + static readonly ILogger log = LogManager.ForContext<RegistryHelper>(); + const string TEGitKey = @"Software\Microsoft\VisualStudio\14.0\TeamFoundation\GitSourceControl"; + static RegistryKey OpenGitKey(string path) + { + return Microsoft.Win32.Registry.CurrentUser.OpenSubKey(TEGitKey + "\\" + path, true); + } + + internal static IEnumerable<ILocalRepositoryModel> PokeTheRegistryForRepositoryList() + { + var key = OpenGitKey("Repositories"); + + if (key != null) + { + using (key) + { + return key.GetSubKeyNames().Select(x => + { + var subkey = key.OpenSubKey(x); + + if (subkey != null) + { + using (subkey) + { + try + { + var path = subkey?.GetValue("Path") as string; + if (path != null && Directory.Exists(path)) + return new LocalRepositoryModel(path, GitService.GitServiceHelper); + } + catch (Exception) + { + // no sense spamming the log, the registry might have ton of stale things we don't care about + } + + } + } + + return null; + }) + .Where(x => x != null) + .ToList(); + } + } + + return new ILocalRepositoryModel[0]; + } + + internal static string PokeTheRegistryForLocalClonePath() + { + using (var key = OpenGitKey("General")) + { + return (string)key?.GetValue("DefaultRepositoryPath", string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + } + } + + const string NewProjectDialogKeyPath = @"Software\Microsoft\VisualStudio\14.0\NewProjectDialog"; + const string MRUKeyPath = "MRUSettingsLocalProjectLocationEntries"; + internal static string SetDefaultProjectPath(string path) + { + var old = String.Empty; + try + { + var newProjectKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(NewProjectDialogKeyPath, true) ?? + Microsoft.Win32.Registry.CurrentUser.CreateSubKey(NewProjectDialogKeyPath); + + if (newProjectKey == null) + { + throw new GitHubLogicException( + string.Format( + CultureInfo.CurrentCulture, + "Could not open or create registry key '{0}'", + NewProjectDialogKeyPath)); + } + + using (newProjectKey) + { + var mruKey = newProjectKey.OpenSubKey(MRUKeyPath, true) ?? + Microsoft.Win32.Registry.CurrentUser.CreateSubKey(MRUKeyPath); + + if (mruKey == null) + { + throw new GitHubLogicException( + string.Format( + CultureInfo.CurrentCulture, + "Could not open or create registry key '{0}'", + MRUKeyPath)); + } + + using (mruKey) + { + // is this already the default path? bail + old = (string)mruKey.GetValue("Value0", string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + if (String.Equals(path.TrimEnd('\\'), old.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase)) + return old; + + // grab the existing list of recent paths, throwing away the last one + var numEntries = (int)mruKey.GetValue("MaximumEntries", 5); + var entries = new List<string>(numEntries); + for (int i = 0; i < numEntries - 1; i++) + { + var val = (string)mruKey.GetValue("Value" + i, String.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + if (!String.IsNullOrEmpty(val)) + entries.Add(val); + } + + newProjectKey.SetValue("LastUsedNewProjectPath", path); + mruKey.SetValue("Value0", path); + // bump list of recent paths one entry down + for (int i = 0; i < entries.Count; i++) + mruKey.SetValue("Value" + (i + 1), entries[i]); + } + } + } + catch (Exception ex) + { + log.Error(ex, "Error setting the create project path in the registry"); + } + return old; + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Services/LocalRepositoryModelFactory.cs b/src/GitHub.TeamFoundation.14/Services/LocalRepositoryModelFactory.cs new file mode 100644 index 0000000000..f6d7dc4a54 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Services/LocalRepositoryModelFactory.cs @@ -0,0 +1,13 @@ +using GitHub.Models; +using GitHub.Services; + +namespace GitHub.TeamFoundation.Services +{ + class LocalRepositoryModelFactory : ILocalRepositoryModelFactory + { + public ILocalRepositoryModel Create(string localPath) + { + return new LocalRepositoryModel(localPath, GitService.GitServiceHelper); + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs b/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs new file mode 100644 index 0000000000..318ddf3c47 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Services/TeamExplorerServices.cs @@ -0,0 +1,92 @@ +using System; +using System.ComponentModel.Composition; +using System.Windows.Input; +using GitHub.Extensions; +using GitHub.VisualStudio.TeamExplorer.Sync; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.Services +{ + [Export(typeof(ITeamExplorerServices))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class TeamExplorerServices : ITeamExplorerServices + { + readonly IGitHubServiceProvider serviceProvider; + + /// <summary> + /// This MEF export requires specific versions of TeamFoundation. ITeamExplorerNotificationManager is declared here so + /// that instances of this type cannot be created if the TeamFoundation dlls are not available + /// (otherwise we'll have multiple instances of ITeamExplorerServices exports, and that would be Bad(tm)) + /// </summary> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] + ITeamExplorerNotificationManager manager; + + [ImportingConstructor] + public TeamExplorerServices(IGitHubServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public void ShowConnectPage() + { + var te = serviceProvider.TryGetService<ITeamExplorer>(); + var foo = te.NavigateToPage(new Guid(TeamExplorerPageIds.Connect), null); + } + + public void ShowPublishSection() + { + var te = serviceProvider.TryGetService<ITeamExplorer>(); + var foo = te.NavigateToPage(new Guid(TeamExplorerPageIds.GitCommits), null); + var publish = foo?.GetSection(new Guid(GitHubPublishSection.GitHubPublishSectionId)) as GitHubPublishSection; + publish?.Connect(); + } + + public void ShowMessage(string message) + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + manager?.ShowNotification(message, NotificationType.Information, NotificationFlags.None, null, default(Guid)); + } + + public void ShowMessage(string message, ICommand command, bool showToolTips = true, Guid guid = default(Guid)) + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + manager?.ShowNotification( + message, + NotificationType.Information, + showToolTips ? NotificationFlags.None : NotificationFlags.NoTooltips, + command, + guid); + } + + public void ShowWarning(string message) + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + manager?.ShowNotification(message, NotificationType.Warning, NotificationFlags.None, null, default(Guid)); + } + + public void ShowError(string message) + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + manager?.ShowNotification(message, NotificationType.Error, NotificationFlags.None, null, default(Guid)); + } + + public void HideNotification(Guid guid) + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + manager?.HideNotification(guid); + } + + public void ClearNotifications() + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + manager?.ClearNotifications(); + } + + public bool IsNotificationVisible(Guid guid) + { + manager = serviceProvider.GetService<ITeamExplorer, ITeamExplorerNotificationManager>(); + return manager?.IsNotificationVisible(guid) ?? false; + } + } +} \ No newline at end of file diff --git a/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs b/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs new file mode 100644 index 0000000000..b2279c8b65 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs @@ -0,0 +1,140 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using GitHub.Models; +using GitHub.Services; +using GitHub.Logging; +using GitHub.Extensions; +using GitHub.TeamFoundation.Services; +using Serilog; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; +using Task = System.Threading.Tasks.Task; +using static Microsoft.VisualStudio.VSConstants; + +namespace GitHub.VisualStudio.Base +{ + /// <summary> + /// This service acts as an always available version of <see cref="IGitExt"/>. + /// </summary> + /// <remarks> + /// Initialization for this service will be done asynchronously and the <see cref="IGitExt" /> service will be + /// retrieved using <see cref="GetServiceAsync" />. This means the service can be constructed and subscribed to from a background thread. + /// </remarks> + public class VSGitExt : IVSGitExt + { + static readonly ILogger log = LogManager.ForContext<VSGitExt>(); + + readonly IAsyncServiceProvider asyncServiceProvider; + readonly ILocalRepositoryModelFactory repositoryFactory; + readonly object refreshLock = new object(); + + IGitExt gitService; + IReadOnlyList<ILocalRepositoryModel> activeRepositories; + + public VSGitExt(IAsyncServiceProvider asyncServiceProvider) + : this(asyncServiceProvider, new VSUIContextFactory(), new LocalRepositoryModelFactory(), ThreadHelper.JoinableTaskContext) + { + } + + public VSGitExt(IAsyncServiceProvider asyncServiceProvider, IVSUIContextFactory factory, ILocalRepositoryModelFactory repositoryFactory, + JoinableTaskContext joinableTaskContext) + { + JoinableTaskCollection = joinableTaskContext.CreateCollection(); + JoinableTaskCollection.DisplayName = nameof(VSGitExt); + JoinableTaskFactory = joinableTaskContext.CreateFactory(JoinableTaskCollection); + + this.asyncServiceProvider = asyncServiceProvider; + this.repositoryFactory = repositoryFactory; + + // Start with empty array until we have a chance to initialize. + ActiveRepositories = Array.Empty<ILocalRepositoryModel>(); + + // Initialize when we enter the context of a Git repository + var context = factory.GetUIContext(UICONTEXT.RepositoryOpen_guid); + context.WhenActivated(() => JoinableTaskFactory.RunAsync(InitializeAsync).Task.Forget(log)); + } + + async Task InitializeAsync() + { + gitService = await GetServiceAsync<IGitExt>(); + if (gitService == null) + { + log.Error("Couldn't find IGitExt service"); + return; + } + + // Refresh on background thread + await Task.Run(() => RefreshActiveRepositories()); + + gitService.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(gitService.ActiveRepositories)) + { + RefreshActiveRepositories(); + } + }; + } + + public void RefreshActiveRepositories() + { + try + { + lock (refreshLock) + { + log.Debug( + "IGitExt.ActiveRepositories (#{Id}) returned {Repositories}", + gitService.GetHashCode(), + gitService.ActiveRepositories.Select(x => x.RepositoryPath)); + + ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList(); + } + } + catch (Exception e) + { + log.Error(e, "Error refreshing repositories"); + ActiveRepositories = Array.Empty<ILocalRepositoryModel>(); + } + } + + public IReadOnlyList<ILocalRepositoryModel> ActiveRepositories + { + get + { + return activeRepositories; + } + + private set + { + if (value != activeRepositories) + { + log.Debug("ActiveRepositories changed to {Repositories}", value?.Select(x => x.CloneUrl)); + activeRepositories = value; + ActiveRepositoriesChanged?.Invoke(); + } + } + } + + public void JoinTillEmpty() + { + JoinableTaskFactory.Context.Factory.Run(async () => + { + await JoinableTaskCollection.JoinTillEmptyAsync(); + }); + } + + async Task<T> GetServiceAsync<T>() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + return (T)await asyncServiceProvider.GetServiceAsync(typeof(T)); + } + + + public event Action ActiveRepositoriesChanged; + + JoinableTaskCollection JoinableTaskCollection { get; } + JoinableTaskFactory JoinableTaskFactory { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs b/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs new file mode 100644 index 0000000000..cb99445424 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Services/VSGitServices.cs @@ -0,0 +1,151 @@ +#if TEAMEXPLORER15 +// Microsoft.VisualStudio.Shell.Framework has an alias to avoid conflict with IAsyncServiceProvider +extern alias SF15; +using ServiceProgressData = SF15::Microsoft.VisualStudio.Shell.ServiceProgressData; +#endif + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using GitHub.TeamFoundation; +using GitHub.VisualStudio; +#if TEAMEXPLORER14 +using Microsoft.TeamFoundation.Git.Controls.Extensibility; +using ReactiveUI; +#else +using Microsoft.VisualStudio.Shell.Interop; +using System.Threading; +#endif +using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; +using Serilog; + +namespace GitHub.Services +{ + [Export(typeof(IVSGitServices))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class VSGitServices : IVSGitServices + { + static readonly ILogger log = LogManager.ForContext<VSGitServices>(); + + readonly IGitHubServiceProvider serviceProvider; + + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Used in VS2017")] + readonly Lazy<IStatusBarNotificationService> statusBar; + + /// <summary> + /// This MEF export requires specific versions of TeamFoundation. IGitExt is declared here so + /// that instances of this type cannot be created if the TeamFoundation dlls are not available + /// (otherwise we'll have multiple instances of IVSServices exports, and that would be Bad(tm)) + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] + IGitExt gitExtService; + + [ImportingConstructor] + public VSGitServices(IGitHubServiceProvider serviceProvider, Lazy<IStatusBarNotificationService> statusBar) + { + this.serviceProvider = serviceProvider; + this.statusBar = statusBar; + } + + // The Default Repository Path that VS uses is hidden in an internal + // service 'ISccSettingsService' registered in an internal service + // 'ISccServiceHost' in an assembly with no public types that's + // always loaded with VS if the git service provider is loaded + public string GetLocalClonePathFromGitProvider() + { + string ret = string.Empty; + + try + { + ret = RegistryHelper.PokeTheRegistryForLocalClonePath(); + } + catch (Exception ex) + { + log.Error(ex, "Error loading the default cloning path from the registry"); + } + return ret; + } + + /// <inheritdoc/> + public async Task Clone( + string cloneUrl, + string clonePath, + bool recurseSubmodules, + object progress = null) + { +#if TEAMEXPLORER14 + var gitExt = serviceProvider.GetService<IGitRepositoriesExt>(); + gitExt.Clone(cloneUrl, clonePath, recurseSubmodules ? CloneOptions.RecurseSubmodule : CloneOptions.None); + + // The operation will have completed when CanClone goes false and then true again. + await gitExt.WhenAnyValue(x => x.CanClone).Where(x => !x).Take(1); + await gitExt.WhenAnyValue(x => x.CanClone).Where(x => x).Take(1); +#else + var gitExt = serviceProvider.GetService<IGitActionsExt>(); + var typedProgress = ((Progress<ServiceProgressData>)progress) ?? new Progress<ServiceProgressData>(); + + await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + typedProgress.ProgressChanged += (s, e) => statusBar.Value.ShowMessage(e.ProgressText); + await gitExt.CloneAsync(cloneUrl, clonePath, recurseSubmodules, default(CancellationToken), typedProgress); + }); +#endif + } + + IGitRepositoryInfo GetRepoFromVS() + { + gitExtService = serviceProvider.GetService<IGitExt>(); + return gitExtService.ActiveRepositories.FirstOrDefault(); + } + + public LibGit2Sharp.IRepository GetActiveRepo() + { + var repo = GetRepoFromVS(); + return repo != null + ? serviceProvider.GetService<IGitService>().GetRepository(repo.RepositoryPath) + : serviceProvider.GetSolution().GetRepositoryFromSolution(); + } + + public string GetActiveRepoPath() + { + string ret = null; + var repo = GetRepoFromVS(); + if (repo != null) + ret = repo.RepositoryPath; + if (ret == null) + { + using (var repository = serviceProvider.GetSolution().GetRepositoryFromSolution()) + { + ret = repository?.Info?.Path; + } + } + return ret ?? String.Empty; + } + + public IEnumerable<ILocalRepositoryModel> GetKnownRepositories() + { + try + { + return RegistryHelper.PokeTheRegistryForRepositoryList(); + } + catch (Exception ex) + { + log.Error(ex, "Error loading the repository list from the registry"); + return Enumerable.Empty<ILocalRepositoryModel>(); + } + } + + public string SetDefaultProjectPath(string path) + { + return RegistryHelper.SetDefaultProjectPath(path); + } + } +} diff --git a/src/GitHub.TeamFoundation.14/Services/VSUIContextFactory.cs b/src/GitHub.TeamFoundation.14/Services/VSUIContextFactory.cs new file mode 100644 index 0000000000..3797f75c02 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Services/VSUIContextFactory.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.VisualStudio.Shell; +using GitHub.Services; + +namespace GitHub.TeamFoundation.Services +{ + class VSUIContextFactory : IVSUIContextFactory + { + public IVSUIContext GetUIContext(Guid contextGuid) + { + return new VSUIContext(UIContext.FromUIContextGuid(contextGuid)); + } + } + + class VSUIContext : IVSUIContext + { + readonly UIContext context; + + public VSUIContext(UIContext context) + { + this.context = context; + } + + public bool IsActive { get { return context.IsActive; } } + + public void WhenActivated(Action action) => context.WhenActivated(action); + } +} diff --git a/src/GitHub.TeamFoundation.14/Settings.cs b/src/GitHub.TeamFoundation.14/Settings.cs new file mode 100644 index 0000000000..ce32331239 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Settings.cs @@ -0,0 +1,18 @@ +// Guids.cs +// MUST match guids.h +using Microsoft.TeamFoundation.Controls; +using System; + +namespace GitHub.VisualStudio +{ + + static class NavigationItemPriority + { + public const int PullRequests = TeamExplorerNavigationItemPriority.GitCommits - 1; + public const int Wiki = TeamExplorerNavigationItemPriority.Settings - 2; + public const int Fork = TeamExplorerNavigationItemPriority.Settings - 1; + public const int Pulse = Wiki - 3; + public const int Graphs = Wiki - 2; + public const int Issues = Wiki - 1; + } +} diff --git a/src/GitHub.TeamFoundation.14/Sync/EnsureLoggedInSectionSync.cs b/src/GitHub.TeamFoundation.14/Sync/EnsureLoggedInSectionSync.cs new file mode 100644 index 0000000000..7bafa574f5 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Sync/EnsureLoggedInSectionSync.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.Composition; +using GitHub.Api; +using GitHub.Services; + +namespace GitHub.VisualStudio.TeamExplorer.Sync +{ + // TODO: The IsAGitHubRepo() is somehow giving false positives, need to fix that + // before reactivating this, it's annoying users. + //[TeamExplorerSection(SyncLoginSectionId, TeamExplorerPageIds.GitCommits, 10)] + //[PartCreationPolicy(CreationPolicy.NonShared)] + public class EnsureLoggedInSectionSync : EnsureLoggedInSection + { + public const string SyncLoginSectionId = "C5975729-3CF1-47B4-AE92-C2934906CDDA"; + + [ImportingConstructor] + public EnsureLoggedInSectionSync(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, + IConnectionManager cm, ITeamExplorerServices teServices, + IDialogService dialogService) + : base(serviceProvider, apiFactory, holder, cm, teServices, dialogService) + {} + } +} \ No newline at end of file diff --git a/src/GitHub.TeamFoundation.14/Sync/GitHubPublishSection.cs b/src/GitHub.TeamFoundation.14/Sync/GitHubPublishSection.cs new file mode 100644 index 0000000000..3ce2d7c286 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/Sync/GitHubPublishSection.cs @@ -0,0 +1,240 @@ +using System; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Reactive.Linq; +using System.Threading.Tasks; +using System.Windows; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Info; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels; +using GitHub.ViewModels.TeamExplorer; +using GitHub.VisualStudio.Base; +using GitHub.VisualStudio.Helpers; +using GitHub.VisualStudio.UI; +using GitHub.VisualStudio.UI.Views; +using Microsoft.TeamFoundation.Controls; +using Microsoft.VisualStudio; +using ReactiveUI; + +namespace GitHub.VisualStudio.TeamExplorer.Sync +{ + [TeamExplorerSection(GitHubPublishSectionId, TeamExplorerPageIds.GitCommits, 10)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class GitHubPublishSection : TeamExplorerSectionBase, IGitHubInvitationSection + { + public const string GitHubPublishSectionId = "92655B52-360D-4BF5-95C5-D9E9E596AC76"; + + readonly Lazy<IVisualStudioBrowser> lazyBrowser; + readonly IDialogService dialogService; + bool loggedIn; + + [ImportingConstructor] + public GitHubPublishSection(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, + IConnectionManager cm, Lazy<IVisualStudioBrowser> browser, + IDialogService dialogService) + : base(serviceProvider, apiFactory, holder, cm) + { + + lazyBrowser = browser; + this.dialogService = dialogService; + Title = Resources.GitHubPublishSectionTitle; + Name = "GitHub"; + Provider = "GitHub, Inc"; + Description = Resources.BlurbText; + ShowLogin = false; + ShowSignup = false; + ShowGetStarted = false; + IsVisible = false; + IsExpanded = true; + InitializeSectionView(); + } + + void InitializeSectionView() + { + var view = new GitHubInvitationContent(); + SectionContent = view; + view.DataContext = this; + } + + async void Setup() + { + if (ActiveRepo != null && ActiveRepoUri == null) + { + IsVisible = true; + ShowGetStarted = true; + loggedIn = await connectionManager.IsLoggedIn(); + ShowLogin = !loggedIn; + ShowSignup = !loggedIn; + } + else + IsVisible = false; + } + + public override void Initialize(IServiceProvider serviceProvider) + { + base.Initialize(serviceProvider); + Setup(); + } + + protected override void RepoChanged(bool changed) + { + base.RepoChanged(changed); + Setup(); + InitializeSectionView(); + } + + public async Task Connect() + { + loggedIn = await connectionManager.IsLoggedIn(); + if (loggedIn) + ShowPublish(); + else + await Login(); + } + + public void SignUp() + { + OpenInBrowser(lazyBrowser, GitHubUrls.Plans); + } + + public void ShowPublish() + { + var factory = ServiceProvider.GetService<IViewViewModelFactory>(); + var viewModel = ServiceProvider.GetService<IRepositoryPublishViewModel>(); + var busy = viewModel.WhenAnyValue(x => x.IsBusy).Subscribe(x => IsBusy = x); + var completed = viewModel.PublishRepository + .Where(x => x == ProgressState.Success) + .Subscribe(_ => + { + ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + HandleCreatedRepo(ActiveRepo); + }); + + var view = factory.CreateView<IRepositoryPublishViewModel>(); + view.DataContext = viewModel; + SectionContent = view; + + Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>( + x => view.Unloaded += x, + x => view.Unloaded -= x) + .Take(1) + .Subscribe(_ => + { + busy.Dispose(); + completed.Dispose(); + }); + } + + void HandleCreatedRepo(ILocalRepositoryModel newrepo) + { + var msg = String.Format(CultureInfo.CurrentCulture, Constants.Notification_RepoCreated, newrepo.Name, newrepo.CloneUrl); + msg += " " + String.Format(CultureInfo.CurrentCulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); + ShowNotification(newrepo, msg); + } + + private void ShowNotification(ILocalRepositoryModel newrepo, string msg) + { + var teServices = ServiceProvider.TryGetService<ITeamExplorerServices>(); + + teServices.ClearNotifications(); + teServices.ShowMessage( + msg, + new RelayCommand(o => + { + var str = o.ToString(); + /* the prefix is the action to perform: + * u: launch browser with url + * c: launch create new project dialog + * o: launch open existing project dialog + */ + var prefix = str.Substring(0, 2); + if (prefix == "u:") + OpenInBrowser(ServiceProvider.TryGetService<IVisualStudioBrowser>(), new Uri(str.Substring(2))); + else if (prefix == "o:") + { + if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(str.Substring(2), 1))) + ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + } + else if (prefix == "c:") + { + var vsGitServices = ServiceProvider.TryGetService<IVSGitServices>(); + vsGitServices.SetDefaultProjectPath(newrepo.LocalPath); + if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().CreateNewProjectViaDlg(null, null, 0))) + ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + } + }) + ); + } + + async Task Login() + { + await dialogService.ShowLoginDialog(); + loggedIn = await connectionManager.IsLoggedIn(); + if (loggedIn) + ShowPublish(); + } + + bool disposed; + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!disposed) + { + disposed = true; + } + } + base.Dispose(disposing); + } + + string name = String.Empty; + public string Name + { + get { return name; } + set { name = value; this.RaisePropertyChange(); } + } + + string provider = String.Empty; + public string Provider + { + get { return provider; } + set { provider = value; this.RaisePropertyChange(); } + } + + string description = String.Empty; + public string Description + { + get { return description; } + set { description = value; this.RaisePropertyChange(); } + } + + bool showLogin; + public bool ShowLogin + { + get { return showLogin; } + set { showLogin = value; this.RaisePropertyChange(); } + } + + + bool showSignup; + public bool ShowSignup + { + get { return showSignup; } + set { showSignup = value; this.RaisePropertyChange(); } + } + + bool showGetStarted; + public bool ShowGetStarted + { + get { return showGetStarted; } + set { showGetStarted = value; this.RaisePropertyChange(); } + } + } +} diff --git a/src/GitHub.TeamFoundation.14/packages.config b/src/GitHub.TeamFoundation.14/packages.config new file mode 100644 index 0000000000..a87a881aa1 --- /dev/null +++ b/src/GitHub.TeamFoundation.14/packages.config @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> + <package id="Stateless" version="2.5.56.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.TeamFoundation.15/GitHub.TeamFoundation.15.csproj b/src/GitHub.TeamFoundation.15/GitHub.TeamFoundation.15.csproj new file mode 100644 index 0000000000..9306a1e81b --- /dev/null +++ b/src/GitHub.TeamFoundation.15/GitHub.TeamFoundation.15.csproj @@ -0,0 +1,321 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <MinimumVisualStudioVersion>$(MSBuildToolsVersion)</MinimumVisualStudioVersion> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">$(MSBuildToolsVersion)</VisualStudioVersion> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{161DBF01-1DBF-4B00-8551-C5C00F26720E}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.TeamFoundation</RootNamespace> + <AssemblyName>GitHub.TeamFoundation.15</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>DEBUG;TRACE;TEAMEXPLORER15</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE;TEAMEXPLORER15</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE;TEAMEXPLORER15</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> + </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Common"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Common.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Client"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Client.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Controls"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Controls.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Git.Controls"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Git.Provider"> + <HintPath>..\..\lib\15.0\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.15.4.27004\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.15.4.27004\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.15.0, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.15.0.15.0.26228\lib\Microsoft.VisualStudio.Shell.15.0.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Framework, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Framework.15.4.27004\lib\net45\Microsoft.VisualStudio.Shell.Framework.dll</HintPath> + <Private>True</Private> + <Aliases>SF15</Aliases> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + <Aliases>global</Aliases> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.15.3.DesignTime, Version=15.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.15.3.DesignTime.15.0.26606\lib\net20\Microsoft.VisualStudio.Shell.Interop.15.3.DesignTime.dll</HintPath> + <EmbedInteropTypes>True</EmbedInteropTypes> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.15.0.240\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.15.4.27004\lib\net46\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=15.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.15.3.15\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Stateless, Version=2.5.56.0, Culture=neutral, PublicKeyToken=93038f0927583c9a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Stateless.2.5.56.0\lib\portable-net40+sl50+win+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Stateless.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Core" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\GitHub.TeamFoundation.14\Home\ForkNavigationItem.cs"> + <Link>Home\ForkNavigationItem.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Services\LocalRepositoryModelFactory.cs"> + <Link>Services\LocalRepositoryModelFactory.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Services\VSGitExt.cs"> + <Link>Services\VSGitExt.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Services\VSUIContextFactory.cs"> + <Link>Services\VSUIContextFactory.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Settings.cs"> + <Link>Settings.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Base\EnsureLoggedInSection.cs"> + <Link>Base\EnsureLoggedInSection.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Base\TeamExplorerInvitationBase.cs"> + <Link>Base\TeamExplorerInvitationBase.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Base\TeamExplorerNavigationItemBase.cs"> + <Link>Base\TeamExplorerNavigationItemBase.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Base\TeamExplorerSectionBase.cs"> + <Link>Base\TeamExplorerSectionBase.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Base\TeamExplorerServiceHolder.cs"> + <Link>Base\TeamExplorerServiceHolder.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Connect\GitHubConnectSection.cs"> + <Link>Connect\GitHubConnectSection.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Connect\GitHubConnectSection0.cs"> + <Link>Connect\GitHubConnectSection0.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Connect\GitHubConnectSection1.cs"> + <Link>Connect\GitHubConnectSection1.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Connect\GitHubInvitationSection.cs"> + <Link>Connect\GitHubInvitationSection.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Home\GitHubHomeSection.cs"> + <Link>Home\GitHubHomeSection.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Home\GraphsNavigationItem.cs"> + <Link>Home\GraphsNavigationItem.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Home\IssuesNavigationItem.cs"> + <Link>Home\IssuesNavigationItem.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Home\PullRequestsNavigationItem.cs"> + <Link>Home\PullRequestsNavigationItem.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Home\PulseNavigationItem.cs"> + <Link>Home\PulseNavigationItem.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Home\WikiNavigationItem.cs"> + <Link>Home\WikiNavigationItem.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Sync\EnsureLoggedInSectionSync.cs"> + <Link>Sync\EnsureLoggedInSectionSync.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Sync\GitHubPublishSection.cs"> + <Link>Sync\GitHubPublishSection.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Services\TeamExplorerServices.cs"> + <Link>Services\TeamExplorerServices.cs</Link> + </Compile> + <Compile Include="..\GitHub.TeamFoundation.14\Services\VSGitServices.cs"> + <Link>Services\VSGitServices.cs</Link> + </Compile> + <Compile Include="RegistryHelper.cs" /> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj"> + <Project>{d1dfbb0c-b570-4302-8f1e-2e3a19c41961}</Project> + <Name>GitHub.VisualStudio.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.12\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.12\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets'))" /> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> + </Target> + <Import Project="..\..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.12\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets" Condition="Exists('..\..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.12\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets')" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/src/GitHub.TeamFoundation.15/Properties/AssemblyInfo.cs b/src/GitHub.TeamFoundation.15/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6dc9797bbf --- /dev/null +++ b/src/GitHub.TeamFoundation.15/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("GitHub.TeamFoundation.14")] +[assembly: AssemblyDescription("GitHub TeamFoundation")] +[assembly: Guid("b389adaf-62cc-486e-85b4-2d8b078df763")] diff --git a/src/GitHub.TeamFoundation.15/RegistryHelper.cs b/src/GitHub.TeamFoundation.15/RegistryHelper.cs new file mode 100644 index 0000000000..54ca566bb1 --- /dev/null +++ b/src/GitHub.TeamFoundation.15/RegistryHelper.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Services; +using Microsoft.Win32; +using Serilog; + +namespace GitHub.TeamFoundation +{ + internal class RegistryHelper + { + static readonly ILogger log = LogManager.ForContext<RegistryHelper>(); + const string TEGitKey = @"Software\Microsoft\VisualStudio\15.0\TeamFoundation\GitSourceControl"; + static RegistryKey OpenGitKey(string path) + { + return Microsoft.Win32.Registry.CurrentUser.OpenSubKey(TEGitKey + "\\" + path, true); + } + + internal static IEnumerable<ILocalRepositoryModel> PokeTheRegistryForRepositoryList() + { + using (var key = OpenGitKey("Repositories")) + { + return key.GetSubKeyNames().Select(x => + { + using (var subkey = key.OpenSubKey(x)) + { + try + { + var path = subkey?.GetValue("Path") as string; + if (path != null) + return new LocalRepositoryModel(path, GitService.GitServiceHelper); + } + catch (Exception) + { + // no sense spamming the log, the registry might have ton of stale things we don't care about + } + return null; + } + }) + .Where(x => x != null) + .ToList(); + } + } + + internal static string PokeTheRegistryForLocalClonePath() + { + using (var key = OpenGitKey("General")) + { + return (string)key?.GetValue("DefaultRepositoryPath", string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + } + } + + const string NewProjectDialogKeyPath = @"Software\Microsoft\VisualStudio\15.0\NewProjectDialog"; + const string MRUKeyPath = "MRUSettingsLocalProjectLocationEntries"; + internal static string SetDefaultProjectPath(string path) + { + var old = String.Empty; + try + { + var newProjectKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(NewProjectDialogKeyPath, true) ?? + Microsoft.Win32.Registry.CurrentUser.CreateSubKey(NewProjectDialogKeyPath); + + if (newProjectKey == null) + { + throw new GitHubLogicException( + string.Format( + CultureInfo.CurrentCulture, + "Could not open or create registry key '{0}'", + NewProjectDialogKeyPath)); + } + + using (newProjectKey) + { + var mruKey = newProjectKey.OpenSubKey(MRUKeyPath, true) ?? + Microsoft.Win32.Registry.CurrentUser.CreateSubKey(MRUKeyPath); + + if (mruKey == null) + { + throw new GitHubLogicException( + string.Format( + CultureInfo.CurrentCulture, + "Could not open or create registry key '{0}'", + MRUKeyPath)); + } + + using (mruKey) + { + // is this already the default path? bail + old = (string)mruKey.GetValue("Value0", string.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + if (String.Equals(path.TrimEnd('\\'), old.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase)) + return old; + + // grab the existing list of recent paths, throwing away the last one + var numEntries = (int)mruKey.GetValue("MaximumEntries", 5); + var entries = new List<string>(numEntries); + for (int i = 0; i < numEntries - 1; i++) + { + var val = (string)mruKey.GetValue("Value" + i, String.Empty, RegistryValueOptions.DoNotExpandEnvironmentNames); + if (!String.IsNullOrEmpty(val)) + entries.Add(val); + } + + newProjectKey.SetValue("LastUsedNewProjectPath", path); + mruKey.SetValue("Value0", path); + // bump list of recent paths one entry down + for (int i = 0; i < entries.Count; i++) + mruKey.SetValue("Value" + (i + 1), entries[i]); + } + } + } + catch (Exception ex) + { + log.Error(ex, "Error setting the create project path in the registry"); + } + return old; + } + } +} diff --git a/src/GitHub.TeamFoundation.15/packages.config b/src/GitHub.TeamFoundation.15/packages.config new file mode 100644 index 0000000000..e6dc9b355a --- /dev/null +++ b/src/GitHub.TeamFoundation.15/packages.config @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="15.4.27004" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Imaging" version="15.4.27004" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.SDK.EmbedInteropTypes" version="15.0.12" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.15.0" version="15.0.26228" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Framework" version="15.4.27004" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.15.3.DesignTime" version="15.0.26606" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="15.0.240" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Utilities" version="15.4.27004" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="15.3.15" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> + <package id="Stateless" version="2.5.56.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.UI.Reactive/Assets/Controls.xaml b/src/GitHub.UI.Reactive/Assets/Controls.xaml index 63c2c19457..a5fdd7125d 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls.xaml @@ -12,5 +12,4 @@ <ResourceDictionary Source="Controls\FilteredComboBox.xaml" /> <ResourceDictionary Source="Controls\GitHubComboBox.xaml" /> </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> diff --git a/src/GitHub.UI.Reactive/Assets/Controls/ErrorMessageDisplay.xaml b/src/GitHub.UI.Reactive/Assets/Controls/ErrorMessageDisplay.xaml index f7292b143b..eea3a757fc 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/ErrorMessageDisplay.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls/ErrorMessageDisplay.xaml @@ -1,6 +1,6 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:uirx="clr-namespace:GitHub.UI"> <Style x:Key="ErrorMessageStyle" TargetType="{x:Type uirx:ErrorMessageDisplay}"> @@ -17,15 +17,15 @@ </Grid.ColumnDefinitions> <Viewbox Grid.Column="0" - VerticalAlignment="Center" + VerticalAlignment="Top" HorizontalAlignment="Right" Margin="{TemplateBinding IconMargin}" Width="16" Height="16"> - <ui:OcticonPath + <ghfvs:OcticonPath x:Name="icon" Icon="{TemplateBinding Icon}" - Height="1024" + Height="16" Fill="{TemplateBinding IconFill}" /> </Viewbox> <ContentPresenter diff --git a/src/GitHub.UI.Reactive/Assets/Controls/FilteredComboBox.xaml b/src/GitHub.UI.Reactive/Assets/Controls/FilteredComboBox.xaml index 7d139e2a80..1a8f7f8789 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/FilteredComboBox.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls/FilteredComboBox.xaml @@ -1,9 +1,9 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:uirx="clr-namespace:GitHub.UI"> - <PathGeometry x:Key="arrow" Figures="M 0,448 224,768 448,448" /> + <PathGeometry x:Key="arrow">M 0,6 3.5,11 7,6</PathGeometry> <ControlTemplate x:Key="FilterComboBoxTemplate" TargetType="{x:Type uirx:FilteredComboBox}"> <Grid x:Name="MainGrid" SnapsToDevicePixels="true" @@ -23,7 +23,7 @@ <RowDefinition/> </Grid.RowDefinitions> <Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{TemplateBinding BorderBrush}" Background="#FCFCFC" Padding="5"> - <ui:FilterTextBox + <ghfvs:FilterTextBox x:Name="PART_FilterTextBox" PromptText="Filter" Margin="0" @@ -40,7 +40,7 @@ </Grid> </Border> </Popup> - <ui:OcticonLinkToggleButton + <ghfvs:OcticonLinkToggleButton x:Name="toggleButton" Focusable="False" PathChecked="{StaticResource arrow}" @@ -50,15 +50,14 @@ Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" FontSize="12" - IconHeight="16" - IconWidth="8" + IconWidth="7" Margin="{TemplateBinding Padding}" HorizontalContentAlignment="Left" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"> - </ui:OcticonLinkToggleButton> + </ghfvs:OcticonLinkToggleButton> </Grid> <ControlTemplate.Triggers> @@ -75,7 +74,7 @@ </ControlTemplate.Triggers> </ControlTemplate> - <ui:IsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter" /> + <ghfvs:IsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter" /> <Style x:Key="GitHubComboBoxItem" TargetType="{x:Type ComboBoxItem}"> <Setter Property="Foreground" Value="{DynamicResource GHTextBrush}"/> diff --git a/src/GitHub.UI.Reactive/Assets/Controls/GitHubComboBox.xaml b/src/GitHub.UI.Reactive/Assets/Controls/GitHubComboBox.xaml index 164ef5c2c6..0a0463db84 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/GitHubComboBox.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls/GitHubComboBox.xaml @@ -1,10 +1,10 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + xmlns:ghfvs="https://github.com/github/VisualStudio"> - <PathGeometry x:Key="arrow" Figures="M 0,448 224,768 448,448" /> + <PathGeometry x:Key="arrow">M 0,6 3.5,11 7,6</PathGeometry> - <ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ui:GitHubComboBox}"> + <ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ghfvs:GitHubComboBox}"> <Grid x:Name="MainGrid" SnapsToDevicePixels="true" MinWidth="{Binding ActualWidth, ElementName=PART_Popup}" Background="{TemplateBinding Background}"> @@ -26,7 +26,7 @@ </ScrollViewer> </Border> </Popup> - <ui:OcticonLinkToggleButton + <ghfvs:OcticonLinkToggleButton x:Name="toggleButton" Focusable="True" FocusVisualStyle="{DynamicResource NegativeMarginFocusVisual}" @@ -37,15 +37,14 @@ Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" FontSize="12" - IconHeight="16" - IconWidth="8" + IconWidth="7" Margin="{TemplateBinding Padding}" HorizontalContentAlignment="Left" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"> - </ui:OcticonLinkToggleButton> + </ghfvs:OcticonLinkToggleButton> </Grid> <ControlTemplate.Triggers> <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true"> @@ -61,7 +60,7 @@ </ControlTemplate.Triggers> </ControlTemplate> - <ui:IsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter" /> + <ghfvs:IsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter" /> <Style x:Key="GitHubComboBoxItem" TargetType="{x:Type ComboBoxItem}"> <Setter Property="Foreground" Value="{DynamicResource GHTextBrush}"/> @@ -88,7 +87,7 @@ </Setter> </Style> - <Style x:Key="GitHubComboBox" TargetType="{x:Type ui:GitHubComboBox}"> + <Style x:Key="GitHubComboBox" TargetType="{x:Type ghfvs:GitHubComboBox}"> <Setter Property="Background" Value="White"/> <Setter Property="BorderBrush" Value="{DynamicResource GHBoxDividerBrush}"/> <Setter Property="Foreground" Value="{DynamicResource GHBlueLinkButtonTextBrush}"/> diff --git a/src/GitHub.UI.Reactive/Assets/Controls/GitHubTabControl.xaml b/src/GitHub.UI.Reactive/Assets/Controls/GitHubTabControl.xaml index 411d50bf11..f73fe07e4b 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/GitHubTabControl.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls/GitHubTabControl.xaml @@ -1,6 +1,6 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:converters="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:ui="clr-namespace:GitHub.UI"> <Style TargetType="{x:Type ui:GitHubTabControl}"> @@ -93,7 +93,7 @@ Fill="{StaticResource GHBlueLinkButtonTextBrush}" Margin="0" Height="2" - Visibility="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Converter={converters:BooleanToVisibilityConverter}}" /> + Visibility="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Converter={ghfvs:BooleanToVisibilityConverter}}" /> </Grid> <ControlTemplate.Triggers> diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml index 5ae2662148..b56bb9a75d 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/UserErrorMessages.xaml @@ -1,8 +1,8 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:local="clr-namespace:GitHub.UI" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" xmlns:rxui="clr-namespace:ReactiveUI;assembly=ReactiveUI"> <Style TargetType="{x:Type local:UserErrorMessages}"> <Setter Property="Template"> @@ -16,8 +16,9 @@ <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> - + <Viewbox Grid.Column="0" VerticalAlignment="Center" @@ -25,22 +26,36 @@ HorizontalAlignment="Right" Width="16" Height="16"> - <ui:OcticonPath x:Name="icon" Icon="{TemplateBinding Icon}" Height="1024" Fill="{TemplateBinding Fill}" /> + <ghfvs:OcticonPath x:Name="icon" Icon="{TemplateBinding Icon}" Height="16" Fill="{TemplateBinding Fill}" /> </Viewbox> - <ContentControl Grid.Column="1" Content="{Binding}"> - <ContentControl.Resources> - <DataTemplate DataType="{x:Type rxui:UserError}"> - <TextBlock - Text="{Binding Path=ErrorMessage}" - ToolTip="{Binding Path=ErrorCauseOrResolution}" - HorizontalAlignment="Stretch" - VerticalAlignment="Center" - TextWrapping="Wrap" - FontWeight="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserErrorMessages}},Path=ErrorMessageFontWeight}" - Margin="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserErrorMessages}},Path=MessageMargin}"/> + + <TextBlock Grid.Column="1" + Text="{Binding Path=ErrorMessage}" + ToolTip="{Binding Path=ErrorCauseOrResolution}" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + TextWrapping="Wrap" + FontWeight="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserErrorMessages}},Path=ErrorMessageFontWeight}" + Margin="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserErrorMessages}},Path=MessageMargin}"/> + + <ItemsControl Grid.Column="2" + ItemsSource="{Binding RecoveryOptions}" + VerticalAlignment="Center"> + <ItemsControl.ItemsPanel> + <ItemsPanelTemplate> + <StackPanel Orientation="Horizontal"/> + </ItemsPanelTemplate> + </ItemsControl.ItemsPanel> + <ItemsControl.ItemTemplate> + <DataTemplate> + <TextBlock Margin="4,0,0,0"> + <Hyperlink Command="{Binding}"> + <TextBlock Text="{Binding CommandName}"/> + </Hyperlink> + </TextBlock> </DataTemplate> - </ContentControl.Resources> - </ContentControl> + </ItemsControl.ItemTemplate> + </ItemsControl> </Grid> <ControlTemplate.Resources> diff --git a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.xaml b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.xaml index db236056f5..fa29442241 100644 --- a/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.xaml +++ b/src/GitHub.UI.Reactive/Assets/Controls/Validation/ValidationMessage.xaml @@ -1,9 +1,9 @@ <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" - xmlns:local="clr-namespace:GitHub.UI" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + xmlns:local="clr-namespace:GitHub.UI"> <Style TargetType="{x:Type local:ValidationMessage}"> <Setter Property="Template"> @@ -29,11 +29,11 @@ Width="16" Height="16" Grid.Column="0" - Visibility="{TemplateBinding ShowError, Converter={ui:BooleanToVisibilityConverter}}"> - <ui:OcticonPath + Visibility="{TemplateBinding ShowError, Converter={ghfvs:BooleanToVisibilityConverter}}"> + <ghfvs:OcticonPath x:Name="icon" Icon="{TemplateBinding Icon}" - Height="1024" + Height="16" Fill="{TemplateBinding Fill}"/> </Viewbox> <TextBlock Grid.Column="1" Text="{TemplateBinding Text}" @@ -68,7 +68,8 @@ <Grid ToolTip="{Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}" Background="Transparent" - Cursor="IBeam"> + Cursor="IBeam" + IsHitTestVisible="False"> <AdornedElementPlaceholder Name="adornerPlaceholder" @@ -80,11 +81,11 @@ Height="16" VerticalAlignment="Center" HorizontalAlignment="Right"> - <ui:OcticonPath + <ghfvs:OcticonPath x:Name="icon" Icon="stop" Fill="{StaticResource GitHubErrorAdornmentBrush}" - Height="1024" /> + Height="16" /> </Viewbox> </Grid> </ControlTemplate> diff --git a/src/GitHub.UI.Reactive/Controls/ErrorMessageDisplay.cs b/src/GitHub.UI.Reactive/Controls/ErrorMessageDisplay.cs index ef6f950103..375c35c602 100644 --- a/src/GitHub.UI.Reactive/Controls/ErrorMessageDisplay.cs +++ b/src/GitHub.UI.Reactive/Controls/ErrorMessageDisplay.cs @@ -1,7 +1,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; -using NullGuard; namespace GitHub.UI { @@ -46,14 +45,12 @@ public Octicon Icon public Thickness IconMargin { - [return: AllowNull] get { return (Thickness)GetValue(IconMarginProperty); } set { SetValue(IconMarginProperty, value); } } public Brush IconFill { - [return: AllowNull] get { return (Brush)GetValue(IconFillProperty); } set { SetValue(IconFillProperty, value); } } diff --git a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs index 3f2a9274b2..d101666b7f 100644 --- a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs +++ b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs @@ -5,7 +5,6 @@ using System.Windows.Controls.Primitives; using System.Windows.Input; using GitHub.Extensions.Reactive; -using NullGuard; using ReactiveUI; namespace GitHub.UI @@ -46,7 +45,6 @@ public FilteredComboBox() public string FilterText { - [return: AllowNull] get { return (string)GetValue(FilterTextProperty); } set { SetValue(FilterTextProperty, value); } } diff --git a/src/GitHub.UI.Reactive/Controls/SimpleViewUserControl.cs b/src/GitHub.UI.Reactive/Controls/SimpleViewUserControl.cs deleted file mode 100644 index 15ca334f2f..0000000000 --- a/src/GitHub.UI.Reactive/Controls/SimpleViewUserControl.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using GitHub.ViewModels; -using NullGuard; -using ReactiveUI; - -namespace GitHub.UI -{ - /// <summary> - /// Base class for all of our user controls. This one does not import GitHub resource/styles and is used by the - /// publish control. - /// </summary> - public class SimpleViewUserControl : UserControl, IDisposable, IActivatable - { - readonly Subject<object> close = new Subject<object>(); - readonly Subject<object> cancel = new Subject<object>(); - readonly Subject<bool> isBusy = new Subject<bool>(); - - public SimpleViewUserControl() - { - this.WhenActivated(d => - { - d(this.Events() - .KeyUp - .Where(x => x.Key == Key.Escape && !x.Handled) - .Subscribe(key => - { - key.Handled = true; - NotifyCancel(); - })); - }); - } - - public IObservable<object> Done => close; - - public IObservable<object> Cancel => cancel; - - public IObservable<bool> IsBusy => isBusy; - - protected void NotifyDone() - { - if (disposed) - return; - - close.OnNext(null); - close.OnCompleted(); - } - - protected void NotifyCancel() - { - if (disposed) - return; - - cancel.OnNext(null); - cancel.OnCompleted(); - } - - protected void NotifyIsBusy(bool busy) - { - if (disposed) - return; - - isBusy.OnNext(busy); - } - - bool disposed; - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - close.Dispose(); - cancel.Dispose(); - isBusy.Dispose(); - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } - - public class SimpleViewUserControl<TViewModel, TImplementor> : SimpleViewUserControl, IViewFor<TViewModel>, IView - where TViewModel : class, IViewModel where TImplementor : class - { - public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( - "ViewModel", typeof(TViewModel), typeof(TImplementor), new PropertyMetadata(null)); - - object IViewFor.ViewModel - { - get { return ViewModel; } - set { ViewModel = (TViewModel)value; } - } - - object IView.ViewModel - { - get { return ViewModel; } - set { ViewModel = (TViewModel)value; } - } - - public TViewModel ViewModel - { - [return: AllowNull] - get { return (TViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - TViewModel IViewFor<TViewModel>.ViewModel - { - get { return ViewModel; } - set { ViewModel = value; } - } - } -} diff --git a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml index 7c76bc0a20..62b47d1114 100644 --- a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml +++ b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml @@ -4,10 +4,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" mc:Ignorable="d" Width="256" d:DesignHeight="47" - d:DesignWidth="262"> + d:DesignWidth="262" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticatonInputCustom}"> <UserControl.Resources> <Style TargetType="{x:Type TextBox}"> <Setter Property="Width" Value="36"/> @@ -18,12 +20,12 @@ <Setter Property="FontSize" Value="20px" /> </Style> </UserControl.Resources> - <StackPanel Orientation="Horizontal" UseLayoutRounding="True" SnapsToDevicePixels="True"> - <TextBox x:Name="one" /> - <TextBox x:Name="two" /> - <TextBox x:Name="three" /> - <TextBox x:Name="four" /> - <TextBox x:Name="five" /> - <TextBox x:Name="six" Margin="0" /> + <StackPanel Orientation="Horizontal" UseLayoutRounding="True" SnapsToDevicePixels="True" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticationInputStackPanel}"> + <TextBox x:Name="one" /> + <TextBox x:Name="two" /> + <TextBox x:Name="three" /> + <TextBox x:Name="four" /> + <TextBox x:Name="five" /> + <TextBox x:Name="six" Margin="0" /> </StackPanel> </UserControl> \ No newline at end of file diff --git a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs index 733c9513f8..099322afdf 100644 --- a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs +++ b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs @@ -4,15 +4,14 @@ using System.Windows.Controls; using System.Windows.Input; using GitHub.Extensions; -using NullGuard; using System.Globalization; namespace GitHub.UI { public class TwoFactorInputToTextBox : ValueConverterMarkupExtension<TwoFactorInputToTextBox> { - public override object Convert([AllowNull] object value, [AllowNull] Type targetType, - [AllowNull] object parameter, [AllowNull] CultureInfo culture) + public override object Convert(object value, Type targetType, + object parameter, CultureInfo culture) { return value is TwoFactorInput ? ((TwoFactorInput)value).TextBox : null; } @@ -56,6 +55,9 @@ public IObservable<bool> TryFocus() private void OnPaste(object sender, DataObjectPastingEventArgs e) { + Guard.ArgumentNotNull(sender, nameof(sender)); + Guard.ArgumentNotNull(e, nameof(e)); + var isText = e.SourceDataObject.GetDataPresent(DataFormats.Text, true); if (!isText) return; @@ -84,16 +86,16 @@ void SetText(string text) SetValue(TextProperty, String.Join("", digits)); } - [AllowNull] public string Text { - [return: AllowNull] get { return (string)GetValue(TextProperty); } set { SetText(value); } } private void SetupTextBox(TextBox textBox) { + Guard.ArgumentNotNull(textBox, nameof(textBox)); + DataObject.AddPastingHandler(textBox, new DataObjectPastingEventHandler(OnPaste)); textBox.GotFocus += (sender, args) => textBox.SelectAll(); @@ -182,6 +184,8 @@ bool MoveFocus(FocusNavigationDirection navigationDirection) private static string GetTextBoxValue(TextBox textBox) { + Guard.ArgumentNotNull(textBox, nameof(textBox)); + return String.IsNullOrEmpty(textBox.Text) ? " " : textBox.Text; } diff --git a/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs b/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs index 39c73ae3fb..5dc77f7873 100644 --- a/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs +++ b/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs @@ -7,7 +7,6 @@ using System.Windows.Media; using GitHub.Extensions; using GitHub.Extensions.Reactive; -using NullGuard; using ReactiveUI; namespace GitHub.UI @@ -32,24 +31,11 @@ public UserErrorMessages() { DataContext = result; }); - - Unloaded += (o, e) => - { - if (whenAnyShowingMessage != null) - { - whenAnyShowingMessage.Dispose(); - } - if (whenAnyDataContext != null) - { - whenAnyDataContext.Dispose(); - } - }; } - public static readonly DependencyProperty IconMarginProperty = DependencyProperty.Register("IconMargin", typeof(Thickness), typeof(UserErrorMessages), new PropertyMetadata(new Thickness(0,10,7,0))); + public static readonly DependencyProperty IconMarginProperty = DependencyProperty.Register("IconMargin", typeof(Thickness), typeof(UserErrorMessages), new PropertyMetadata(new Thickness(0,0,8,0))); public Thickness IconMargin { - [return: AllowNull] get { return (Thickness)GetValue(IconMarginProperty); } set { SetValue(IconMarginProperty, value); } } @@ -57,7 +43,6 @@ public Thickness IconMargin public static readonly DependencyProperty MessageMarginProperty = DependencyProperty.Register("MessageMargin", typeof(Thickness), typeof(UserErrorMessages)); public Thickness MessageMargin { - [return: AllowNull] get { return (Thickness)GetValue(MessageMarginProperty); } set { SetValue(MessageMarginProperty, value); } } @@ -65,7 +50,6 @@ public Thickness MessageMargin public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(Octicon), typeof(UserErrorMessages), new PropertyMetadata(Octicon.stop)); public Octicon Icon { - [return: AllowNull] get { return (Octicon)GetValue(IconProperty); } set { SetValue(IconProperty, value); } } @@ -73,7 +57,6 @@ public Octicon Icon public static readonly DependencyProperty FillProperty = DependencyProperty.Register("Fill", typeof(Brush), typeof(UserErrorMessages), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0xe7, 0x4c, 0x3c)))); public Brush Fill { - [return: AllowNull] get { return (Brush)GetValue(FillProperty); } set { SetValue(FillProperty, value); } } @@ -81,7 +64,6 @@ public Brush Fill public static readonly DependencyProperty ErrorMessageFontWeightProperty = DependencyProperty.Register("ErrorMessageFontWeight", typeof(FontWeight), typeof(UserErrorMessages), new PropertyMetadata(FontWeights.Normal)); public FontWeight ErrorMessageFontWeight { - [return: AllowNull] get { return (FontWeight)GetValue(ErrorMessageFontWeightProperty); } set { SetValue(ErrorMessageFontWeightProperty, value); } } @@ -94,10 +76,8 @@ public bool IsShowingMessage } public static readonly DependencyProperty UserErrorProperty = DependencyProperty.Register("UserError", typeof(UserError), typeof(UserErrorMessages)); - [AllowNull] public UserError UserError { - [return: AllowNull] get { return (UserError)GetValue(UserErrorProperty); } set { SetValue(UserErrorProperty, value); } } @@ -106,18 +86,14 @@ public UserError UserError Justification = "We're registering a handler for a type so this is appropriate.")] public IDisposable RegisterHandler<TUserError>(IObservable<bool> clearWhen) where TUserError : UserError { - if (IsVisible) + return UserError.RegisterHandler<TUserError>(userError => { - return UserError.RegisterHandler<TUserError>(userError => - { - UserError = userError; - return clearWhen - .Skip(1) - .Do(_ => UserError = null) - .Select(x => RecoveryOptionResult.CancelOperation); - }); - } - return Disposable.Empty; + UserError = userError; + return clearWhen + .Skip(1) + .Do(_ => UserError = null) + .Select(x => RecoveryOptionResult.CancelOperation); + }); } } } diff --git a/src/GitHub.UI.Reactive/Controls/Validation/ValidationMessage.cs b/src/GitHub.UI.Reactive/Controls/Validation/ValidationMessage.cs index 0fe6eb9063..de1a49b7af 100644 --- a/src/GitHub.UI.Reactive/Controls/Validation/ValidationMessage.cs +++ b/src/GitHub.UI.Reactive/Controls/Validation/ValidationMessage.cs @@ -9,7 +9,6 @@ using System.Windows.Media; using GitHub.Extensions.Reactive; using GitHub.Validation; -using NullGuard; using ReactiveUI; namespace GitHub.UI @@ -17,6 +16,7 @@ namespace GitHub.UI public class ValidationMessage : UserControl { const double defaultTextChangeThrottle = 0.2; + bool userHasInteracted; public ValidationMessage() { @@ -33,6 +33,7 @@ public ValidationMessage() .Do(CreateBinding) .Select(control => Observable.Merge( + this.WhenAnyValue(x => x.ShowError).Where(x => userHasInteracted), control.Events().TextChanged .Throttle(TimeSpan.FromSeconds(ShowError ? defaultTextChangeThrottle : TextChangeThrottle), RxApp.MainThreadScheduler) @@ -56,7 +57,6 @@ public bool IsShowingMessage public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ValidationMessage)); public string Text { - [return: AllowNull] get { return (string)GetValue(TextProperty); } private set { SetValue(TextProperty, value); } } @@ -78,7 +78,6 @@ public double TextChangeThrottle public static readonly DependencyProperty ValidatesControlProperty = DependencyProperty.Register("ValidatesControl", typeof(TextBox), typeof(ValidationMessage), new PropertyMetadata(default(TextBox))); public TextBox ValidatesControl { - [return: AllowNull] get { return (TextBox)GetValue(ValidatesControlProperty); } set { SetValue(ValidatesControlProperty, value); } } @@ -86,7 +85,6 @@ public TextBox ValidatesControl public static readonly DependencyProperty ReactiveValidatorProperty = DependencyProperty.Register("ReactiveValidator", typeof(ReactivePropertyValidator), typeof(ValidationMessage)); public ReactivePropertyValidator ReactiveValidator { - [return: AllowNull] get { return (ReactivePropertyValidator)GetValue(ReactiveValidatorProperty); } set { SetValue(ReactiveValidatorProperty, value); } } @@ -94,7 +92,6 @@ public ReactivePropertyValidator ReactiveValidator public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(Octicon), typeof(ValidationMessage), new PropertyMetadata(Octicon.stop)); public Octicon Icon { - [return: AllowNull] get { return (Octicon) GetValue(IconProperty); } set { SetValue(IconProperty, value); } } @@ -103,16 +100,13 @@ public Octicon Icon DependencyProperty.Register("Fill", typeof(Brush), typeof(ValidationMessage), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0xe7, 0x4c, 0x3c)))); public Brush Fill { - [return: AllowNull] get { return (Brush)GetValue(FillProperty); } set { SetValue(FillProperty, value); } } public static readonly DependencyProperty ErrorAdornerTemplateProperty = DependencyProperty.Register("ErrorAdornerTemplate", typeof(string), typeof(ValidationMessage), new PropertyMetadata("validationTemplate")); - [AllowNull] public string ErrorAdornerTemplate { - [return: AllowNull] get { return (string)GetValue(ErrorAdornerTemplateProperty); } set { SetValue(ErrorAdornerTemplateProperty, value); } } @@ -120,6 +114,7 @@ public string ErrorAdornerTemplate void ShowValidateError(bool showError) { IsShowingMessage = showError; + userHasInteracted = true; if (ValidatesControl == null || !IsAdornerEnabled()) return; diff --git a/src/GitHub.UI.Reactive/Controls/ViewBase.cs b/src/GitHub.UI.Reactive/Controls/ViewBase.cs new file mode 100644 index 0000000000..38a95271b9 --- /dev/null +++ b/src/GitHub.UI.Reactive/Controls/ViewBase.cs @@ -0,0 +1,68 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Automation.Peers; +using GitHub.ViewModels; +using ReactiveUI; +using System.Reactive.Linq; + +namespace GitHub.UI +{ + /// <summary> + /// Base class for views. + /// </summary> + public class ViewBase<TInterface, TImplementor> : UserControl, IViewFor<TInterface> + where TInterface : class, IViewModel + where TImplementor : class + { + public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( + "ViewModel", typeof(TInterface), typeof(TImplementor), new PropertyMetadata(null)); + + /// <summary> + /// Initializes a new instance of the <see cref="ViewBase{TInterface, TImplementor}"/> class. + /// </summary> + public ViewBase() + { + DataContextChanged += (s, e) => ViewModel = (TInterface)e.NewValue; + this.WhenAnyValue(x => x.ViewModel).Skip(1).Subscribe(x => DataContext = x); + } + + /// <summary> + /// Gets or sets the control's data context as a typed view model. + /// </summary> + public TInterface ViewModel + { + get { return (TInterface)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + /// <summary> + /// Gets or sets the control's data context as a typed view model. Required for interaction + /// with ReactiveUI. + /// </summary> + TInterface IViewFor<TInterface>.ViewModel + { + get { return ViewModel; } + set { ViewModel = value; } + } + + /// <summary> + /// Gets or sets the control's data context. Required for interaction with ReactiveUI. + /// </summary> + object IViewFor.ViewModel + { + get { return ViewModel; } + set { ViewModel = (TInterface)value; } + } + + /// <summary> + /// Add an automation peer to views and custom controls + /// They do not have automation peers or properties by default + /// https://stackoverflow.com/questions/30198109/automationproperties-automationid-on-custom-control-not-exposed + /// </summary> + protected override AutomationPeer OnCreateAutomationPeer() + { + return new UIElementAutomationPeer(this); + } + } +} diff --git a/src/GitHub.UI.Reactive/FodyWeavers.xml b/src/GitHub.UI.Reactive/FodyWeavers.xml deleted file mode 100644 index 9321cb912f..0000000000 --- a/src/GitHub.UI.Reactive/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard/> -</Weavers> \ No newline at end of file diff --git a/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj b/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj index f649fcf797..9d3d489032 100644 --- a/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj +++ b/src/GitHub.UI.Reactive/GitHub.UI.Reactive.csproj @@ -5,56 +5,49 @@ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{158B05E8-FDBC-4D71-B871-C96E28D5ADF5}</ProjectGuid> + <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub</RootNamespace> <AssemblyName>GitHub.UI.Reactive</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <NuGetPackageImportStamp>0264ca35</NuGetPackageImportStamp> - <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>true</RunCodeAnalysis> <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>false</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>false</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <Private>True</Private> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> - </Reference> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> <Reference Include="System" /> @@ -92,13 +85,11 @@ <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> - <Compile Include="Controls\SimpleViewUserControl.cs" /> + <Compile Include="Controls\ViewBase.cs" /> <Compile Include="Controls\TwoFactorInput.xaml.cs"> <DependentUpon>TwoFactorInput.xaml</DependentUpon> </Compile> + <Compile Include="GlobalSuppressions.cs" /> <Compile Include="Helpers\FocusHelper.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> @@ -184,24 +175,16 @@ <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> <Project>{346384DD-2445-4A28-AF22-B45F3957BD89}</Project> <Name>GitHub.UI</Name> </ProjectReference> </ItemGroup> - <ItemGroup> - <Content Include="FodyWeavers.xml"> - <SubType>Designer</SubType> - </Content> - </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>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}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> - </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/GitHub.UI.Reactive/GlobalSuppressions.cs b/src/GitHub.UI.Reactive/GlobalSuppressions.cs new file mode 100644 index 0000000000..5f57521f46 Binary files /dev/null and b/src/GitHub.UI.Reactive/GlobalSuppressions.cs differ diff --git a/src/GitHub.UI.Reactive/Properties/AssemblyInfo.cs b/src/GitHub.UI.Reactive/Properties/AssemblyInfo.cs index 6d63eb6435..37e833d006 100644 --- a/src/GitHub.UI.Reactive/Properties/AssemblyInfo.cs +++ b/src/GitHub.UI.Reactive/Properties/AssemblyInfo.cs @@ -1,6 +1,19 @@ using System.Reflection; using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Markup; -[assembly: AssemblyTitle("GitHub.UI.Recative")] +[assembly: AssemblyTitle("GitHub.UI.Reactive")] [assembly: AssemblyDescription("GitHub flavored WPF styles and controls that require Rx and RxUI")] [assembly: Guid("885a491c-1d13-49e7-baa6-d61f424befcb")] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) + )] + +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.UI")] diff --git a/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs b/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs index 8f6f2b0cc6..5b52a4cf23 100644 --- a/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs +++ b/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs @@ -8,8 +8,8 @@ using System.Reactive.Linq; using System.Reflection; using ReactiveUI; -using NullGuard; using GitHub.Services; +using GitHub.Extensions; namespace GitHub.Validation { @@ -23,8 +23,10 @@ public class ReactiveValidatableObject : ReactiveObject, IDataErrorInfo readonly Dictionary<string, bool> enabledProperties = new Dictionary<string, bool>(); bool validationEnabled = true; - public ReactiveValidatableObject([AllowNull]IUIProvider serviceProvider) + public ReactiveValidatableObject(IGitHubServiceProvider serviceProvider) { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + validatedProperties = typeValidatorsMap.GetOrAdd(GetType(), GetValidatedProperties); this.serviceProvider = serviceProvider; // This is allowed to be null. @@ -38,6 +40,8 @@ public string this[string propertyName] { get { + Guard.ArgumentNotNull(propertyName, nameof(propertyName)); + if (!validationEnabled || !enabledProperties.ContainsKey(propertyName)) return null; string errorMessage = GetErrorMessage(propertyName); @@ -112,6 +116,8 @@ IEnumerable<string> EnableValidationForUnvalidatedProperties() // raise a property changed event. void TriggerValidationForProperty(string propertyName) { + Guard.ArgumentNotNull(propertyName, nameof(propertyName)); + this.RaisePropertyChanged(propertyName); } @@ -123,6 +129,8 @@ void TriggerValidationForAllProperties() string GetErrorMessage(string propertyName) { + Guard.ArgumentNotEmptyString(propertyName, nameof(propertyName)); + ValidatedProperty validatedProperty; if (!validatedProperties.TryGetValue(propertyName, out validatedProperty)) return null; // TODO: This would be a good place to do default data type validation as the need arises. @@ -146,6 +154,8 @@ public void SetErrorMessage(string propertyName, string errorMessage) static Dictionary<string, ValidatedProperty> GetValidatedProperties(Type type) { + Guard.ArgumentNotNull(type, nameof(type)); + return (from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public) let validated = new ValidatedProperty(property) where validated.Validators.Any() @@ -158,6 +168,8 @@ class ValidatedProperty public ValidatedProperty(PropertyInfo property) { + Guard.ArgumentNotNull(property, nameof(property)); + Property = property; validators = property.GetCustomAttributes(typeof(ValidationAttribute), true).Cast<ValidationAttribute>().ToList(); Validators = validators.Where(v => !(v is ValidateIfAttribute)); @@ -166,11 +178,16 @@ public ValidatedProperty(PropertyInfo property) public void AddValidator(ValidationAttribute validator) { + Guard.ArgumentNotNull(validator, nameof(validator)); + validators.Add(validator); } public ValidationResult GetFirstValidationError(object instance, IServiceProvider serviceProvider) { + Guard.ArgumentNotNull(instance, nameof(instance)); + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + var validationContext = new ValidationContext(instance, serviceProvider, null) { MemberName = Property.Name }; if (ConditionalValidation != null && !ConditionalValidation.IsValidationRequired(validationContext)) @@ -203,12 +220,18 @@ sealed class SetErrorValidator : ValidationAttribute public SetErrorValidator(string errorMessage, object originalValue) { + Guard.ArgumentNotEmptyString(errorMessage, nameof(errorMessage)); + Guard.ArgumentNotNull(originalValue, nameof(originalValue)); + this.errorMessage = errorMessage; this.originalValue = originalValue; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { + Guard.ArgumentNotNull(value, nameof(value)); + Guard.ArgumentNotNull(validationContext, nameof(validationContext)); + if (originalValue.Equals(value)) return new ValidationResult(errorMessage); diff --git a/src/GitHub.UI.Reactive/Validation/ValidateIfAttribute.cs b/src/GitHub.UI.Reactive/Validation/ValidateIfAttribute.cs index 26446fdf55..7bac18c1d3 100644 --- a/src/GitHub.UI.Reactive/Validation/ValidateIfAttribute.cs +++ b/src/GitHub.UI.Reactive/Validation/ValidateIfAttribute.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Reflection; using GitHub.Extensions; -using NullGuard; namespace GitHub.Validation { @@ -23,15 +22,22 @@ public ValidateIfAttribute(string dependentPropertyName) DependentPropertyName = dependentPropertyName; } - protected override ValidationResult IsValid([AllowNull]object value, [AllowNull]ValidationContext validationContext) + protected override ValidationResult IsValid(object value, ValidationContext validationContext) { return ValidationResult.Success; } public bool IsValidationRequired(ValidationContext validationContext) { + Guard.ArgumentNotNull(validationContext, nameof(validationContext)); + var instance = validationContext.ObjectInstance; - Debug.Assert(instance != null, "The ValidationContext does not allow null instances."); + + if (instance == null) + { + throw new ArgumentException("ValidationContext.ObjectInstance must not be null."); + } + var property = instance.GetType().GetProperty(DependentPropertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (property == null || property.PropertyType != typeof(bool)) { diff --git a/src/GitHub.UI.Reactive/packages.config b/src/GitHub.UI.Reactive/packages.config index c8a3b04c60..3a6b3c833a 100644 --- a/src/GitHub.UI.Reactive/packages.config +++ b/src/GitHub.UI.Reactive/packages.config @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Fody" version="1.28.0" targetFramework="net45" developmentDependency="true" userInstalled="true" /> <package id="Ix_Experimental-Main" version="1.1.10823" targetFramework="net45" userInstalled="true" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net45" developmentDependency="true" userInstalled="true" /> <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" userInstalled="true" /> diff --git a/src/GitHub.UI/Assets/Buttons.xaml b/src/GitHub.UI/Assets/Buttons.xaml index 15f223c118..dd442160d7 100644 --- a/src/GitHub.UI/Assets/Buttons.xaml +++ b/src/GitHub.UI/Assets/Buttons.xaml @@ -10,122 +10,11 @@ <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="../Controls/Buttons/OcticonCircleButton.xaml" /> <ResourceDictionary Source="../Controls/Buttons/OcticonLinkButton.xaml" /> + <ResourceDictionary Source="../Controls/Buttons/OcticonButton.xaml" /> <ResourceDictionary Source="../Controls/ToggleButtons/OcticonCircleToggleButton.xaml" /> <ResourceDictionary Source="../Controls/ToggleButtons/OcticonLinkToggleButton.xaml" /> </ResourceDictionary.MergedDictionaries> - <!-- Button --> - <Style x:Key="GitHubButton" TargetType="{x:Type Button}"> - <Style.Resources> - <LinearGradientBrush x:Key="GitHubButtonBackgroundGradientBrush" StartPoint="0.5,0" EndPoint="0.5,1"> - <GradientStop Color="#FFF1F1F1" Offset="0" /> - <GradientStop Color="#FFE1E1E1" Offset="1" /> - </LinearGradientBrush> - <LinearGradientBrush x:Key="GitHubButtonBackgroundMouseOverGradientBrush" StartPoint="0.5,0" EndPoint="0.5,1"> - <GradientStop Color="#FFE8E8E8" Offset="0" /> - <GradientStop Color="#FFD7D7D7" Offset="1" /> - </LinearGradientBrush> - <LinearGradientBrush x:Key="GitHubButtonBackgroundPressedGradientBrush" StartPoint="0.5,0" EndPoint="0.5,1"> - <GradientStop Color="#FFE8E8E8" Offset="0" /> - <GradientStop Color="#FFCDCDCD" Offset="1" /> - </LinearGradientBrush> - <LinearGradientBrush x:Key="GitHubButtonBackgroundDisabledGradientBrush" StartPoint="0.5,0" EndPoint="0.5,1"> - <GradientStop Color="#FFFCFCFC" Offset="0" /> - <GradientStop Color="#FFEBEBEB" Offset="1" /> - </LinearGradientBrush> - <SolidColorBrush x:Key="GitHubButtonForegroundBrush" Color="#FF666666" /> - <SolidColorBrush x:Key="GitHubButtonBorderBrush" Color="#FFCACACA" /> - <SolidColorBrush x:Key="GitHubButtonBorderMouseOverBrush" Color="#FFCACACA" /> - <SolidColorBrush x:Key="GitHubButtonBorderPressedBrush" Color="#FFBFBFBF" /> - </Style.Resources> - <Setter Property="Background" Value="{DynamicResource GitHubButtonBackgroundGradientBrush}" /> - <Setter Property="BorderBrush" Value="{DynamicResource GitHubButtonBorderBrush}" /> - <Setter Property="Foreground" Value="{DynamicResource GitHubButtonForegroundBrush}" /> - <Setter Property="FontFamily" Value="{DynamicResource GitHubFontFamilyNormal}" /> - <Setter Property="FontSize" Value="12" /> - <Setter Property="Padding" Value="12,5" /> - <Setter Property="Margin" Value="0" /> - <Setter Property="MinWidth" Value="76" /> - <Setter Property="FocusVisualStyle" Value="{x:Null}" /> - <Setter Property="BorderThickness" Value="1" /> - <Setter Property="HorizontalAlignment" Value="Left" /> - <Setter Property="ToolTipService.ShowDuration" Value="30000" /> - <Setter Property="ToolTipService.ShowOnDisabled" Value="True" /> - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="{x:Type Button}"> - <Grid> - <VisualStateManager.VisualStateGroups> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="MouseOver"> - <Storyboard> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" - Storyboard.TargetName="MouseOverBorder"> - <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1" /> - </DoubleAnimationUsingKeyFrames> - </Storyboard> - </VisualState> - <VisualState x:Name="Pressed"> - <Storyboard> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" - Storyboard.TargetName="PressedBorder"> - <EasingDoubleKeyFrame KeyTime="0" Value="1" /> - </DoubleAnimationUsingKeyFrames> - </Storyboard> - </VisualState> - <VisualState x:Name="Disabled"> - <Storyboard> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" - Storyboard.TargetName="DisabledVisualElement"> - <SplineDoubleKeyFrame KeyTime="0" Value="0.5" /> - </DoubleAnimationUsingKeyFrames> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" - Storyboard.TargetName="contentPresenter"> - <EasingDoubleKeyFrame KeyTime="0" Value="0.5" /> - </DoubleAnimationUsingKeyFrames> - </Storyboard> - </VisualState> - </VisualStateGroup> - <VisualStateGroup x:Name="FocusStates"> - <VisualState x:Name="Focused" /> - <VisualState x:Name="Unfocused" /> - </VisualStateGroup> - </VisualStateManager.VisualStateGroups> - <Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" - BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" - Background="{TemplateBinding Background}" /> - <Rectangle x:Name="DisabledVisualElement" - Fill="{StaticResource GitHubButtonBackgroundDisabledGradientBrush}" SnapsToDevicePixels="True" IsHitTestVisible="false" - Opacity="0" /> - <Border x:Name="MouseOverBorder" BorderBrush="{StaticResource GitHubButtonBorderMouseOverBrush}" - Background="{StaticResource GitHubButtonBackgroundMouseOverGradientBrush}" - BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> - <Border x:Name="PressedBorder" BorderBrush="{StaticResource GitHubButtonBorderPressedBrush}" - Background="{StaticResource GitHubButtonBackgroundPressedGradientBrush}" - BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> - <Border x:Name="DefaultVisualElement" BorderBrush="{DynamicResource GitHubAccentBrush}" - Background="Transparent" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> - <ContentPresenter x:Name="contentPresenter" - SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentTemplate="{TemplateBinding ContentTemplate}" - Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" - Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> - </Grid> - <ControlTemplate.Triggers> - <Trigger Property="IsDefaulted" Value="True"> - <Setter Property="Opacity" Value="1" TargetName="DefaultVisualElement" /> - </Trigger> - <Trigger Property="IsKeyboardFocused" Value="True"> - <Setter Property="Opacity" Value="1" TargetName="DefaultVisualElement" /> - </Trigger> - </ControlTemplate.Triggers> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - <Style TargetType="{x:Type Button}" BasedOn="{StaticResource GitHubButton}" /> - <!-- End Button --> - <!-- LinkButton --> <SolidColorBrush x:Key="GitHubLinkButtonBrush" Color="#FF999999" /> <SolidColorBrush x:Key="GitHubLinkButtonMouseOverBrush" Color="#FF666666" /> @@ -231,4 +120,5 @@ </Setter.Value> </Setter> </Style> + </ResourceDictionary> diff --git a/src/GitHub.UI/Assets/Controls.xaml b/src/GitHub.UI/Assets/Controls.xaml index 47977c775a..4e0abcc18b 100644 --- a/src/GitHub.UI/Assets/Controls.xaml +++ b/src/GitHub.UI/Assets/Controls.xaml @@ -19,13 +19,6 @@ Styles for standard windows controls --> - <ContextMenu x:Key="DefaultContextMenu"> - <MenuItem Header="Cut" Command="ApplicationCommands.Cut"/> - <MenuItem Header="Copy" Command="ApplicationCommands.Copy"/> - <MenuItem Header="Paste" Command="ApplicationCommands.Paste"/> - </ContextMenu> - <!--End ContextMenu--> - <!-- ProgressBar --> <Style x:Key="GitHubProgressBar" TargetType="{x:Type ui:GitHubProgressBar}"> <Setter Property="Foreground" Value="#999999" /> @@ -419,7 +412,130 @@ </Setter.Value> </Setter> </Style> + <!-- End ProgressBar --> + <!-- Expander --> + + <!-- + A toggle button that displays a triangle expander for use in an Expander. Note that when + the button is disabled, the triangle expander is hidden. This is because this is the + behavior we want in the only place where we use disabled expanders currently (in + PullRequestUserReviewsView). + --> + <Style x:Key="TriangleToggleButton" TargetType="{x:Type ToggleButton}"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ToggleButton}"> + <Border Background="{TemplateBinding Background}" + Padding="{TemplateBinding Padding}"> + <DockPanel> + <Border Background="Transparent" Width="10" Margin="5,0"> + <Path Name="arrow" + DockPanel.Dock="Left" + Fill="{TemplateBinding Foreground}" + Height="7" + Width="7" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Stretch="UniformToFill" + Data="M7 1l-.025 5H2z" + Visibility="{TemplateBinding IsEnabled, Converter={ui:BooleanToHiddenVisibilityConverter}}"/> + </Border> + <ContentPresenter Margin="0" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + RecognizesAccessKey="True" + SnapsToDevicePixels="True" /> + </DockPanel> + </Border> + + <ControlTemplate.Triggers> + <Trigger Property="IsChecked" Value="False"> + <Setter TargetName="arrow" Property="LayoutTransform"> + <Setter.Value> + <RotateTransform Angle="-45" /> + </Setter.Value> + </Setter> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="ExpanderHeaderFocusVisual"> + <Setter Property="Control.Template"> + <Setter.Value> + <ControlTemplate> + <Border> + <Rectangle Margin="0" + SnapsToDevicePixels="true" + Stroke="Black" + StrokeDashArray="1 2" + StrokeThickness="1" /> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style TargetType="{x:Type Expander}"> + <Setter Property="Background" Value="Transparent" /> + <Setter Property="HorizontalContentAlignment" Value="Stretch" /> + <Setter Property="VerticalContentAlignment" Value="Stretch" /> + <Setter Property="BorderBrush" Value="Transparent" /> + <Setter Property="BorderThickness" Value="0" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Expander}"> + <Border SnapsToDevicePixels="true"> + <DockPanel> + <ToggleButton x:Name="HeaderSite" + MinWidth="0" + MinHeight="0" + Margin="0" + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + Content="{TemplateBinding Header}" + ContentTemplate="{TemplateBinding HeaderTemplate}" + ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" + DockPanel.Dock="Top" + FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" + FontFamily="{TemplateBinding FontFamily}" + FontSize="{TemplateBinding FontSize}" + FontStretch="{TemplateBinding FontStretch}" + FontStyle="{TemplateBinding FontStyle}" + FontWeight="{TemplateBinding FontWeight}" + Foreground="{TemplateBinding Foreground}" + IsEnabled="{TemplateBinding IsEnabled}" + IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" + Padding="{TemplateBinding Padding}" + Style="{StaticResource TriangleToggleButton}" /> + <ContentPresenter x:Name="ExpandSite" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + DockPanel.Dock="Bottom" + Focusable="false" + Visibility="Collapsed" /> + </DockPanel> + </Border> + <ControlTemplate.Triggers> + <Trigger Property="IsExpanded" Value="true"> + <Setter TargetName="ExpandSite" Property="Visibility" Value="Visible" /> + </Trigger> + <Trigger Property="IsEnabled" Value="false"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + <!-- End Expander --> <ControlTemplate x:Key="ValidationAdorner"> <Grid> diff --git a/src/GitHub.UI/Assets/Controls/FilterTextBox.xaml b/src/GitHub.UI/Assets/Controls/FilterTextBox.xaml index a379c03530..dc1708811c 100644 --- a/src/GitHub.UI/Assets/Controls/FilterTextBox.xaml +++ b/src/GitHub.UI/Assets/Controls/FilterTextBox.xaml @@ -13,9 +13,7 @@ <Setter Property="AllowDrop" Value="true"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> - <Setter Property="Height" Value="24" /> <Setter Property="Padding" Value="3,3,18,3" /> - <Setter Property="ContextMenu" Value="{DynamicResource DefaultContextMenu}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ui:FilterTextBox}"> @@ -40,7 +38,7 @@ </Border> </Border> - <Grid Margin="1,0,0,0"> + <Grid Margin="1,1,0,0"> <ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" @@ -51,7 +49,7 @@ Margin="0"/> <Label x:Name="PromptLabel" FontSize="{TemplateBinding FontSize}" - Foreground="{DynamicResource GitHubForegroundBrush}" + Foreground="{TemplateBinding Foreground}" Margin="2,0,0,0" Padding="{TemplateBinding Padding}" Opacity="0" @@ -63,6 +61,7 @@ VerticalAlignment="Top"> <TextBlock Text="{TemplateBinding PromptText}" + Opacity="0.5" TextTrimming="CharacterEllipsis" /> </Label> </Grid> @@ -122,7 +121,6 @@ </Trigger> <DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0"> <Setter Property="Opacity" TargetName="PromptLabel" Value="0.7" /> - <Setter Property="Foreground" Value="Transparent" /> <Setter Property="Visibility" TargetName="clearButton" Value="Collapsed" /> </DataTrigger> </ControlTemplate.Triggers> diff --git a/src/GitHub.UI/Assets/Markdown.xaml b/src/GitHub.UI/Assets/Markdown.xaml new file mode 100644 index 0000000000..ae1d329900 --- /dev/null +++ b/src/GitHub.UI/Assets/Markdown.xaml @@ -0,0 +1,55 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"> + <Style TargetType="{x:Type FlowDocument}" x:Key="{x:Static markdig:Styles.DocumentStyleKey}"> + <Setter Property="FontFamily" Value="Segoe UI"/> + <Setter Property="FontSize" Value="12"/> + <Setter Property="TextAlignment" Value="Left"/> + <Setter Property="PagePadding" Value="0"/> + </Style> + <Style TargetType="{x:Type List}"> + <Setter Property="Margin" Value="0" /> + </Style> + <Style TargetType="{x:Type Paragraph}" x:Key="{x:Static markdig:Styles.Heading1StyleKey}"> + <Setter Property="FontSize" Value="19" /> + <Setter Property="Foreground" Value="{DynamicResource VsBrush.WindowText}" /> + <Setter Property="FontWeight" Value="Light" /> + </Style> + <Style TargetType="{x:Type Paragraph}" x:Key="{x:Static markdig:Styles.Heading2StyleKey}"> + <Setter Property="FontSize" Value="17" /> + <Setter Property="Foreground" Value="{DynamicResource VsBrush.WindowText}" /> + <Setter Property="FontWeight" Value="Light" /> + </Style> + <Style TargetType="{x:Type Paragraph}" x:Key="{x:Static markdig:Styles.Heading3StyleKey}"> + <Setter Property="FontSize" Value="16" /> + <Setter Property="Foreground" Value="{DynamicResource VsBrush.WindowText}" /> + <Setter Property="FontWeight" Value="Light" /> + </Style> + <Style TargetType="{x:Type Paragraph}" x:Key="{x:Static markdig:Styles.Heading4StyleKey}"> + <Setter Property="FontSize" Value="14" /> + <Setter Property="Foreground" Value="{DynamicResource VsBrush.WindowText}" /> + <Setter Property="FontWeight" Value="Light" /> + <Setter Property="TextDecorations" Value="Underline" /> + </Style> + <Style TargetType="{x:Type Table}" x:Key="{x:Static markdig:Styles.TableStyleKey}"> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubHeaderSeparatorBrush}"/> + <Setter Property="BorderThickness" Value="0,0,1,1"/> + <Setter Property="CellSpacing" Value="0"/> + </Style> + <Style TargetType="{x:Type TableCell}" x:Key="{x:Static markdig:Styles.TableCellStyleKey}"> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubHeaderSeparatorBrush}"/> + <Setter Property="BorderThickness" Value="1,1,0,0"/> + <Setter Property="Padding" Value="4"/> + </Style> + <Style TargetType="{x:Type Paragraph}" x:Key="{x:Static markdig:Styles.CodeBlockStyleKey}"> + <Setter Property="Foreground" Value="{DynamicResource VsBrush.Text}" /> + <Setter Property="Background" Value="#66d3d3d3" /> + <Setter Property="Padding" Value="2" /> + <Setter Property="FontFamily" Value="Consolas, Lucida Sans Typewriter, Courier New" /> + </Style> + <Style TargetType="{x:Type Run}" x:Key="{x:Static markdig:Styles.CodeStyleKey}"> + <Setter Property="Foreground" Value="{DynamicResource VsBrush.Text}" /> + <Setter Property="Background" Value="#66d3d3d3" /> + <Setter Property="FontFamily" Value="Consolas, Lucida Sans Typewriter, Courier New" /> + </Style> +</ResourceDictionary> diff --git a/src/GitHub.UI/Assets/Styles.xaml b/src/GitHub.UI/Assets/Styles.xaml index 89fce77076..b7c80a3c6d 100644 --- a/src/GitHub.UI/Assets/Styles.xaml +++ b/src/GitHub.UI/Assets/Styles.xaml @@ -183,7 +183,7 @@ </Setter> </Style> - <Style x:Key="DialogUserControl" TargetType="UserControl"> + <Style x:Key="DialogUserControl" TargetType="Control"> <Setter Property="Margin" Value="30,30,30,0" /> </Style> diff --git a/src/GitHub.UI/Assets/TextBlocks.xaml b/src/GitHub.UI/Assets/TextBlocks.xaml index 26786fd1d3..5e3c635117 100644 --- a/src/GitHub.UI/Assets/TextBlocks.xaml +++ b/src/GitHub.UI/Assets/TextBlocks.xaml @@ -15,7 +15,6 @@ <Setter Property="BorderThickness" Value="1" /> <Setter Property="ToolTipService.ShowDuration" Value="30000" /> <Setter Property="ToolTipService.ShowOnDisabled" Value="True" /> - <Setter Property="ContextMenu" Value="{DynamicResource DefaultContextMenu}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> @@ -71,7 +70,11 @@ </Border> </Border> - <Grid Margin="1,0,0,0"> + <Grid Margin="1,2,0,0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> <ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Top" Margin="0"/> <Label x:Name="PromptLabel" HorizontalAlignment="Left" Foreground="{DynamicResource GHTextBrush}" @@ -81,6 +84,10 @@ VerticalAlignment="Top"> <TextBlock Text="{TemplateBinding PromptText}" TextTrimming="CharacterEllipsis" /> </Label> + <ContentControl Grid.Column="1" + Content="{TemplateBinding IconContent}" + ContentTemplate="{TemplateBinding IconContentTemplate}" + Foreground="{TemplateBinding Foreground}"/> </Grid> </Grid> @@ -96,7 +103,6 @@ </Trigger> <DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0"> <Setter Property="Opacity" TargetName="PromptLabel" Value="0.7" /> - <Setter Property="Foreground" Value="Transparent" /> </DataTrigger> </ControlTemplate.Triggers> diff --git a/src/GitHub.UI/Behaviours/ClosePopupAction.cs b/src/GitHub.UI/Behaviours/ClosePopupAction.cs new file mode 100644 index 0000000000..382fbb75e0 --- /dev/null +++ b/src/GitHub.UI/Behaviours/ClosePopupAction.cs @@ -0,0 +1,13 @@ +using System.Windows.Controls.Primitives; +using System.Windows.Interactivity; + +namespace GitHub.UI +{ + public class ClosePopupAction : TargetedTriggerAction<Popup> + { + protected override void Invoke(object parameter) + { + Target.IsOpen = false; + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Behaviours/OpenPopupAction.cs b/src/GitHub.UI/Behaviours/OpenPopupAction.cs new file mode 100644 index 0000000000..8a3a63ca3d --- /dev/null +++ b/src/GitHub.UI/Behaviours/OpenPopupAction.cs @@ -0,0 +1,13 @@ +using System.Windows.Controls.Primitives; +using System.Windows.Interactivity; + +namespace GitHub.UI +{ + public class OpenPopupAction : TargetedTriggerAction<Popup> + { + protected override void Invoke(object parameter) + { + Target.IsOpen = true; + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AppendingPathTextBox.cs b/src/GitHub.UI/Controls/AppendingPathTextBox.cs index 8ebc340dcd..52299831c4 100644 --- a/src/GitHub.UI/Controls/AppendingPathTextBox.cs +++ b/src/GitHub.UI/Controls/AppendingPathTextBox.cs @@ -1,7 +1,6 @@ using System; using System.Windows; using System.Windows.Controls; -using NullGuard; namespace GitHub.UI { @@ -12,14 +11,12 @@ public class AppendingPathTextBox : TextBox public string ParentFolderPath { - [return: AllowNull] get { return (string)GetValue(ParentFolderPathProperty); } set { SetValue(ParentFolderPathProperty, value); } } public string ChildFolderName { - [return: AllowNull] get { return (string)GetValue(ChildFolderNameProperty); } set { SetValue(ChildFolderNameProperty, value); } } @@ -29,7 +26,6 @@ public string ChildFolderName public Visibility PathSeparatorVisibility { - [return: AllowNull] get { return (Visibility)GetValue(PathSeparatorVisibilityProperty); } set { SetValue(PathSeparatorVisibilityProperty, value); } } @@ -39,7 +35,6 @@ public Visibility PathSeparatorVisibility public Visibility ChildFolderVisibility { - [return: AllowNull] get { return (Visibility)GetValue(ChildFolderVisibilityProperty); } set { SetValue(ChildFolderVisibilityProperty, value); } } diff --git a/src/GitHub.UI/Controls/Buttons/GitHubActionLink.cs b/src/GitHub.UI/Controls/Buttons/GitHubActionLink.cs new file mode 100644 index 0000000000..a7b0f3d0bf --- /dev/null +++ b/src/GitHub.UI/Controls/Buttons/GitHubActionLink.cs @@ -0,0 +1,39 @@ +using System.Windows; +using System.Windows.Controls; + +namespace GitHub.UI +{ + public partial class GitHubActionLink : Button + { + public static readonly DependencyProperty HasDropDownProperty = DependencyProperty.Register( + "HasDropDown", typeof(bool), typeof(GitHubActionLink)); + + public static readonly DependencyProperty TextTrimmingProperty = + TextBlock.TextTrimmingProperty.AddOwner(typeof(GitHubActionLink)); + + public static readonly DependencyProperty TextWrappingProperty = + TextBlock.TextWrappingProperty.AddOwner(typeof(GitHubActionLink)); + + public bool HasDropDown + { + get { return (bool)GetValue(HasDropDownProperty); } + set { SetValue(HasDropDownProperty, value); } + } + + public TextTrimming TextTrimming + { + get { return (TextTrimming)GetValue(TextTrimmingProperty); } + set { SetValue(TextTrimmingProperty, value); } + } + + public TextWrapping TextWrapping + { + get { return (TextWrapping)GetValue(TextWrappingProperty); } + set { SetValue(TextWrappingProperty, value); } + } + + public GitHubActionLink() + { + } + } +} diff --git a/src/GitHub.UI/Controls/Buttons/OcticonButton.cs b/src/GitHub.UI/Controls/Buttons/OcticonButton.cs index 8d9b152799..6ecd689870 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonButton.cs +++ b/src/GitHub.UI/Controls/Buttons/OcticonButton.cs @@ -4,10 +4,12 @@ using System.Text; using System.Windows; using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; namespace GitHub.UI { - public class OcticonButton: Button + public class OcticonButton : Button { public static readonly DependencyProperty IconRotationAngleProperty = DependencyProperty.Register( "IconRotationAngle", typeof(double), typeof(OcticonButton), @@ -18,5 +20,34 @@ public double IconRotationAngle get { return (double)GetValue(IconRotationAngleProperty); } set { SetValue(IconRotationAngleProperty, value); } } + + public static DependencyProperty DataProperty = + Path.DataProperty.AddOwner(typeof(OcticonButton)); + + public Geometry Data + { + get { return (Geometry)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public static DependencyProperty IconProperty = + OcticonPath.IconProperty.AddOwner( + typeof(OcticonButton), + new FrameworkPropertyMetadata(defaultValue: Octicon.mark_github, flags: + FrameworkPropertyMetadataOptions.AffectsArrange | + FrameworkPropertyMetadataOptions.AffectsMeasure | + FrameworkPropertyMetadataOptions.AffectsRender, + propertyChangedCallback: OnIconChanged)); + + public Octicon Icon + { + get { return (Octicon)GetValue(OcticonPath.IconProperty); } + set { SetValue(OcticonPath.IconProperty, value); } + } + + static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + d.SetValue(DataProperty, OcticonPath.GetGeometryForIcon((Octicon)e.NewValue)); + } } } diff --git a/src/GitHub.UI/Controls/Buttons/OcticonButton.xaml b/src/GitHub.UI/Controls/Buttons/OcticonButton.xaml new file mode 100644 index 0000000000..dfbb963c87 --- /dev/null +++ b/src/GitHub.UI/Controls/Buttons/OcticonButton.xaml @@ -0,0 +1,106 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:GitHub.UI"> + + <Style x:Key="OcticonButton" TargetType="{x:Type ui:OcticonButton}"> + <Setter Property="Focusable" Value="True"/> + <Setter Property="Foreground" Value="Black"/> + <Setter Property="Height" Value="16"/> + <Setter Property="Width" Value="16"/> + <Setter Property="Margin" Value="0"/> + <Setter Property="Padding" Value="0"/> + <Setter Property="HorizontalAlignment" Value="Center"/> + <Setter Property="VerticalAlignment" Value="Center"/> + <Setter Property="BorderBrush" Value="Transparent"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="ui:OcticonButton"> + <Grid x:Name="content" Margin="{TemplateBinding Margin}" Background="Transparent"> + <VisualStateManager.VisualStateGroups> + <VisualStateGroup x:Name="CommonStates"> + <VisualState x:Name="Normal"/> + <VisualState x:Name="MouseOver"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MouseOverBorder" Storyboard.TargetProperty="(UIElement.Opacity)"> + <EasingDoubleKeyFrame KeyTime="0" Value="1"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Pressed"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PressedBorder" Storyboard.TargetProperty="(UIElement.Opacity)"> + <EasingDoubleKeyFrame KeyTime="0" Value="1"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Disabled"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity"> + <SplineDoubleKeyFrame KeyTime="0" Value="0.5"/> + </DoubleAnimationUsingKeyFrames> + <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Path" Storyboard.TargetProperty="(UIElement.Opacity)"> + <EasingDoubleKeyFrame KeyTime="0" Value="0.5"/> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + </VisualStateGroup> + <VisualStateGroup x:Name="FocusStates"> + <VisualState x:Name="Focused"/> + <VisualState x:Name="Unfocused"/> + </VisualStateGroup> + </VisualStateManager.VisualStateGroups> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <Border x:Name="Background" + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="1" + SnapsToDevicePixels="True"/> + <Rectangle x:Name="DisabledVisualElement" + Fill="#FFFCFCFC" + IsHitTestVisible="False" + Opacity="0" + SnapsToDevicePixels="True"/> + <Border x:Name="MouseOverBorder" + Background="{DynamicResource VsBrush.CommandBarHover}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="1" + Opacity="0" + SnapsToDevicePixels="True"/> + <Border x:Name="PressedBorder" + Background="{DynamicResource VsBrush.CommandBarMouseDownBackgroundBegin}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="1" + Opacity="0" + SnapsToDevicePixels="True"/> + <Border x:Name="DefaultVisualElement" + Background="Transparent" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="1" + Opacity="0" + SnapsToDevicePixels="True"/> + <Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="16" Height="16"> + <Border BorderBrush="Transparent" BorderThickness="1"> + + <ui:OcticonPath x:Name="Path" + Height="16" + Fill="{TemplateBinding Foreground}" + Icon="{TemplateBinding Icon}" + SnapsToDevicePixels="True"/> + </Border> + </Viewbox> + </Grid> + <ControlTemplate.Triggers> + <Trigger Property="IsDefaulted" Value="True"> + <Setter TargetName="DefaultVisualElement" Property="Opacity" Value="1"/> + </Trigger> + <Trigger Property="IsKeyboardFocused" Value="True"> + <Setter TargetName="DefaultVisualElement" Property="Opacity" Value="1"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style BasedOn="{StaticResource OcticonButton}" TargetType="{x:Type ui:OcticonButton}"/> +</ResourceDictionary> diff --git a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs b/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs index 0fbb3dc69f..b40a6876c0 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs +++ b/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs @@ -1,7 +1,6 @@ using System.Windows; using System.Windows.Media; using System.Windows.Shapes; -using NullGuard; namespace GitHub.UI { @@ -28,16 +27,6 @@ public class OcticonCircleButton : OcticonButton FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)); - public static readonly DependencyProperty IconProperty = DependencyProperty.Register( - "Icon", typeof(Octicon), typeof(OcticonCircleButton), - new FrameworkPropertyMetadata(defaultValue: Octicon.mark_github, flags: - FrameworkPropertyMetadataOptions.AffectsArrange | - FrameworkPropertyMetadataOptions.AffectsMeasure | - FrameworkPropertyMetadataOptions.AffectsRender, - propertyChangedCallback: OnIconChanged - ) - ); - public bool ShowSpinner { get { return (bool)GetValue(ShowSpinnerProperty); } @@ -46,28 +35,24 @@ public bool ShowSpinner public Brush IconForeground { - [return: AllowNull] get { return (Brush)GetValue(IconForegroundProperty); } set { SetValue(IconForegroundProperty, value); } } public Brush ActiveBackground { - [return: AllowNull] get { return (Brush)GetValue(ActiveBackgroundProperty); } set { SetValue(ActiveBackgroundProperty, value); } } public Brush ActiveForeground { - [return: AllowNull] get { return (Brush)GetValue(ActiveForegroundProperty); } set { SetValue(ActiveForegroundProperty, value); } } public Brush PressedBackground { - [return: AllowNull] get { return (Brush)GetValue(PressedBackgroundProperty); } set { SetValue(PressedBackgroundProperty, value); } } @@ -78,20 +63,6 @@ public double IconSize set { SetValue(IconSizeProperty, value); } } - public Octicon Icon - { - [return: AllowNull] - get { return (Octicon)GetValue(OcticonPath.IconProperty); } - set { SetValue(OcticonPath.IconProperty, value); } - } - - public Geometry Data - { - [return: AllowNull] - get { return (Geometry)GetValue(Path.DataProperty); } - set { SetValue(Path.DataProperty, value); } - } - static OcticonCircleButton() { Path.DataProperty.AddOwner(typeof(OcticonCircleButton)); diff --git a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml b/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml index 8080a0f200..09fa8bdb25 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml +++ b/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml @@ -191,7 +191,7 @@ Stretch="Uniform"> <Path x:Name="octicon" - Height="1024" + Height="16" Stretch="None" Fill="{Binding Path=IconForeground, RelativeSource={RelativeSource TemplatedParent}}" @@ -253,7 +253,7 @@ RelativeSource={RelativeSource TemplatedParent}}" Stretch="Uniform"> <Path - Height="1024" + Height="16" Stretch="None" Fill="{Binding Path=ActiveForeground, RelativeSource={RelativeSource TemplatedParent}}" diff --git a/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.cs b/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.cs index a1e15d29a4..c5cea6ae1f 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.cs +++ b/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.cs @@ -10,25 +10,11 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; -using NullGuard; namespace GitHub.UI { public partial class OcticonLinkButton : OcticonButton { - public Octicon Icon - { - get { return (Octicon)GetValue(OcticonPath.IconProperty); } - set { SetValue(OcticonPath.IconProperty, value); } - } - - public Geometry Data - { - [return: AllowNull] - get { return (Geometry)GetValue(Path.DataProperty); } - set { SetValue(Path.DataProperty, value); } - } - public static readonly DependencyProperty IconHeightProperty = DependencyProperty.Register("IconHeight", typeof(int), typeof(OcticonLinkButton),new PropertyMetadata(16)); public int IconHeight { diff --git a/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.xaml b/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.xaml index 640605434d..72f59b02f9 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.xaml +++ b/src/GitHub.UI/Controls/Buttons/OcticonLinkButton.xaml @@ -60,7 +60,7 @@ Width="{Binding Path=IconWidth, RelativeSource={RelativeSource TemplatedParent}, FallbackValue=16.0}"> <Path x:Name="octicon" SnapsToDevicePixels="True" - Height="1024" + Height="16" Data="{Binding Path=(Path.Data), RelativeSource={RelativeSource TemplatedParent}}"> <Path.Fill> <SolidColorBrush Color="{DynamicResource GHBlueLinkButtonIconColor}" /> diff --git a/src/GitHub.UI/Controls/ComboBoxes/LinkDropDown.cs b/src/GitHub.UI/Controls/ComboBoxes/LinkDropDown.cs new file mode 100644 index 0000000000..e1335e9fc6 --- /dev/null +++ b/src/GitHub.UI/Controls/ComboBoxes/LinkDropDown.cs @@ -0,0 +1,97 @@ +using System.Windows; +using System.Windows.Controls; + +namespace GitHub.UI +{ + /// <summary> + /// A ComboBox that displays as a link with a dropdown. + /// </summary> + public class LinkDropDown : ComboBox + { + /// <summary> + /// Defines the <see cref="LinkItem"/> property. + /// </summary> + static readonly DependencyPropertyKey LinkItemPropertyKey = + DependencyProperty.RegisterReadOnly( + "LinkItem", + typeof(object), + typeof(LinkDropDown), + new FrameworkPropertyMetadata(string.Empty)); + + /// <summary> + /// Defines the <see cref="LinkItemTemplate"/> property. + /// </summary> + public static readonly DependencyProperty LinkItemTemplateProperty = + DependencyProperty.Register( + "LinkItemTemplate", + typeof(DataTemplate), + typeof(LinkDropDown)); + + /// <summary> + /// Defines the <see cref="Header"/> property. + /// </summary> + public static readonly DependencyProperty HeaderProperty = + HeaderedItemsControl.HeaderProperty.AddOwner( + typeof(LinkDropDown), + new FrameworkPropertyMetadata(typeof(LinkDropDown), HeaderChanged)); + + /// <summary> + /// Defines the readonly <see cref="LinkItem"/> property. + /// </summary> + public static readonly DependencyProperty LinkItemProperty = + LinkItemPropertyKey.DependencyProperty; + + /// <summary> + /// Initializes static members of the <see cref="LinkDropDown"/> class. + /// </summary> + static LinkDropDown() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(LinkDropDown), + new FrameworkPropertyMetadata(typeof(LinkDropDown))); + } + + /// <summary> + /// Gets or sets a header to use in the link when no item is selected. + /// </summary> + public object Header + { + get { return GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + /// <summary> + /// Gets the data to display in the link. + /// </summary> + public object LinkItem + { + get { return (string)GetValue(LinkItemProperty); } + private set { SetValue(LinkItemPropertyKey, value); } + } + + /// <summary> + /// Gets the template to use to display the link. + /// </summary> + public object LinkItemTemplate + { + get { return (string)GetValue(LinkItemTemplateProperty); } + set { SetValue(LinkItemTemplateProperty, value); } + } + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + UpdateLinkItem(); + } + + private static void HeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var source = (LinkDropDown)d; + source.UpdateLinkItem(); + } + + private void UpdateLinkItem() + { + LinkItem = SelectedItem ?? Header; + } + } +} diff --git a/src/GitHub.UI/Controls/DropDownButton.cs b/src/GitHub.UI/Controls/DropDownButton.cs new file mode 100644 index 0000000000..4df0c57fbc --- /dev/null +++ b/src/GitHub.UI/Controls/DropDownButton.cs @@ -0,0 +1,89 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; + +namespace GitHub.UI +{ + public class DropDownButton : ContentControl + { + public static readonly DependencyProperty AutoCloseOnClickProperty = + DependencyProperty.Register( + "AutoCloseOnClick", + typeof(bool), + typeof(DropDownButton), + new FrameworkPropertyMetadata(true)); + public static readonly DependencyProperty DropDownContentProperty = + DependencyProperty.Register(nameof(DropDownContent), typeof(object), typeof(DropDownButton)); + public static readonly DependencyProperty IsOpenProperty = + Popup.IsOpenProperty.AddOwner(typeof(DropDownButton)); + + Button button; + Popup popup; + + static DropDownButton() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(DropDownButton), + new FrameworkPropertyMetadata(typeof(DropDownButton))); + } + + public bool AutoCloseOnClick + { + get { return (bool)GetValue(AutoCloseOnClickProperty); } + set { SetValue(AutoCloseOnClickProperty, value); } + } + + public object DropDownContent + { + get { return GetValue(DropDownContentProperty); } + set { SetValue(DropDownContentProperty, value); } + } + + public bool IsOpen + { + get { return (bool)GetValue(IsOpenProperty); } + set { SetValue(IsOpenProperty, value); } + } + + public event EventHandler PopupOpened; + public event EventHandler PopupClosed; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + button = (Button)Template.FindName("PART_Button", this); + popup = (Popup)Template.FindName("PART_Popup", this); + button.Click += ButtonClick; + popup.Opened += OnPopupOpened; + popup.Closed += OnPopupClosed; + popup.AddHandler(MouseUpEvent, new RoutedEventHandler(PopupMouseUp), true); + } + + void ButtonClick(object sender, RoutedEventArgs e) + { + IsOpen = true; + } + + private void OnPopupOpened(object sender, EventArgs e) + { + IsHitTestVisible = false; + PopupOpened?.Invoke(this, e); + } + + private void OnPopupClosed(object sender, EventArgs e) + { + IsOpen = false; + IsHitTestVisible = true; + PopupClosed?.Invoke(this, e); + } + + private void PopupMouseUp(object sender, RoutedEventArgs e) + { + if (AutoCloseOnClick) + { + IsOpen = false; + } + } + } +} diff --git a/src/GitHub.UI/Controls/DropDownButton.xaml b/src/GitHub.UI/Controls/DropDownButton.xaml new file mode 100644 index 0000000000..4b80ab5eb2 --- /dev/null +++ b/src/GitHub.UI/Controls/DropDownButton.xaml @@ -0,0 +1,41 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:GitHub.UI"> + <Style TargetType="local:DropDownButton"> + <Setter Property="Background" Value="Transparent"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="local:DropDownButton"> + <StackPanel> + <Button Name="PART_Button" + Background="{TemplateBinding Background}" + Content="{TemplateBinding Content}" + Foreground="{TemplateBinding Foreground}" + Margin="{TemplateBinding Padding}"> + <Button.Template> + <ControlTemplate TargetType="Button"> + <DockPanel Background="{TemplateBinding Background}"> + <Polygon DockPanel.Dock="Right" + Margin="4,0" + Fill="{TemplateBinding Foreground}" + Points="0,0 8,0 4,4 0,0" + VerticalAlignment="Center"/> + <ContentPresenter/> + </DockPanel> + </ControlTemplate> + </Button.Template> + </Button> + <Popup Name="PART_Popup" + IsOpen="{TemplateBinding IsOpen}" + Placement="Bottom" + PlacementTarget="{Binding ElementName=PART_Button}" + StaysOpen="False" + MinWidth="{Binding ActualWidth,ElementName=PART_Button}"> + <ContentControl Content="{TemplateBinding DropDownContent}"/> + </Popup> + </StackPanel> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.UI/Controls/FilterTextBox.cs b/src/GitHub.UI/Controls/FilterTextBox.cs index 7ffc7ee209..dafd704731 100644 --- a/src/GitHub.UI/Controls/FilterTextBox.cs +++ b/src/GitHub.UI/Controls/FilterTextBox.cs @@ -4,7 +4,6 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -using NullGuard; namespace GitHub.UI { @@ -17,7 +16,6 @@ public class FilterTextBox : TextBox [DefaultValue("Filter")] public string PromptText { - [return: AllowNull] get { return (string)GetValue(PromptTextProperty); } set { SetValue(PromptTextProperty, value); } } diff --git a/src/GitHub.UI/Controls/Octicons/OcticonImage.cs b/src/GitHub.UI/Controls/Octicons/OcticonImage.cs index a2937631bc..62484addad 100644 --- a/src/GitHub.UI/Controls/Octicons/OcticonImage.cs +++ b/src/GitHub.UI/Controls/Octicons/OcticonImage.cs @@ -1,6 +1,5 @@ using System.Windows; using System.Windows.Controls; -using NullGuard; namespace GitHub.UI { @@ -8,7 +7,6 @@ public class OcticonImage : Control { public Octicon Icon { - [return: AllowNull] get { return (Octicon)GetValue(OcticonPath.IconProperty); } set { SetValue(OcticonPath.IconProperty, value); } } diff --git a/src/GitHub.UI/Controls/Octicons/OcticonImage.xaml b/src/GitHub.UI/Controls/Octicons/OcticonImage.xaml index b8a739a366..54d7cba55d 100644 --- a/src/GitHub.UI/Controls/Octicons/OcticonImage.xaml +++ b/src/GitHub.UI/Controls/Octicons/OcticonImage.xaml @@ -5,7 +5,6 @@ <Style x:Key="OcticonImage" TargetType="{x:Type ui:OcticonImage}"> <Setter Property="Focusable" Value="False" /> - <Setter Property="Foreground" Value="Black" /> <Setter Property="Height" Value="16" /> <Setter Property="Width" Value="16" /> <Setter Property="HorizontalAlignment" Value="Center" /> @@ -13,12 +12,14 @@ <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ui:OcticonImage"> - <ui:FixedAspectRatioPanel HorizontalContentAlignment="Center" VerticalContentAlignment="Center"> + <ui:FixedAspectRatioPanel Background="{TemplateBinding Background}" + HorizontalContentAlignment="Center" + VerticalContentAlignment="Center"> <Viewbox> <ui:OcticonPath Fill="{TemplateBinding Foreground}" Icon="{TemplateBinding Icon}" - Height="1024" + Height="16" SnapsToDevicePixels="True" /> </Viewbox> </ui:FixedAspectRatioPanel> diff --git a/src/GitHub.UI/Controls/Octicons/OcticonPath.cs b/src/GitHub.UI/Controls/Octicons/OcticonPath.cs index 74459f727f..8a87a661bd 100644 --- a/src/GitHub.UI/Controls/Octicons/OcticonPath.cs +++ b/src/GitHub.UI/Controls/Octicons/OcticonPath.cs @@ -7,7 +7,6 @@ using System.Windows.Media; using System.Windows.Shapes; using GitHub.UI.Controls.Octicons; -using NullGuard; namespace GitHub.UI { @@ -28,13 +27,13 @@ public class OcticonPath : Shape new FrameworkPropertyMetadata(defaultValue: Octicon.mark_github, flags: FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | - FrameworkPropertyMetadataOptions.AffectsRender + FrameworkPropertyMetadataOptions.AffectsRender, + propertyChangedCallback: OnIconChanged ) ); public Octicon Icon { - [return: AllowNull] get { return (Octicon)GetValue(IconProperty); } set { SetValue(IconProperty, value); } } @@ -85,5 +84,9 @@ static Geometry LoadGeometry(Octicon icon) return path; } + static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + d.SetValue(Path.DataProperty, OcticonPath.GetGeometryForIcon((Octicon)e.NewValue)); + } } } \ No newline at end of file diff --git a/src/GitHub.UI/Controls/Octicons/OcticonPaths.Designer.cs b/src/GitHub.UI/Controls/Octicons/OcticonPaths.Designer.cs index 143fe8ac5b..803178a2b3 100644 --- a/src/GitHub.UI/Controls/Octicons/OcticonPaths.Designer.cs +++ b/src/GitHub.UI/Controls/Octicons/OcticonPaths.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.18051 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -19,7 +19,7 @@ namespace GitHub.UI.Controls.Octicons { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class OcticonPaths { @@ -61,7 +61,7 @@ internal OcticonPaths() { } /// <summary> - /// Looks up a localized string similar to M704,448h-64V256c0,0,0-256-256-256S128,256,128,256v192H64c0,0-64,0-64,64v448c0,64,64,64,64,64h640c0,0,64,0,64-64V512 C768,448,704,448,704,448z M512,576H128v64h384v64H128v64h384v64H128v64h384v64H64V512h448V576z M512,448H256V256c0,0,0-128,128-128 s128,128,128,128V448z. + /// Looks up a localized string similar to M4 13h-1v-1h1v1z m8-6v7c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V7c0-0.55 0.45-1 1-1h1V4C2 1.8 3.8 0 6 0s4 1.8 4 4v2h1c0.55 0 1 0.45 1 1z m-8.2-1h4.41V4c0-1.22-0.98-2.2-2.2-2.2s-2.2 0.98-2.2 2.2v2z m7.2 1H2v7h9V7z m-7 1h-1v1h1v-1z m0 2h-1v1h1v-1z. /// </summary> internal static string _lock { get { @@ -70,7 +70,7 @@ internal static string _lock { } /// <summary> - /// Looks up a localized string similar to M448,768h128v-64H448V768z M448,384v256h128V384H448z M512,0L0,896h1024L512,0z M512,128l384,704H128L512,128z. + /// Looks up a localized string similar to M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z. /// </summary> internal static string alert { get { @@ -79,34 +79,7 @@ internal static string alert { } /// <summary> - /// Looks up a localized string similar to M192,64C85.938,64,0,149.938,0,256s85.938,192,192,192c106.062,0,192-85.938,192-192S298.062,64,192,64z M672,608l160-160 H384v448l160-160l288,288l128-128L672,608z. - /// </summary> - internal static string alignment_align { - get { - return ResourceManager.GetString("alignment_align", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M384,576l128-128l288,288l160-160v448H512l160-160L384,576z M192,448C85.938,448,0,362.062,0,256S85.938,64,192,64 c106.062,0,192,85.938,192,192S298.062,448,192,448z. - /// </summary> - internal static string alignment_aligned_to { - get { - return ResourceManager.GetString("alignment_aligned_to", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M512,192L384,320L128,64L0,192l256,256L128,576l64,64l384-384L512,192z M640,576l128-128l-64-64L320,768l64,64l128-128 l256,256l128-128L640,576z. - /// </summary> - internal static string alignment_unalign { - get { - return ResourceManager.GetString("alignment_unalign", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M448,448V192H192v256H0l320,384l320-384H448z. + /// Looks up a localized string similar to M7 7V3H3v4H0l5 6 5-6H7z. /// </summary> internal static string arrow_down { get { @@ -115,7 +88,7 @@ internal static string arrow_down { } /// <summary> - /// Looks up a localized string similar to M384,384V192L0,512l384,320V640h256V384H384z. + /// Looks up a localized string similar to M6 6V3L0 8l6 5V10h4V6H6z. /// </summary> internal static string arrow_left { get { @@ -124,7 +97,7 @@ internal static string arrow_left { } /// <summary> - /// Looks up a localized string similar to M640,512L256,192v192H0v256h256v192L640,512z. + /// Looks up a localized string similar to M10 8L4 3v3H0v4h4v3L10 8z. /// </summary> internal static string arrow_right { get { @@ -133,7 +106,7 @@ internal static string arrow_right { } /// <summary> - /// Looks up a localized string similar to M256,448V320H128v128H0l192,256l192-256H256z. + /// Looks up a localized string similar to M4 7V5H2v2H0l3 4 3-4H4z. /// </summary> internal static string arrow_small_down { get { @@ -142,7 +115,7 @@ internal static string arrow_small_down { } /// <summary> - /// Looks up a localized string similar to M256,448V320L0,512l256,192V576h128V448H256z. + /// Looks up a localized string similar to M4 7V5L0 8l4 3V9h2V7H4z. /// </summary> internal static string arrow_small_left { get { @@ -151,7 +124,7 @@ internal static string arrow_small_left { } /// <summary> - /// Looks up a localized string similar to M384,512L128,320v128H0v128h128v128L384,512z. + /// Looks up a localized string similar to M6 8L2 5v2H0v2h2v2L6 8z. /// </summary> internal static string arrow_small_right { get { @@ -160,7 +133,7 @@ internal static string arrow_small_right { } /// <summary> - /// Looks up a localized string similar to M192,320L0,576h128v128h128V576h128L192,320z. + /// Looks up a localized string similar to M3 5L0 9h2v2h2V9h2L3 5z. /// </summary> internal static string arrow_small_up { get { @@ -169,7 +142,7 @@ internal static string arrow_small_up { } /// <summary> - /// Looks up a localized string similar to M320,192L0,576h192v256h256V576h192L320,192z. + /// Looks up a localized string similar to M5 3L0 9h3v4h4V9h3L5 3z. /// </summary> internal static string arrow_up { get { @@ -178,16 +151,34 @@ internal static string arrow_up { } /// <summary> - /// Looks up a localized string similar to M896,256H704V128C704,57.344,546.375,0,352,0S0,57.344,0,128v768c0,70.625,157.625,128,352,128s352-57.375,352-128V768h192 c0,0,64,0,64-64V320C960,256,896,256,896,256z M192,832h-64V320h64V832z M384,896h-64V384h64V896z M576,832h-64V320h64V832z M352,192c-123.719,0-224-28.594-224-64c0-35.312,100.281-64,224-64s224,28.688,224,64C576,163.406,475.719,192,352,192z M832,640 H704V384h128V640z. + /// Looks up a localized string similar to M14.38 14.59L11 7V3h1v-1H3v1h1v4L0.63 14.59c-0.3 0.66 0.19 1.41 0.91 1.41h11.94c0.72 0 1.2-0.75 0.91-1.41zM3.75 10l1.25-3V3h5v4l1.25 3H3.75z m4.25-2h1v1h-1v-1z m-1-1h-1v-1h1v1z m0-3h1v1h-1v-1z m0-3h-1V0h1v1z. /// </summary> - internal static string beer { + internal static string beaker { get { - return ResourceManager.GetString("beer", resourceCulture); + return ResourceManager.GetString("beaker", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M320,320H192c-64,0-64,64-64,64h256C384,384,384,320,320,320z M320,448H192c-64,0-64,64-64,64h256C384,512,384,448,320,448z M320,576H192c-64,0-64,64-64,64h256C384,640,384,576,320,576z M768,320H640c-64,0-64,64-64,64h256C832,384,832,320,768,320z M768,448H640c-64,0-64,64-64,64h256C832,512,832,448,768,448z M768,576H640c-64,0-64,64-64,64h256C832,640,832,576,768,576z M608,160c-64,0-128,64-128,64s-64-64-128-64C192,160,0,224,0,224v608l448-32c0,0-1.281,32,31.375,32C512,832,512,800,512,800 l448,32V224C960,224,768, [rest of string was truncated]";. + /// Looks up a localized string similar to M14 12v1H0v-1l0.73-0.58c0.77-0.77 0.81-2.55 1.19-4.42 0.77-3.77 4.08-5 4.08-5 0-0.55 0.45-1 1-1s1 0.45 1 1c0 0 3.39 1.23 4.16 5 0.38 1.88 0.42 3.66 1.19 4.42l0.66 0.58z m-7 4c1.11 0 2-0.89 2-2H5c0 1.11 0.89 2 2 2z. + /// </summary> + internal static string bell { + get { + return ResourceManager.GetString("bell", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M0 2h3.83c2.48 0 4.3 0.75 4.3 2.95 0 1.14-0.63 2.23-1.67 2.61v0.06c1.33 0.3 2.3 1.23 2.3 2.86 0 2.39-1.97 3.52-4.61 3.52H0V2z m3.66 4.95c1.67 0 2.38-0.66 2.38-1.69 0-1.17-0.78-1.61-2.34-1.61H2.13v3.3h1.53z m0.27 5.39c1.77 0 2.75-0.64 2.75-1.98 0-1.27-0.95-1.81-2.75-1.81H2.13v3.8h1.8z. + /// </summary> + internal static string bold { + get { + return ResourceManager.GetString("bold", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M2 5h4v1H2v-1z m0 3h4v-1H2v1z m0 2h4v-1H2v1z m11-5H9v1h4v-1z m0 2H9v1h4v-1z m0 2H9v1h4v-1z m2-6v9c0 0.55-0.45 1-1 1H8.5l-1 1-1-1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h5.5l1 1 1-1h5.5c0.55 0 1 0.45 1 1z m-8 0.5l-0.5-0.5H1v9h6V3.5z m7-0.5H8.5l-0.5 0.5v8.5h6V3z. /// </summary> internal static string book { get { @@ -196,7 +187,7 @@ internal static string book { } /// <summary> - /// Looks up a localized string similar to M0,128v768l192-128l192,128V128H0z M316.25,324.75l-71.875,51.938l27.188,83.406c2.75,8.375-0.688,11.062-7.562,6.594 l-72-52.094l-72,52.031c-6.844,4.469-10.312,1.781-7.562-6.594l27.219-83.406L67.783,324.75c-6.469-5.125-5-9.219,3.906-9.219 l88-0.125l27.125-83.094c2.812-8.812,7.562-8.812,10.375,0l27.188,83.094l87.938,0.125 C321.25,315.531,322.688,319.625,316.25,324.75z. + /// Looks up a localized string similar to M9 0H1C0.27 0 0 0.27 0 1v15l5-3.09 5 3.09V1c0-0.73-0.27-1-1-1z m-0.78 4.25l-1.86 1.36 0.72 2.16c0.06 0.22-0.02 0.28-0.2 0.17l-1.88-1.34-1.88 1.34c-0.19 0.11-0.25 0.05-0.2-0.17l0.72-2.16-1.86-1.36c-0.17-0.16-0.14-0.23 0.09-0.23l2.3-0.03 0.7-2.16h0.25l0.7 2.16 2.3 0.03c0.23 0 0.27 0.08 0.09 0.23z. /// </summary> internal static string bookmark { get { @@ -205,7 +196,16 @@ internal static string bookmark { } /// <summary> - /// Looks up a localized string similar to M320,576v128c0,64,64,64,64,64v256h128V768c0,0,64,0,64-64V576c0-64-64-64-64-64h-63.938H384C384,512,320,512,320,576z M576,384c0-128-128-128-128-128s-128,0-128,128s128.062,128,128.062,128S576,512,576,384z M448,0C200.562,0,0,200.562,0,448 c0,197.125,128.188,362.688,305.156,422.625l-12.031-71.75C158.406,739.312,64,604.875,64,448C64,235.969,235.969,64,448,64 c212,0,384,171.969,384,384c0,156.875-94.375,291.312-229.125,350.875l-12,71.625C767.812,810.688,896,645.125,896,448 C896,200.562,695.438,0,448,0z M448,19 [rest of string was truncated]";. + /// Looks up a localized string similar to M9 4v-1c0-0.55-0.45-1-1-1H6c-0.55 0-1 0.45-1 1v1H1c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V5c0-0.55-0.45-1-1-1H9z m-3-1h2v1H6v-1z m7 6H8v1H6v-1H1V5h1v3h10V5h1v4z. + /// </summary> + internal static string briefcase { + get { + return ResourceManager.GetString("briefcase", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M9 9h-1c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1h-1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1h-1c-0.55 0-1 0.45-1 1v2h1v3c0 0.55 0.45 1 1 1h1c0.55 0 1-0.45 1-1V12h1V10c0-0.55-0.45-1-1-1zM7 7h1v1h-1v-1z m2 4h-1v4h-1V11h-1v-1h3v1z m2.09-3.5c0-1.98-1.61-3.59-3.59-3.59S3.91 5.52 3.91 7.5c0 0.28 0.03 0.55 0.09 0.81v1.98c-0.61-0.77-1-1.73-1-2.8 0-2.48 2.02-4.5 4.5-4.5s4.5 2.02 4.5 4.5c0 1.06-0.39 2.03-1 2.8V8.31c0.06-0.27 0.09-0.53 0.09-0.81z m3.91 0c0 2.88-1.63 5.38-4 6.63v-1.05c1.86-1.16 3.09-3.22 3.09-5.58 0-3.64-2 [rest of string was truncated]";. /// </summary> internal static string broadcast { get { @@ -214,7 +214,16 @@ internal static string broadcast { } /// <summary> - /// Looks up a localized string similar to M243.621,156.531C190.747,213.312,205.34,304,205.34,304s53.968,64,160,64c106.031,0,160.031-64,160.031-64 s14.375-89.469-37.375-146.312c32.375-18.031,51.438-44.094,43.562-61.812c-8.938-19.969-48.375-21.75-88.25-3.969 c-14.812,6.594-27.438,14.969-37.25,23.875c-12.438-2.25-25.625-3.781-40.72-3.781c-14.061,0-26.561,1.344-38.344,3.25 c-9.656-8.75-22.062-16.875-36.531-23.344c-39.875-17.719-79.375-15.938-88.25,3.969 C194.465,113.219,212.497,138.562,243.621,156.531z M644.746,569.75c-8.25-1.75-16.125-2.75-23.75-3 [rest of string was truncated]";. + /// Looks up a localized string similar to M5 3h1v1h-1V3zM3 3h1v1h-1V3zM1 3h1v1H1V3zM13 13H1V5h12V13zM13 4H7v-1h6V4zM14 3c0-0.55-0.45-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V3z. + /// </summary> + internal static string browser { + get { + return ResourceManager.GetString("browser", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M11 10h3v-1H11v-1l3.17-1.03-0.34-0.94-2.83 0.97v-1c0-0.55-0.45-1-1-1v-1c0-0.48-0.36-0.88-0.83-0.97l1.03-1.03h1.8V1H9.8L7.8 3h-0.59L5.2 1H3v1h1.8l1.03 1.03c-0.47 0.09-0.83 0.48-0.83 0.97v1c-0.55 0-1 0.45-1 1v1L1.17 6.03l-0.34 0.94 3.17 1.03v1H1v1h3v1L0.83 12.03l0.34 0.94 2.83-0.97v1c0 0.55 0.45 1 1 1h1l1-1V6h1v7l1 1h1c0.55 0 1-0.45 1-1v-1l2.83 0.97 0.34-0.94-3.17-1.03v-1zM9 5H6v-1h3v1z. /// </summary> internal static string bug { get { @@ -223,7 +232,7 @@ internal static string bug { } /// <summary> - /// Looks up a localized string similar to M704,512h-64v128h64V512z M576,512h-64v128h64V512z M704,320h-64v128h64V320z M832,512h-64v128h64V512z M576,704h-64v128h64 V704z M768,0h-64v128h64V0z M256,0h-64v128h64V0z M832,320h-64v128h64V320z M576,320h-64v128h64V320z M320,704h-64v128h64V704z M192,512h-64v128h64V512z M320,512h-64v128h64V512z M832,64v128H640V64H320v128H128V64H0v896h960V64H832z M896,896H64V256h832V896z M192,704h-64v128h64V704z M448,320h-64v128h64V320z M448,704h-64v128h64V704z M320,320h-64v128h64V320z M448,512h-64v128h64V512z M704,704h- [rest of string was truncated]";. + /// Looks up a localized string similar to M12 2h-1v1.5c0 0.28-0.22 0.5-0.5 0.5H8.5c-0.28 0-0.5-0.22-0.5-0.5v-1.5H5v1.5c0 0.28-0.22 0.5-0.5 0.5H2.5c-0.28 0-0.5-0.22-0.5-0.5v-1.5H1c-0.55 0-1 0.45-1 1v11c0 0.55 0.45 1 1 1h11c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 12H1V5h11v9zM4 3h-1V1h1v2z m6 0h-1V1h1v2zM5 7h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1zM3 9h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1zM3 11h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1zM3 13h-1v-1h1v1z m2 0h-1v-1 [rest of string was truncated]";. /// </summary> internal static string calendar { get { @@ -232,7 +241,7 @@ internal static string calendar { } /// <summary> - /// Looks up a localized string similar to M640,192L256,576L128,448L0,576l256,256l512-512L640,192z. + /// Looks up a localized string similar to M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z. /// </summary> internal static string check { get { @@ -241,7 +250,7 @@ internal static string check { } /// <summary> - /// Looks up a localized string similar to M760.688,516.219l-49.812-49.656c-6.438-6.529-16.938-6.594-23.375,0L582.5,571.5L462.375,691.875l-93.031-93.125 c-6.531-6.562-17.031-6.562-23.5,0l-49.719,49.688c-6.531,6.562-6.531,17.062,0,23.562l104.781,104.875l17.969,17.875l31.688,31.812 c6.562,6.562,17.188,6.562,23.562,0l49.625-49.688L760.625,539.78C767.25,533.312,767.25,522.812,760.688,516.219z M228.469,580.812 L278.156,531c42.469-42.375,116.344-42.438,158.781,0.062l25.312,25.312L576,448V128H0v704h320l-91.531-92.125 C184.688,695.938,184.688,624.625,22 [rest of string was truncated]";. + /// Looks up a localized string similar to M16 8.5L10 14.5 7 11.5l1.5-1.5 1.5 1.5 4.5-4.5 1.5 1.5zM5.7 12.2l0.8 0.8H2c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h7c0.55 0 1 0.45 1 1v6.5l-0.8-0.8c-0.39-0.39-1.03-0.39-1.42 0L5.7 10.8c-0.39 0.39-0.39 1.02 0 1.41zM4 4h5v-1H4v1z m0 2h5v-1H4v1z m0 2h3v-1H4v1z m-1 1h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z. /// </summary> internal static string checklist { get { @@ -250,7 +259,7 @@ internal static string checklist { } /// <summary> - /// Looks up a localized string similar to M512,320L320,512L128,320L0,448l320,320l320-320L512,320z. + /// Looks up a localized string similar to M5 11L0 6l1.5-1.5 3.5 3.75 3.5-3.75 1.5 1.5-5 5z. /// </summary> internal static string chevron_down { get { @@ -259,7 +268,7 @@ internal static string chevron_down { } /// <summary> - /// Looks up a localized string similar to M448,320L320,192L0,512l320,320l128-128L256,512L448,320z. + /// Looks up a localized string similar to M5.5 3l1.5 1.5-3.75 3.5 3.75 3.5-1.5 1.5L0.5 8l5-5z. /// </summary> internal static string chevron_left { get { @@ -268,7 +277,7 @@ internal static string chevron_left { } /// <summary> - /// Looks up a localized string similar to M128,192L0,320l192,192L0,704l128,128l320-320L128,192z. + /// Looks up a localized string similar to M7.5 8L2.5 13l-1.5-1.5 3.75-3.5L1 4.5l1.5-1.5 5 5z. /// </summary> internal static string chevron_right { get { @@ -277,7 +286,7 @@ internal static string chevron_right { } /// <summary> - /// Looks up a localized string similar to M320,256L0,576l128,128l192-192l192,192l128-128L320,256z. + /// Looks up a localized string similar to M10 9l-1.5 1.5-3.5-3.75L1.5 10.5 0 9l5-5 5 5z. /// </summary> internal static string chevron_up { get { @@ -286,7 +295,7 @@ internal static string chevron_up { } /// <summary> - /// Looks up a localized string similar to M320,192C143.219,192,0,335.219,0,512c0,176.75,143.219,320,320,320c176.75,0,320-143.25,320-320 C640,335.219,496.75,192,320,192z M320,320c27.656,0,53.688,6.094,77.438,16.562L144.562,589.438 C134.094,565.688,128,539.656,128,512C128,406,213.938,320,320,320z M320,704c-28.031,0-54.531-6.375-78.594-17.125l253.906-252.5 C505.875,458.188,512,484.281,512,512C512,618.062,426.062,704,320,704z. + /// Looks up a localized string similar to M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m0 1.3c1.3 0 2.5 0.44 3.47 1.17L2.47 11.47c-0.73-0.97-1.17-2.17-1.17-3.47 0-3.14 2.56-5.7 5.7-5.7z m0 11.41c-1.3 0-2.5-0.44-3.47-1.17l8-8c0.73 0.97 1.17 2.17 1.17 3.47 0 3.14-2.56 5.7-5.7 5.7z. /// </summary> internal static string circle_slash { get { @@ -295,7 +304,16 @@ internal static string circle_slash { } /// <summary> - /// Looks up a localized string similar to M704,896H64V320h640v192h64V192c0-35.312-28.625-64-64-64H512C512,57.344,454.656,0,384,0S256,57.344,256,128H64 c-35.406,0-64,28.688-64,64v704c0,35.375,28.594,64,64,64h640c35.375,0,64-28.625,64-64V768h-64V896z M192,192c0,0,0,0,64,0 s64-64,64-64c0-35.312,28.594-64,64-64s64,28.688,64,64c0,0,0,64,64,64h64c64,0,64,64,64,64H128C128,256,128,192,192,192z M128,704 h128v-64H128V704z M576,576V448L320,640l256,192V704h320V576H576z M128,832h192v-64H128V832z M448,384H128v64h320V384z M256,512H128 v64h128V512z. + /// Looks up a localized string similar to M3 5c0-0.55 0.45-1 1-1s1 0.45 1 1-0.45 1-1 1-1-0.45-1-1z m8 0c0-0.55-0.45-1-1-1s-1 0.45-1 1 0.45 1 1 1 1-0.45 1-1z m0 6c0-0.55-0.45-1-1-1s-1 0.45-1 1 0.45 1 1 1 1-0.45 1-1zM13 1H5v2.17c0.36 0.19 0.64 0.47 0.83 0.83h2.34c0.42-0.78 1.33-1.28 2.34-1.05 0.75 0.19 1.36 0.8 1.53 1.55 0.31 1.38-0.72 2.59-2.05 2.59-0.8 0-1.48-0.44-1.83-1.09H5.83c-0.42 0.8-1.33 1.28-2.34 1.03-0.73-0.17-1.34-0.78-1.52-1.52-0.25-1.02 0.23-1.92 1.03-2.34V1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1l5-5h2.17c0.42-0.78 1.33-1.28 2.34-1.05 [rest of string was truncated]";. + /// </summary> + internal static string circuit_board { + get { + return ResourceManager.GetString("circuit_board", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M2 12h4v1H2v-1z m5-6H2v1h5v-1z m2 3V7L6 10l3 3V11h5V9H9z m-4.5-1H2v1h2.5v-1zM2 11h2.5v-1H2v1z m9 1h1v2c-0.02 0.28-0.11 0.52-0.3 0.7s-0.42 0.28-0.7 0.3H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3C4 0.89 4.89 0 6 0s2 0.89 2 2h3c0.55 0 1 0.45 1 1v5h-1V5H1v9h10V12zM2 4h8c0-0.55-0.45-1-1-1h-1c-0.55 0-1-0.45-1-1s-0.45-1-1-1-1 0.45-1 1-0.45 1-1 1h-1c-0.55 0-1 0.45-1 1z. /// </summary> internal static string clippy { get { @@ -304,7 +322,7 @@ internal static string clippy { } /// <summary> - /// Looks up a localized string similar to M384,576h256l64-64l-64-64H512V256l-64-64l-64,64V576z M448,64C200.562,64,0,264.562,0,512c0,247.438,200.562,448,448,448 c247.438,0,448-200.562,448-448C896,264.562,695.438,64,448,64z M448,832c-176.25,0-320-143.75-320-320 c0-175.938,144.188-319.5,320-320c175.812,0.5,320,144.062,320,320C768,688.25,624.25,832,448,832z. + /// Looks up a localized string similar to M8 8h3v2H7c-0.55 0-1-0.45-1-1V4h2v4z m-1-5.7c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z. /// </summary> internal static string clock { get { @@ -313,7 +331,7 @@ internal static string clock { } /// <summary> - /// Looks up a localized string similar to M832,320c-8.75,0-17.125,1.406-25.625,2.562C757.625,208.25,644.125,128,512,128c-132.156,0-245.562,80.25-294.406,194.562 C209.156,321.406,200.781,320,192,320C85.938,320,0,405.938,0,512s85.938,192,192,192c20.531,0,39.875-4.25,58.375-10.375 C284.469,731.375,331.312,756.75,384,764.5v-65.25c-49.844-10.375-91.594-42.812-112.625-87.875C249.531,629,222.219,640,192,640 c-70.656,0-128-57.375-128-128c0-70.656,57.344-128,128-128c25.281,0,48.625,7.562,68.406,20.094 C281.344,283.781,385.594,192,512,192c126.5,0,229.75, [rest of string was truncated]";. + /// Looks up a localized string similar to M9 13h2l-3 3-3-3h2V8h2v5z m3-8c0-0.44-0.91-3-4.5-3-2.42 0-4.5 1.92-4.5 4C1.02 6 0 7.52 0 9c0 1.53 1 3 3 3 0.44 0 2.66 0 3 0v-1.3H3C1.38 10.7 1.3 9.28 1.3 9c0-0.17 0.05-1.7 1.7-1.7h1.3v-1.3c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2h1.3c0.81 0 2.7 0.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2H10v1.3c0.38 0 1.98 0 2 0 2.08 0 4-1.16 4-3.5 0-2.44-1.92-3.5-4-3.5z. /// </summary> internal static string cloud_download { get { @@ -322,7 +340,7 @@ internal static string cloud_download { } /// <summary> - /// Looks up a localized string similar to M512,384L320,576h128v320h128V576h128L512,384z M832,320c-8.75,0-17.125,1.406-25.625,2.562 C757.625,208.188,644.125,128,512,128c-132.156,0-245.562,80.188-294.406,194.562C209.156,321.406,200.781,320,192,320 C85.938,320,0,406,0,512c0,106.062,85.938,192,192,192c20.531,0,39.875-4.25,58.375-10.438 C284.469,731.375,331.312,756.75,384,764.5v-65.25c-49.844-10.375-91.594-42.812-112.625-87.75C249.531,629,222.219,640,192,640 c-70.656,0-128-57.375-128-128c0-70.656,57.344-128,128-128c25.281,0,48.625,7.562,68.406,20.15 [rest of string was truncated]";. + /// Looks up a localized string similar to M7 9H5l3-3 3 3H9v5H7V9z m5-4c0-0.44-0.91-3-4.5-3-2.42 0-4.5 1.92-4.5 4C1.02 6 0 7.52 0 9c0 1.53 1 3 3 3 0.44 0 2.66 0 3 0v-1.3H3C1.38 10.7 1.3 9.28 1.3 9c0-0.17 0.05-1.7 1.7-1.7h1.3v-1.3c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2h1.3c0.81 0 2.7 0.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2H10v1.3c0.38 0 1.98 0 2 0 2.08 0 4-1.16 4-3.5 0-2.44-1.92-3.5-4-3.5z. /// </summary> internal static string cloud_upload { get { @@ -331,7 +349,7 @@ internal static string cloud_upload { } /// <summary> - /// Looks up a localized string similar to M608,192l-96,96l224,224L512,736l96,96l288-320L608,192z M288,192L0,512l288,320l96-96L160,512l224-224L288,192z. + /// Looks up a localized string similar to M9.5 3l-1.5 1.5 3.5 3.5L8 11.5l1.5 1.5 4.5-5L9.5 3zM4.5 3L0 8l4.5 5 1.5-1.5L2.5 8l3.5-3.5L4.5 3z. /// </summary> internal static string code { get { @@ -340,16 +358,7 @@ internal static string code { } /// <summary> - /// Looks up a localized string similar to M0,128v768h768V128H0z M64,832V192h640L64,832z. - /// </summary> - internal static string color_mode { - get { - return ResourceManager.GetString("color_mode", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M768,128H128C66,128,0,192,0,256v384c0,128,128,128,128,128h64v256l256-256c0,0,258,0,320,0s128-68,128-128V256 C896,194,832,128,768,128z. + /// Looks up a localized string similar to M13 2H1c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1h2v3.5l3.5-3.5h6.5c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 9H6L4 13V11H1V3h12v8z. /// </summary> internal static string comment { get { @@ -358,34 +367,34 @@ internal static string comment { } /// <summary> - /// Looks up a localized string similar to M768,64H128C64,64,0,130,0,192v384c0,60,66,128,128,128s320,0,320,0l256,256V704h64c0,0,128,0,128-128V192 C896,128,830,64,768,64z M640,448H512v128H384V448H256V320h128V192h128v128h128V448z. + /// Looks up a localized string similar to M15 2H6c-0.55 0-1 0.45-1 1v2H1c-0.55 0-1 0.45-1 1v6c0 0.55 0.45 1 1 1h1v3l3-3h4c0.55 0 1-0.45 1-1V10h1l3 3V10h1c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1zM9 12H4.5l-1.5 1.5v-1.5H1V6h4v3c0 0.55 0.45 1 1 1h3v2z m6-3H13v1.5l-1.5-1.5H6V3h9v6z. /// </summary> - internal static string comment_add { + internal static string comment_discussion { get { - return ResourceManager.GetString("comment_add", resourceCulture); + return ResourceManager.GetString("comment_discussion", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M256,512V320H64c-64,0-64,64-64,64s0,258,0,320s64,64,64,64h64v192l194-192h192c0,0,62-4,62-64v-64c0,0-64,0-192,0 S256,512,256,512z M832,128c0,0-384,0-448,0s-64,64-64,64s0,259.969,0,320c0,60,62,64,62,64h192l194,192V576h64c0,0,64-2,64-64V192 C896,128,832,128,832,128z. + /// Looks up a localized string similar to M12 9H2v-1h10v1z m4-6v9c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h14c0.55 0 1 0.45 1 1z m-1 3H1v6h14V6z m0-3H1v1h14v-1zM6 10H2v1h4v-1z. /// </summary> - internal static string comment_discussion { + internal static string credit_card { get { - return ResourceManager.GetString("comment_discussion", resourceCulture); + return ResourceManager.GetString("credit_card", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M128,704h128v-64H128V704z M320,704h128v-64H320V704z M384,512H128v64h256V512z M256,448h64l128-128h-64L256,448z M448,576 h192v-64H448V576z M960,192H64c-64,0-64,64-64,64v512c0,0,0,64,64,64h896c64,0,64-64,64-64V256C1024,256,1024,192,960,192z M960,448 v288c0,0,0,32-32,32H96c-32,0-32-32-32-32V448h64l128-128H64v-32c0,0,0-32,32-32h832c32,0,32,32,32,32v32H576L448,448H960z. + /// Looks up a localized string similar to M0 7v2h8V7H0z. /// </summary> - internal static string credit_card { + internal static string dash { get { - return ResourceManager.GetString("credit_card", resourceCulture); + return ResourceManager.GetString("dash", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M416,464.5c-61.562,0-111.5,49.938-111.5,111.5S354.438,687.5,416,687.5S527.5,637.562,527.5,576 c0-8.5-1.125-16.75-3-24.688C606.125,456.375,732.5,308.344,800,224c23.125-28.875-2.312-56.188-32-32 c-85.188,69.375-232.312,194.688-326.906,275.594C433.031,465.719,424.625,464.5,416,464.5z M447.875,255.875 c0-17.656-14.344-32-32-32s-32,14.344-32,32s14.344,32,32,32S447.875,273.531,447.875,255.875z M639.875,511.875 c0,17.656,14.375,32,32,32s32-14.344,32-32s-14.375-32-32-32S639.875,494.219,639.875,511.875z M287.875 [rest of string was truncated]";. + /// Looks up a localized string similar to M8 5h-1v-1h1v1z m4 3h-1v1h1v-1zM5 5h-1v1h1v-1z m-1 3h-1v1h1v-1z m11-5.5l-0.5-0.5-6.5 5c-0.06-0.02-1 0-1 0-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1h1c0.55 0 1-0.45 1-1v-0.92l6-5.58zM13.41 6.59c0.19 0.61 0.3 1.25 0.3 1.91 0 3.42-2.78 6.2-6.2 6.2S1.3 11.92 1.3 8.5s2.78-6.2 6.2-6.2c1.2 0 2.31 0.34 3.27 0.94l0.94-0.94c-1.19-0.81-2.64-1.3-4.2-1.3C3.36 1 0 4.36 0 8.5s3.36 7.5 7.5 7.5 7.5-3.36 7.5-7.5c0-1.03-0.2-2.02-0.59-2.91l-1 1z. /// </summary> internal static string dashboard { get { @@ -394,7 +403,7 @@ internal static string dashboard { } /// <summary> - /// Looks up a localized string similar to M384,960C171.969,960,0,902.625,0,832c0-38.625,0-80.875,0-128c0-11.125,5.562-21.688,13.562-32 C56.375,727.125,205.25,768,384,768s327.625-40.875,370.438-96c8,10.312,13.562,20.875,13.562,32c0,37.062,0,76.375,0,128 C768,902.625,596,960,384,960z M384,704C171.969,704,0,646.625,0,576c0-38.656,0-80.844,0-128c0-6.781,2.562-13.375,6-19.906l0,0 C7.938,424,10.5,419.969,13.562,416C56.375,471.094,205.25,512,384,512s327.625-40.906,370.438-96c3.062,3.969,5.625,8,7.562,12.094 l0,0c3.438,6.531,6,13.125,6,19.906c0,37.062, [rest of string was truncated]";. + /// Looks up a localized string similar to M6 15C2.69 15 0 14.1 0 13c0-0.6 0-1.26 0-2 0-0.17 0.09-0.34 0.21-0.5C0.88 11.36 3.21 12 6 12s5.12-0.64 5.79-1.5c0.13 0.16 0.21 0.33 0.21 0.5 0 0.58 0 1.19 0 2C12 14.1 9.31 15 6 15zM6 11C2.69 11 0 10.1 0 9c0-0.6 0-1.26 0-2 0-0.11 0.04-0.21 0.09-0.31l0 0C0.12 6.63 0.16 6.56 0.21 6.5 0.88 7.36 3.21 8 6 8s5.12-0.64 5.79-1.5c0.05 0.06 0.09 0.13 0.12 0.19l0 0c0.05 0.1 0.09 0.21 0.09 0.31 0 0.58 0 1.19 0 2C12 10.1 9.31 11 6 11zM6 7C2.69 7 0 6.1 0 5c0-0.32 0-0.65 0-1 0-0.32 0-0.65 0-1C0 1.9 2.69 1 6 1c3.31 0 6 0.9 [rest of string was truncated]";. /// </summary> internal static string database { get { @@ -403,7 +412,16 @@ internal static string database { } /// <summary> - /// Looks up a localized string similar to M512,384.001c-70.691,0-127.999,57.308-127.999,127.999S441.309,639.999,512,639.999 c5.713,0,11.337-0.38,16.852-1.105c-46.344-7.058-81.851-47.079-81.851-95.394c0-53.295,43.204-96.499,96.499-96.499 c48.314,0,88.336,35.507,95.394,81.851c0.726-5.515,1.105-11.139,1.105-16.852C639.999,441.309,582.691,384.001,512,384.001z M896,256H767.999L640,128H384L255.999,256H128c-35.348,0-64,28.652-64,64v448c0,35.347,28.652,64,64,64h768 c35.347,0,64-28.653,64-64V320C960,284.652,931.347,256,896,256z M416,192h192l64,64H352L4 [rest of string was truncated]";. + /// Looks up a localized string similar to M4 6h3V0h2v6h3L8 10 4 6z m11-4H11v1h4v8H1V3h4v-1H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h5.34c-0.25 0.61-0.86 1.39-2.34 2h8c-1.48-0.61-2.09-1.39-2.34-2h5.34c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z. + /// </summary> + internal static string desktop_download { + get { + return ResourceManager.GetString("desktop_download", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M15 3H7c0-0.55-0.45-1-1-1H2c-0.55 0-1 0.45-1 1-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h14c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1zM6 5H2v-1h4v1z m4.5 7c-1.94 0-3.5-1.56-3.5-3.5s1.56-3.5 3.5-3.5 3.5 1.56 3.5 3.5-1.56 3.5-3.5 3.5z m2.5-3.5c0 1.38-1.13 2.5-2.5 2.5s-2.5-1.13-2.5-2.5 1.13-2.5 2.5-2.5 2.5 1.13 2.5 2.5z. /// </summary> internal static string device_camera { get { @@ -412,7 +430,7 @@ internal static string device_camera { } /// <summary> - /// Looks up a localized string similar to M576,192c-35.347,0-64,28.653-64,64s28.653,64,64,64s64-28.653,64-64S611.347,192,576,192z M896,384L768,512v-64 c0-30.625-21.515-56.21-50.25-62.503C748.958,351.354,768,305.903,768,256.002C768,149.962,682.039,64,576,64 c-101.123,0-183.986,78.178-191.45,177.393C350.516,210.694,305.442,192,256,192c-106.038,0-192,85.962-192,192.002 C64,490.039,149.962,576,256,576h-64v128h64v128c0,35.347,28.653,64,64,64h384c35.347,0,64-28.653,64-64v-64l128,128h64V384H896z M256,320c-35.347,0-64,28.653-64,64s28.653,64,64,64v64c- [rest of string was truncated]";. + /// Looks up a localized string similar to M15.2 3.09L10 6.72V4c0-0.55-0.45-1-1-1H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1V10.28l5.2 3.63c0.33 0.23 0.8 0 0.8-0.41V3.5c0-0.41-0.47-0.64-0.8-0.41z. /// </summary> internal static string device_camera_video { get { @@ -421,7 +439,7 @@ internal static string device_camera_video { } /// <summary> - /// Looks up a localized string similar to M960,64c-64,0-896,0-896,0C0,64,0,128,0,128v576c0,0,0,64,64,64h320c0,0-192,64-192,128s64,64,64,64h512c0,0,64,0,64-64 S640,768,640,768h320c64,0,64-64,64-64V128C1024,128,1024,64,960,64z M960,704H64V128h896V704z M896,192H704 c-384,64-576,384-576,384v64h768V192z. + /// Looks up a localized string similar to M15 2H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h5.34c-0.25 0.61-0.86 1.39-2.34 2h8c-1.48-0.61-2.09-1.39-2.34-2h5.34c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 9H1V3h14v8z. /// </summary> internal static string device_desktop { get { @@ -430,7 +448,7 @@ internal static string device_desktop { } /// <summary> - /// Looks up a localized string similar to M576,0H64C28.688,0,0,28.688,0,64v896c0,35.375,28.688,64,64,64h512c35.375,0,64-28.625,64-64V64 C640,28.688,611.375,0,576,0z M288,64h64c17.625,0,32,14.344,32,32s-14.375,32-32,32h-64c-17.656,0-32-14.344-32-32 S270.344,64,288,64z M352,960h-64c-17.656,0-32-14.375-32-32s14.344-32,32-32h64c17.625,0,32,14.375,32,32S369.625,960,352,960z M576,832H64V192h512V832z. + /// Looks up a localized string similar to M9 0H1C0.45 0 0 0.45 0 1v14c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1zM5 15.3c-0.72 0-1.3-0.58-1.3-1.3s0.58-1.3 1.3-1.3 1.3 0.58 1.3 1.3-0.58 1.3-1.3 1.3z m4-3.3H1V2h8v10z. /// </summary> internal static string device_mobile { get { @@ -439,7 +457,7 @@ internal static string device_mobile { } /// <summary> - /// Looks up a localized string similar to M448,256H320v128H192v128h128v128h128V512h128V384H448V256z M192,896h384V768H192V896z M640,0H128v64h480l224,224v608h64 V256L640,0z M0,128v896h768V320L576,128H0z M704,960H64V192h480l160,160V960z. + /// Looks up a localized string similar to M6 7h2v1H6v2h-1V8H3v-1h2V5h1v2zM3 13h5v-1H3v1z m4.5-11l3.5 3.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h6.5z m2.5 4L7 3H1v12h9V6zM8.5 0S3 0 3 0v1h5l4 4v8h1V4.5L8.5 0z. /// </summary> internal static string diff { get { @@ -448,7 +466,7 @@ internal static string diff { } /// <summary> - /// Looks up a localized string similar to M512,320H384v128H256v128h128v128h128V576h128V448H512V320z M832,64H64C0,64,0,128,0,128v768c0,0,0,64,64,64 c0,0,704,0,768,0s64-64,64-64V128C896,128,896,64,832,64z M768,800c0,32-32,32-32,32H160c0,0-32,0-32-32V224c0-32,32-32,32-32h576 c0,0,32,0,32,32C768,224,768,773.25,768,800z. + /// Looks up a localized string similar to M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z. /// </summary> internal static string diff_added { get { @@ -457,7 +475,7 @@ internal static string diff_added { } /// <summary> - /// Looks up a localized string similar to M832,64H64C0,64,0,128,0,128v768c0,0,0,64,64,64c0,0,704,0,768,0s64-64,64-64V128C896,128,896,64,832,64z M768,800 c0,32-32,32-32,32H160c0,0-32,0-32-32V224c0-32,32-32,32-32h576c0,0,32,0,32,32C768,224,768,773.25,768,800z M256,606v98h98l286-286 v-98h-98L256,606z. + /// Looks up a localized string similar to M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4.5 12h-1.5v-1.5l6.5-6.5h1.5v1.5L4.5 12z. /// </summary> internal static string diff_ignored { get { @@ -466,7 +484,7 @@ internal static string diff_ignored { } /// <summary> - /// Looks up a localized string similar to M832,64H64C0,64,0,128,0,128v768c0,0,0,64,64,64c0,0,704,0,768,0s64-64,64-64V128C896,128,896,64,832,64z M768,800 c0,32-32,32-32,32H160c0,0-32,0-32-32V224c0-32,32-32,32-32h576c0,0,32,0,32,32C768,224,768,773.25,768,800z M448,384 c-70.688,0-128,57.312-128,128s57.312,128,128,128s128-57.312,128-128S518.688,384,448,384z. + /// Looks up a localized string similar to M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z. /// </summary> internal static string diff_modified { get { @@ -475,7 +493,7 @@ internal static string diff_modified { } /// <summary> - /// Looks up a localized string similar to M256,448v128h384V448H256z M832,64H64C0,64,0,128,0,128v768c0,0,0,64,64,64c0,0,704,0,768,0s64-64,64-64V128 C896,128,896,64,832,64z M768,800c0,32-32,32-32,32H160c0,0-32,0-32-32V224c0-32,32-32,32-32h576c0,0,32,0,32,32 C768,224,768,773.25,768,800z. + /// Looks up a localized string similar to M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z. /// </summary> internal static string diff_removed { get { @@ -484,7 +502,7 @@ internal static string diff_removed { } /// <summary> - /// Looks up a localized string similar to M832,64H64C0,64,0,128,0,128v768c0,0,0,64,64,64c0,0,704,0,768,0s64-64,64-64V128C896,128,896,64,832,64z M768,800 c0,32-32,32-32,32H160c0,0-32,0-32-32V224c0-32,32-32,32-32h576c0,0,32,0,32,32C768,224,768,773.25,768,800z M448,448H256v128h192 v128l256-192L448,320V448z. + /// Looks up a localized string similar to M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z. /// </summary> internal static string diff_renamed { get { @@ -493,7 +511,7 @@ internal static string diff_renamed { } /// <summary> - /// Looks up a localized string similar to M640,320c-102.75,0-512,0-512,0S0,320,0,448s0,128,0,128s0,128,128,128s512,0,512,0s128-4,128-128c0-124.031,0-128,0-128 S764,320,640,320z M256,576H128V448h128V576z M448,576H320V448h128V576z M640,576H512V448h128V576z. + /// Looks up a localized string similar to M11 5H1c-0.55 0-1 0.45-1 1v4c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V6c0-0.55-0.45-1-1-1zM4 9H2V7h2v2z m3 0H5V7h2v2z m3 0H8V7h2v2z. /// </summary> internal static string ellipsis { get { @@ -502,7 +520,7 @@ internal static string ellipsis { } /// <summary> - /// Looks up a localized string similar to M512,128C128,128,0,512,0,512s122,320,512,320s512-320,512-320S896,128,512,128z M512,768c-320,0-384-256-384-256 s66-256,384-256s384,256,384,256S832,768,512,768z M512,320c-19.531,0-37.938,3.781-55.688,9.219 C489.156,344.438,512,377.469,512,416c0,53-43,96-96,96c-38.531,0-71.562-22.844-86.781-55.688C323.781,474.062,320,492.469,320,512 c0,106.062,86,192,192,192c106.062,0,192-85.938,192-192C704,406,618.062,320,512,320z. + /// Looks up a localized string similar to M8.06 2C3 2 0 8 0 8s3 6 8.06 6c4.94 0 7.94-6 7.94-6S13 2 8.06 2z m-0.06 10c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4z m2-4c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z. /// </summary> internal static string eye { get { @@ -511,34 +529,7 @@ internal static string eye { } /// <summary> - /// Looks up a localized string similar to M456.312,329.156C489.156,344.438,512,377.469,512,416c0,53-43,96-96,96c-38.531,0-71.562-22.844-86.844-55.688 C323.781,474.062,320,492.469,320,512c0,106.062,85.938,192,192,192s192-85.938,192-192c0-106-85.938-192-192-192 C492.469,320,474.062,323.781,456.312,329.156z M995.875,448H868.25c20.5,36.688,27.75,64,27.75,64s-64,256-384,256S128,512,128,512 s66-256,384-256c25.344,0,48.562,2.125,70.75,5.125L576,256V131.969c-20.5-2.438-41.719-3.969-64-3.969C128,128,0,512,0,512 s122,320,512,320s512-320,512-320S1015.25,4 [rest of string was truncated]";. - /// </summary> - internal static string eye_unwatch { - get { - return ResourceManager.GetString("eye_unwatch", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M960,448l-81.375,20.281C891.188,494.281,896,512,896,512s-64,256-384,256S128,512,128,512s66-256,384-256 c17.094,0,33,1.094,48.625,2.5L528.25,129.031C522.75,128.906,517.688,128,512,128C128,128,0,512,0,512s122,320,512,320 s512-320,512-320s-19.062-56.625-64-126.344V448z M416,512c-38.656,0-71.5-23.094-86.656-56.062 C323.844,473.781,320,492.344,320,512c0,106.062,85.938,192,192,192s192-85.938,192-192v-0.062C704,405.938,618,320,512,320 c-19.656,0-38.219,3.844-56.062,9.344C488.906,344.5,512,377.344,512,416C512,4 [rest of string was truncated]";. - /// </summary> - internal static string eye_watch { - get { - return ResourceManager.GetString("eye_watch", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M448,320H320v128H192v128h128v128h128V576h128V448H448V320z M576,64H0v896h768V256L576,64z M704,896H64V128h480l160,160V896 z. - /// </summary> - internal static string file_add { - get { - return ResourceManager.GetString("file_add", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M0,960V64h576l192,192v704H0z M704,320L512,128H64v768h640V320z M320,512H128V256h192V512z M256,320h-64v128h64V320z M256,768h64v64H128v-64h64V640h-64v-64h128V768z M512,448h64v64H384v-64h64V320h-64v-64h128V448z M576,832H384V576h192V832z M512,640h-64v128h64V640z. + /// Looks up a localized string similar to M4 12h1v1H2v-1h1V10h-1v-1h2v3z m8-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z m-3-1H6v1h1v2h-1v1h3v-1h-1V4z m-6 0h3v4H2V4z m1 3h1V5h-1v2z m3 2h3v4H6V9z m1 3h1V10h-1v2z. /// </summary> internal static string file_binary { get { @@ -547,7 +538,7 @@ internal static string file_binary { } /// <summary> - /// Looks up a localized string similar to M288,384L128,544l160,160l64-64l-96-96l96-96L288,384z M416,448l96,96l-96,96l64,64l160-160L480,384L416,448z M576,64H0v896 h768V256L576,64z M704,896H64V128h448l192,192V896z. + /// Looks up a localized string similar to M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1z m2.5 13H1V2h7l3 3v9zM5 6.98l-1.5 1.52 1.5 1.5-0.5 1-2.5-2.5 2.5-2.5 0.5 0.98z m2.5-0.98l2.5 2.5-2.5 2.5-0.5-0.98 1.5-1.52-1.5-1.5 0.5-1z. /// </summary> internal static string file_code { get { @@ -556,7 +547,7 @@ internal static string file_code { } /// <summary> - /// Looks up a localized string similar to M832,192c0,0-320,0-352,0s-32-32-32-32s0-21.25,0-32c0-64-64-64-64-64s-256,0-320,0S0,128,0,128v704h896c0,0,0-510,0-576 S832,192,832,192z M384,192H64v-32c0,0,0-32,32-32h256c32,0,32,32,32,32V192z. + /// Looks up a localized string similar to M13 4H7v-1c0-0.66-0.31-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V5c0-0.55-0.45-1-1-1z m-7 0H1v-1h5v1z. /// </summary> internal static string file_directory { get { @@ -565,16 +556,7 @@ internal static string file_directory { } /// <summary> - /// Looks up a localized string similar to M832,192c0,0-320,0-352,0s-32-32-32-32s0-21.25,0-32c0-64-64-64-64-64s-256,0-320,0S0,128,0,128v704h896c0,0,0-510,0-576 S832,192,832,192z M64,160c0,0,0-32,32-32h256c32,0,32,32,32,32v32H64V160z M640,576H512v128H384V576H256V448h128V320h128v128h128 V576z. - /// </summary> - internal static string file_directory_create { - get { - return ResourceManager.GetString("file_directory_create", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M576,64H0v896h768V256L576,64z M704,896H64V128h448l192,192V896z M128,256v512h128c0-70.625,57.344-128,128-128 c-70.656,0-128-57.375-128-128c0-70.656,57.344-128,128-128c70.625,0,128,57.344,128,128c0,70.625-57.375,128-128,128 c70.625,0,128,57.375,128,128h128V384L512,256H128z. + /// Looks up a localized string similar to M6 5h2v2H6V5z m6-0.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v11l3-5 2 4 2-2 3 3V5z. /// </summary> internal static string file_media { get { @@ -583,7 +565,7 @@ internal static string file_media { } /// <summary> - /// Looks up a localized string similar to M576,64H0v896h768V256L576,64z M64,128h255.812c-13.188,4.094-27.281,15.031-34.625,42.875 c-13.25,49.406-7.031,130.75,15.625,209.344C276.688,461.438,178.188,656.875,171.531,668.5 c-15.625,4.875-65.344,23.625-107.531,59.812V128z M347.125,435.469c57.625,149.781,95,149.531,135.188,167.594 C398.344,616,334.219,625.25,249.781,662.5C246.094,668.938,326.281,516.594,347.125,435.469z M704,896H65.844H64v-0.375 c0.781,0.062,1.094,0.375,1.844,0.375c33.812,0,84.75-21,180.562-182.375c38.188-15.438,72.062-26.875,78.469- [rest of string was truncated]";. + /// Looks up a localized string similar to M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1zM1 2h4c-0.11 0.03-0.2 0.09-0.31 0.2-0.09 0.09-0.17 0.25-0.23 0.47-0.11 0.39-0.14 0.89-0.09 1.47s0.17 1.17 0.34 1.8c-0.23 0.73-0.61 1.61-1.11 2.66s-0.8 1.66-0.91 1.84c-0.14 0.05-0.36 0.14-0.69 0.3-0.33 0.14-0.66 0.36-1 0.64V2z m4.42 4.8c0.45 1.13 0.84 1.83 1.17 2.09s0.64 0.45 0.94 0.53c-0.64 0.09-1.23 0.2-1.81 0.33-0.56 0.13-1.17 0.33-1.81 0.59 0.02-0.02 0.22-0.44 0.61-1.25s0.7-1.58 0.91-2.3z m5.58 7.2H1.5c-0.06 0-0.13-0.02-0.17-0 [rest of string was truncated]";. /// </summary> internal static string file_pdf { get { @@ -592,7 +574,7 @@ internal static string file_pdf { } /// <summary> - /// Looks up a localized string similar to M832,512H640c0,0,2-64-64-64s-64,0-128,0s-64,64-64,64v320h512c0,0,0-190,0-256S832,512,832,512z M576,576H448v-32 c0,0,0-32,32-32h64c32,0,32,32,32,32V576z M832,256c0,0-320,0-352,0s-32-32-32-32v-32c0,0,0-64-64-64H64c-64,0-64,64-64,64v640h320 V448c0-62,64-64,64-64s190,0,256,0s64,64,64,64h192V320C896,254,832,256,832,256z M384,256H64v-32c0,0,0-32,32-32h256 c32,0,32,32,32,32V256z. + /// Looks up a localized string similar to M10 7H4v7h9c0.55 0 1-0.45 1-1V8H10v-1z m-1 2H5v-1h4v1z m4-5H7v-1c0-0.66-0.31-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h2V7c0-0.55 0.45-1 1-1h6c0.55 0 1 0.45 1 1h3V5c0-0.55-0.45-1-1-1z m-7 0H1v-1h5v1z. /// </summary> internal static string file_submodule { get { @@ -601,7 +583,7 @@ internal static string file_submodule { } /// <summary> - /// Looks up a localized string similar to M832,192c0,0-320,0-352,0s-32-32-32-32s0-21.25,0-32c0-64-64-64-64-64s-256,0-320,0S0,128,0,128v704h896c0,0,0-510,0-576 S832,192,832,192z M64,160c0,0,0-32,32-32h256c32,0,32,32,32,32v32H64V160z M448,704V576c0,0-192,5.125-256,192 c0-314.875,256-320,256-320V320l256,192L448,704z. + /// Looks up a localized string similar to M13 4H7v-1c0-0.66-0.31-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V5c0-0.55-0.45-1-1-1zM1 3h5v1H1v-1z m6 9V10c-0.98-0.02-1.84 0.22-2.55 0.7s-1.19 1.25-1.45 2.3c0.02-1.64 0.39-2.88 1.13-3.73 0.73-0.84 1.69-1.27 2.88-1.27V6l4 3-4 3z. /// </summary> internal static string file_symlink_directory { get { @@ -610,7 +592,7 @@ internal static string file_symlink_directory { } /// <summary> - /// Looks up a localized string similar to M576,64H0v896h768V256L576,64z M704,896H64V128h448l192,192V896z M384,448c0,0-256,0-256,314.875C192,576,384,576,384,576 v128l256-192L384,320V448z. + /// Looks up a localized string similar to M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1z m2.5 13H1V2h7l3 3v9zM6 4.5l4 3-4 3V8.5c-0.98-0.02-1.84 0.22-2.55 0.7s-1.19 1.25-1.45 2.3c0.02-1.64 0.39-2.88 1.13-3.73 0.73-0.84 1.69-1.27 2.88-1.27V4.5z. /// </summary> internal static string file_symlink_file { get { @@ -619,7 +601,7 @@ internal static string file_symlink_file { } /// <summary> - /// Looks up a localized string similar to M448,256H128v64h320V256z M576,64H0v896h768V256L576,64z M704,896H64V128h448l192,192V896z M128,768h512v-64H128V768z M128,640h512v-64H128V640z M128,512h512v-64H128V512z. + /// Looks up a localized string similar to M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z. /// </summary> internal static string file_text { get { @@ -628,7 +610,7 @@ internal static string file_text { } /// <summary> - /// Looks up a localized string similar to M320,576v-64h-64v64H320z M320,448v-64h-64v64H320z M320,320v-64h-64v64H320z M192,384h64v-64h-64V384z M576,64H0v896h768 V256L576,64z M704,896H64V128h192v64h64v-64h192l192,192V896z M192,256h64v-64h-64V256z M192,512h64v-64h-64V512z M192,640l-64,64 v128h256V704l-64-64h-64v-64h-64V640z M320,704v64H192v-64H320z. + /// Looks up a localized string similar to M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1z m2.5 13H1V2h3v1h1v-1h3l3 3v9zM5 4v-1h1v1h-1z m-1 0h1v1h-1v-1z m1 2v-1h1v1h-1z m-1 0h1v1h-1v-1z m1 2v-1h1v1h-1z m-1 1.28c-0.59 0.34-1 0.98-1 1.72v1h4v-1c0-1.11-0.89-2-2-2v-1h-1v1.28z m2 0.72v1H4v-1h2z. /// </summary> internal static string file_zip { get { @@ -637,61 +619,52 @@ internal static string file_zip { } /// <summary> - /// Looks up a localized string similar to M447.938,350C358.531,350,286,422.531,286,512c0,89.375,72.531,162.062,161.938,162.062 c89.438,0,161.438-72.688,161.438-162.062C609.375,422.531,537.375,350,447.938,350z M772.625,605.062l-29.188,70.312l52.062,102.25 l6.875,13.5l-72.188,72.188L611.75,807.375l-70.312,28.875L505.75,945.5l-4.562,14.5H399.156L355,836.688l-70.312-29 l-102.404,51.938l-13.5,6.75l-72.156-72.125l55.875-118.5l-28.969-70.25L14.469,569.875L0,565.188V463.219L123.406,419 l28.969-70.188l-51.906-102.469l-6.844-13.438l72.062-72.062l118.594, [rest of string was truncated]";. + /// Looks up a localized string similar to M5.05 0.31c0.81 2.17 0.41 3.38-0.52 4.31-0.98 1.05-2.55 1.83-3.63 3.36-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-0.3-6.61-0.61 2.03 0.53 3.33 1.94 2.86 1.39-0.47 2.3 0.53 2.27 1.67-0.02 0.78-0.31 1.44-1.13 1.81 3.42-0.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52 0.13-2.03 1.13-1.89 2.75 0.09 1.08-1.02 1.8-1.86 1.33-0.67-0.41-0.66-1.19-0.06-1.78 1.25-1.23 1.75-4.09-1.88-6.22l-0.02-0.02z. /// </summary> - internal static string gear { + internal static string flame { get { - return ResourceManager.GetString("gear", resourceCulture); + return ResourceManager.GetString("flame", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,960h320V640H448V960z M64,960h320V640H64V960z M447.75,376.188c31.469-3.5,66.875-7.406,87.375-9.719 C619,357.125,694.5,281.594,703.812,197.75c9.312-83.75-51-144.125-134.688-134.719C503.688,70.344,443.844,118,416,178.375 C388.156,118,328.312,70.344,262.906,62.969C179.188,53.625,118.781,114,128.188,197.75 c9.344,83.844,84.875,159.312,168.656,168.719c20.531,2.312,55.938,6.281,87.406,9.719C383.75,380.406,384,384,384,384h64 C448,384,448.25,380.406,447.75,376.188z M555.375,140.688c45.25-5.062,78,27.562,72. [rest of string was truncated]";. + /// Looks up a localized string similar to M7 9l3 3H8v3H6V12H4l3-3z m3-6H8V0H6v3H4l3 3 3-3z m4 2c0-0.55-0.45-1-1-1H10.5l-1 1h3L10.5 7H3.5L1.5 5h3l-1-1H1c-0.55 0-1 0.45-1 1l2.5 2.5L0 10c0 0.55 0.45 1 1 1h2.5l1-1H1.5l2-2h7l2 2H9.5l1 1h2.5c0.55 0 1-0.45 1-1L11.5 7.5l2.5-2.5z. /// </summary> - internal static string gift { + internal static string fold { get { - return ResourceManager.GetString("gift", resourceCulture); + return ResourceManager.GetString("fold", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M416,448l96,96l-96,96l64,64l160-160L480,384L416,448z M576,192H192c-62,0-64,64-64,64h512C640,256,640,192,576,192z M0,64 v832h768V64H0z M704,832H64V128h640V832z M352,640l-96-96l96-96l-64-64L128,544l160,160L352,640z. + /// Looks up a localized string similar to M14 8.77V7.17l-1.94-0.64-0.45-1.09 0.88-1.84-1.13-1.13-1.81 0.91-1.09-0.45-0.69-1.92H6.17l-0.63 1.94-1.11 0.45-1.84-0.88-1.13 1.13 0.91 1.81-0.45 1.09L0 7.23v1.59l1.94 0.64 0.45 1.09-0.88 1.84 1.13 1.13 1.81-0.91 1.09 0.45 0.69 1.92h1.59l0.63-1.94 1.11-0.45 1.84 0.88 1.13-1.13-0.92-1.81 0.47-1.09 1.92-0.69zM7 11c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z. /// </summary> - internal static string gist { - get { - return ResourceManager.GetString("gist", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M448,448l-64,64l-64-62V320H192v192l128,128v128h128V640l128-128V320H448V448z M576,192H192c-64,0-64,64-64,64h512 C640,256,638,192,576,192z M0,64v832h768V64H0z M704,832H64V128h640V832z. - /// </summary> - internal static string gist_fork { + internal static string gear { get { - return ResourceManager.GetString("gist_fork", resourceCulture); + return ResourceManager.GetString("gear", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,320H320v128H192v128h128v128h128V576h128V448H448V320z M576,192H192c-64,0-64,64-64,64h512C640,256,638,192,576,192z M0,64v832h768V64H0z M704,832H64V128h640V832z. + /// Looks up a localized string similar to M13 4h-1.38c0.19-0.33 0.33-0.67 0.36-0.91 0.06-0.67-0.11-1.22-0.52-1.61-0.36-0.38-0.81-0.48-1.36-0.48-0.05 0-0.08 0-0.11 0-0.53 0.02-1.11 0.25-1.53 0.58s-0.73 0.72-0.97 1.2c-0.23-0.48-0.55-0.88-0.97-1.2s-1-0.58-1.53-0.58c-0.02 0-0.03 0-0.03 0-0.56 0-1.06 0.09-1.44 0.48-0.41 0.39-0.58 0.94-0.52 1.61 0.03 0.23 0.17 0.58 0.36 0.91h-1.38c-0.55 0-1 0.45-1 1v3h1v5c0 0.55 0.45 1 1 1h9c0.55 0 1-0.45 1-1V8h1V5c0-0.55-0.45-1-1-1z m-4.78-0.88c0.17-0.36 0.42-0.67 0.75-0.92 0.3-0.23 0.72-0.39 1.05-0.41h0.09c0.45 0 0.66 [rest of string was truncated]";. /// </summary> - internal static string gist_new { + internal static string gift { get { - return ResourceManager.GetString("gist_new", resourceCulture); + return ResourceManager.GetString("gift", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M384,384c-128,0-128,128-128,128v192h256V512C512,512,512,384,384,384z M320,512c0,0,0-64,64-64s64,64,64,64H320z M0,64v832 h768V64H0z M704,832H64V128h640V832z M576,192H192c-64,0-64,64-64,64h512C640,256,640,192,576,192z. + /// Looks up a localized string similar to M7.5 5l2.5 2.5-2.5 2.5-0.75-0.75 1.75-1.75-1.75-1.75 0.75-0.75z m-3 0L2 7.5l2.5 2.5 0.75-0.75-1.75-1.75 1.75-1.75-0.75-0.75zM0 13V2c0-0.55 0.45-1 1-1h10c0.55 0 1 0.45 1 1v11c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1z m1 0h10V2H1v11z. /// </summary> - internal static string gist_private { + internal static string gist { get { - return ResourceManager.GetString("gist_private", resourceCulture); + return ResourceManager.GetString("gist", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,320l-64,32l-64-32l-64,128h256L448,320z M192,512h384l-64-32H256L192,512z M576,192H192c-62,0-64,64-64,64h512 C640,256,640,192,576,192z M640,608l-160-32l32,64l-64,128h160L640,608z M416,640l32-64H320l32,64l-32,128h128L416,640z M0,64v832 h768V64H0z M704,832H64V128h640V832z M320,768l-64-128l32-64l-160,32l32,160H320z. + /// Looks up a localized string similar to M8 10.5l1 3.5H5l1-3.5-0.75-1.5h3.5l-0.75 1.5z m2-4.5H4l-2 1h10l-2-1z m-1-4l-2 1-2-1-1 3h6l-1-3z m4.03 7.75l-3.03-0.75 1 2-2 3h3.22c0.45 0 0.86-0.31 0.97-0.75l0.56-2.28c0.14-0.53-0.19-1.08-0.72-1.22z m-9.03-0.75L0.97 9.75c-0.53 0.14-0.86 0.69-0.72 1.22l0.56 2.28c0.11 0.44 0.52 0.75 0.97 0.75h3.22L3 11l1-2z. /// </summary> internal static string gist_secret { get { @@ -700,7 +673,7 @@ internal static string gist_secret { } /// <summary> - /// Looks up a localized string similar to M512,192c-70.625,0-128,57.344-128,128c0,47.219,25.875,88.062,64,110.281V448c0,0,0,128-128,128 c-53.062,0-94.656,11.375-128,28.812V302.281c38.156-22.219,64-63.062,64-110.281c0-70.656-57.344-128-128-128S0,121.344,0,192 c0,47.219,25.844,88.062,64,110.281V721.75C25.844,743.938,0,784.75,0,832c0,70.625,57.344,128,128,128s128-57.375,128-128 c0-33.5-13.188-63.75-34.25-86.625C240.375,722.5,270.656,704,320,704c254,0,256-256,256-256v-17.719 c38.125-22.219,64-63.062,64-110.281C640,249.344,582.625,192,512,192z M128, [rest of string was truncated]";. + /// Looks up a localized string similar to M10 5c0-1.11-0.89-2-2-2s-2 0.89-2 2c0 0.73 0.41 1.38 1 1.72v0.3c-0.02 0.52-0.23 0.98-0.63 1.38s-0.86 0.61-1.38 0.63c-0.83 0.02-1.48 0.16-2 0.45V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v6.56C0.41 11.63 0 12.27 0 13c0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.53-0.2-1-0.53-1.36 0.09-0.06 0.48-0.41 0.59-0.47 0.25-0.11 0.56-0.17 0.94-0.17 1.05-0.05 1.95-0.45 2.75-1.25s1.2-1.98 1.25-3.02h-0.02c0.61-0.36 1.02-1 1.02-1.73zM2 1.8c0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.2-0.55-1.2-1 [rest of string was truncated]";. /// </summary> internal static string git_branch { get { @@ -709,25 +682,7 @@ internal static string git_branch { } /// <summary> - /// Looks up a localized string similar to M576,128V0H448v128H320v128h128v128h128V256h128V128H576z M320,576c-53.062,0-94.656,11.375-128,28.812V302.281 c38.156-22.219,64-63.062,64-110.281c0-70.656-57.344-128-128-128S0,121.344,0,192c0,47.219,25.906,88.062,64,110.281V721.75 C25.906,743.938,0,784.75,0,832c0,70.625,57.344,128,128,128s128-57.375,128-128c0-33.5-13.125-63.75-34.25-86.625 C240.375,722.5,270.656,704,320,704c254,0,256-256,256-256H448C448,448,448,576,320,576z M128,128c35.406,0,64,28.594,64,64 s-28.594,64-64,64c-35.312,0-64-28.594-64-64S92.6 [rest of string was truncated]";. - /// </summary> - internal static string git_branch_create { - get { - return ResourceManager.GetString("git_branch_create", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M448,448c0,0,0,128-128,128c-53.062,0-94.656,11.375-128,28.812V302.281c38.156-22.219,64-63.062,64-110.281 c0-70.656-57.344-128-128-128S0,121.344,0,192c0,47.219,25.844,88.062,64,110.281V721.75C25.844,743.938,0,784.75,0,832 c0,70.625,57.344,128,128,128s128-57.375,128-128c0-33.5-13.188-63.75-34.25-86.625C240.375,722.5,270.656,704,320,704 c254,0,256-256,256-256l-64-64L448,448z M128,128c35.406,0,64,28.594,64,64s-28.594,64-64,64s-64-28.594-64-64S92.594,128,128,128z M128,896c-35.406,0-64-28.625-64-64c0-35.312, [rest of string was truncated]";. - /// </summary> - internal static string git_branch_delete { - get { - return ResourceManager.GetString("git_branch_delete", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M694.875,448C666.375,337.781,567.125,256,448,256c-119.094,0-218.375,81.781-246.906,192H0v128h201.094 C229.625,686.25,328.906,768,448,768c119.125,0,218.375-81.75,246.875-192H896V448H694.875z M448,640c-70.656,0-128-57.375-128-128 c0-70.656,57.344-128,128-128c70.625,0,128,57.344,128,128C576,582.625,518.625,640,448,640z. + /// Looks up a localized string similar to M10.86 7c-0.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H0v2h3.14c0.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3h3.14V7H10.86zM7 10.2c-1.22 0-2.2-0.98-2.2-2.2s0.98-2.2 2.2-2.2 2.2 0.98 2.2 2.2-0.98 2.2-2.2 2.2z. /// </summary> internal static string git_commit { get { @@ -736,7 +691,7 @@ internal static string git_commit { } /// <summary> - /// Looks up a localized string similar to M832,721.75V320c0-192.5-192-192-192-192h-64V0L384,192l192,192V256c0,0,26.688,0,64,0c56.438,0,64,64,64,64v401.75 c-38.125,22.188-64,62.875-64,110.25c0,70.625,57.375,128,128,128s128-57.375,128-128C896,784.75,870.125,743.938,832,721.75z M768,896c-35.312,0-64-28.625-64-64c0-35.312,28.688-64,64-64c35.375,0,64,28.688,64,64C832,867.375,803.375,896,768,896z M64,315.594v401.719c0,192.5,192,192,192,192h64v128l192-192l-192-192v128c0,0-26.688,0-64,0c-56.438,0-64-64-64-64V315.594 c38.156-22.219,64-62.906,64-110.28 [rest of string was truncated]";. + /// Looks up a localized string similar to M5 12h-1c-0.27-0.02-0.48-0.11-0.69-0.31s-0.3-0.42-0.31-0.69V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.73 0 6.28 0 6.28 0.03 0.78 0.34 1.47 0.94 2.06s1.28 0.91 2.06 0.94c0 0 1.02 0 1 0v2l3-3-3-3v2zM2 1.8c0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2z m11 9.48c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L6 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 [rest of string was truncated]";. /// </summary> internal static string git_compare { get { @@ -745,16 +700,7 @@ internal static string git_compare { } /// <summary> - /// Looks up a localized string similar to M320,576c-46.844,0-90.188,13.5-128,35.5V302.281c38.156-22.219,64-63.062,64-110.281c0-70.656-57.344-128-128-128 S0,121.344,0,192c0,47.219,25.844,88.062,64,110.281V721.75C25.844,743.938,0,784.75,0,832c0,70.625,57.344,128,128,128 s128-57.375,128-128c0-32.25-12.281-61.375-32-83.875C247.5,721.375,281.531,704,320,704c141.375,0,256-114.625,256-256 c0,0-64,0-128,0C448,518.656,390.625,576,320,576z M128,128c35.406,0,64,28.594,64,64s-28.594,64-64,64s-64-28.594-64-64 S92.594,128,128,128z M128,896c-35.406,0-64-28.62 [rest of string was truncated]";. - /// </summary> - internal static string git_fork_private { - get { - return ResourceManager.GetString("git_fork_private", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M640,448c-47.625,0-88.625,26.312-110.625,64.906C523.625,512.5,518,512,512,512c-131.062,0-255.438-99.844-300.812-223.438 C238.469,265.094,256,230.719,256,192c0-70.656-57.344-128-128-128S0,121.344,0,192c0,47.219,25.844,88.062,64,110.281V721.75 C25.844,743.938,0,784.75,0,832c0,70.625,57.344,128,128,128s128-57.375,128-128c0-47.25-25.844-88.062-64-110.25V491.469 C276.156,580.5,392.375,640,512,640c6.375,0,11.625-0.438,17.375-0.625C551.5,677.812,592.5,704,640,704 c70.625,0,128-57.375,128-128C768,505.344,710.62 [rest of string was truncated]";. + /// Looks up a localized string similar to M10 7c-0.73 0-1.38 0.41-1.73 1.02v-0.02c-1.05-0.02-2.27-0.36-3.13-1.02-0.75-0.58-1.5-1.61-1.89-2.44 0.45-0.36 0.75-0.92 0.75-1.55 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v6.56C0.41 11.63 0 12.27 0 13c0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V7.67c0.67 0.7 1.44 1.27 2.3 1.69s2.03 0.63 2.97 0.64v-0.02c0.36 0.61 1 1.02 1.73 1.02 1.11 0 2-0.89 2-2s-0.89-2-2-2zM3.2 13c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1. [rest of string was truncated]";. /// </summary> internal static string git_merge { get { @@ -763,7 +709,7 @@ internal static string git_merge { } /// <summary> - /// Looks up a localized string similar to M128,64C57.344,64,0,121.344,0,192c0,47.219,25.906,88.062,64,110.281V721.75C25.906,743.938,0,784.75,0,832 c0,70.625,57.344,128,128,128s128-57.375,128-128c0-47.25-25.844-88.062-64-110.25V302.281c38.156-22.219,64-63.062,64-110.281 C256,121.344,198.656,64,128,64z M128,896c-35.312,0-64-28.625-64-64c0-35.312,28.688-64,64-64c35.406,0,64,28.688,64,64 C192,867.375,163.406,896,128,896z M128,256c-35.312,0-64-28.594-64-64s28.688-64,64-64c35.406,0,64,28.594,64,64 S163.406,256,128,256z M704,721.75V320c0-192.5-192-192 [rest of string was truncated]";. + /// Looks up a localized string similar to M11 11.28c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L4 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2zM4 3c0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.55 0 5.56 0 6.56-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V4.72c0.59-0.34 1-0.98 1-1.7 [rest of string was truncated]";. /// </summary> internal static string git_pull_request { get { @@ -772,34 +718,34 @@ internal static string git_pull_request { } /// <summary> - /// Looks up a localized string similar to M256,192c0-70.656-57.344-128-128-128S0,121.344,0,192c0,47.219,25.906,88.062,64,110.281V721.75 C25.906,743.938,0,784.75,0,832c0,70.625,57.344,128,128,128s128-57.375,128-128c0-47.25-25.844-88.062-64-110.25V302.281 C230.156,280.062,256,239.219,256,192z M128,896c-35.312,0-64-28.625-64-64c0-35.312,28.688-64,64-64c35.406,0,64,28.688,64,64 C192,867.375,163.406,896,128,896z M128,256c-35.312,0-64-28.594-64-64s28.688-64,64-64c35.406,0,64,28.594,64,64 S163.406,256,128,256z M704,721.75V448l-64-64l-64,64v273.75c-38. [rest of string was truncated]";. + /// Looks up a localized string similar to M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7c0.48 0 0.94-0.05 1.38-0.14-0.17-0.08-0.2-0.73-0.02-1.09 0.19-0.41 0.81-1.45 0.2-1.8s-0.44-0.5-0.81-0.91-0.22-0.47-0.25-0.58c-0.08-0.34 0.36-0.89 0.39-0.94 0.02-0.06 0.02-0.27 0-0.33 0-0.08-0.27-0.22-0.34-0.23-0.06 0-0.11 0.11-0.2 0.13s-0.5-0.25-0.59-0.33-0.14-0.23-0.27-0.34c-0.13-0.13-0.14-0.03-0.33-0.11s-0.8-0.31-1.28-0.48c-0.48-0.19-0.52-0.47-0.52-0.66-0.02-0.2-0.3-0.47-0.42-0.67-0.14-0.2-0.16-0.47-0.2-0.41s0.25 0.78 0.2 0.81c-0.05 0.02-0.16-0.2-0.3-0.38-0.14-0.19 0.14-0. [rest of string was truncated]";. /// </summary> - internal static string git_pull_request_abandoned { + internal static string globe { get { - return ResourceManager.GetString("git_pull_request_abandoned", resourceCulture); + return ResourceManager.GetString("globe", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M512,128c-212.077,0-384,171.923-384,384s171.923,384,384,384c25.953,0,51.303-2.582,75.812-7.49 c-9.879-4.725-10.957-40.174-1.188-60.385c10.875-22.5,45-79.5,11.25-98.625s-24.375-27.75-45-49.875s-12.19-25.451-13.5-31.125 c-4.5-19.5,19.875-48.75,21-51.75s1.125-14.25,0.75-17.625S545.75,566.75,542,566.375s-5.625,6-10.875,6.375 s-28.125-13.875-33-17.625s-7.125-12.75-13.875-19.5s-7.5-1.5-18-5.625s-44.25-16.5-70.125-27s-28.125-25.219-28.5-35.625 s-15.75-25.5-22.961-36.375c-7.209-10.875-8.539-25.875-11.164-22.5s1 [rest of string was truncated]";. + /// Looks up a localized string similar to M16 14v1H0V0h1v14h15z m-11-1H3V8h2v5z m4 0H7V3h2v10z m4 0H11V6h2v7z. /// </summary> - internal static string globe { + internal static string graph { get { - return ResourceManager.GetString("globe", resourceCulture); + return ResourceManager.GetString("graph", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M704,256H512v640h192V256z M960,448H768v448h192V448z M64,960V832h64v-64H64V640h64v-64H64V448h64v-64H64V256h64v-64H64V64 h64V0H0v1024h1024v-64H64z M448,576H256v320h192V576z. + /// Looks up a localized string similar to M11.2 4c-0.52-0.63-1.25-0.95-2.2-1-0.97 0-1.69 0.42-2.2 1s-0.78 0.92-0.8 1c-0.02-0.08-0.28-0.42-0.8-1s-1.17-1-2.2-1c-0.95 0.05-1.69 0.38-2.2 1-0.52 0.61-0.78 1.28-0.8 2 0 0.52 0.09 1.52 0.67 2.67s2.34 2.94 5.33 5.33c2.98-2.39 4.77-4.17 5.34-5.33s0.66-2.17 0.66-2.67c-0.02-0.72-0.28-1.39-0.8-2.02z. /// </summary> - internal static string graph { + internal static string heart { get { - return ResourceManager.GetString("graph", resourceCulture); + return ResourceManager.GetString("heart", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,64c-90.938,0-175.312,27.531-245.938,74.062L128,64v256h256l-88-88c45.438-24.688,96.688-40,152-40 c176.75,0,320,143.219,320,320c0,176.75-143.25,320-320,320c-176.781,0-320-143.25-320-320c0-45.562,9.781-88.781,27-128H64v-99.406 C24.312,351.5,0,428.594,0,512c0,247.438,200.562,448,448,448c247.438,0,448-200.562,448-448C896,264.562,695.438,64,448,64z M447.031,831L512,768V576h128l64-64l-64-64H512l-64-64L320,512l64,64v192L447.031,831z. + /// Looks up a localized string similar to M8 13H6V6h5v2H8v5zM7 1c-2.19 0-4.13 1.02-5.41 2.59L0 2v4h4l-1.5-1.5c1.05-1.33 2.67-2.2 4.5-2.2 3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8c0-0.34 0.03-0.67 0.09-1H0.08c-0.05 0.33-0.08 0.66-0.08 1 0 3.86 3.14 7 7 7s7-3.14 7-7S10.86 1 7 1z. /// </summary> internal static string history { get { @@ -808,7 +754,7 @@ internal static string history { } /// <summary> - /// Looks up a localized string similar to M192,576l64,384h192V640h128v320h192l64-384L512,256L192,576z M832,384V128H704l0.312,128.312L512,64L0,576h128l384-384 l384,384h128L832,384z. + /// Looks up a localized string similar to M16 9L13 6V2H11v2L8 1 0 9h2l1 5c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1l1-5h2zM12 14H9V10H7v4H4l-1.19-6.31 5.19-5.19 5.19 5.19-1.19 6.31z. /// </summary> internal static string home { get { @@ -817,7 +763,7 @@ internal static string home { } /// <summary> - /// Looks up a localized string similar to M63.938,448h128v128h64V192.062h-64V384h-128V192.062H0V576h63.938V448z M639.875,576V448h-63.938v128H639.875z M639.875,384V256.062h-63.938V384H639.875z M447.938,384V256.062h128v-64h-192V576h64V448h128v-64H447.938z M0,832h639.875V704H0 V832z. + /// Looks up a localized string similar to M1 7h2v2h1V3h-1v3H1V3H0v6h1V7z m9 2V7h-1v2h1z m0-3V4h-1v2h1z m-3 0V4h2v-1H6v6h1V7h2v-1H7zM0 13h10V11H0v2z. /// </summary> internal static string horizontal_rule { get { @@ -826,25 +772,25 @@ internal static string horizontal_rule { } /// <summary> - /// Looks up a localized string similar to M570.625,513.906C688.312,429.375,768,274.25,768,129.906c0-70.656-172-128-384-128c-212.031,0-384,57.344-384,128 c0,144.344,79.688,299.469,197.375,384C79.688,598.5,0,753.625,0,897.875c0,70.688,171.969,128,384,128c212,0,384-57.312,384-128 C768,753.625,688.312,598.5,570.625,513.906z M384,65.906c141.375,0,256,28.625,256,64c0,35.406-114.625,64-256,64 s-256-28.594-256-64C128,94.531,242.625,65.906,384,65.906z M320,771.5c-153.719,7.125-238.281,40.25-252.688,81.688 C82.875,739.625,156.031,637.75,256,577.875c0,0,6 [rest of string was truncated]";. + /// Looks up a localized string similar to M3 6c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1V7c0-0.55-0.45-1-1-1H3z m8 1.75l-1.25 1.25h-1.5l-1.25-1.25-1.25 1.25h-1.5l-1.25-1.25v-0.75h0.75l1.25 1.25 1.25-1.25h1.5l1.25 1.25 1.25-1.25h0.75v0.75zM5 11h4v1H5v-1z m2-9C3.14 2 0 4.91 0 8.5v4.5c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V8.5c0-3.59-3.14-6.5-7-6.5z m6 11H1V8.5c0-3.09 2.64-5.59 6-5.59s6 2.5 6 5.59v4.5z. /// </summary> - internal static string hourglass { + internal static string hubot { get { - return ResourceManager.GetString("hourglass", resourceCulture); + return ResourceManager.GetString("hubot", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M512,64C229.25,64,0,293.25,0,576v256c0,126.375,128,128,128,128h768c0,0,128-1.375,128-128V576 C1024,293.25,794.75,64,512,64z M608,832H416c-17.719,0-32-14.375-32-32s14.281-32,32-32h192c17.625,0,32,14.375,32,32 S625.625,832,608,832z M896,704c0,61.062-64,64-64,64H704c0,0,0-64-64-64H384c-64,0-64,64-64,64H192c-63.094,0-64-64-64-64V344.062 C206,215.219,347.969,128,512,128c164,0,305.875,87.219,384,216V704z M768,320H256c-35.406,0-64,28.594-64,64v128 c0,35.375,28.594,64,64,64h512c35.375,0,64-28.625,64-64V384C832, [rest of string was truncated]";. + /// Looks up a localized string similar to M14 9l-1.13-7.14c-0.08-0.48-0.5-0.86-1-0.86H2.13c-0.5 0-0.92 0.38-1 0.86L0 9v5c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V9z m-3.28 0.55l-0.44 0.89c-0.17 0.34-0.52 0.56-0.91 0.56H4.61c-0.38 0-0.72-0.22-0.89-0.55l-0.44-0.91c-0.17-0.33-0.52-0.55-0.89-0.55H1l1-7h10l1 7h-1.38c-0.39 0-0.73 0.22-0.91 0.55z. /// </summary> - internal static string hubot { + internal static string inbox { get { - return ResourceManager.GetString("hubot", resourceCulture); + return ResourceManager.GetString("inbox", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,384c35.375,0,64-28.594,64-64c0-35.312-28.625-64-64-64c-35.406,0-64,28.688-64,64C384,355.406,412.594,384,448,384z M448,64C200.562,64,0,264.562,0,512c0,247.438,200.562,448,448,448c247.438,0,448-200.562,448-448C896,264.562,695.438,64,448,64z M448,832c-176.781,0-320-143.25-320-320c0-176.781,143.219-320,320-320c176.75,0,320,143.219,320,320 C768,688.75,624.75,832,448,832z M512,512c0-66-64-64-64-64s0,0-64,0s-64,64-64,64h64c0,0,0,128,0,192s64,64,64,64s0,0,64,0 s64-64,64-64h-64C512,704,512,578,512,512z. + /// Looks up a localized string similar to M6.3 5.69c-0.19-0.19-0.28-0.42-0.28-0.7s0.09-0.52 0.28-0.7 0.42-0.28 0.7-0.28 0.52 0.09 0.7 0.28 0.28 0.42 0.28 0.7-0.09 0.52-0.28 0.7-0.42 0.3-0.7 0.3-0.52-0.11-0.7-0.3z m1.7 2.3c-0.02-0.25-0.11-0.48-0.31-0.69-0.2-0.19-0.42-0.3-0.69-0.31h-1c-0.27 0.02-0.48 0.13-0.69 0.31-0.2 0.2-0.3 0.44-0.31 0.69h1v3c0.02 0.27 0.11 0.5 0.31 0.69 0.2 0.2 0.42 0.31 0.69 0.31h1c0.27 0 0.48-0.11 0.69-0.31 0.2-0.19 0.3-0.42 0.31-0.69h-1V7.98z m-1-5.69C3.86 2.3 1.3 4.84 1.3 7.98s2.56 5.7 5.7 5.7 5.7-2.55 5.7-5.7-2.56-5.69-5.7-5 [rest of string was truncated]";. /// </summary> internal static string info { get { @@ -853,7 +799,7 @@ internal static string info { } /// <summary> - /// Looks up a localized string similar to M704,316.031l-96,96L768,576l256-256l-96-96L769.25,382.781L704,316.031z M512,832c-176.781,0-320-143.25-320-320 c0-176.781,143.219-320,320-320c88.375,0,168.375,35.844,226.25,93.75l90.562-90.5C747.75,114.125,635.75,64,512,64 C264.562,64,64,264.562,64,512c0,247.438,200.562,448,448,448c247.438,0,448-200.562,448-448L759.75,712.25 C768.688,701.25,684.75,832,512,832z M576,256H448v320h128V256z M448,768h128V640H448V768z. + /// Looks up a localized string similar to M7 10h2v2H7V10z m2-6H7v5h2V4z m1.5 1.5l-1 1 2.5 2.5 4-4.5-1-1-3 3.5-1.5-1.5zM8 13.7c-3.14 0-5.7-2.56-5.7-5.7s2.56-5.7 5.7-5.7c1.83 0 3.45 0.88 4.5 2.2l0.92-0.92C12.14 2 10.19 1 8 1 4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-0.66 2.41-2.86 4.19-5.48 4.19z. /// </summary> internal static string issue_closed { get { @@ -862,7 +808,7 @@ internal static string issue_closed { } /// <summary> - /// Looks up a localized string similar to M448,64C200.562,64,0,264.562,0,512c0,247.438,200.562,448,448,448c247.438,0,448-200.562,448-448 C896,264.562,695.438,64,448,64z M448,832c-176.781,0-320-143.25-320-320c0-176.781,143.219-320,320-320 c176.75,0,320,143.219,320,320C768,688.75,624.75,832,448,832z M384,768h128V640H384V768z M384,576h128V256H384V576z. + /// Looks up a localized string similar to M7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m1 3H6v5h2V4z m0 6H6v2h2V10z. /// </summary> internal static string issue_opened { get { @@ -871,7 +817,7 @@ internal static string issue_opened { } /// <summary> - /// Looks up a localized string similar to M639.125,767.25C585.75,807.375,520,832,448,832c-176.781,0-320-143.25-320-320c0-45.562,9.781-88.781,27-128H64v-99.469 C24.312,351.438,0,428.594,0,512c0,247.438,200.562,448,448,448c107.375,0,204.5-39.312,281.75-102.25L768,896V704H576 L639.125,767.25z M384,768h128V640H384V768z M512,256H384v320h128V256z M896,512c0-247.438-200.562-448-448-448 c-107.406,0-204.531,39.312-281.656,102.344L128,128v192h192l-63.156-63.156C310.281,216.688,376,192,448,192 c176.75,0,320,143.219,320,320c0,45.562-9.75,88.75-27,128h91v99 [rest of string was truncated]";. + /// Looks up a localized string similar to M8 9H6V4h2v5zM6 12h2V10H6v2z m6.33-2H10l1.5 1.5c-1.05 1.33-2.67 2.2-4.5 2.2-3.14 0-5.7-2.56-5.7-5.7 0-0.34 0.03-0.67 0.09-1H0.08c-0.05 0.33-0.08 0.66-0.08 1 0 3.86 3.14 7 7 7 2.19 0 4.13-1.02 5.41-2.59l1.59 1.59V10H12.33zM1.67 6h2.33l-1.5-1.5c1.05-1.33 2.67-2.2 4.5-2.2 3.14 0 5.7 2.56 5.7 5.7 0 0.34-0.03 0.67-0.09 1h1.31c0.05-0.33 0.08-0.66 0.08-1 0-3.86-3.14-7-7-7-2.19 0-4.13 1.02-5.41 2.59L0 2v4h1.67z. /// </summary> internal static string issue_reopened { get { @@ -880,70 +826,52 @@ internal static string issue_reopened { } /// <summary> - /// Looks up a localized string similar to M704,0H512.5c0,0-0.75,64-96.625,64S320.5,0,320.5,0h-192c0,128-1.598,384-128,384L0,960c0,0,0,64,64,64h704 c64,0,64-64,64-64V384C705.598,384,704,128,704,0z M95.322,960c-31.946,0-31.946-31.946-31.946-31.946l0.5-480.024L63.71,448 C182.875,384,192,256,192,64h64c0,0,0,191.746,160,191.746S576,64,576,64s37.583,0,64,0c0,185.875,32.431,275.776,63.998,338.875 L703.999,960H95.322z M480,384l-32,32v320l32,32h128l32-32V416l-32-32H480z M576.062,704h-64V448h64V704z M224,384l-32,32v320l32,32 h128l32-32V416l-32-32H224z M3 [rest of string was truncated]";. - /// </summary> - internal static string jersey { - get { - return ResourceManager.GetString("jersey", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M767.75,192H0.25L384,575.75L767.75,192z M0,704v128h768V704H0z. + /// Looks up a localized string similar to M2.81 5h1.98L3 14H1l1.81-9z m0.36-2.7c0-0.7 0.58-1.3 1.33-1.3 0.56 0 1.13 0.38 1.13 1.03 0 0.75-0.59 1.3-1.33 1.3-0.58 0-1.13-0.38-1.13-1.03z. /// </summary> - internal static string jump_down { + internal static string italic { get { - return ResourceManager.GetString("jump_down", resourceCulture); + return ResourceManager.GetString("italic", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M256.25,512L640,895.75v-767.5L256.25,512z M0,896h128V128H0V896z. + /// Looks up a localized string similar to M3.5 6l-0.5 0.5v5l0.5 0.5h2l0.5-0.5V6.5l-0.5-0.5H3.5z m1.5 5h-1V7h1v4z m6.27-7.25c-0.22-1.38-0.31-2.63-0.27-3.75H8.02c0 0.27-0.13 0.48-0.39 0.69-0.25 0.2-0.63 0.3-1.13 0.3s-0.88-0.09-1.13-0.3c-0.23-0.2-0.36-0.42-0.36-0.69H2c0.05 1.13-0.03 2.38-0.25 3.75-0.2 1.38-0.8 2.13-1.75 2.25v9c0.02 0.27 0.11 0.48 0.31 0.69s0.42 0.3 0.69 0.31h11c0.27-0.02 0.48-0.11 0.69-0.31s0.3-0.42 0.31-0.69V6c-0.95-0.13-1.53-0.88-1.75-2.25z m0.73 11.25H1V7c0.89-0.5 1.48-1.25 1.72-2.25s0.31-2.25 0.28-3.75h1c-0.02 0.78 0.16 1.47 0.52 [rest of string was truncated]";. /// </summary> - internal static string jump_left { - get { - return ResourceManager.GetString("jump_left", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M0,895.75L383.75,512L0,128.188V895.75z M512,128v768h128V128H512z. - /// </summary> - internal static string jump_right { + internal static string jersey { get { - return ResourceManager.GetString("jump_right", resourceCulture); + return ResourceManager.GetString("jersey", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M0.188,832h767.5L384,448.25L0.188,832z M0,192v128h768V192H0z. + /// Looks up a localized string similar to M12.83 2.17c-0.75-0.75-1.69-1.14-2.83-1.17-1.13 0.03-2.08 0.42-2.83 1.17s-1.13 1.69-1.16 2.83c0 0.3 0.03 0.59 0.09 0.89L0 12v1l1 1h2l1-1v-1h1v-1h1v-1h2l1.09-1.11c0.3 0.08 0.59 0.11 0.91 0.11 1.14-0.03 2.08-0.42 2.83-1.17s1.14-1.69 1.17-2.83c-0.03-1.14-0.42-2.08-1.17-2.83zM11 5.38c-0.77 0-1.38-0.61-1.38-1.38s0.61-1.38 1.38-1.38 1.38 0.61 1.38 1.38-0.61 1.38-1.38 1.38z. /// </summary> - internal static string jump_up { + internal static string key { get { - return ResourceManager.GetString("jump_up", resourceCulture); + return ResourceManager.GetString("key", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M384,384v-64h-64c0,0,0-64-64-64H128c0,0-44.031-8-64,22C44.031,308.031,0,384,0,384v192c0,0,38,60,64,96s64,32,64,32h128 c64,0,64-64,64-64h64v-64h64l128-128l128,128l128-128l128,128l64-64V384H384z M256,640H128l128-64V640z M160,576l-96-96l96-96l96,96 L160,576z M256,384l-128-64h128V384z. + /// Looks up a localized string similar to M10 5h-1v-1h1v1z m-7 1h-1v1h1v-1z m5-2h-1v1h1v-1z m-4 0H2v1h2v-1z m8 7h2v-1H12v1zM8 7h1v-1h-1v1zM4 10H2v1h2v-1z m8-6h-1v1h1v-1z m2 0h-1v1h1v-1zM12 9h2V6H12v3z m4-6v9c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h14c0.55 0 1 0.45 1 1z m-1 0H1v9h14V3zM6 7h1v-1h-1v1z m0-3h-1v1h1v-1zM4 7h1v-1h-1v1z m1 4h6v-1H5v1z m5-4h1v-1h-1v1z m-7 1h-1v1h1v-1z m5 0v1h1v-1h-1z m-2 0v1h1v-1h-1z m-1 0h-1v1h1v-1z m5 1h1v-1h-1v1z. /// </summary> - internal static string key { + internal static string keyboard { get { - return ResourceManager.GetString("key", resourceCulture); + return ResourceManager.GetString("keyboard", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M640,576h64V448h-64V576z M768,256h-64v128h64V256z M640,256h-64v128h64V256z M512,576h64V448h-64V576z M384,768h320V640 H384V768z M768,576h128V256h-64v192h-64V576z M256,768h64V640h-64V768z M768,768h128V640H768V768z M512,256h-64v128h64V256z M192,448h-64v128h64V448z M192,640h-64v128h64V640z M0,128v768h1024V128H0z M960,832H64V192h896V832z M384,576h64V448h-64V576z M256,256H128v128h128V256z M384,256h-64v128h64V256z M256,576h64V448h-64V576z. + /// Looks up a localized string similar to M7 4c-0.83 0-1.5-0.67-1.5-1.5s0.67-1.5 1.5-1.5 1.5 0.67 1.5 1.5-0.67 1.5-1.5 1.5z m7 6c0 1.11-0.89 2-2 2h-1c-1.11 0-2-0.89-2-2l2-4h-1c-0.55 0-1-0.45-1-1h-1v8c0.42 0 1 0.45 1 1h1c0.42 0 1 0.45 1 1H3c0-0.55 0.58-1 1-1h1c0-0.55 0.58-1 1-1h0.03l-0.03-8h-1c0 0.55-0.45 1-1 1h-1l2 4c0 1.11-0.89 2-2 2h-1C0.89 12 0 11.11 0 10l2-4H1v-1h3c0-0.55 0.45-1 1-1h4c0.55 0 1 0.45 1 1h3v1h-1l2 4zM2.5 7L1 10h3l-1.5-3z m10.5 3l-1.5-3-1.5 3h3z. /// </summary> - internal static string keyboard { + internal static string law { get { - return ResourceManager.GetString("keyboard", resourceCulture); + return ResourceManager.GetString("law", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M512,64c-176.731,0-320,143.269-320,320c0,104.69,50.278,197.633,128,256.015V832c0,35.346,28.653,64,64,64 c0,35.346,28.653,64,64,64h128c35.347,0,64-28.654,64-64c35.347,0,64-28.654,64-64V640.015C781.722,581.633,832,488.69,832,384 C832,207.269,688.731,64,512,64z M640,800c0,17.673-14.326,32-32,32H416c-17.674,0-32-14.327-32-32v-32h256V800z M704,553.307 c-33.234,33.03-64,42.389-64,124.041V704h-64V576l128-128v-64l-64-64l-64,64l-64-64l-64,64l-64-64l-64,64v64l128,128v128h-64 v-26.652c0-81.652-30.766-91.011-64 [rest of string was truncated]";. + /// Looks up a localized string similar to M5.5 0C2.48 0 0 2.19 0 5c0 0.92 0.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c0.22-1.22 0.66-1.75 2-4 0.45-0.75 1-2.08 1-3C11 2.19 8.52 0 5.5 0z m3.64 7.48c-0.25 0.44-0.47 0.8-0.67 1.11-0.86 1.41-1.25 2.06-1.45 3.23-0.02 0.05-0.02 0.11-0.02 0.17H4c0-0.06 0-0.13-0.02-0.17-0.2-1.17-0.59-1.83-1.45-3.23-0.2-0.31-0.42-0.67-0.67-1.11-0.42-0.7-0.86-1.83-0.86-2.48C1 2.8 3.02 1 5.5 1c1.22 0 2.36 0.42 3.22 1.19 0.83 0.75 1.28 1.75 1.28 2.81 0 0.66-0.44 1.78-0.86 2.48zM3 14h5c-0.23 1.14-1.3 2-2.5 2s-2.27-0.86-2.5-2z. /// </summary> internal static string light_bulb { get { @@ -952,7 +880,7 @@ internal static string light_bulb { } /// <summary> - /// Looks up a localized string similar to M768,768H576c0,0-254.281,0-256-256c0-22.281,3.469-43.469,8.312-64h137.405c-11,18.875-17.719,40.562-17.719,64 c0,128,128,128,128,128h192c0,0,128,0,128-128S768,384,768,384s-0.125-64-64-128h64c0,0,256,0,256,256S768,768,768,768z M695.688,576H558.25c11-18.875,17.75-40.562,17.75-64c0-128-128-128-128-128H256c0,0-128,0-128,128s128,128,128,128 s-3.906,64.875,65.719,128H256c0,0-256,0-256-256s256-256,256-256h192c0,0,256,0,256,256C704,534.281,700.5,555.5,695.688,576z. + /// Looks up a localized string similar to M4 9h1v1h-1c-1.5 0-3-1.69-3-3.5s1.55-3.5 3-3.5h4c1.45 0 3 1.69 3 3.5 0 1.41-0.91 2.72-2 3.25v-1.16c0.58-0.45 1-1.27 1-2.09 0-1.28-1.02-2.5-2-2.5H4c-0.98 0-2 1.22-2 2.5s1 2.5 2 2.5z m9-3h-1v1h1c1 0 2 1.22 2 2.5s-1.02 2.5-2 2.5H9c-0.98 0-2-1.22-2-2.5 0-0.83 0.42-1.64 1-2.09v-1.16c-1.09 0.53-2 1.84-2 3.25 0 1.81 1.55 3.5 3 3.5h4c1.45 0 3-1.69 3-3.5s-1.5-3.5-3-3.5z. /// </summary> internal static string link { get { @@ -961,7 +889,7 @@ internal static string link { } /// <summary> - /// Looks up a localized string similar to M640,768H128V257.906L256,256V128H0v768h768V576H640V768z M384,128l128,128L320,448l128,128l192-192l128,128V128H384z. + /// Looks up a localized string similar to M11 10h1v3c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3v1H1v10h10V10zM6 2l2.25 2.25-3.25 3.25 1.5 1.5 3.25-3.25 2.25 2.25V2H6z. /// </summary> internal static string link_external { get { @@ -970,7 +898,7 @@ internal static string link_external { } /// <summary> - /// Looks up a localized string similar to M0,704h64v-64h128v64L0,864v96h256v-64H64l192-160V576H0V704z M384,960h512V832H384V960z M192,128H64v64h128v256h64V64h-64 V128z M384,64v128h512V64H384z M384,448h512V320H384V448z M384,704h512V576H384V704z. + /// Looks up a localized string similar to M12 13c0 0.59 0 1-0.59 1H4.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h6.81c0.59 0 0.59 0.41 0.59 1zM4.59 4h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1z m6.81 3H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1zM2 1H1.28C0.98 1.19 0.7 1.25 0.25 1.34v0.66h0.75v2.14H0.16v0.86h2.84v-0.86h-1V1z m0.25 8.13c-0.17 0-0.45 0.03-0.66 0.06 0.53-0.56 1.14-1.25 1.14-1.89-0.02-0.78-0.56-1.3-1.36-1.3-0.59 0-0.97 0.2-1.38 0.64l0.58 0.58c0.19-0.19 0.38-0.38 0.64 [rest of string was truncated]";. /// </summary> internal static string list_ordered { get { @@ -979,7 +907,7 @@ internal static string list_ordered { } /// <summary> - /// Looks up a localized string similar to M0,192h128V64H0V192z M0,704h128V576H0V704z M0,448h128V320H0V448z M0,960h128V832H0V960z M256,704h640V576H256V704z M256,448h640V320H256V448z M256,960h640V832H256V960z M256,64v128h640V64H256z. + /// Looks up a localized string similar to M2 13c0 0.59 0 1-0.59 1H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h0.81c0.59 0 0.59 0.41 0.59 1z m2.59-9h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1zM1.41 7H0.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h0.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z m0-5H0.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h0.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z m10 5H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z m0 5H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h6.81c0.59 0 0.59- [rest of string was truncated]";. /// </summary> internal static string list_unordered { get { @@ -988,7 +916,7 @@ internal static string list_unordered { } /// <summary> - /// Looks up a localized string similar to M320,0C143.219,0,0,143.219,0,320s320,704,320,704s320-527.219,320-704S496.75,0,320,0z M320,448 c-70.656,0-128-57.344-128-128s57.344-128,128-128c70.625,0,128,57.344,128,128S390.625,448,320,448z. + /// Looks up a localized string similar to M6 0C2.69 0 0 2.5 0 5.5c0 4.52 6 10.5 6 10.5s6-5.98 6-10.5C12 2.5 9.31 0 6 0z m0 14.55C4.14 12.52 1 8.44 1 5.5 1 3.02 3.25 1 6 1c1.34 0 2.61 0.48 3.56 1.36 0.92 0.86 1.44 1.97 1.44 3.14 0 2.94-3.14 7.02-5 9.05z m2-9.05c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z. /// </summary> internal static string location { get { @@ -997,52 +925,7 @@ internal static string location { } /// <summary> - /// Looks up a localized string similar to M640,576L640 448 896 448 896 320 640 320 640 192 448 336 448 192 192 64 704 64 704 256 768 256 768 0 64 0 64 832 448 1024 448 832 768 832 768 512 704 512 704 768 448 768 448 432 z. - /// </summary> - internal static string log_in { - get { - return ResourceManager.GetString("log_in", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M640,768H384V192L128,64h512v192h64V0H0v832l384,192V832h320V512h-64V768z M1024,384L768,192v128H512v128h256v128L1024,384z . - /// </summary> - internal static string log_out { - get { - return ResourceManager.GetString("log_out", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M265.521,0.253L0,761.749h77.15l69.137-210.413h288.566l69.137,210.413h77.149L315.619,0.253H265.521z M165.324,492.22L290.57,114.478L414.814,492.22H165.324z M864.492,250.745c-54.108,0-104.205,18.036-156.308,51.101l-15.03-41.081 h-39.076v751.476h70.139V730.688c43.084,28.056,94.184,41.081,140.275,41.082c161.316,0,205.402-116.229,205.402-260.513 C1069.895,366.974,1025.808,250.745,864.492,250.745z M864.492,709.645c-65.129,0-101.199-11.021-140.275-32.062V344.929 c39.076-21.041,75.146-32.062,140.275-32.062c120. [rest of string was truncated]";. - /// </summary> - internal static string logo_apps { - get { - return ResourceManager.GetString("logo_apps", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M265.521,0.253L0,761.749h77.15l69.137-210.413h288.566l69.137,210.413h77.15L315.619,0.253H265.521z M165.324,492.22L290.57,114.478L414.814,492.22H165.324z M733.535,100.45l-70.139,20.039v135.266l-100.197,29.058v31.061h100.197 v319.628c0,98.193,77.152,136.268,160.314,136.268c13.025,0,33.064-1.002,45.09-4.008v-56.11 c-13.027,1.002-26.053,2.004-39.078,2.004c-63.123,0-96.188-25.049-96.188-86.169V315.873H888.84v-55.108H733.535V100.45z M1147.951,250.745c-171.338,0-210.414,107.211-210.414,260.512s39.076,260.512 [rest of string was truncated]";. - /// </summary> - internal static string logo_atom { - get { - return ResourceManager.GetString("logo_atom", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M0,386.011C0,599.43,71.14,771.769,295.58,771.768c92.182,0,154.304-17.032,195.385-35.067v-67.131 c-69.137,28.055-120.236,39.075-195.385,39.075c-164.322,0-223.438-141.276-223.438-322.634 c0-181.357,59.116-322.634,223.438-322.634c75.148,0,126.248,11.021,195.385,39.077V35.322 C449.884,17.286,387.762,0.253,295.58,0.253C71.14,0.253,0,172.592,0,386.011z M784.53,250.745 c-46.091,0-94.186,13.025-137.271,41.081V10.272h-68.134v751.476h68.134V344.929c37.074-21.041,72.143-32.062,137.271-32.062 c62.122,0,100.198,20. [rest of string was truncated]";. - /// </summary> - internal static string logo_chat { - get { - return ResourceManager.GetString("logo_chat", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M300.59,430.098h157.31v256.504c-35.069,17.033-105.206,22.043-162.319,22.043 c-164.322,0-223.438-141.276-223.438-322.634c0-181.357,59.116-322.634,223.438-322.634c81.16,0,132.26,15.029,210.414,47.093 V42.336C464.913,21.294,399.785,0.253,295.58,0.253C71.14,0.253,0,172.592,0,386.011s71.14,385.758,295.58,385.758 c105.207,0,179.353-17.032,230.453-41.079V366.974H300.59V430.098z M709.487,669.569V260.765h-68.134v400.787 c0,80.158,38.073,110.217,110.216,110.217v-57.111C720.508,714.657,709.487,704.637,709.487,669. [rest of string was truncated]";. + /// Looks up a localized string similar to M4.7 6.72h2.45v4.02c-0.55 0.27-1.64 0.34-2.53 0.34-2.56 0-3.48-2.2-3.48-5.05 0-2.83 0.92-5.05 3.48-5.05 1.27 0 2.06 0.23 3.28 0.73v-1.06c-0.64-0.33-1.66-0.66-3.28-0.66-3.5 0-4.63 2.69-4.63 6.03s1.11 6.03 4.63 6.03c1.64 0 2.8-0.27 3.59-0.64v-5.69h-3.52v0.98z m6.39 3.73v-6.39h-1.06v6.27c0 1.25 0.59 1.72 1.72 1.72v-0.89c-0.48 0-0.66-0.16-0.66-0.7z m0.25-8.73c0-0.44-0.34-0.78-0.78-0.78s-0.78 0.34-0.78 0.78 0.34 0.78 0.78 0.78 0.78-0.34 0.78-0.78z m4.33 5.69c-1.5-0.13-1.78-0.48-1.78-1.17 0-0.77 0.33-1.34 1.88-1. [rest of string was truncated]";. /// </summary> internal static string logo_gist { get { @@ -1051,7 +934,7 @@ internal static string logo_gist { } /// <summary> - /// Looks up a localized string similar to M552.73,332.135H311.557c-6.205,0-11.25,5.045-11.25,11.297v117.887c0,6.252,5.045,11.272,11.25,11.272 h94.109v146.542c0,0-21.145,7.057-79.496,7.057c-68.914,0-165.156-25.244-165.156-236.795 c0-211.642,100.197-239.491,194.307-239.491c81.465,0,116.514,14.304,138.869,21.241c7.01,2.203,13.404-4.831,13.404-11.105 L534.543,46.13c0-2.912-1.041-6.417-4.262-8.785C521.186,30.952,465.865,0,326.168,0C165.133,0,0,68.487,0,397.757 C0,726.979,189.051,776,348.381,776c131.883,0,212.021-56.314,212.021-56.314c3.268-1.801,3.6 [rest of string was truncated]";. + /// Looks up a localized string similar to M8.64 5.19H4.88c-0.11 0-0.19 0.08-0.19 0.17v1.84c0 0.09 0.08 0.17 0.19 0.17h1.47v2.3s-0.33 0.11-1.25 0.11c-1.08 0-2.58-0.39-2.58-3.7s1.58-3.73 3.05-3.73c1.27 0 1.81 0.22 2.17 0.33 0.11 0.03 0.2-0.08 0.2-0.17l0.42-1.78c0-0.05-0.02-0.09-0.06-0.14-0.14-0.09-1.02-0.58-3.2-0.58C2.58 0 0 1.06 0 6.2s2.95 5.92 5.44 5.92c2.06 0 3.31-0.89 3.31-0.89 0.05-0.02 0.06-0.09 0.06-0.13V5.36c0-0.09-0.08-0.17-0.19-0.17h0.02zM27.7 0.44h-2.13c-0.09 0-0.17 0.08-0.17 0.17v4.09h-3.31V0.61c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0. [rest of string was truncated]";. /// </summary> internal static string logo_github { get { @@ -1060,25 +943,7 @@ internal static string logo_github { } /// <summary> - /// Looks up a localized string similar to M280.551,0.253C35.068,0.253,0,189.625,0,386.011s35.068,385.758,280.551,385.758 s280.552-189.372,280.552-385.758S526.033,0.253,280.551,0.253z M280.551,708.645c-181.355,0-208.409-142.279-208.409-322.634 S99.195,63.377,280.551,63.377c181.356,0,208.41,142.279,208.41,322.634S461.907,708.645,280.551,708.645z M644.162,511.257 c0,144.283,42.083,260.512,211.416,260.512c49.096,0,105.207-6.012,149.293-25.049v-60.118 c-39.076,13.025-79.156,23.045-150.295,23.045c-126.248,0-140.275-99.194-140.275-198.39c0-99.194,14.0 [rest of string was truncated]";. - /// </summary> - internal static string logo_octicons { - get { - return ResourceManager.GetString("logo_octicons", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M482.949,10.272H0v63.124h207.408v688.353h68.133V73.396h207.408V10.272z M631.641,250.745 c-48.094,0-106.209,8.016-150.295,27.053v60.118c39.076-13.026,83.164-25.049,150.295-25.049 c93.184,0,119.234,32.063,119.235,105.207v28.055l-132.26,8.017c-131.258,8.016-178.351,71.14-178.351,156.308 c0,84.165,44.086,161.316,175.345,161.316c50.1,0,106.209-17.034,151.297-50.1l15.028,40.079h39.078V419.076 C821.014,318.879,781.936,250.745,631.641,250.745z M750.875,669.568c-43.084,25.049-79.155,40.078-133.262,40.078 c-95.1 [rest of string was truncated]";. - /// </summary> - internal static string logo_talks { - get { - return ResourceManager.GetString("logo_talks", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M0,192v640h896V192H0z M768,256L448,520L128,256H768z M64,320l252.031,191.625L64,704V320z M128,768l254-206.25L448,612 l65.875-50.125L768,768H128z M832,704L579.625,511.938L832,320V704z. + /// Looks up a localized string similar to M0 4v8c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1H1c-0.55 0-1 0.45-1 1z m13 0L7 9 1 4h12zM1 5.5l4 3L1 11.5V5.5z m1 6.5l3.5-3 1.5 1.5 1.5-1.5 3.5 3H2z m11-0.5L9 8.5l4-3v6z. /// </summary> internal static string mail { get { @@ -1087,7 +952,7 @@ internal static string mail { } /// <summary> - /// Looks up a localized string similar to M576,384H256v64h320V384z M384,256H256v64h128V256z M768,228.531V128H627.188L448,0L268.812,128H128v100.531L0,320v640h896 V320L768,228.531z M192,192h512v244.812L448,648L192,436.812V192z M64,448l252.031,191.625L64,832V448z M128,896l254-206.25L448,740 l65.875-50.125L768,896H128z M832,832L579.625,639.938L832,448V832z. + /// Looks up a localized string similar to M6 5H4v-1h2v1z m3 1H4v1h5v-1z m5-0.48v8.48c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V5.52c0-0.33 0.16-0.63 0.42-0.81l1.58-1.13v-0.58c0-0.55 0.45-1 1-1h1.2L7 0l2.8 2h1.2c0.55 0 1 0.45 1 1v0.58l1.58 1.13c0.27 0.19 0.42 0.48 0.42 0.81zM3 7.5l4 2.5 4-2.5V3H3v4.5zM1 13.5l4.5-3L1 7.5v6z m11 0.5L7 11 2 14h10z m1-6.5L8.5 10.5l4.5 3V7.5z. /// </summary> internal static string mail_read { get { @@ -1096,7 +961,7 @@ internal static string mail_read { } /// <summary> - /// Looks up a localized string similar to M255.929,256h128v320h128c0,0,0-306,0-384s-64-64-64-64h-192V32l-192,160l192,160V256z M575.929,384v64h192l-320,256 l-320-256h192v-64H0v640h896V384H575.929z M63.929,512l256,192l-256,192V512z M127.929,960l254-206.286l66,46.286l65.857-46.143 L767.929,960H127.929z M831.929,896l-256-192l256-192V896z. + /// Looks up a localized string similar to M6 2.5l-6 4.5 6 4.5v-3c1.73 0 5.14 0.95 6 4.38 0-4.55-3.06-7.05-6-7.38v-3z. /// </summary> internal static string mail_reply { get { @@ -1105,7 +970,7 @@ internal static string mail_reply { } /// <summary> - /// Looks up a localized string similar to M512,0C229.25,0,0,229.25,0,512c0,226.25,146.688,418.125,350.156,485.812c25.594,4.688,34.938-11.125,34.938-24.625 c0-12.188-0.469-52.562-0.719-95.312C242,908.812,211.906,817.5,211.906,817.5c-23.312-59.125-56.844-74.875-56.844-74.875 c-46.531-31.75,3.53-31.125,3.53-31.125c51.406,3.562,78.47,52.75,78.47,52.75c45.688,78.25,119.875,55.625,149,42.5 c4.654-33,17.904-55.625,32.5-68.375C304.906,725.438,185.344,681.5,185.344,485.312c0-55.938,19.969-101.562,52.656-137.406 c-5.219-13-22.844-65.094,5.062-135.562c0,0 [rest of string was truncated]";. + /// Looks up a localized string similar to M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 [rest of string was truncated]";. /// </summary> internal static string mark_github { get { @@ -1114,25 +979,16 @@ internal static string mark_github { } /// <summary> - /// Looks up a localized string similar to M512,0C229.25,0,0,229.25,0,512c0,226.25,146.688,418.125,350.156,485.812c25.594,4.688,34.938-11.125,34.938-24.625 c0-12.188-0.469-52.562-0.719-95.312C242,908.812,211.906,817.5,211.906,817.5c-23.312-59.125-56.844-74.875-56.844-74.875 c-46.531-31.75,3.531-31.125,3.531-31.125c51.406,3.562,78.469,52.75,78.469,52.75c45.688,78.25,119.875,55.625,149,42.5 c4.655-33,17.905-55.625,32.5-68.375C304.906,725.438,185.344,681.5,185.344,485.312c0-55.938,19.969-101.562,52.656-137.406 c-5.219-13-22.844-65.094,5.062-135.562 [rest of string was truncated]";. - /// </summary> - internal static string mark_github_detail { - get { - return ResourceManager.GetString("mark_github_detail", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M1024,194.418c-37.675,16.709-78.165,28.004-120.66,33.08c43.37-26,76.686-67.17,92.37-116.23 c-40.595,24.074-85.555,41.561-133.41,50.98c-38.319-40.83-92.92-66.34-153.345-66.34c-116.021,0-210.085,94.059-210.085,210.08 c0,16.465,1.854,32.5,5.439,47.875c-174.601-8.762-329.405-92.4-433.021-219.5c-18.085,31.022-28.445,67.109-28.445,105.613 c0,72.887,37.085,137.191,93.46,174.865c-34.435-1.09-66.835-10.539-95.154-26.279c-0.021,0.881-0.021,1.76-0.021,2.645 c0,101.785,72.42,186.695,168.521,206c-17.625,4.801-36.186 [rest of string was truncated]";. + /// Looks up a localized string similar to M14.85 3H1.15C0.52 3 0 3.52 0 4.15v7.69C0 12.48 0.52 13 1.15 13h13.69c0.64 0 1.15-0.52 1.15-1.15V4.15C16 3.52 15.48 3 14.85 3zM9 11L7 11V8l-1.5 1.92L4 8v3H2V5h2l1.5 2 1.5-2 2 0V11zM11.99 11.5L9.5 8h1.5V5h2v3h1.5L11.99 11.5z. /// </summary> - internal static string mark_twitter { + internal static string markdown { get { - return ResourceManager.GetString("mark_twitter", resourceCulture); + return ResourceManager.GetString("markdown", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M832,32c-130,0-124,130-704,128C57.344,160,0,274.625,0,416s57.344,256,128,256c22.781,0,43.188,0.5,64.188,0.875L256,960 l192,32l64-96l-45.125-203.125C709.375,729.125,733.75,800,832,800c106,0,192-172,192-384C1024,203.969,938,32,832,32z M197,482.938 c-39.188-1.469-82.188-2.25-127.562-2.625C66,460.594,64,438.906,64,416c0-88.375,28.688-192,64-192 c39.031,0.125,75-0.438,109-1.406C209.656,269.562,192,338.312,192,416C192,439.312,194.062,461.438,197,482.938z M261.312,485.938 C258.125,463.688,256,440.375,256,416c0 [rest of string was truncated]";. + /// Looks up a localized string similar to M10 1c-0.17 0-0.36 0.05-0.52 0.14-1.44 0.88-4.98 3.44-6.48 3.86-1.38 0-3 0.67-3 2.5s1.63 2.5 3 2.5c0.3 0.08 0.64 0.23 1 0.41v4.59h2V11.55c1.34 0.86 2.69 1.83 3.48 2.31 0.16 0.09 0.34 0.14 0.52 0.14 0.52 0 1-0.42 1-1V2c0-0.58-0.48-1-1-1z m0 12c-0.38-0.23-0.89-0.58-1.5-1-0.16-0.11-0.33-0.22-0.5-0.34V3.31c0.16-0.11 0.31-0.2 0.47-0.31 0.61-0.41 1.16-0.77 1.53-1v11z m2-6h4v1H12v-1z m0 2l4 2v1L12 10v-1z m4-6v1L12 6v-1l4-2z. /// </summary> internal static string megaphone { get { @@ -1141,16 +997,16 @@ internal static string megaphone { } /// <summary> - /// Looks up a localized string similar to M617,896c86.312-18.75,151-100,151-192c0-58.438-26.625-110.125-67.875-145.375C702.5,543.375,704,527.875,704,512 c0-104.844-49.875-197.875-128-256l64-64v-64l64-64L640,0l-64,64h-64L256,320l-128,64v128l64,64h128l64-128l96-96 c55.5,33.406,96,90.438,96,160c-106.062,0-192,85.938-192,192H0v64h192c19.125,14.25,42.062,22.125,64,32v96H128L0,1024h768L640,896 H617z M512,704c0-35.375,28.625-64,64-64s64,28.625,64,64c0,35.312-28.625,64-64,64S512,739.312,512,704z. + /// Looks up a localized string similar to M6.58 15c1.25 0 2.52-0.31 3.56-0.94l-0.42-0.94c-0.84 0.52-1.89 0.83-3.03 0.83-3.23 0-5.64-2.08-5.64-5.72C1.05 3.86 4.28 1.05 7.63 1.05c3.45 0 5.22 2.19 5.22 5.2 0 2.39-1.34 3.86-2.5 3.86-1.05 0-1.36-0.73-1.05-2.19l0.73-3.75h-1.05l-0.11 0.72c-0.41-0.63-0.94-0.83-1.56-0.83-2.19 0-3.66 2.39-3.66 4.38 0 1.67 0.94 2.61 2.3 2.61 0.84 0 1.67-0.53 2.3-1.25 0.11 0.94 0.94 1.45 1.98 1.45 1.67 0 3.77-1.67 3.77-5C14 2.61 11.59 0 7.83 0 3.66 0 0 3.33 0 8.33c0 4.38 2.92 6.67 6.58 6.67z m-0.31-5c-0.73 0-1.36-0.52-1.36-1.6 [rest of string was truncated]";. /// </summary> - internal static string microscope { + internal static string mention { get { - return ResourceManager.GetString("microscope", resourceCulture); + return ResourceManager.GetString("mention", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M704,192H0v256h704l128-128L704,192z M448,384H320V256h128V384z M448,0H320v128h128V0z M320,1024h128V512H320V1024z. + /// Looks up a localized string similar to M8 2H6V0h2v2z m4 5H2c-0.55 0-1-0.45-1-1V4c0-0.55 0.45-1 1-1h10l2 2-2 2zM8 4H6v2h2V4z m-2 12h2V8H6v8z. /// </summary> internal static string milestone { get { @@ -1159,61 +1015,25 @@ internal static string milestone { } /// <summary> - /// Looks up a localized string similar to M704,320v320l192-160L704,320z M320,640V320L128,480L320,640z M512,0L0,256v768l512-256l512,256V256L512,0z M960,896 L576,704H448L64,896V320l384-192v128h128V128l384,192V896z M384,448v192h256V448c0-70.656-57.375-128-128-128 C441.344,320,384,377.344,384,448z M576,448H448c0-35.312,28.594-64,64-64C547.375,384,576,412.688,576,448z. + /// Looks up a localized string similar to M15.5 4.7L8.5 0 1.5 4.7c-0.3 0.19-0.5 0.45-0.5 0.8v10.5l7.5-4 7.5 4V5.5c0-0.34-0.2-0.61-0.5-0.8z m-0.5 9.8L9 11.25v-1.25h-1v1.25L2 14.5V5.5L8 1.5v4.5h1V1.5l6 4v9zM6 7h5V5l3 3-3 3V9H6v2L3 8l3-3v2z. /// </summary> - internal static string mirror_private { + internal static string mirror { get { - return ResourceManager.GetString("mirror_private", resourceCulture); + return ResourceManager.GetString("mirror", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M320,320L128,512l192,192V576h384v128l192-192L704,320v128H320V320z M512,0L0,320v704l512-256l512,256V320L512,0z M960,896 L576,704v-64H448v64L64,896V384l384-256v256h128V128l384,256V896z. + /// Looks up a localized string similar to M7.83 9.19l-3.83-1.19s0 1.5 0 2.5 1.8 1.5 4 1.5 4-0.5 4-1.5 0-2.5 0-2.5l-3.83 1.19c-0.11 0.03-0.23 0.03-0.36 0h0.02z m0.28-6.39c-0.06-0.02-0.14-0.02-0.2 0l-7.64 2.38c-0.33 0.11-0.33 0.56 0 0.67l1.73 0.55v1.77c-0.3 0.17-0.5 0.5-0.5 0.86 0 0.19 0.05 0.36 0.14 0.5-0.08 0.14-0.14 0.31-0.14 0.5v2.58c0 0.55 2 0.55 2 0v-2.58c0-0.19-0.05-0.36-0.14-0.5 0.08-0.14 0.14-0.31 0.14-0.5 0-0.38-0.2-0.69-0.5-0.86v-1.45l4.89 1.53c0.06 0.02 0.14 0.02 0.2 0l7.64-2.38c0.33-0.11 0.33-0.56 0-0.67l-7.63-2.39z m-0.09 3.2c-0.55 0-1- [rest of string was truncated]";. /// </summary> - internal static string mirror_public { + internal static string mortar_board { get { - return ResourceManager.GetString("mirror_public", resourceCulture); + return ResourceManager.GetString("mortar_board", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M640,320H448V0H192v320H0l320,384L640,320z M0,1024h640V832H0V1024z. - /// </summary> - internal static string move_down { - get { - return ResourceManager.GetString("move_down", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M0,832h192V192H0V832z M704,384V192L320,512l384,320V640h320V384H704z. - /// </summary> - internal static string move_left { - get { - return ResourceManager.GetString("move_left", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M832,192v640h192V192H832z M320,384H0v256h320v192l384-320L320,192V384z. - /// </summary> - internal static string move_right { - get { - return ResourceManager.GetString("move_right", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M0,704h192v320h256V704h192L320,320L0,704z M0,0v192h640V0H0z. - /// </summary> - internal static string move_up { - get { - return ResourceManager.GetString("move_up", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M128,384H0v256h128l256,192h64V192h-64L128,384z M864,416l-64-64l-96,96l-96-96l-63,63.5l95,96.5l-96,96l64,64l96-96l96,96 l64-64l-96-96L864,416z. + /// Looks up a localized string similar to M8 2.81v10.38c0 0.67-0.81 1-1.28 0.53L3 10H1c-0.55 0-1-0.45-1-1V7c0-0.55 0.45-1 1-1h2l3.72-3.72c0.47-0.47 1.28-0.14 1.28 0.53z m7.53 3.22l-1.06-1.06-1.97 1.97-1.97-1.97-1.06 1.06 1.97 1.97-1.97 1.97 1.06 1.06 1.97-1.97 1.97 1.97 1.06-1.06-1.97-1.97 1.97-1.97z. /// </summary> internal static string mute { get { @@ -1222,7 +1042,7 @@ internal static string mute { } /// <summary> - /// Looks up a localized string similar to M896,320v128H768V320L576,512l192,192V576h192c0,0,64-0.375,64-64s0-192,0-192H896z M224,288C100.281,288,0,388.281,0,512 c0,123.75,100.281,224,224,224s224-100.25,224-224C448,388.281,347.719,288,224,288z M96,512c0-70.656,57.344-128,128-128 c18.75,0,36.406,4.219,52.469,11.531L107.531,564.5C100.219,548.375,96,530.75,96,512z M224,640c-18.75,0-36.406-4.25-52.469-11.5 l168.938-168.969C347.781,475.594,352,493.25,352,512C352,582.625,294.656,640,224,640z. + /// Looks up a localized string similar to M16 5v3c0 0.55-0.45 1-1 1H12v2L9 8l3-3v2h2V5h2zM8 8c0 2.2-1.8 4-4 4S0 10.2 0 8s1.8-4 4-4 4 1.8 4 4zM1.5 9.66l4.16-4.16c-0.48-0.31-1.05-0.5-1.66-0.5-1.66 0-3 1.34-3 3 0 0.61 0.19 1.17 0.5 1.66z m5.5-1.66c0-0.61-0.19-1.17-0.5-1.66L2.34 10.5c0.48 0.31 1.05 0.5 1.66 0.5 1.66 0 3-1.34 3-3z. /// </summary> internal static string no_newline { get { @@ -1231,7 +1051,7 @@ internal static string no_newline { } /// <summary> - /// Looks up a localized string similar to M940.812,277.688c8.25-20.219,35.375-101.75-8.562-211.906c0,0-67.375-21.312-219.875,82.906 C648.5,131.125,579.875,128.5,512,128.5c-67.906,0-136.438,2.625-200.5,20.25C159.031,44.469,91.719,65.781,91.719,65.781 C47.812,176,74.938,257.469,83.188,277.688C31.5,333.562,0,404.875,0,492.344C0,821.562,213.25,896,510.844,896 C808.562,896,1024,821.562,1024,492.344C1024,404.875,992.5,333.562,940.812,277.688z M512,833 c-211.406,0-382.781-9.875-382.781-214.688c0-48.938,24.062-94.595,65.344-132.312c68.75-62.969,185.281 [rest of string was truncated]";. + /// Looks up a localized string similar to M14.7 4.34c0.13-0.32 0.55-1.59-0.13-3.31 0 0-1.05-0.33-3.44 1.3C10.13 2.05 9.06 2.01 8 2.01c-1.06 0-2.13 0.04-3.13 0.32C2.48 0.69 1.43 1.03 1.43 1.03 0.75 2.75 1.17 4.02 1.3 4.34 0.49 5.21 0 6.33 0 7.69 0 12.84 3.33 14 7.98 14 12.63 14 16 12.84 16 7.69 16 6.33 15.51 5.21 14.7 4.34zM8 13.02c-3.3 0-5.98-0.15-5.98-3.35 0-0.76 0.38-1.48 1.02-2.07 1.07-0.98 2.9-0.46 4.96-0.46 2.07 0 3.88-0.52 4.96 0.46 0.65 0.59 1.02 1.3 1.02 2.07C13.98 12.86 11.3 13.02 8 13.02zM5.49 8.01c-0.66 0-1.2 0.8-1.2 1.78s0.54 1.79 1.2 1 [rest of string was truncated]";. /// </summary> internal static string octoface { get { @@ -1240,7 +1060,7 @@ internal static string octoface { } /// <summary> - /// Looks up a localized string similar to M768,384h-64H576h-64h-64h-64h-64H192h-64C57.344,384,0,441.344,0,512v64c0,47.25,25.844,88.062,64,110.25 V896h256v128h256V896h256V686.25c38.125-22.188,64-62.938,64-110.25v-64C896,441.344,838.625,384,768,384z M256,832H128V576H64v-64 c0-35.312,28.688-64,64-64h81.719c-11,18.875-17.719,40.562-17.719,64v128c0,47.25,25.844,88.062,64,110.25V832z M576,704V576h-64 v384H384V576h-64v128c-35.312,0-64-28.625-64-64V512c0-35.312,28.688-64,64-64h256c35.375,0,64,28.688,64,64v128 C640,675.375,611.375,704,576,704z M832,576h [rest of string was truncated]";. + /// Looks up a localized string similar to M4.75 4.95c0.55 0.64 1.34 1.05 2.25 1.05s1.7-0.41 2.25-1.05c0.34 0.63 1 1.05 1.75 1.05 1.11 0 2-0.89 2-2s-0.89-2-2-2c-0.41 0-0.77 0.13-1.08 0.33C9.61 1 8.42 0 7 0S4.39 1 4.08 2.33c-0.31-0.2-0.67-0.33-1.08-0.33-1.11 0-2 0.89-2 2s0.89 2 2 2c0.75 0 1.41-0.42 1.75-1.05z m5.2-1.52c0.2-0.38 0.59-0.64 1.05-0.64 0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.17-0.53-1.19-1.17c0.06-0.19 0.11-0.39 0.14-0.59zM7 0.98c1.11 0 2.02 0.91 2.02 2.02s-0.91 2.02-2.02 2.02-2.02-0.91-2.02-2.02S5.89 0.98 7 0.98zM3 5.2c-0.66 0-1.2-0. [rest of string was truncated]";. /// </summary> internal static string organization { get { @@ -1249,52 +1069,43 @@ internal static string organization { } /// <summary> - /// Looks up a localized string similar to M704,64L576,192l192,192l128-128L704,64z M0,768l0.688,192.562L192,960l512-512L512,256L0,768z M192,896H64V768h64v64h64 V896z. + /// Looks up a localized string similar to M0 4.27v7.47c0 0.45 0.3 0.84 0.75 0.97l6.5 1.73c0.16 0.05 0.34 0.05 0.5 0l6.5-1.73c0.45-0.13 0.75-0.52 0.75-0.97V4.27c0-0.45-0.3-0.84-0.75-0.97L7.75 1.56c-0.16-0.03-0.34-0.03-0.5 0L0.75 3.3c-0.45 0.13-0.75 0.52-0.75 0.97z m7 9.09L1 11.77V5l6 1.61v6.75zM1 4l2.5-0.67 6.5 1.73-2.5 0.67L1 4z m13 7.77L8 13.36V6.61l2-0.55v2.44l2-0.53V5.53l2-0.53v6.77zM12 4.53L5.5 2.8l2-0.53 6.5 1.73-2 0.53z. /// </summary> - internal static string pencil { + internal static string package { get { - return ResourceManager.GetString("pencil", resourceCulture); + return ResourceManager.GetString("package", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,192C448,86,362.062,0,256,0S64,86,64,192c0,106.062,85.938,192,192,192S448,298.062,448,192z M256,320 c-70.656,0-128-57.344-128-128S185.344,64,256,64c70.625,0,128,57.344,128,128S326.625,320,256,320z M384,384H256H128 C57.344,384,0,441.344,0,512v128c0,70.625,57.344,128,128,128v256h256V768c70.625,0,128-57.375,128-128V512 C512,441.344,454.625,384,384,384z M448,640c0,35.375-28.625,64-64,64V576h-64v384H192V576h-64v128c-35.312,0-64-28.625-64-64V512 c0-35.312,28.688-64,64-64h256c35.375,0,64,28.688,64,64V640z. + /// Looks up a localized string similar to M6 0C2.69 0 0 2.69 0 6v1c0 0.55 0.45 1 1 1v5c0 1.1 2.24 2 5 2s5-0.9 5-2V8c0.55 0 1-0.45 1-1v-1C12 2.69 9.31 0 6 0zM9 10v0.5c0 0.28-0.22 0.5-0.5 0.5s-0.5-0.22-0.5-0.5v-0.5c0-0.28-0.22-0.5-0.5-0.5s-0.5 0.22-0.5 0.5v2.5c0 0.28-0.22 0.5-0.5 0.5s-0.5-0.22-0.5-0.5V10.5c0-0.28-0.22-0.5-0.5-0.5s-0.5 0.22-0.5 0.5v0.5c0 0.55-0.45 1-1 1s-1-0.45-1-1v-1c-0.55 0-1-0.45-1-1V7.2C2.91 7.69 4.36 8 6 8s3.09-0.31 4-0.8V9C10 9.55 9.55 10 9 10zM6 7c-1.68 0-3.12-0.41-3.71-1 0.59-0.59 2.03-1 3.71-1s3.12 0.41 3.71 1C9.12 6.59 7.68 [rest of string was truncated]";. /// </summary> - internal static string person { + internal static string paintcan { get { - return ResourceManager.GetString("person", resourceCulture); + return ResourceManager.GetString("paintcan", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M448,192C448,86,362.062,0,256,0C150,0,64,86,64,192c0,106.062,86,192,192,192C362.062,384,448,298.062,448,192z M256,320 c-70.656,0-128-57.344-128-128S185.344,64,256,64s128,57.344,128,128S326.656,320,256,320z M384,384H256H128 C57.344,384,0,441.344,0,512v128c0,70.625,57.344,128,128,128v256h256V768c70.656,0,128-57.375,128-128V512 C512,441.344,454.656,384,384,384z M448,640c0,35.375-28.594,64-64,64V576h-64v384H192V576h-64v128c-35.312,0-64-28.625-64-64V512 c0-35.312,28.688-64,64-64h256c35.406,0,64,28.688,64,64V [rest of string was truncated]";. + /// Looks up a localized string similar to M0 12v3h3l8-8-3-3L0 12z m3 2H1V12h1v1h1v1z m10.3-9.3l-1.3 1.3-3-3 1.3-1.3c0.39-0.39 1.02-0.39 1.41 0l1.59 1.59c0.39 0.39 0.39 1.02 0 1.41z. /// </summary> - internal static string person_add { - get { - return ResourceManager.GetString("person_add", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M128,448H0v128h128v128l192-192L128,320V448z M768,384H640H512c-70.656,0-128,57.344-128,128v128 c0,70.625,57.344,128,128,128v256h256V768c70.625,0,128-57.375,128-128V512C896,441.344,838.625,384,768,384z M832,640 c0,35.375-28.625,64-64,64V576h-64v384H576V576h-64v128c-35.406,0-64-28.625-64-64V512c0-35.312,28.594-64,64-64h256 c35.375,0,64,28.688,64,64V640z M832,192C832,86,746.062,0,640,0S448,86,448,192c0,106.062,85.938,192,192,192S832,298.062,832,192z M640,320c-70.625,0-128-57.344-128-128S569.375,64,640,64s1 [rest of string was truncated]";. - /// </summary> - internal static string person_follow { + internal static string pencil { get { - return ResourceManager.GetString("person_follow", resourceCulture); + return ResourceManager.GetString("pencil", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M384,384H256H128C57.344,384,0,441.344,0,512v128c0,70.625,57.344,128,128,128v256h256V768c70.656,0,128-57.375,128-128V512 C512,441.344,454.656,384,384,384z M448,640c0,35.375-28.594,64-64,64V576h-64v384H192V576h-64v128c-35.312,0-64-28.625-64-64V512 c0-35.312,28.688-64,64-64h256c35.406,0,64,28.688,64,64V640z M512,192v128h384V192H512z M448,192C448,86,362.062,0,256,0 C150,0,64,86,64,192c0,106.062,86,192,192,192C362.062,384,448,298.062,448,192z M256,320c-70.656,0-128-57.344-128-128 S185.344,64,256,64s128,57.34 [rest of string was truncated]";. + /// Looks up a localized string similar to M 12 14.002 a 0.998 0.998 0 0 1 -0.998 0.998 H 1.001 A 1 1 0 0 1 0 13.999 V 13 c 0 -2.633 4 -4 4 -4 s 0.229 -0.409 0 -1 c -0.841 -0.62 -0.944 -1.59 -1 -4 c 0.173 -2.413 1.867 -3 3 -3 s 2.827 0.586 3 3 c -0.056 2.41 -0.159 3.38 -1 4 c -0.229 0.59 0 1 0 1 s 4 1.367 4 4 v 1.002 Z. /// </summary> - internal static string person_remove { + internal static string person { get { - return ResourceManager.GetString("person_remove", resourceCulture); + return ResourceManager.GetString("person", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M196.032,704l64,320l64-320c-20.125,2-41.344,3.188-62.281,3.188C239.22,707.188,217.47,706.312,196.032,704z M450.032,404.688c-16.188-15.625-40.312-44.375-62-84.688v-64c7.562-12.406,12.25-39.438,23.375-51.969 c15.25-13.375,24-28.594,24-44.875c0-53.094-61.062-95.156-175.375-95.156c-114.25,0-182.469,42.062-182.469,95.094 c0,16,8.469,31.062,23.375,44.312c13.438,14.844,22.719,38,31.094,52.594v64c-32.375,62.656-82,96.188-82,96.188h0.656 C18.749,437.876,0,464.126,0,492.344C0.063,566.625,101.063,640.062,260.032, [rest of string was truncated]";. + /// Looks up a localized string similar to M10 1.2v0.8l0.5 1-4.5 3H2.2c-0.44 0-0.67 0.53-0.34 0.86l3.14 3.14L1 15l5-4 3.14 3.14c0.33 0.33 0.86 0.09 0.86-0.34V10l3-4.5 1 0.5h0.8c0.44 0 0.67-0.53 0.34-0.86L10.86 0.86c-0.33-0.33-0.86-0.09-0.86 0.34z. /// </summary> internal static string pin { get { @@ -1303,25 +1114,25 @@ internal static string pin { } /// <summary> - /// Looks up a localized string similar to M384,448V192H256v256H0v128h256v256h128V576h256V448H384z. + /// Looks up a localized string similar to M15 6v-1H11V3H9v1H7c-1.03 0-1.77 0.81-2 2l-1 1c-1.66 0-3 1.34-3 3v2h1V10c0-1.11 0.89-2 2-2l1 1c0.25 1.16 0.98 2 2 2h2v1h2V10h4v-1H11V6h4z. /// </summary> - internal static string plus { + internal static string plug { get { - return ResourceManager.GetString("plus", resourceCulture); + return ResourceManager.GetString("plug", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M320,0c-64,0-64,64-64,64v63.874h-64l-192,192v128h192l64,384l-128,64v64h512v-64l-128-64l64-384h192v-128l-192-192H320V64 h32c17.673,0,32-14.327,32-32S369.673,0,352,0H320z M320,831.874L266.688,512h118.033L384,831.874H320z M96,319.874l128-128h32v64 h64v-64h224l128,128H96z. + /// Looks up a localized string similar to M12 9H7v5H5V9H0V7h5V2h2v5h5v2z. /// </summary> - internal static string podium { + internal static string plus { get { - return ResourceManager.GetString("podium", resourceCulture); + return ResourceManager.GetString("plus", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M-0.088,512c0-141.5,114.5-256,256-256c141.438,0,256,114.5,256,256s-114.562,256-256,256 C114.413,768-0.088,653.5-0.088,512z. + /// Looks up a localized string similar to M0 8c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4S0 10.2 0 8z. /// </summary> internal static string primitive_dot { get { @@ -1330,7 +1141,7 @@ internal static string primitive_dot { } /// <summary> - /// Looks up a localized string similar to M512,768H0V256h512V768z. + /// Looks up a localized string similar to M8 12H0V4h8V12z. /// </summary> internal static string primitive_square { get { @@ -1339,7 +1150,7 @@ internal static string primitive_square { } /// <summary> - /// Looks up a localized string similar to M736,511.938L563.188,345.594L422.406,544L352,102.406L152.438,511.938H0V640h230.406L288,524.812l57.594,345.562L576,544 l102.375,96H896V511.938H736z. + /// Looks up a localized string similar to M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z. /// </summary> internal static string pulse { get { @@ -1348,7 +1159,7 @@ internal static string pulse { } /// <summary> - /// Looks up a localized string similar to M448,768h128V640H448V768z M512,256c0,0-192,0-192,192c8,1.344,128,0,128,0s0-64,64-64s64,64,64,64 c0,64-129.781,64-129.781,128H576c0,0,128,0,128-160S512,256,512,256z M512,0C229.25,0,0,229.25,0,512s229.25,512,512,512 s512-229.25,512-512S794.75,0,512,0z M512,896c-212.031,0-384-172-384-384c0-212.031,171.969-384,384-384c212,0,384,171.969,384,384 C896,724,724,896,512,896z. + /// Looks up a localized string similar to M6 10h2v2H6V10z m4-3.5c0 2.14-2 2.5-2 2.5H6c0-0.55 0.45-1 1-1h0.5c0.28 0 0.5-0.22 0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28 0-0.5 0.22-0.5 0.5v0.5H4c0-1.5 1.5-3 3-3s3 1 3 2.5zM7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z. /// </summary> internal static string question { get { @@ -1357,7 +1168,7 @@ internal static string question { } /// <summary> - /// Looks up a localized string similar to M0,512v256h256V512H128c0,0,0-128,128-128V256C256,256,0,256,0,512z M640,384V256c0,0-256,0-256,256v256h256V512H512 C512,512,512,384,640,384z. + /// Looks up a localized string similar to M6.16 3.17C3.73 4.73 2.55 6.34 2.55 9.03c0.16-0.05 0.3-0.05 0.44-0.05 1.27 0 2.5 0.86 2.5 2.41 0 1.61-1.03 2.61-2.5 2.61C1.09 14 0 12.48 0 9.75 0 5.95 1.75 3.22 5.02 1.33l1.14 1.84z m7 0C10.73 4.73 9.55 6.34 9.55 9.03c0.16-0.05 0.3-0.05 0.44-0.05 1.27 0 2.5 0.86 2.5 2.41 0 1.61-1.03 2.61-2.5 2.61-1.89 0-2.98-1.52-2.98-4.25 0-3.8 1.75-6.53 5.02-8.42l1.14 1.84z. /// </summary> internal static string quote { get { @@ -1366,7 +1177,7 @@ internal static string quote { } /// <summary> - /// Looks up a localized string similar to M306.838,390.739c15.868-16.306,15.868-42.731,0-59.037c-20.521-21.116-30.643-48.417-30.705-76.124 c0.062-27.77,10.183-55.039,30.705-76.186c15.868-16.337,15.868-42.764,0-59.069c-7.934-8.184-18.272-12.275-28.706-12.275 c-10.371,0-20.804,4.029-28.738,12.213c-36.266,37.297-54.633,86.433-54.57,135.317c-0.062,48.792,18.305,97.927,54.57,135.161 C265.262,407.045,290.97,407.045,306.838,390.739z M149.093,33.142c-8.121-8.309-18.68-12.463-29.3-12.463 c-10.558,0-21.179,4.154-29.237,12.463C30.8,94.491,0.751,175.144,0. [rest of string was truncated]";. + /// Looks up a localized string similar to M4.79 6.11c0.25-0.25 0.25-0.67 0-0.92-0.32-0.33-0.48-0.76-0.48-1.19 0-0.43 0.16-0.86 0.48-1.19 0.25-0.26 0.25-0.67 0-0.92-0.12-0.13-0.29-0.19-0.45-0.19-0.16 0-0.33 0.06-0.45 0.19-0.57 0.58-0.85 1.35-0.85 2.11 0 0.76 0.29 1.53 0.85 2.11C4.14 6.36 4.55 6.36 4.79 6.11zM2.33 0.52c-0.13-0.13-0.29-0.19-0.46-0.19-0.16 0-0.33 0.06-0.46 0.19C0.48 1.48 0.01 2.74 0.01 3.99 0.01 5.25 0.48 6.51 1.41 7.47c0.25 0.26 0.66 0.26 0.91 0 0.25-0.26 0.25-0.68 0-0.94-0.68-0.7-1.02-1.62-1.02-2.54s0.34-1.84 1.02-2.54C2.58 1.2 2.58 [rest of string was truncated]";. /// </summary> internal static string radio_tower { get { @@ -1375,16 +1186,7 @@ internal static string radio_tower { } /// <summary> - /// Looks up a localized string similar to M448,960C200.562,960,0,759.438,0,512C0,264.562,200.562,64,448,64c247.438,0,448,200.562,448,448 C896,759.438,695.438,960,448,960z M448,192c-176.781,0-320,143.219-320,320c0,176.75,143.219,320,320,320 c176.75,0,320-143.25,320-320C768,335.219,624.75,192,448,192z M554.5,701L448,594.5L341.5,701L259,618.5L365.5,512L259,405.5 l82.5-82.5L448,429.5L554.5,323l82.5,82.5L530.5,512L637,618.5L554.5,701z. - /// </summary> - internal static string remove_close { - get { - return ResourceManager.GetString("remove_close", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M320,256h-64v64h64V256z M320,128h-64v64h64V128z M704,0H64C64,0,0,0,0,64v768c0,64,64,64,64,64h128v128l96-96l96,96V896 h320c0,0,64-1.125,64-64V64C768,0,704,0,704,0z M704,768c0,61.625-64,64-64,64H384v-64H192v64h-64c-64,0-64-64-64-64v-64h640V768z M704,640H192V64h513L704,640z M320,512h-64v64h64V512z M320,384h-64v64h64V384z. + /// Looks up a localized string similar to M4 9h-1v-1h1v1z m0-3h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m8-1v12c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1z m-1 10H1v2h2v-1h3v1h5V11z m0-10H2v9h9V1z. /// </summary> internal static string repo { get { @@ -1393,7 +1195,7 @@ internal static string repo { } /// <summary> - /// Looks up a localized string similar to M384,448.002l128-160l-128-160V448.002z M320,384.002h-64v64h64V384.002z M192,64.002h256v-64H64c0,0-64-1.219-64,64 c0,293.438,0,768,0,768h0.062c0.125,9.625,3.406,64,63.938,64h128v128l96-96l96,96v-128h318c66.875,0,66-64,66-64v-192H192V64.002z M704,704.002c0,0,0,32,0,64c0,61.625-66,64-66,64s-124,0-254,0v-64H192v64h-66c-63.531,0-62-64-62-64v-64H704z M320,256.002h-64v64 h64V256.002z M256,576.002h64v-64h-64V576.002z M960,0.001H640c0,0-64,0-64,64v384c0,64,64,64,64,64h64v64l32-32l32,32v-64h192 c0,0,64,0,64-64v- [rest of string was truncated]";. + /// Looks up a localized string similar to M15 0H9v7c0 0.55 0.45 1 1 1h1v1h1v-1h3c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1zM11 7h-1v-1h1v1z m4 0H12v-1h3v1z m0-2H11V1h4v4z m-11 0h-1v-1h1v1z m0-2h-1v-1h1v1zM2 1h6V0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h2v2l1.5-1.5 1.5 1.5V14h5c0.55 0 1-0.45 1-1V10H2V1z m9 10v2H6v-1H3v1H1V11h10zM3 8h1v1h-1v-1z m1-1h-1v-1h1v1z. /// </summary> internal static string repo_clone { get { @@ -1402,25 +1204,7 @@ internal static string repo_clone { } /// <summary> - /// Looks up a localized string similar to M768,128V0H640v128H512v128h128v128h128V256h128V128H768z M384,384h-64v64h64V384z M384,128h-64v64h64V128z M768,640H256V64 h256V0H128c0,0-64,0-64,64v768c0,64,64,64,64,64h128v128l96-96l96,96V896h320c0,0,64-1.125,64-64V448h-64V640z M768,768 c0,61.625-64,64-64,64H448v-64H256v64h-64c-64,0-64-64-64-64v-64h640V768z M320,576h64v-64h-64V576z M384,256h-64v64h64V256z. - /// </summary> - internal static string repo_create { - get { - return ResourceManager.GetString("repo_create", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M512,128v128h384V128H512z M384,384h-64v64h64V384z M384,128h-64v64h64V128z M768,640H256V64h576c0,0,0-64-64-64H128 c0,0-64,0-64,64v768c0,64,64,64,64,64h128v128l96-96l96,96V896h320c0,0,64-1.125,64-64V320h-64V640z M768,768c0,61.625-64,64-64,64 H448v-64H256v64h-64c-64,0-64-64-64-64v-64h640V768z M320,576h64v-64h-64V576z M384,256h-64v64h64V256z. - /// </summary> - internal static string repo_delete { - get { - return ResourceManager.GetString("repo_delete", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to M767.698,63.752c-1.625-64.251-63.75-63.751-63.75-63.751h-640c0,0-64-1.219-64,64c0,293.438,0,768,0,768H0.01 c0.125,9.625,3.406,64,63.938,64h128v128l128-128v-128h-128v64h-66c-64,0-62-64-62-64v-64h256v-64h-128v-576h512v576h-128v64h128 c0,0,0,32,0,64c0,61.625-66,64-66,64s-24.5,0-62,0v64h126c66.875,0,66-64,66-64L767.698,63.752z M495.948,384.002h144l-192-256 l-192,256h144l-144,192h128v448h128v-448h128L495.948,384.002z. + /// Looks up a localized string similar to M10 9H8v7H6V9H4l2.25-3H4l3-4 3 4H7.75l2.25 3zM11 0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h4v-1H1V11h4v-1H2V1h9v9H9v1h2v2H9v1h2c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1z. /// </summary> internal static string repo_force_push { get { @@ -1429,7 +1213,7 @@ internal static string repo_force_push { } /// <summary> - /// Looks up a localized string similar to M384,576h128V448l128-128V128H512v128l-64,64l-64-64V128H256v192l128,128V576z M704,0H64C0,0,0,64,0,64v768c0,0,0,64,64,64 h128v128l96-96l96,96V896h320c64,0,64-64,64-64V64C768,64,768,0,704,0z M704,768c0,0,0,64-64,64H384v-64H192v64h-64 c-64,0-64-64-64-64v-64h640V768z M704,640H192V64h512V640z. + /// Looks up a localized string similar to M8 1c-1.11 0-2 0.89-2 2 0 0.73 0.41 1.38 1 1.72v1.28L5 8 3 6v-1.28c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v1.78l3 3v1.78c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V9.5l3-3V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2zM2 4.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3 10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3-10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0. [rest of string was truncated]";. /// </summary> internal static string repo_forked { get { @@ -1438,7 +1222,7 @@ internal static string repo_forked { } /// <summary> - /// Looks up a localized string similar to M1024,320L832,128v128H448v128h384v128L1024,320z M704,640H192V64h512v128h64V64c0-64-64-64-64-64H64C64,0,0,0,0,64v768 c0,64,64,64,64,64h128v128l96-96l96,96V896h320c0,0,64-1.125,64-64V448h-64V640z M704,768c0,61.625-64,64-64,64H384v-64H192v64h-64 c-64,0-64-64-64-64v-64h640V768z M320,256h-64v64h64V256z M320,128h-64v64h64V128z M320,384h-64v64h64V384z M256,576h64v-64h-64V576 z. + /// Looks up a localized string similar to M13 8V6H7V4h6V2l3 3-3 3zM4 2h-1v1h1v-1z m7 5h1v6c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1v2h-1V1H2v9h9V7z m0 4H1v2h2v-1h3v1h5V11zM4 6h-1v1h1v-1z m0-2h-1v1h1v-1z m-1 5h1v-1h-1v1z. /// </summary> internal static string repo_pull { get { @@ -1447,7 +1231,7 @@ internal static string repo_pull { } /// <summary> - /// Looks up a localized string similar to M448,320L256,576h128v448h128V576h128L448,320z M256,320h64v-64h-64V320z M320,128h-64v64h64V128z M704,0H64C64,0,0,0,0,64 v768c0,64,64,64,64,64h128v128l128-128V768H192v64h-64c-64,0-64-64-64-64v-64h256v-64H192V64h513l-1,576H576v64h128v64 c0,61.625-64,64-64,64h-64v64h128c0,0,64-1.125,64-64V64C768,0,704,0,704,0z. + /// Looks up a localized string similar to M4 3h-1v-1h1v1z m-1 2h1v-1h-1v1z m4 0L4 9h2v7h2V9h2L7 5zM11 0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h4v-1H1V11h4v-1H2V1h9.02l-0.02 9H9v1h2v2H9v1h2c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1z. /// </summary> internal static string repo_push { get { @@ -1456,97 +1240,97 @@ internal static string repo_push { } /// <summary> - /// Looks up a localized string similar to M254,64.002h512v64h64l-0.25-64.25C828.125-0.499,766,0.001,766,0.001H126c0,0-64-1.219-64,64c0,293.438,0,768,0,768h0.062 c0.125,9.625,3.406,64,63.938,64h128v128l96-96l96,96v-128h318c66.875,0,66-64,66-64v-192H254V64.002z M766,704.002c0,0,0,32,0,64 c0,61.625-66,64-66,64s-124,0-254,0v-64H254v64h-66c-63.531,0-62-64-62-64v-64H766z M766,256.002v-64H574v128h-98l130,166l130-166 h-98v-64H766z M862,282.002l-130,166h98v64H702v64h192v-128h98L862,282.002z M382,128.002h-64v64h64V128.002z M382,256.002h-64v64 h64V256.002 [rest of string was truncated]";. + /// Looks up a localized string similar to M16 0s-0.09 0.38-0.3 1.06c-0.2 0.7-0.55 1.58-1.06 2.66-0.7-0.08-1.27-0.33-1.66-0.72s-0.63-0.94-0.7-1.64c1.08-0.52 1.95-0.88 2.64-1.08 0.7-0.2 1.08-0.28 1.08-0.28zM12.17 3.83c-0.27-0.27-0.47-0.55-0.63-0.88-0.16-0.31-0.27-0.66-0.34-1.02-0.58 0.33-1.16 0.7-1.73 1.13-0.58 0.44-1.14 0.94-1.69 1.48-0.7 0.7-1.33 1.81-1.78 2.45H3L0 10h3l2-2c-0.34 0.77-1.02 2.98-1 3l1 1c0.02 0.02 2.23-0.64 3-1L6 13v3l3-3V10c0.64-0.45 1.75-1.09 2.45-1.78 0.55-0.55 1.05-1.13 1.47-1.7 0.44-0.58 0.81-1.16 1.14-1.72-0.36-0.08-0.7-0.19-1. [rest of string was truncated]";. /// </summary> - internal static string repo_sync { + internal static string rocket { get { - return ResourceManager.GetString("repo_sync", resourceCulture); + return ResourceManager.GetString("rocket", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M716.737,124.056c-71.926,41.686-148.041,96.13-218.436,166.555c-45,45.031-81.213,88.78-110.39,129.778L209.538,453.35 L0.047,662.997l186.818,5.815l131.562-131.562c-46.439,96.224-50.536,160.019-50.536,160.019l58.854,58.792 c0,0,65.827-6.255,162.737-53.163L355.107,837.119l5.88,186.881l209.585-209.521l33.086-179.252 c41.403-29.02,85.185-65.046,129.716-109.545c70.425-70.455,124.837-146.541,166.555-218.466 c-45.97-9.351-88.125-28.488-121.397-61.668C745.257,212.181,725.994,170.025,716.737,124.056z M786.161,86.8 [rest of string was truncated]";. + /// Looks up a localized string similar to M2 13H0V11c1.11 0 2 0.89 2 2zM0 3v1c4.97 0 9 4.03 9 9h1c0-5.52-4.48-10-10-10z m0 4v1c2.75 0 5 2.25 5 5h1c0-3.31-2.69-6-6-6z. /// </summary> - internal static string rocket { + internal static string rss { get { - return ResourceManager.GetString("rocket", resourceCulture); + return ResourceManager.GetString("rss", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M283.869,876.404c2.312,1.461,4.928,2.297,7.312,2.374c3.971,0.135,7.303-1.847,7.516-6.595 c0.701-15.666,9.863-19.854,18.738-20.106c6.9-0.195,13.625,1.992,16.064,3.02c9.088,3.832,16.49,7.622,22.721,6.372 c4.85-0.97,8.986-4.988,12.65-14.416c0.896-2.306,8.705-1.258,7.617-7.2c-0.836-4.578-5.363-6.672-9.26-7.192 c-12.586-1.655-26.031-5.779-38.314-5.9c-4.096-0.041-8.061,0.365-11.822,1.452c-4.932,1.425-9.494,2.221-13.795,2.492 c-15.834,1.003-27.908-5.487-39.746-17.428c-2.498-2.52-5.254-5.397-8.771-5.43c-1.172-0 [rest of string was truncated]";. + /// Looks up a localized string similar to M13 6L8 11V4h3l2 2z m3 0L8 14 0 6l4-4h8l4 4zM8 12.5l6.5-6.5-3-3H4.5L1.5 6l6.5 6.5z. /// </summary> - internal static string rohan { + internal static string ruby { get { - return ResourceManager.GetString("rohan", resourceCulture); + return ResourceManager.GetString("ruby", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M128,640C57.344,640,0,697.375,0,768s57.344,128,128,128s128-57.375,128-128S198.656,640,128,640z M128,384c0,0-64,2-64,64 s64,64,64,64c141.375,0,256,114.625,256,256c0,0,0,64,64,64s64-64,64-64C512,556,340.031,384,128,384z M128,128c0,0-64,0-64,64 s64,64,64,64c282.75,0,512,229.25,512,512c0,0,0,64,64,64s64-64,64-64C768,414.594,481.5,128,128,128z. + /// Looks up a localized string similar to M15.7 14.3L11.89 10.47c0.7-0.98 1.11-2.17 1.11-3.47 0-3.31-2.69-6-6-6S1 3.69 1 7s2.69 6 6 6c1.3 0 2.48-0.41 3.47-1.11l3.83 3.81c0.19 0.2 0.45 0.3 0.7 0.3s0.52-0.09 0.7-0.3c0.39-0.39 0.39-1.02 0-1.41zM7 11.7c-2.59 0-4.7-2.11-4.7-4.7s2.11-4.7 4.7-4.7 4.7 2.11 4.7 4.7-2.11 4.7-4.7 4.7z. /// </summary> - internal static string rss { + internal static string search { get { - return ResourceManager.GetString("rss", resourceCulture); + return ResourceManager.GetString("search", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M768,128H256L0,384l512,512l512-512L768,128z M128,384l192-192h384l192,192L512,768L128,384z M704,256H512v448l320-320 L704,256z. + /// Looks up a localized string similar to M11 6H1c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V7c0-0.55-0.45-1-1-1zM2 9H1V7h1v2z m2 0h-1V7h1v2z m2 0h-1V7h1v2z m2 0h-1V7h1v2zM11 1H1C0.45 1 0 1.45 0 2v2c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1zM2 4H1V2h1v2z m2 0h-1V2h1v2z m2 0h-1V2h1v2z m2 0h-1V2h1v2z m3-1h-1v-1h1v1z m0 8H1c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V12c0-0.55-0.45-1-1-1zM2 14H1V12h1v2z m2 0h-1V12h1v2z m2 0h-1V12h1v2z m2 0h-1V12h1v2z. /// </summary> - internal static string ruby { + internal static string server { get { - return ResourceManager.GetString("ruby", resourceCulture); + return ResourceManager.GetString("server", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M128,768h639.875V256H128V768z M255.938,384h384v256h-384V384z M64,192.062h191.938v-64H0V384h64V192.062z M64,640H0 v255.938h255.938V832H64V640z M639.938,128.062v64h191.938V384h64V128.062H639.938z M831.875,832H639.938v63.938h255.938V640h-64 V832z. + /// Looks up a localized string similar to M3 7h-1V2h1v5z m-1 7h1V11h-1v3z m5 0h1V8h-1v6z m5 0h1V12h-1v2z m1-12h-1v6h1V2z m-5 0h-1v2h1V2zM4 8H1c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1s-0.45-1-1-1z m5-3H6c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1s-0.45-1-1-1z m5 4H11c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1s-0.45-1-1-1z. /// </summary> - internal static string screen_full { + internal static string settings { get { - return ResourceManager.GetString("screen_full", resourceCulture); + return ResourceManager.GetString("settings", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M127.938,191.938H0v64h191.938V64h-64V191.938z M0,832.062h127.938V960h64V768.062H0V832.062z M768.062,191.938V64h-64 v191.938H896v-64H768.062z M704.062,960h64V832.062H896v-64H704.062V960z M192.062,704H704V320H192.062V704z M320,448h256v128H320 V448z. + /// Looks up a localized string similar to M7 0L0 2v6.02c0 4.67 5.31 7.98 7 7.98s7-3.31 7-7.98V2L7 0zM5 11l1.14-2.8c0.05-0.23-0.06-0.47-0.25-0.59-0.56-0.36-0.89-0.95-0.89-1.61 0-1.09 0.89-2 1.98-2 1.08 0 2.02 0.91 2.02 2 0 0.66-0.33 1.25-0.89 1.61-0.19 0.13-0.3 0.36-0.25 0.59l1.14 2.8H5z. /// </summary> - internal static string screen_normal { + internal static string shield { get { - return ResourceManager.GetString("screen_normal", resourceCulture); + return ResourceManager.GetString("shield", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M960,832L710.875,582.875C746.438,524.812,768,457.156,768,384C768,171.969,596,0,384,0C171.969,0,0,171.969,0,384 c0,212,171.969,384,384,384c73.156,0,140.812-21.562,198.875-57L832,960c17.5,17.5,46.5,17.375,64,0l64-64 C977.5,878.5,977.5,849.5,960,832z M384,640c-141.375,0-256-114.625-256-256s114.625-256,256-256s256,114.625,256,256 S525.375,640,384,640z. + /// Looks up a localized string similar to M6 6.75v5.25h4V8h1v4c0 0.55-0.45 1-1 1H6v3L0.55 13.28c-0.33-0.17-0.55-0.52-0.55-0.91V1C0 0.45 0.45 0 1 0h9c0.55 0 1 0.45 1 1v3h-1V1H2l4 2v2.25l3-2.25v2h4v2H9v2L6 6.75z. /// </summary> - internal static string search { + internal static string sign_in { get { - return ResourceManager.GetString("search", resourceCulture); + return ResourceManager.GetString("sign_in", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M0,128v128c0,0,1.906,64,64,64s577.125,0,640,0s64-64,64-64V128c0,0-0.5-64-64-64s-588.468,0-640,0C-0.906,64,0,128,0,128z M0,448v128c0,0,1.906,64,64,64s577.125,0,640,0s64-64,64-64V448c0,0-0.5-64-64-64s-588.468,0-640,0C-0.906,384,0,448,0,448z M0,768 v128c0,0,1.906,64,64,64s577.125,0,640,0s64-64,64-64V768c0,0-0.5-64-64-64s-588.468,0-640,0C-0.906,704,0,768,0,768z M64,576V448 h64v128H64z M192,576V448h64v128H192z M320,576V448h64v128H320z M448,576V448h64v128H448z M640,512v-64h64v64H640z M64,256V128h64 v128H64z [rest of string was truncated]";. + /// Looks up a localized string similar to M12 9V7H8V5h4V3l4 3-4 3zM10 12H6V3L2 1h8v3h1V1c0-0.55-0.45-1-1-1H1C0.45 0 0 0.45 0 1v11.38c0 0.39 0.22 0.73 0.55 0.91l5.45 2.72V13h4c0.55 0 1-0.45 1-1V8h-1v4z. /// </summary> - internal static string server { + internal static string sign_out { get { - return ResourceManager.GetString("server", resourceCulture); + return ResourceManager.GetString("sign_out", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M64,896h128V704H64V896z M192,128H64v320h128V128z M512,128H384v128h128V128z M0,640h256V512H0V640z M384,896h128V512H384 V896z M320,448h256V320H320V448z M832,128H704v384h128V128z M640,576v128h256V576H640z M704,896h128V768H704V896z. + /// Looks up a localized string similar to M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8S12.42 0 8 0z m4.81 12.81c-0.63 0.63-1.36 1.11-2.17 1.45-0.83 0.36-1.72 0.53-2.64 0.53s-1.81-0.17-2.64-0.53c-0.81-0.34-1.55-0.83-2.17-1.45s-1.11-1.36-1.45-2.17c-0.36-0.83-0.53-1.72-0.53-2.64s0.17-1.81 0.53-2.64c0.34-0.81 0.83-1.55 1.45-2.17s1.36-1.11 2.17-1.45c0.83-0.36 1.72-0.53 2.64-0.53s1.81 0.17 2.64 0.53c0.81 0.34 1.55 0.83 2.17 1.45s1.11 1.36 1.45 2.17c0.36 0.83 0.53 1.72 0.53 2.64s-0.17 1.81-0.53 2.64c-0.34 0.81-0.83 1.55-1.45 2.17zM4 5.8v-0.59c0-0.66 0.53- [rest of string was truncated]";. /// </summary> - internal static string settings { + internal static string smiley { get { - return ResourceManager.GetString("settings", resourceCulture); + return ResourceManager.GetString("smiley", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M768,64c-141.385,0-256,83.75-256,186.875C512,374.75,544,445,512,640c0-288-177-405.783-256-405.783 c3.266-32.17-30.955-42.217-30.955-42.217s-14,7.124-19.354,21.583c-17.231-20.053-36.154-17.54-36.154-17.54l-8.491,37.081 c0,0-117.045,40.876-118.635,206.292C56,461,141.311,478.102,201.887,467.118c57.157,2.956,42.991,50.648,30.193,63.446 C178.083,584.562,128,512,64,512s-64,64,0,64s64,64,192,64c-198,77,0,256,0,256h-64c-64,0-64,64-64,64s256,0,384,0 c192,0,320-64,320-222.182c0-54.34-27.699-114.629-64-162.228 [rest of string was truncated]";. + /// Looks up a localized string similar to M12 1c-2.21 0-4 1.31-4 2.92C8 5.86 8.5 6.95 8 10c0-4.5-2.77-6.34-4-6.34 0.05-0.5-0.48-0.66-0.48-0.66s-0.22 0.11-0.3 0.34c-0.27-0.31-0.56-0.27-0.56-0.27l-0.13 0.58c0 0-1.83 0.64-1.85 3.22C0.88 7.2 2.21 7.47 3.15 7.3c0.89 0.05 0.67 0.79 0.47 0.99C2.78 9.13 2 8 1 8s-1 1 0 1 1 1 3 1c-3.09 1.2 0 4 0 4h-1c-1 0-1 1-1 1s4 0 6 0c3 0 5-1 5-3.47 0-0.85-0.43-1.79-1-2.53C10.89 7.54 12.23 6.32 13 7s3 1 3-2C16 2.79 14.21 1 12 1zM2.5 6c-0.28 0-0.5-0.22-0.5-0.5 0-0.28 0.22-0.5 0.5-0.5 0.28 0 0.5 0.22 0.5 0.5C3 5.78 2.78 6 2 [rest of string was truncated]";. /// </summary> internal static string squirrel { get { @@ -1555,7 +1339,7 @@ internal static string squirrel { } /// <summary> - /// Looks up a localized string similar to M896,384l-313.5-40.781L448,64L313.469,343.219L0,384l230.469,208.875L171,895.938l277-148.812l277.062,148.812 L665.5,592.875L896,384z. + /// Looks up a localized string similar to M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z. /// </summary> internal static string star { get { @@ -1564,88 +1348,97 @@ internal static string star { } /// <summary> - /// Looks up a localized string similar to M896,381.938l0.25-0.188H896V381.938z M640,448v-97.656L571.312,320H512V196.875L448,64L313.469,343.219L0,384 l230.469,208.875l-57.594,303.062L448,748.812l275.188,147.125l-57.562-303.062L832,448H640z M832,128V0H704v128H576v128h128v128 h128V256h128V128H832z. + /// Looks up a localized string similar to M10 1H4L0 5v6l4 4h6l4-4V5L10 1z m3 9.5L9.5 14H4.5L1 10.5V5.5l3.5-3.5h5l3.5 3.5v5zM6 4h2v5H6V4z m0 6h2v2H6V10z. /// </summary> - internal static string star_add { + internal static string stop { get { - return ResourceManager.GetString("star_add", resourceCulture); + return ResourceManager.GetString("stop", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M576,384l-64-128v-64L448,64L313.469,343.312L0,384l230.469,208.875l-57.594,303.062L448,747.125l275.188,148.812 l-57.562-303.062L832,448L576,384z M934.375,89.594l-64-64L768,128L665.5,22.969l-63.875,66.625L704,192L601.625,294.406l64,64 L768,256l102.375,102.406l64-64L832,192L934.375,89.594z. + /// Looks up a localized string similar to M10.24 7.4c0.19 1.28-0.2 2.62-1.2 3.6-1.47 1.45-3.74 1.63-5.41 0.54l1.17-1.14L0.5 9.8 1.1 14l1.31-1.26c2.36 1.74 5.7 1.57 7.84-0.54 1.24-1.23 1.81-2.85 1.74-4.46L10.24 7.4zM2.96 5c1.47-1.45 3.74-1.63 5.41-0.54l-1.17 1.14 4.3 0.6L10.9 2l-1.31 1.26C7.23 1.52 3.89 1.69 1.74 3.8 0.5 5.03-0.06 6.65 0.01 8.26l1.75 0.35C1.57 7.33 1.96 5.98 2.96 5z. /// </summary> - internal static string star_delete { + internal static string sync { get { - return ResourceManager.GetString("star_delete", resourceCulture); + return ResourceManager.GetString("sync", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M704,0H320L0,320v384l320,320h384l320-320V320L704,0z M896,640L640,896H384L128,640V384l256-256h256l256,256V640z M448,576 h128V256H448V576z M448,768h128V640H448V768z. + /// Looks up a localized string similar to M6.73 2.73c-0.47-0.47-1.11-0.73-1.77-0.73H2.5C1.13 2 0 3.13 0 4.5v2.47c0 0.66 0.27 1.3 0.73 1.77l6.06 6.06c0.39 0.39 1.02 0.39 1.41 0l4.59-4.59c0.39-0.39 0.39-1.02 0-1.41L6.73 2.73zM1.38 8.09c-0.31-0.3-0.47-0.7-0.47-1.13V4.5c0-0.88 0.72-1.59 1.59-1.59h2.47c0.42 0 0.83 0.16 1.13 0.47l6.14 6.13-4.73 4.73L1.38 8.09z m0.63-4.09h2v2H2V4z. /// </summary> - internal static string stop { + internal static string tag { get { - return ResourceManager.GetString("stop", resourceCulture); + return ResourceManager.GetString("tag", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M655.461,473.469c11.875,81.719-13.062,167.781-76.812,230.594c-94.188,92.938-239.5,104.375-346.375,34.562l74.875-73 L31.96,627.25L70.367,896l84.031-80.5c150.907,111.25,364.938,100.75,502.063-34.562c79.5-78.438,115.75-182.562,111.25-285.312 L655.461,473.469z M189.46,320.062c94.156-92.938,239.438-104.438,346.313-34.562l-75,72.969l275.188,38.406L697.586,128 l-83.938,80.688C462.711,97.344,248.742,107.969,111.585,243.25C32.085,321.656-4.133,425.781,0.335,528.5l112.25,22.125 C100.71,468.875,125.71,382.906,189. [rest of string was truncated]";. + /// Looks up a localized string similar to M15.41 9H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM9.59 4c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h5.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H9.59zM0 3.91l1.41-1.3 1.59 1.59L7.09 0l1.41 1.41-5.5 5.5L0 3.91z m7.59 8.09h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z. /// </summary> - internal static string sync { + internal static string tasklist { get { - return ResourceManager.GetString("sync", resourceCulture); + return ResourceManager.GetString("tasklist", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M384,64H128L0,192v256l512,512l384-384L384,64z M64,416V224l96-96h192l448,448L512,864L64,416z M448,320L256,512l256,256 l192-192L448,320z M352,512l96-96l160,160l-96,96L352,512z M320,288c0-53-43-96-96-96s-96,43-96,96s43,96,96,96S320,341,320,288z M224,320c-17.656,0-32-14.344-32-32s14.344-32,32-32s32,14.344,32,32S241.656,320,224,320z. + /// Looks up a localized string similar to M8 9l3 6h-1L8 11v5h-1V10L5 15h-1l2-5 2-1zM7 0h-1v1h1V0zM5 3h-1v1h1v-1zM2 1H1v1h1V1zM0.63 9c-0.22 0.16-0.28 0.44-0.16 0.67l0.55 0.92c0.13 0.23 0.41 0.31 0.64 0.2l1.39-0.66-1.16-2-1.27 0.86z m7.89-5.39L2.72 7.56l1.23 2.14 6.33-3.03-1.77-3.06z m4.22 1.28l-1.47-2.52c-0.14-0.25-0.47-0.33-0.72-0.17l-1.2 0.83 1.84 3.2 1.33-0.64c0.27-0.13 0.36-0.44 0.22-0.7z. /// </summary> - internal static string tag { + internal static string telescope { get { - return ResourceManager.GetString("tag", resourceCulture); + return ResourceManager.GetString("telescope", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M736,512l64,64L512,864L64,416V224l96-96h186l102,96v-96l-64-64H128L0,192v256l512,512l384-384l-64-64H736z M768,192V64H640 v128H512v128h128v128h128V320h128V192H768z M224,192c-53,0-96,43-96,96s43,96,96,96s96-43,96-96S277,192,224,192z M224,320 c-17.656,0-32-14.344-32-32s14.344-32,32-32s32,14.344,32,32S241.656,320,224,320z M704,576L448,320L256,512l256,256L704,576z M448,416l160,160l-96,96L352,512L448,416z. + /// Looks up a localized string similar to M7 10h4v1H7v-1z m-3 1l3-3-3-3-0.75 0.75 2.25 2.25-2.25 2.25 0.75 0.75z m10-8v10c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v10h12V3z. /// </summary> - internal static string tag_add { + internal static string terminal { get { - return ResourceManager.GetString("tag_add", resourceCulture); + return ResourceManager.GetString("terminal", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M672,448l128,128L512,864L64,416V224l96-96h192l160,160v-96L384,64H128L0,192v256l512,512l384-384L768,448H672z M704,576 L448,320L256,512l256,256L704,576z M448,416l160,160l-96,96L352,512L448,416z M224,192c-53,0-96,43-96,96s43,96,96,96s96-43,96-96 S277,192,224,192z M224,320c-17.656,0-32-14.344-32-32s14.344-32,32-32s32,14.344,32,32S241.656,320,224,320z M937,89.469 l-66.5-66.5L768,125.5L665.5,22.969l-66.5,66.5L701.5,192L599,294.531l66.5,66.5L768,258.5l102.5,102.531l66.5-66.5L834.5,192 L937,89.469z. + /// Looks up a localized string similar to M17.97 14h-2.25l-0.95-3.25H10.7l-0.95 3.25H7.5l-0.69-2.33H3.53l-0.7 2.33H0.66l3.3-9.59h2.5l2.17 6.34 2.89-8.75h2.52l3.94 12zM6.36 10.13s-1.02-3.61-1.17-4.11h-0.08l-1.13 4.11h2.38z m7.92-1.05l-1.52-5.42h-0.06l-1.5 5.42h3.08z. /// </summary> - internal static string tag_remove { + internal static string text_size { get { - return ResourceManager.GetString("tag_remove", resourceCulture); + return ResourceManager.GetString("text_size", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M77.771,425.469l1.281-3.062l225.906-57.812C304.083,371,303.052,384,303.052,384c0,128,128,128,128,128s128,0,128-128 c0-10.812-8.438-22.094-19.062-32.125l48.938,2.938c0,0,7.375-1.969,30.875-8.312c-51.25,13.75-107.625-30.531-125.875-98.75 c-18.25-68.281,8.439-134.781,59.625-148.5c-23.5,6.281-30.875,8.281-30.875,8.281L354.24,217.594 c-34.094,9.156-54.904,45.625-45.75,79.812c2.375,8.844,6.596,16.594,11.971,23.219c-7.031,12.312-12.346,25.5-15.031,39.812 c-27-1.344-51.406-18.938-58.75-46.438c-9.094-34.188,11.1 [rest of string was truncated]";. + /// Looks up a localized string similar to M11.41 9H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h10.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1z m0-4H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h10.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM0.59 11h10.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z. /// </summary> - internal static string telescope { + internal static string three_bars { get { - return ResourceManager.GetString("telescope", resourceCulture); + return ResourceManager.GetString("three_bars", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M0,192v128h640V192H0z M0,576h640V448H0V576z M0,832h640V704H0V832z. + /// Looks up a localized string similar to M15.98 7.83l-0.97-5.95C14.84 0.5 13.13 0 12 0H5.69c-0.2 0-0.38 0.05-0.53 0.14l-1.44 0.86H2C0.94 1 0 1.94 0 3v4c0 1.06 0.94 2.02 2 2h2c0.91 0 1.39 0.45 2.39 1.55 0.91 1 0.88 1.8 0.63 3.27-0.08 0.5 0.06 1 0.42 1.42 0.39 0.47 0.98 0.77 1.56 0.77 1.83 0 3-3.72 3-5.02l-0.02-0.98c0.02 0 0.02 0 0.02 0h2.02c1.16 0 1.95-0.8 1.98-1.97 0-0.06 0.02-0.13-0.02-0.2z m-1.97 1.19H12.02c-0.7 0-1.03 0.28-1.03 0.97l0.03 1.03c0 1.27-1.17 4-2 4-0.5 0-1.08-0.5-1-1 0.25-1.58 0.34-2.78-0.89-4.14-1.02-1.13-1.77-1.88-3.13-1.88V2l1.67 [rest of string was truncated]";. /// </summary> - internal static string three_bars { + internal static string thumbsdown { get { - return ResourceManager.GetString("three_bars", resourceCulture); + return ResourceManager.GetString("thumbsdown", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M14 6H12s0 0-0.02 0l0.02-0.98c0-1.3-1.17-5.02-3-5.02-0.58 0-1.17 0.3-1.56 0.77-0.36 0.41-0.5 0.91-0.42 1.41 0.25 1.48 0.28 2.28-0.63 3.28-1 1.09-1.48 1.55-2.39 1.55H2C0.94 7 0 7.94 0 9v4c0 1.06 0.94 2 2 2h1.72l1.44 0.86c0.16 0.09 0.33 0.14 0.52 0.14h6.33c1.13 0 2.84-0.5 3-1.88l0.98-5.95c0.02-0.08 0.02-0.14 0.02-0.2-0.03-1.17-0.84-1.97-2-1.97z m0 8c-0.05 0.69-1.27 1-2 1H5.67l-1.67-1V8c1.36 0 2.11-0.75 3.13-1.88 1.23-1.36 1.14-2.56 0.88-4.13-0.08-0.5 0.5-1 1-1 0.83 0 2 2.73 2 4l-0.02 1.03c0 0.69 0.33 0.97 1.0 [rest of string was truncated]";. + /// </summary> + internal static string thumbsup { + get { + return ResourceManager.GetString("thumbsup", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M286.547,465.016c16.843,16.812,81.716,85.279,81.716,85.279l35.968-37.093l-56.373-58.248L456.072,340.02 c0,0-48.842-47.623-27.468-28.655c20.438-75.903,1.812-160.589-55.842-220.243C315.608,31.936,234.392,12.53,161.425,32.904 l123.653,127.715l-32.53,125.309l-121.06,33.438L7.898,191.618c-19.718,75.436-0.969,159.339,56.311,218.556 C124.302,472.297,210.83,490.547,286.547,465.016z M698.815,589.231L549.694,736.539l245.932,254.805 c20.062,20.812,46.498,31.188,72.872,31.188c26.25,0,52.624-10.375,72.811-31.188c40. [rest of string was truncated]";. + /// Looks up a localized string similar to M4.48 7.27c0.26 0.26 1.28 1.33 1.28 1.33l0.56-0.58-0.88-0.91L7.13 5.31c0 0-0.76-0.74-0.43-0.45 0.32-1.19 0.03-2.51-0.87-3.44C4.93 0.5 3.66 0.2 2.52 0.51l1.93 2-0.51 1.96-1.89 0.52L0.12 2.99c-0.31 1.18-0.02 2.49 0.88 3.41C1.94 7.38 3.29 7.66 4.48 7.27zM10.92 9.21L8.59 11.51l3.84 3.98c0.31 0.33 0.73 0.49 1.14 0.49 0.41 0 0.82-0.16 1.14-0.49 0.63-0.65 0.63-1.7 0-2.35L10.92 9.21zM16 2.53L13.55 0 6.33 7.46l0.88 0.91L2.9 12.83l-0.99 0.53-1.39 2.27 0.35 0.37 2.2-1.44 0.51-1.02 4.32-4.46 0.88 0.91L16 2.53z. /// </summary> internal static string tools { get { @@ -1654,7 +1447,16 @@ internal static string tools { } /// <summary> - /// Looks up a localized string similar to M0,384l383.75,383.75L767.5,384H0z. + /// Looks up a localized string similar to M10 2H8c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1H1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1v9c0 0.55 0.45 1 1 1h7c0.55 0 1-0.45 1-1V5c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1z m-1 12H2V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9z m1-10H1v-1h9v1z. + /// </summary> + internal static string trashcan { + get { + return ResourceManager.GetString("trashcan", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M0 5l6 6 6-6H0z. /// </summary> internal static string triangle_down { get { @@ -1663,7 +1465,7 @@ internal static string triangle_down { } /// <summary> - /// Looks up a localized string similar to M0,511.875l383.75,383.75v-767.5L0,511.875z. + /// Looks up a localized string similar to M6 2L0 8l6 6V2z. /// </summary> internal static string triangle_left { get { @@ -1672,7 +1474,7 @@ internal static string triangle_left { } /// <summary> - /// Looks up a localized string similar to M0.062,128.25L383.812,512L0.062,895.75V128.25z. + /// Looks up a localized string similar to M0 14l6-6L0 2v12z. /// </summary> internal static string triangle_right { get { @@ -1681,7 +1483,7 @@ internal static string triangle_right { } /// <summary> - /// Looks up a localized string similar to M383.75,256L0,639.75h767.5L383.75,256z. + /// Looks up a localized string similar to M12 11L6 5 0 11h12z. /// </summary> internal static string triangle_up { get { @@ -1690,25 +1492,34 @@ internal static string triangle_up { } /// <summary> - /// Looks up a localized string similar to M1018.312,375.028c-4.062-9.094-10.625-18-16.438-25.219c-12.438-15.5-26.438-29.844-40.5-42.75 c-15-13.844-28.625-26.75-39.75-43.469c-11-16.375-18.875-35.781-29.125-54.281c-10.062-18.156-20.562-35.312-32.5-50.625 c-12.062-15.281-27.125-28.469-42.75-40.562c-7.75-5.875-16.688-11.906-26.125-15.562c-9.938-3.906-22.75-5.094-36-5.75 C728.75,95.528,702,95.778,673.25,95.403H566.125c-70.375,1.594-141.344,0.844-206.906,7.438c-32.125,3.25-63.844,8.062-94,13.094 c-30.594,5.125-59.062,12.031-84.344,22.719c-24.969,10.5 [rest of string was truncated]";. + /// Looks up a localized string similar to M11.5 8.5l2.5 2.5c0 0.55-0.45 1-1 1H9v-1h3.5L10.5 9H3.5L1.5 11h3.5v1H1c-0.55 0-1-0.45-1-1l2.5-2.5L0 6c0-0.55 0.45-1 1-1h4v1H1.5l2 2h7l2-2H9v-1h4c0.55 0 1 0.45 1 1L11.5 8.5z m-5.5-1.5h2V4h2L7 1 4 4h2v3z m2 3H6v3H4l3 3 3-3H8V10z. /// </summary> - internal static string trollface { + internal static string unfold { get { - return ResourceManager.GetString("trollface", resourceCulture); + return ResourceManager.GetString("unfold", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M384,320h128V192h128L448,0L256,192h128V320z M576,256v64h224L672,448H224L96,320h224v-64H0v63.999L160,480L0,640v64h320 v-64H96l128-128h448l128,128H576v64h320v-64L736,480l160-160.001V256H576z M512,576H384v192H256l192,192l192-192H512V576z. + /// Looks up a localized string similar to M11 8.02c0 1.09-0.45 2.09-1.17 2.83l-0.67-0.67c0.55-0.56 0.89-1.31 0.89-2.16s-0.34-1.61-0.89-2.16l0.67-0.67c0.72 0.72 1.17 1.72 1.17 2.83zM6.72 2.28L3 6H1c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h2l3.72 3.72c0.47 0.47 1.28 0.14 1.28-0.53V2.81c0-0.67-0.81-1-1.28-0.53z m5.94 0.08l-0.67 0.67c1.28 1.28 2.06 3.03 2.06 4.98 0 1.94-0.78 3.7-2.06 4.98l0.67 0.67c1.45-1.45 2.34-3.45 2.34-5.66 0-2.22-0.89-4.22-2.34-5.66z m-1.41 1.41l-0.69 0.67c0.92 0.92 1.48 2.19 1.48 3.58s-0.56 2.66-1.48 3.56l0.69 0.67c1.08-1.08 1.75-2 [rest of string was truncated]";. /// </summary> - internal static string unfold { + internal static string unmute { get { - return ResourceManager.GetString("unfold", resourceCulture); + return ResourceManager.GetString("unmute", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to M15.66 7.36l-1.31-1c-0.47-0.34-0.78-1.11-0.7-1.69l0.22-1.63c0.08-0.58-0.33-0.98-0.91-0.91l-1.63 0.22c-0.58 0.08-1.34-0.23-1.69-0.7l-1-1.31c-0.36-0.45-0.92-0.45-1.28 0l-1 1.31c-0.34 0.47-1.11 0.78-1.69 0.7l-1.63-0.22c-0.58-0.08-0.98 0.33-0.91 0.91l0.22 1.63c0.08 0.58-0.23 1.34-0.7 1.69l-1.31 1c-0.45 0.36-0.45 0.92 0 1.28l1.31 1c0.47 0.34 0.78 1.11 0.7 1.69l-0.22 1.63c-0.08 0.58 0.33 0.98 0.91 0.91l1.63-0.22c0.58-0.08 1.34 0.23 1.69 0.7l1 1.31c0.36 0.45 0.92 0.45 1.28 0l1-1.31c0.34-0.47 1.11-0.78 1.69-0.7l1.6 [rest of string was truncated]";. + /// </summary> + internal static string verified { + get { + return ResourceManager.GetString("verified", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M0,704h128v-64H64V384h64v-64H0V704z M384,192v640h512V192H384z M768,704H512V320h256V704z M192,768h128v-64h-64V320h64v-64 H192V768z. + /// Looks up a localized string similar to M13 3H7c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1z m-1 8H8V5h4v6zM4 4h1v1h-1v6h1v1h-1c-0.55 0-1-0.45-1-1V5c0-0.55 0.45-1 1-1zM1 5h1v1H1v4h1v1H1c-0.55 0-1-0.45-1-1V6c0-0.55 0.45-1 1-1z. /// </summary> internal static string versions { get { @@ -1717,16 +1528,16 @@ internal static string versions { } /// <summary> - /// Looks up a localized string similar to M893,225.906l-89.875,89.812L676.5,281.844L642.625,155.25l88.562-88.562C722.25,65.406,713.375,64,704,64 c-106.062,0-192,85.938-192,192c0,19.25,3.75,37.5,9,54.969L246.969,585c-17.469-5.25-35.719-9-54.969-9C85.938,576,0,662,0,768 c0,9.375,1.406,18.25,2.75,27.188l88.562-88.562L217.906,740.5l33.875,126.625L161.906,957c9.938,1.625,19.781,3,30.094,3 c106.062,0,192-85.938,192-192c0-19.25-3.719-37.5-9.031-55L649,439.094c17.5,5.188,35.75,8.906,55,8.906 c106.062,0,192-85.938,192-192C896,245.688,894.5,235.844,893,2 [rest of string was truncated]";. + /// Looks up a localized string similar to M6 8h2v1H5V5h1v3z m6 0c0 2.22-1.2 4.16-3 5.19v1.81c0 0.55-0.45 1-1 1H4c-0.55 0-1-0.45-1-1V13.19C1.2 12.16 0 10.22 0 8s1.2-4.16 3-5.19V1c0-0.55 0.45-1 1-1h4c0.55 0 1 0.45 1 1v1.81c1.8 1.03 3 2.97 3 5.19z m-1 0c0-2.77-2.23-5-5-5S1 5.23 1 8s2.23 5 5 5 5-2.23 5-5z. /// </summary> - internal static string wrench { + internal static string watch { get { - return ResourceManager.GetString("wrench", resourceCulture); + return ResourceManager.GetString("watch", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to M640,320L512,192L320,384L128,192L0,320l192,192L0,704l128,128l192-192l192,192l128-128L448,512L640,320z. + /// Looks up a localized string similar to M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z. /// </summary> internal static string x { get { @@ -1735,7 +1546,7 @@ internal static string x { } /// <summary> - /// Looks up a localized string similar to M640,448H384L576,0L0,576h256L64,1024L640,448z. + /// Looks up a localized string similar to M10 7H6L9 0 0 9h4L1 16 10 7z. /// </summary> internal static string zap { get { diff --git a/src/GitHub.UI/Controls/Octicons/OcticonPaths.resx b/src/GitHub.UI/Controls/Octicons/OcticonPaths.resx index c1299e3eec..288efa37c3 100644 --- a/src/GitHub.UI/Controls/Octicons/OcticonPaths.resx +++ b/src/GitHub.UI/Controls/Octicons/OcticonPaths.resx @@ -118,534 +118,501 @@ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="alert" xml:space="preserve"> - <value>M1005.854 800.247l-438.286-767C556.173 13.30600000000004 534.967 1 512 1s-44.173 12.306-55.567 32.247l-438.286 767c-11.319 19.809-11.238 44.144 0.213 63.876C29.811 883.855 50.899 896 73.714 896h876.572c22.814 0 43.903-12.145 55.354-31.877S1017.173 820.056 1005.854 800.247zM576 768H448V640h128V768zM576 576H448V320h128V576z</value> - </data> - <data name="alignment_align" xml:space="preserve"> - <value>M192 64C85.938 64 0 149.938 0 256s85.938 192 192 192c106.062 0 192-85.938 192-192S298.062 64 192 64zM672 608l160-160H384v448l160-160 288 288 128-128L672 608z</value> - </data> - <data name="alignment_aligned_to" xml:space="preserve"> - <value>M384 576l128-128 288 288 160-160v448H512l160-160L384 576zM192 448C85.938 448 0 362.062 0 256S85.938 64 192 64c106.062 0 192 85.938 192 192S298.062 448 192 448z</value> - </data> - <data name="alignment_unalign" xml:space="preserve"> - <value>M512 192L384 320 128 64 0 192l256 256L128 576l64 64 384-384L512 192zM640 576l128-128-64-64L320 768l64 64 128-128 256 256 128-128L640 576z</value> + <value>M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z</value> </data> <data name="arrow_down" xml:space="preserve"> - <value>M448 448V192H192v256H0l320 384 320-384H448z</value> + <value>M7 7V3H3v4H0l5 6 5-6H7z</value> </data> <data name="arrow_left" xml:space="preserve"> - <value>M384 384V192L0 512l384 320V640h256V384H384z</value> + <value>M6 6V3L0 8l6 5V10h4V6H6z</value> </data> <data name="arrow_right" xml:space="preserve"> - <value>M640 512L256 192v192H0v256h256v192L640 512z</value> + <value>M10 8L4 3v3H0v4h4v3L10 8z</value> </data> <data name="arrow_small_down" xml:space="preserve"> - <value>M256 448V320H128v128H0l192 256 192-256H256z</value> + <value>M4 7V5H2v2H0l3 4 3-4H4z</value> </data> <data name="arrow_small_left" xml:space="preserve"> - <value>M256 448V320L0 512l256 192V576h128V448H256z</value> + <value>M4 7V5L0 8l4 3V9h2V7H4z</value> </data> <data name="arrow_small_right" xml:space="preserve"> - <value>M384 512L128 320v128H0v128h128v128L384 512z</value> + <value>M6 8L2 5v2H0v2h2v2L6 8z</value> </data> <data name="arrow_small_up" xml:space="preserve"> - <value>M192 320L0 576h128v128h128V576h128L192 320z</value> + <value>M3 5L0 9h2v2h2V9h2L3 5z</value> </data> <data name="arrow_up" xml:space="preserve"> - <value>M320 192L0 576h192v256h256V576h192L320 192z</value> + <value>M5 3L0 9h3v4h4V9h3L5 3z</value> + </data> + <data name="beaker" xml:space="preserve"> + <value>M14.38 14.59L11 7V3h1v-1H3v1h1v4L0.63 14.59c-0.3 0.66 0.19 1.41 0.91 1.41h11.94c0.72 0 1.2-0.75 0.91-1.41zM3.75 10l1.25-3V3h5v4l1.25 3H3.75z m4.25-2h1v1h-1v-1z m-1-1h-1v-1h1v1z m0-3h1v1h-1v-1z m0-3h-1V0h1v1z</value> + </data> + <data name="bell" xml:space="preserve"> + <value>M14 12v1H0v-1l0.73-0.58c0.77-0.77 0.81-2.55 1.19-4.42 0.77-3.77 4.08-5 4.08-5 0-0.55 0.45-1 1-1s1 0.45 1 1c0 0 3.39 1.23 4.16 5 0.38 1.88 0.42 3.66 1.19 4.42l0.66 0.58z m-7 4c1.11 0 2-0.89 2-2H5c0 1.11 0.89 2 2 2z</value> </data> - <data name="beer" xml:space="preserve"> - <value>M896 256H704V128C704 57.34400000000005 546.375 0 352 0S0 57.34400000000005 0 128v768c0 70.625 157.625 128 352 128s352-57.375 352-128V768h192c0 0 64 0 64-64V320C960 256 896 256 896 256zM192 832h-64V320h64V832zM384 896h-64V384h64V896zM576 832h-64V320h64V832zM352 192c-123.719 0-224-28.594-224-64 0-35.312 100.281-64 224-64s224 28.688 224 64C576 163.40599999999995 475.719 192 352 192zM832 640H704V384h128V640z</value> + <data name="bold" xml:space="preserve"> + <value>M0 2h3.83c2.48 0 4.3 0.75 4.3 2.95 0 1.14-0.63 2.23-1.67 2.61v0.06c1.33 0.3 2.3 1.23 2.3 2.86 0 2.39-1.97 3.52-4.61 3.52H0V2z m3.66 4.95c1.67 0 2.38-0.66 2.38-1.69 0-1.17-0.78-1.61-2.34-1.61H2.13v3.3h1.53z m0.27 5.39c1.77 0 2.75-0.64 2.75-1.98 0-1.27-0.95-1.81-2.75-1.81H2.13v3.8h1.8z</value> </data> <data name="book" xml:space="preserve"> - <value>M320 320H192c-64 0-64 64-64 64h256C384 384 384 320 320 320zM320 448H192c-64 0-64 64-64 64h256C384 512 384 448 320 448zM320 576H192c-64 0-64 64-64 64h256C384 640 384 576 320 576zM768 320H640c-64 0-64 64-64 64h256C832 384 832 320 768 320zM768 448H640c-64 0-64 64-64 64h256C832 512 832 448 768 448zM768 576H640c-64 0-64 64-64 64h256C832 640 832 576 768 576zM608 160c-64 0-128 64-128 64s-64-64-128-64C192 160 0 224 0 224v608l448-32c0 0-1.281 32 31.375 32C512 832 512 800 512 800l448 32V224C960 224 768 160 608 160zM448 768c0 0 1.031-64-64-64-193.031 0-320 64-320 64V256c0 0 128-32 256-32 64 0 128 64 128 64V768zM896 736c0 0-97-32-320-32-62 0-64 64-64 64V288c0 0 64-64 128-64 128 0 256 64 256 64V736z</value> + <value>M2 5h4v1H2v-1z m0 3h4v-1H2v1z m0 2h4v-1H2v1z m11-5H9v1h4v-1z m0 2H9v1h4v-1z m0 2H9v1h4v-1z m2-6v9c0 0.55-0.45 1-1 1H8.5l-1 1-1-1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h5.5l1 1 1-1h5.5c0.55 0 1 0.45 1 1z m-8 0.5l-0.5-0.5H1v9h6V3.5z m7-0.5H8.5l-0.5 0.5v8.5h6V3z</value> </data> <data name="bookmark" xml:space="preserve"> - <value>M0 128v768l192-128 192 128V128H0zM316.25 324.75l-71.875 51.938 27.188 83.406c2.75 8.375-0.688 11.062-7.562 6.594l-72-52.094-72 52.031c-6.844 4.469-10.312 1.781-7.562-6.594l27.219-83.406L67.783 324.75c-6.469-5.125-5-9.219 3.906-9.219l88-0.125 27.125-83.094c2.812-8.812 7.562-8.812 10.375 0l27.188 83.094 87.938 0.125C321.25 315.53099999999995 322.688 319.625 316.25 324.75z</value> + <value>M9 0H1C0.27 0 0 0.27 0 1v15l5-3.09 5 3.09V1c0-0.73-0.27-1-1-1z m-0.78 4.25l-1.86 1.36 0.72 2.16c0.06 0.22-0.02 0.28-0.2 0.17l-1.88-1.34-1.88 1.34c-0.19 0.11-0.25 0.05-0.2-0.17l0.72-2.16-1.86-1.36c-0.17-0.16-0.14-0.23 0.09-0.23l2.3-0.03 0.7-2.16h0.25l0.7 2.16 2.3 0.03c0.23 0 0.27 0.08 0.09 0.23z</value> </data> <data name="briefcase" xml:space="preserve"> - <value>M896 192H640v-66c0-34.2-27.8-62-62-62H446c-34.2 0-62 27.8-62 62v66H128c-35.3 0-64 28.7-64 64v512c0 35.3 28.7 64 64 64h768c35.3 0 64-28.7 64-64V256C960 220.70000000000005 931.3 192 896 192zM448 144c0-8.8 7.2-16 16-16h96c8.8 0 16 7.2 16 16v48H448V144zM896 512H576v64H448v-64H128V256h64v192h640V256h64V512z</value> + <value>M9 4v-1c0-0.55-0.45-1-1-1H6c-0.55 0-1 0.45-1 1v1H1c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V5c0-0.55-0.45-1-1-1H9z m-3-1h2v1H6v-1z m7 6H8v1H6v-1H1V5h1v3h10V5h1v4z</value> </data> <data name="broadcast" xml:space="preserve"> - <value>M320 576v128c0 64 64 64 64 64v256h128V768c0 0 64 0 64-64V576c0-64-64-64-64-64h-63.938H384C384 512 320 512 320 576zM576 384c0-128-128-128-128-128s-128 0-128 128 128.062 128 128.062 128S576 512 576 384zM448 0C200.562 0 0 200.562 0 448c0 197.125 128.188 362.688 305.156 422.625l-12.031-71.75C158.406 739.312 64 604.875 64 448 64 235.96900000000005 235.969 64 448 64c212 0 384 171.969 384 384 0 156.875-94.375 291.312-229.125 350.875l-12 71.625C767.812 810.688 896 645.125 896 448 896 200.562 695.438 0 448 0zM448 192c141.562 0 256 114.5 256 256 0 69.438-27.625 132.125-72.375 178.312l-15.5 92.812C707 662.75 768 562.875 768 448c0-176.781-143.25-320-320-320-176.781 0-320 143.219-320 320 0 114.875 61 214.75 151.875 271.125l-15.5-92.812C219.594 580.125 192 517.438 192 448 192 306.5 306.5 192 448 192z</value> + <value>M9 9h-1c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1h-1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1h-1c-0.55 0-1 0.45-1 1v2h1v3c0 0.55 0.45 1 1 1h1c0.55 0 1-0.45 1-1V12h1V10c0-0.55-0.45-1-1-1zM7 7h1v1h-1v-1z m2 4h-1v4h-1V11h-1v-1h3v1z m2.09-3.5c0-1.98-1.61-3.59-3.59-3.59S3.91 5.52 3.91 7.5c0 0.28 0.03 0.55 0.09 0.81v1.98c-0.61-0.77-1-1.73-1-2.8 0-2.48 2.02-4.5 4.5-4.5s4.5 2.02 4.5 4.5c0 1.06-0.39 2.03-1 2.8V8.31c0.06-0.27 0.09-0.53 0.09-0.81z m3.91 0c0 2.88-1.63 5.38-4 6.63v-1.05c1.86-1.16 3.09-3.22 3.09-5.58 0-3.64-2.95-6.59-6.59-6.59S0.91 3.86 0.91 7.5c0 2.36 1.23 4.42 3.09 5.58v1.05C1.63 12.88 0 10.38 0 7.5 0 3.36 3.36 0 7.5 0s7.5 3.36 7.5 7.5z</value> </data> <data name="browser" xml:space="preserve"> - <value>M320 192h64v64h-64V192zM192 192h64v64h-64V192zM64 192h64v64H64V192zM832 832H64V320h768V832zM832 256H448v-64h384V256zM896 192c0-35.35-28.65-64-64-64H64c-35.35 0-64 28.65-64 64v640c0 35.35 28.65 64 64 64h768c35.35 0 64-28.65 64-64V192z</value> + <value>M5 3h1v1h-1V3zM3 3h1v1h-1V3zM1 3h1v1H1V3zM13 13H1V5h12V13zM13 4H7v-1h6V4zM14 3c0-0.55-0.45-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V3z</value> </data> <data name="bug" xml:space="preserve"> - <value>M243.621 156.53099999999995C190.747 213.312 205.34 304 205.34 304s53.968 64 160 64c106.031 0 160.031-64 160.031-64s14.375-89.469-37.375-146.312c32.375-18.031 51.438-44.094 43.562-61.812-8.938-19.969-48.375-21.75-88.25-3.969-14.812 6.594-27.438 14.969-37.25 23.875-12.438-2.25-25.625-3.781-40.72-3.781-14.061 0-26.561 1.344-38.344 3.25-9.656-8.75-22.062-16.875-36.531-23.344-39.875-17.719-79.375-15.938-88.25 3.969C194.465 113.21900000000005 212.497 138.562 243.621 156.53099999999995zM644.746 569.75c-8.25-1.75-16.125-2.75-23.75-3.5 0-2.125 0.375-4.125 0.375-6.312 0-33.594-4.75-65.654-12.438-96.125 16.438 1.406 37.375-2.375 58.562-11.779 39.875-17.781 65-48.375 56.125-68.219-8.875-19.969-48.375-21.75-88.25-3.969-18.625 8.312-33.812 19.469-44 30.906-7.75-18.25-16.5-35.781-26.812-51.719-30.188 25.156-87.312 62.719-167.062 71.062v321.781c0 0-0.25 32-32.031 32-31.75 0-32-32-32-32V430.219c-79.811-8.344-136.968-45.969-167.093-71.062-9.875 15.312-18.375 32-25.938 49.344-10.281-10.625-24.625-20.844-41.969-28.594-39.875-17.719-79.375-15.938-88.25 3.969-8.906 19.906 16.25 50.438 56.125 68.219 19.844 8.846 39.531 12.812 55.469 12.096-7.656 30.404-12.469 62.344-12.469 95.812 0 2.188 0.375 4.25 0.438 6.5-6.719 0.75-13.688 1.75-20.781 3.25-51.969 10.75-91.781 37.625-88.844 59.812 2.938 22.312 47.5 31.5 99.594 20.688 6.781-1.375 13.438-3.125 19.781-5.062C128.684 686 143.34 723.875 163.622 756.5c-12.031 6.062-24.531 15-36.031 26.625C95.715 815 82.779 853.75 98.715 869.688c15.938 15.937 54.656 3 86.531-28.812 9.344-9.375 16.844-19.25 22.656-29C251.434 854.5 305.965 880 365.465 880c60.343 0 115.781-26.25 159.531-69.938 5.875 10.312 13.75 20.812 23.625 30.688 31.812 31.875 70.625 44.812 86.562 28.875s3-54.625-28.875-86.5c-12.312-12.375-25.688-21.75-38.438-27.938 20.125-32.5 34.625-70.375 43.688-111.062 7.188 2.25 14.688 4.375 22.562 6.062 52.061 10.812 96.625 1.562 99.625-20.688C736.558 607.375 696.746 580.5 644.746 569.75z</value> + <value>M11 10h3v-1H11v-1l3.17-1.03-0.34-0.94-2.83 0.97v-1c0-0.55-0.45-1-1-1v-1c0-0.48-0.36-0.88-0.83-0.97l1.03-1.03h1.8V1H9.8L7.8 3h-0.59L5.2 1H3v1h1.8l1.03 1.03c-0.47 0.09-0.83 0.48-0.83 0.97v1c-0.55 0-1 0.45-1 1v1L1.17 6.03l-0.34 0.94 3.17 1.03v1H1v1h3v1L0.83 12.03l0.34 0.94 2.83-0.97v1c0 0.55 0.45 1 1 1h1l1-1V6h1v7l1 1h1c0.55 0 1-0.45 1-1v-1l2.83 0.97 0.34-0.94-3.17-1.03v-1zM9 5H6v-1h3v1z</value> </data> <data name="calendar" xml:space="preserve"> - <value>M704 512h-64v128h64V512zM576 512h-64v128h64V512zM704 320h-64v128h64V320zM832 512h-64v128h64V512zM576 704h-64v128h64V704zM768 0h-64v128h64V0zM256 0h-64v128h64V0zM832 320h-64v128h64V320zM576 320h-64v128h64V320zM320 704h-64v128h64V704zM192 512h-64v128h64V512zM320 512h-64v128h64V512zM832 64v128H640V64H320v128H128V64H0v896h960V64H832zM896 896H64V256h832V896zM192 704h-64v128h64V704zM448 320h-64v128h64V320zM448 704h-64v128h64V704zM320 320h-64v128h64V320zM448 512h-64v128h64V512zM704 704h-64v128h64V704z</value> + <value>M12 2h-1v1.5c0 0.28-0.22 0.5-0.5 0.5H8.5c-0.28 0-0.5-0.22-0.5-0.5v-1.5H5v1.5c0 0.28-0.22 0.5-0.5 0.5H2.5c-0.28 0-0.5-0.22-0.5-0.5v-1.5H1c-0.55 0-1 0.45-1 1v11c0 0.55 0.45 1 1 1h11c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 12H1V5h11v9zM4 3h-1V1h1v2z m6 0h-1V1h1v2zM5 7h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1zM3 9h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1zM3 11h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1zM3 13h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z m2 0h-1v-1h1v1z</value> </data> <data name="check" xml:space="preserve"> - <value>M640 192L256 576 128 448 0 576l256 256 512-512L640 192z</value> + <value>M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z</value> </data> <data name="checklist" xml:space="preserve"> - <value>M760.688 516.219l-49.812-49.656c-6.438-6.529-16.938-6.594-23.375 0L582.5 571.5 462.375 691.875l-93.031-93.125c-6.531-6.562-17.031-6.562-23.5 0l-49.719 49.688c-6.531 6.562-6.531 17.062 0 23.562l104.781 104.875 17.969 17.875 31.688 31.812c6.562 6.562 17.188 6.562 23.562 0l49.625-49.688L760.625 539.78C767.25 533.312 767.25 522.812 760.688 516.219zM228.469 580.812L278.156 531c42.469-42.375 116.344-42.438 158.781 0.062l25.312 25.312L576 448V128H0v704h320l-91.531-92.125C184.688 695.938 184.688 624.625 228.469 580.812zM192 192h320v64H192V192zM192 320h320v64H192V320zM128 512H64v-64h64V512zM128 384H64v-64h64V384zM128 256H64v-64h64V256zM192 448h64v64h-64V448z</value> + <value>M16 8.5L10 14.5 7 11.5l1.5-1.5 1.5 1.5 4.5-4.5 1.5 1.5zM5.7 12.2l0.8 0.8H2c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h7c0.55 0 1 0.45 1 1v6.5l-0.8-0.8c-0.39-0.39-1.03-0.39-1.42 0L5.7 10.8c-0.39 0.39-0.39 1.02 0 1.41zM4 4h5v-1H4v1z m0 2h5v-1H4v1z m0 2h3v-1H4v1z m-1 1h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z</value> </data> <data name="chevron_down" xml:space="preserve"> - <value>M512 320L320 512 128 320 0 448l320 320 320-320L512 320z</value> + <value>M5 11L0 6l1.5-1.5 3.5 3.75 3.5-3.75 1.5 1.5-5 5z</value> </data> <data name="chevron_left" xml:space="preserve"> - <value>M448 320L320 192 0 512l320 320 128-128L256 512 448 320z</value> + <value>M5.5 3l1.5 1.5-3.75 3.5 3.75 3.5-1.5 1.5L0.5 8l5-5z</value> </data> <data name="chevron_right" xml:space="preserve"> - <value>M128 192L0 320l192 192L0 704l128 128 320-320L128 192z</value> + <value>M7.5 8L2.5 13l-1.5-1.5 3.75-3.5L1 4.5l1.5-1.5 5 5z</value> </data> <data name="chevron_up" xml:space="preserve"> - <value>M320 256L0 576l128 128 192-192 192 192 128-128L320 256z</value> + <value>M10 9l-1.5 1.5-3.5-3.75L1.5 10.5 0 9l5-5 5 5z</value> </data> <data name="circle_slash" xml:space="preserve"> - <value>M320 192C143.219 192 0 335.219 0 512c0 176.75 143.219 320 320 320 176.75 0 320-143.25 320-320C640 335.219 496.75 192 320 192zM320 320c27.656 0 53.688 6.094 77.438 16.562L144.562 589.438C134.094 565.688 128 539.656 128 512 128 406 213.938 320 320 320zM320 704c-28.031 0-54.531-6.375-78.594-17.125l253.906-252.5C505.875 458.188 512 484.281 512 512 512 618.062 426.062 704 320 704z</value> + <value>M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m0 1.3c1.3 0 2.5 0.44 3.47 1.17L2.47 11.47c-0.73-0.97-1.17-2.17-1.17-3.47 0-3.14 2.56-5.7 5.7-5.7z m0 11.41c-1.3 0-2.5-0.44-3.47-1.17l8-8c0.73 0.97 1.17 2.17 1.17 3.47 0 3.14-2.56 5.7-5.7 5.7z</value> </data> <data name="circuit_board" xml:space="preserve"> - <value>M320 256c35.346 0 64 28.654 64 64 0 35.346-28.654 64-64 64s-64-28.654-64-64C256 284.654 284.654 256 320 256zM960 768c0 106.039-85.961 192-192 192H320l192-192h81.128c22.132 38.258 63.494 64 110.872 64 70.692 0 128-57.308 128-128s-57.308-128-128-128c-47.377 0-88.74 25.742-110.872 64H448L156.044 931.956C100.845 898.232 64 837.42 64 768V256c0-106.039 85.961-192 192-192v145.128C217.742 231.26 192 272.62300000000005 192 320c0 70.692 57.308 128 128 128 47.276 0 88.56-25.633 110.727-63.756l162.416-0.219C615.279 422.269 656.633 448 704 448c70.692 0 128-57.308 128-128s-57.308-128-128-128c-47.388 0-88.758 25.753-110.887 64.025l-162.097 0.219c-11.246-19.54-27.503-35.828-47.016-47.116V64h384c106.039 0 192 85.961 192 192V768zM640 704c0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64S640 739.346 640 704zM640 320c0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64S640 355.346 640 320z</value> + <value>M3 5c0-0.55 0.45-1 1-1s1 0.45 1 1-0.45 1-1 1-1-0.45-1-1z m8 0c0-0.55-0.45-1-1-1s-1 0.45-1 1 0.45 1 1 1 1-0.45 1-1z m0 6c0-0.55-0.45-1-1-1s-1 0.45-1 1 0.45 1 1 1 1-0.45 1-1zM13 1H5v2.17c0.36 0.19 0.64 0.47 0.83 0.83h2.34c0.42-0.78 1.33-1.28 2.34-1.05 0.75 0.19 1.36 0.8 1.53 1.55 0.31 1.38-0.72 2.59-2.05 2.59-0.8 0-1.48-0.44-1.83-1.09H5.83c-0.42 0.8-1.33 1.28-2.34 1.03-0.73-0.17-1.34-0.78-1.52-1.52-0.25-1.02 0.23-1.92 1.03-2.34V1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1l5-5h2.17c0.42-0.78 1.33-1.28 2.34-1.05 0.75 0.19 1.36 0.8 1.53 1.55 0.31 1.38-0.72 2.59-2.05 2.59-0.8 0-1.48-0.44-1.83-1.09h-1.17L4 15h9c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z</value> </data> <data name="clippy" xml:space="preserve"> - <value>M704 896H64V320h640v192h64V192c0-35.312-28.625-64-64-64H512C512 57.34400000000005 454.656 0 384 0S256 57.34400000000005 256 128H64c-35.406 0-64 28.688-64 64v704c0 35.375 28.594 64 64 64h640c35.375 0 64-28.625 64-64V768h-64V896zM192 192c0 0 0 0 64 0s64-64 64-64c0-35.312 28.594-64 64-64s64 28.688 64 64c0 0 0 64 64 64h64c64 0 64 64 64 64H128C128 256 128 192 192 192zM128 704h128v-64H128V704zM576 576V448L320 640l256 192V704h320V576H576zM128 832h192v-64H128V832zM448 384H128v64h320V384zM256 512H128v64h128V512z</value> + <value>M2 12h4v1H2v-1z m5-6H2v1h5v-1z m2 3V7L6 10l3 3V11h5V9H9z m-4.5-1H2v1h2.5v-1zM2 11h2.5v-1H2v1z m9 1h1v2c-0.02 0.28-0.11 0.52-0.3 0.7s-0.42 0.28-0.7 0.3H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3C4 0.89 4.89 0 6 0s2 0.89 2 2h3c0.55 0 1 0.45 1 1v5h-1V5H1v9h10V12zM2 4h8c0-0.55-0.45-1-1-1h-1c-0.55 0-1-0.45-1-1s-0.45-1-1-1-1 0.45-1 1-0.45 1-1 1h-1c-0.55 0-1 0.45-1 1z</value> </data> <data name="clock" xml:space="preserve"> - <value>M384 576h256l64-64-64-64H512V256l-64-64-64 64V576zM448 64C200.562 64 0 264.562 0 512c0 247.438 200.562 448 448 448 247.438 0 448-200.562 448-448C896 264.562 695.438 64 448 64zM448 832c-176.25 0-320-143.75-320-320 0-175.938 144.188-319.5 320-320 175.812 0.5 320 144.062 320 320C768 688.25 624.25 832 448 832z</value> + <value>M8 8h3v2H7c-0.55 0-1-0.45-1-1V4h2v4z m-1-5.7c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z</value> </data> <data name="cloud_download" xml:space="preserve"> - <value>M832 320c-8.75 0-17.125 1.406-25.625 2.562C757.625 208.25 644.125 128 512 128c-132.156 0-245.562 80.25-294.406 194.562C209.156 321.406 200.781 320 192 320 85.938 320 0 405.938 0 512s85.938 192 192 192c20.531 0 39.875-4.25 58.375-10.375C284.469 731.375 331.312 756.75 384 764.5v-65.25c-49.844-10.375-91.594-42.812-112.625-87.875C249.531 629 222.219 640 192 640c-70.656 0-128-57.375-128-128 0-70.656 57.344-128 128-128 25.281 0 48.625 7.562 68.406 20.094C281.344 283.78099999999995 385.594 192 512 192c126.5 0 229.75 92.219 250.5 212.75 20-13 43.875-20.75 69.5-20.75 70.625 0 128 57.344 128 128 0 70.625-57.375 128-128 128-10.25 0-20-1.5-29.625-3.75C773.438 677.125 725.938 704 672 704c-11.062 0-21.625-1.625-32-4v64.938c10.438 1.688 21.062 3.062 32 3.062 61.188 0 116.5-24.625 156.938-64.438C830 703.625 830.875 704 832 704c106.062 0 192-85.938 192-192S938.062 320 832 320zM576 512H448v320H320l192 192 192-192H576V512z</value> + <value>M9 13h2l-3 3-3-3h2V8h2v5z m3-8c0-0.44-0.91-3-4.5-3-2.42 0-4.5 1.92-4.5 4C1.02 6 0 7.52 0 9c0 1.53 1 3 3 3 0.44 0 2.66 0 3 0v-1.3H3C1.38 10.7 1.3 9.28 1.3 9c0-0.17 0.05-1.7 1.7-1.7h1.3v-1.3c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2h1.3c0.81 0 2.7 0.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2H10v1.3c0.38 0 1.98 0 2 0 2.08 0 4-1.16 4-3.5 0-2.44-1.92-3.5-4-3.5z</value> </data> <data name="cloud_upload" xml:space="preserve"> - <value>M512 384L320 576h128v320h128V576h128L512 384zM832 320c-8.75 0-17.125 1.406-25.625 2.562C757.625 208.188 644.125 128 512 128c-132.156 0-245.562 80.188-294.406 194.562C209.156 321.406 200.781 320 192 320 85.938 320 0 406 0 512c0 106.062 85.938 192 192 192 20.531 0 39.875-4.25 58.375-10.438C284.469 731.375 331.312 756.75 384 764.5v-65.25c-49.844-10.375-91.594-42.812-112.625-87.75C249.531 629 222.219 640 192 640c-70.656 0-128-57.375-128-128 0-70.656 57.344-128 128-128 25.281 0 48.625 7.562 68.406 20.156C281.344 283.78099999999995 385.594 192 512 192c126.5 0 229.75 92.219 250.5 212.75 20-13 43.875-20.75 69.5-20.75 70.625 0 128 57.344 128 128 0 70.625-57.375 128-128 128-10.25 0-20-1.5-29.625-3.75C773.438 677.125 725.938 704 672 704c-11.062 0-21.625-1.625-32-4v64.938c10.438 1.688 21.062 3.062 32 3.062 61.188 0 116.5-24.688 157-64.438 1 0 1.875 0.438 3 0.438 106.062 0 192-85.938 192-192C1024 406 938.062 320 832 320z</value> + <value>M7 9H5l3-3 3 3H9v5H7V9z m5-4c0-0.44-0.91-3-4.5-3-2.42 0-4.5 1.92-4.5 4C1.02 6 0 7.52 0 9c0 1.53 1 3 3 3 0.44 0 2.66 0 3 0v-1.3H3C1.38 10.7 1.3 9.28 1.3 9c0-0.17 0.05-1.7 1.7-1.7h1.3v-1.3c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2h1.3c0.81 0 2.7 0.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2H10v1.3c0.38 0 1.98 0 2 0 2.08 0 4-1.16 4-3.5 0-2.44-1.92-3.5-4-3.5z</value> </data> <data name="code" xml:space="preserve"> - <value>M608 192l-96 96 224 224L512 736l96 96 288-320L608 192zM288 192L0 512l288 320 96-96L160 512l224-224L288 192z</value> - </data> - <data name="color_mode" xml:space="preserve"> - <value>M0 128v768h768V128H0zM64 832V192h640L64 832z</value> + <value>M9.5 3l-1.5 1.5 3.5 3.5L8 11.5l1.5 1.5 4.5-5L9.5 3zM4.5 3L0 8l4.5 5 1.5-1.5L2.5 8l3.5-3.5L4.5 3z</value> </data> <data name="comment_discussion" xml:space="preserve"> - <value>M256 512V320H64c-64 0-64 64-64 64s0 258 0 320 64 64 64 64h64v192l194-192h192c0 0 62-4 62-64v-64c0 0-64 0-192 0S256 512 256 512zM832 128c0 0-384 0-448 0s-64 64-64 64 0 259.969 0 320c0 60 62 64 62 64h192l194 192V576h64c0 0 64-2 64-64V192C896 128 832 128 832 128z</value> + <value>M15 2H6c-0.55 0-1 0.45-1 1v2H1c-0.55 0-1 0.45-1 1v6c0 0.55 0.45 1 1 1h1v3l3-3h4c0.55 0 1-0.45 1-1V10h1l3 3V10h1c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1zM9 12H4.5l-1.5 1.5v-1.5H1V6h4v3c0 0.55 0.45 1 1 1h3v2z m6-3H13v1.5l-1.5-1.5H6V3h9v6z</value> </data> <data name="comment" xml:space="preserve"> - <value>M768 128H128C66 128 0 192 0 256v384c0 128 128 128 128 128h64v256l256-256c0 0 258 0 320 0s128-68 128-128V256C896 194 832 128 768 128z</value> + <value>M13 2H1c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1h2v3.5l3.5-3.5h6.5c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 9H6L4 13V11H1V3h12v8z</value> </data> <data name="credit_card" xml:space="preserve"> - <value>M128 704h128v-64H128V704zM320 704h128v-64H320V704zM384 512H128v64h256V512zM256 448h64l128-128h-64L256 448zM448 576h192v-64H448V576zM960 192H64c-64 0-64 64-64 64v512c0 0 0 64 64 64h896c64 0 64-64 64-64V256C1024 256 1024 192 960 192zM960 448v288c0 0 0 32-32 32H96c-32 0-32-32-32-32V448h64l128-128H64v-32c0 0 0-32 32-32h832c32 0 32 32 32 32v32H576L448 448H960z</value> + <value>M12 9H2v-1h10v1z m4-6v9c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h14c0.55 0 1 0.45 1 1z m-1 3H1v6h14V6z m0-3H1v1h14v-1zM6 10H2v1h4v-1z</value> </data> <data name="dash" xml:space="preserve"> - <value>M0 448v128h512V448H0z</value> + <value>M0 7v2h8V7H0z</value> </data> <data name="dashboard" xml:space="preserve"> - <value>M416 464.5c-61.562 0-111.5 49.938-111.5 111.5S354.438 687.5 416 687.5 527.5 637.562 527.5 576c0-8.5-1.125-16.75-3-24.688C606.125 456.375 732.5 308.34400000000005 800 224c23.125-28.875-2.312-56.188-32-32-85.188 69.375-232.312 194.688-326.906 275.594C433.031 465.719 424.625 464.5 416 464.5zM447.875 255.875c0-17.656-14.344-32-32-32s-32 14.344-32 32 14.344 32 32 32S447.875 273.53099999999995 447.875 255.875zM639.875 511.875c0 17.656 14.375 32 32 32s32-14.344 32-32-14.375-32-32-32S639.875 494.219 639.875 511.875zM287.875 255.875c-17.656 0-32 14.344-32 32s14.344 32 32 32 32-14.344 32-32S305.531 255.875 287.875 255.875zM223.875 383.875c0-17.656-14.344-32-32-32s-32 14.344-32 32 14.344 32 32 32S223.875 401.531 223.875 383.875zM127.875 511.875c0 17.656 14.344 32 32 32s32-14.344 32-32-14.344-32-32-32S127.875 494.219 127.875 511.875zM575.875 287.875c0-17.656-14.375-32-32-32s-32 14.344-32 32 14.375 32 32 32S575.875 305.53099999999995 575.875 287.875zM792.875 336.688l-68.75 89.938C731.625 453.812 736 482.375 736 512c0 176.75-143.312 320-320 320S96 688.75 96 512c0-176.688 143.312-320 320-320 65.875 0 127 19.969 177.875 54.094l79.25-60.625C602.375 129.59400000000005 513.25 96 416 96 186.25 96 0 282.25 0 512s186.25 416 416 416 416-186.25 416-416C832 449.281 817.75 390 792.875 336.688z</value> + <value>M8 5h-1v-1h1v1z m4 3h-1v1h1v-1zM5 5h-1v1h1v-1z m-1 3h-1v1h1v-1z m11-5.5l-0.5-0.5-6.5 5c-0.06-0.02-1 0-1 0-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1h1c0.55 0 1-0.45 1-1v-0.92l6-5.58zM13.41 6.59c0.19 0.61 0.3 1.25 0.3 1.91 0 3.42-2.78 6.2-6.2 6.2S1.3 11.92 1.3 8.5s2.78-6.2 6.2-6.2c1.2 0 2.31 0.34 3.27 0.94l0.94-0.94c-1.19-0.81-2.64-1.3-4.2-1.3C3.36 1 0 4.36 0 8.5s3.36 7.5 7.5 7.5 7.5-3.36 7.5-7.5c0-1.03-0.2-2.02-0.59-2.91l-1 1z</value> </data> <data name="database" xml:space="preserve"> - <value>M384 960C171.969 960 0 902.625 0 832c0-38.625 0-80.875 0-128 0-11.125 5.562-21.688 13.562-32C56.375 727.125 205.25 768 384 768s327.625-40.875 370.438-96c8 10.312 13.562 20.875 13.562 32 0 37.062 0 76.375 0 128C768 902.625 596 960 384 960zM384 704C171.969 704 0 646.625 0 576c0-38.656 0-80.844 0-128 0-6.781 2.562-13.375 6-19.906l0 0C7.938 424 10.5 419.969 13.562 416 56.375 471.094 205.25 512 384 512s327.625-40.906 370.438-96c3.062 3.969 5.625 8 7.562 12.094l0 0c3.438 6.531 6 13.125 6 19.906 0 37.062 0 76.344 0 128C768 646.625 596 704 384 704zM384 448C171.969 448 0 390.656 0 320c0-20.219 0-41.594 0-64 0-20.344 0-41.469 0-64C0 121.34400000000005 171.969 64 384 64c212 0 384 57.344 384 128 0 19.969 0 41.156 0 64 0 19.594 0 40.25 0 64C768 390.656 596 448 384 448zM384 128c-141.375 0-256 28.594-256 64s114.625 64 256 64 256-28.594 256-64S525.375 128 384 128z</value> + <value>M6 15C2.69 15 0 14.1 0 13c0-0.6 0-1.26 0-2 0-0.17 0.09-0.34 0.21-0.5C0.88 11.36 3.21 12 6 12s5.12-0.64 5.79-1.5c0.13 0.16 0.21 0.33 0.21 0.5 0 0.58 0 1.19 0 2C12 14.1 9.31 15 6 15zM6 11C2.69 11 0 10.1 0 9c0-0.6 0-1.26 0-2 0-0.11 0.04-0.21 0.09-0.31l0 0C0.12 6.63 0.16 6.56 0.21 6.5 0.88 7.36 3.21 8 6 8s5.12-0.64 5.79-1.5c0.05 0.06 0.09 0.13 0.12 0.19l0 0c0.05 0.1 0.09 0.21 0.09 0.31 0 0.58 0 1.19 0 2C12 10.1 9.31 11 6 11zM6 7C2.69 7 0 6.1 0 5c0-0.32 0-0.65 0-1 0-0.32 0-0.65 0-1C0 1.9 2.69 1 6 1c3.31 0 6 0.9 6 2 0 0.31 0 0.64 0 1 0 0.31 0 0.63 0 1C12 6.1 9.31 7 6 7zM6 2c-2.21 0-4 0.45-4 1s1.79 1 4 1 4-0.45 4-1S8.21 2 6 2z</value> + </data> + <data name="desktop_download" xml:space="preserve"> + <value>M4 6h3V0h2v6h3L8 10 4 6z m11-4H11v1h4v8H1V3h4v-1H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h5.34c-0.25 0.61-0.86 1.39-2.34 2h8c-1.48-0.61-2.09-1.39-2.34-2h5.34c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z</value> </data> <data name="device_camera_video" xml:space="preserve"> - <value>M576 192c-35.347 0-64 28.653-64 64s28.653 64 64 64 64-28.653 64-64S611.347 192 576 192zM896 384L768 512v-64c0-30.625-21.515-56.21-50.25-62.503C748.958 351.354 768 305.903 768 256.00199999999995 768 149.962 682.039 64 576 64c-101.123 0-183.986 78.178-191.45 177.393C350.516 210.69399999999996 305.442 192 256 192c-106.038 0-192 85.962-192 192.002C64 490.039 149.962 576 256 576h-64v128h64v128c0 35.347 28.653 64 64 64h384c35.347 0 64-28.653 64-64v-64l128 128h64V384H896zM256 320c-35.347 0-64 28.653-64 64s28.653 64 64 64v64c-70.692 0-128-57.308-128-127.999C128 313.308 185.308 256 256 256s128 57.307 128 128h-64C320 348.653 291.347 320 256 320zM576 704H448V576h128V704zM704 594.787c-33.526-33.547-70.276-70.317-73.373-73.414C624.837 515.582 616.837 512 608 512H416c-17.674 0-32 14.326-32 32v192c0 8.329 3.183 15.915 8.396 21.607 0.53 0.58 39.123 39.164 74.409 74.393H352c-17.674 0-32-14.326-32-32V480c0-17.674 14.326-32 32-32h320c17.674 0 32 14.326 32 32V594.787zM576 384c-70.692 0-128-57.308-128-127.999C448 185.308 505.308 128 576 128s128 57.308 128 128.001C704 326.692 646.692 384 576 384zM896 704l-64-64 0.082-128.084L896 447.998V704z</value> + <value>M15.2 3.09L10 6.72V4c0-0.55-0.45-1-1-1H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1V10.28l5.2 3.63c0.33 0.23 0.8 0 0.8-0.41V3.5c0-0.41-0.47-0.64-0.8-0.41z</value> </data> <data name="device_camera" xml:space="preserve"> - <value>M512 384.001c-70.691 0-127.999 57.308-127.999 127.999S441.309 639.999 512 639.999c5.713 0 11.337-0.38 16.852-1.105-46.344-7.058-81.851-47.079-81.851-95.394 0-53.295 43.204-96.499 96.499-96.499 48.314 0 88.336 35.507 95.394 81.851 0.726-5.515 1.105-11.139 1.105-16.852C639.999 441.309 582.691 384.001 512 384.001zM896 256H767.999L640 128H384L255.999 256H128c-35.348 0-64 28.652-64 64v448c0 35.347 28.652 64 64 64h768c35.347 0 64-28.653 64-64V320C960 284.65200000000004 931.347 256 896 256zM416 192h192l64 64H352L416 192zM160.143 768C142.391 768 128 753.609 128 735.857V448h64v-64h-64v-31.857C128 334.391 142.391 320 160.143 320h182.526c-3.98 3.518-7.881 7.174-11.688 10.98-99.974 99.975-99.974 262.064 0 362.039l74.98 74.98H160.143zM512 703.999c-106.038 0-191.999-85.961-191.999-191.999S405.962 320.001 512 320.001 703.999 405.962 703.999 512 618.038 703.999 512 703.999zM832 480L681.327 320H832V480z</value> + <value>M15 3H7c0-0.55-0.45-1-1-1H2c-0.55 0-1 0.45-1 1-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h14c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1zM6 5H2v-1h4v1z m4.5 7c-1.94 0-3.5-1.56-3.5-3.5s1.56-3.5 3.5-3.5 3.5 1.56 3.5 3.5-1.56 3.5-3.5 3.5z m2.5-3.5c0 1.38-1.13 2.5-2.5 2.5s-2.5-1.13-2.5-2.5 1.13-2.5 2.5-2.5 2.5 1.13 2.5 2.5z</value> </data> <data name="device_desktop" xml:space="preserve"> - <value>M960 64c-64 0-896 0-896 0C0 64 0 128 0 128v576c0 0 0 64 64 64h320c0 0-192 64-192 128s64 64 64 64h512c0 0 64 0 64-64S640 768 640 768h320c64 0 64-64 64-64V128C1024 128 1024 64 960 64zM960 704H64V128h896V704zM896 192H704c-384 64-576 384-576 384v64h768V192z</value> + <value>M15 2H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h5.34c-0.25 0.61-0.86 1.39-2.34 2h8c-1.48-0.61-2.09-1.39-2.34-2h5.34c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 9H1V3h14v8z</value> </data> <data name="device_mobile" xml:space="preserve"> - <value>M576 0H64C28.688 0 0 28.687999999999988 0 64v896c0 35.375 28.688 64 64 64h512c35.375 0 64-28.625 64-64V64C640 28.687999999999988 611.375 0 576 0zM288 64h64c17.625 0 32 14.344 32 32s-14.375 32-32 32h-64c-17.656 0-32-14.344-32-32S270.344 64 288 64zM352 960h-64c-17.656 0-32-14.375-32-32s14.344-32 32-32h64c17.625 0 32 14.375 32 32S369.625 960 352 960zM576 832H64V192h512V832z</value> + <value>M9 0H1C0.45 0 0 0.45 0 1v14c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1zM5 15.3c-0.72 0-1.3-0.58-1.3-1.3s0.58-1.3 1.3-1.3 1.3 0.58 1.3 1.3-0.58 1.3-1.3 1.3z m4-3.3H1V2h8v10z</value> </data> <data name="diff_added" xml:space="preserve"> - <value>M512 320H384v128H256v128h128v128h128V576h128V448H512V320zM832 64H64C0 64 0 128 0 128v768c0 0 0 64 64 64 0 0 704 0 768 0s64-64 64-64V128C896 128 896 64 832 64zM768 800c0 32-32 32-32 32H160c0 0-32 0-32-32V224c0-32 32-32 32-32h576c0 0 32 0 32 32C768 224 768 773.25 768 800z</value> + <value>M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM6 9H3V7h3V4h2v3h3v2H8v3H6V9z</value> </data> <data name="diff_ignored" xml:space="preserve"> - <value>M832 64H64C0 64 0 128 0 128v768c0 0 0 64 64 64 0 0 704 0 768 0s64-64 64-64V128C896 128 896 64 832 64zM768 800c0 32-32 32-32 32H160c0 0-32 0-32-32V224c0-32 32-32 32-32h576c0 0 32 0 32 32C768 224 768 773.25 768 800zM256 606v98h98l286-286v-98h-98L256 606z</value> + <value>M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4.5 12h-1.5v-1.5l6.5-6.5h1.5v1.5L4.5 12z</value> </data> <data name="diff_modified" xml:space="preserve"> - <value>M832 64H64C0 64 0 128 0 128v768c0 0 0 64 64 64 0 0 704 0 768 0s64-64 64-64V128C896 128 896 64 832 64zM768 800c0 32-32 32-32 32H160c0 0-32 0-32-32V224c0-32 32-32 32-32h576c0 0 32 0 32 32C768 224 768 773.25 768 800zM448 384c-70.688 0-128 57.312-128 128s57.312 128 128 128 128-57.312 128-128S518.688 384 448 384z</value> + <value>M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM4 8c0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3-3-1.34-3-3z</value> </data> <data name="diff_removed" xml:space="preserve"> - <value>M256 448v128h384V448H256zM832 64H64C0 64 0 128 0 128v768c0 0 0 64 64 64 0 0 704 0 768 0s64-64 64-64V128C896 128 896 64 832 64zM768 800c0 32-32 32-32 32H160c0 0-32 0-32-32V224c0-32 32-32 32-32h576c0 0 32 0 32 32C768 224 768 773.25 768 800z</value> + <value>M13 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1z m0 13H1V2h12v12zM11 9H3V7h8v2z</value> </data> <data name="diff_renamed" xml:space="preserve"> - <value>M832 64H64C0 64 0 128 0 128v768c0 0 0 64 64 64 0 0 704 0 768 0s64-64 64-64V128C896 128 896 64 832 64zM768 800c0 32-32 32-32 32H160c0 0-32 0-32-32V224c0-32 32-32 32-32h576c0 0 32 0 32 32C768 224 768 773.25 768 800zM448 448H256v128h192v128l256-192L448 320V448z</value> + <value>M6 9H3V7h3V4l5 4-5 4V9z m8-7v12c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v12h12V2z</value> </data> <data name="diff" xml:space="preserve"> - <value>M448 256H320v128H192v128h128v128h128V512h128V384H448V256zM192 896h384V768H192V896zM640 0H128v64h480l224 224v608h64V256L640 0zM0 128v896h768V320L576 128H0zM704 960H64V192h480l160 160V960z</value> + <value>M6 7h2v1H6v2h-1V8H3v-1h2V5h1v2zM3 13h5v-1H3v1z m4.5-11l3.5 3.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h6.5z m2.5 4L7 3H1v12h9V6zM8.5 0S3 0 3 0v1h5l4 4v8h1V4.5L8.5 0z</value> </data> <data name="ellipsis" xml:space="preserve"> - <value>M640 320c-102.75 0-512 0-512 0S0 320 0 448s0 128 0 128 0 128 128 128 512 0 512 0 128-4 128-128c0-124.031 0-128 0-128S764 320 640 320zM256 576H128V448h128V576zM448 576H320V448h128V576zM640 576H512V448h128V576z</value> + <value>M11 5H1c-0.55 0-1 0.45-1 1v4c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V6c0-0.55-0.45-1-1-1zM4 9H2V7h2v2z m3 0H5V7h2v2z m3 0H8V7h2v2z</value> </data> <data name="eye" xml:space="preserve"> - <value>M512 128C128 128 0 512 0 512s122 320 512 320 512-320 512-320S896 128 512 128zM512 768c-320 0-384-256-384-256s66-256 384-256 384 256 384 256S832 768 512 768zM512 320c-19.531 0-37.938 3.781-55.688 9.219C489.156 344.438 512 377.469 512 416c0 53-43 96-96 96-38.531 0-71.562-22.844-86.781-55.688C323.781 474.062 320 492.469 320 512c0 106.062 86 192 192 192 106.062 0 192-85.938 192-192C704 406 618.062 320 512 320z</value> + <value>M8.06 2C3 2 0 8 0 8s3 6 8.06 6c4.94 0 7.94-6 7.94-6S13 2 8.06 2z m-0.06 10c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4z m2-4c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z</value> </data> <data name="file_binary" xml:space="preserve"> - <value>M0 960V64h576l192 192v704H0zM704 320L512 128H64v768h640V320zM320 512H128V256h192V512zM256 320h-64v128h64V320zM256 768h64v64H128v-64h64V640h-64v-64h128V768zM512 448h64v64H384v-64h64V320h-64v-64h128V448zM576 832H384V576h192V832zM512 640h-64v128h64V640z</value> + <value>M4 12h1v1H2v-1h1V10h-1v-1h2v3z m8-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z m-3-1H6v1h1v2h-1v1h3v-1h-1V4z m-6 0h3v4H2V4z m1 3h1V5h-1v2z m3 2h3v4H6V9z m1 3h1V10h-1v2z</value> </data> <data name="file_code" xml:space="preserve"> - <value>M288 384L128 544l160 160 64-64-96-96 96-96L288 384zM416 448l96 96-96 96 64 64 160-160L480 384 416 448zM576 64H0v896h768V256L576 64zM704 896H64V128h448l192 192V896z</value> + <value>M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1z m2.5 13H1V2h7l3 3v9zM5 6.98l-1.5 1.52 1.5 1.5-0.5 1-2.5-2.5 2.5-2.5 0.5 0.98z m2.5-0.98l2.5 2.5-2.5 2.5-0.5-0.98 1.5-1.52-1.5-1.5 0.5-1z</value> </data> <data name="file_directory" xml:space="preserve"> - <value>M832 192c0 0-320 0-352 0s-32-32-32-32 0-21.25 0-32c0-64-64-64-64-64s-256 0-320 0S0 128 0 128v704h896c0 0 0-510 0-576S832 192 832 192zM384 192H64v-32c0 0 0-32 32-32h256c32 0 32 32 32 32V192z</value> + <value>M13 4H7v-1c0-0.66-0.31-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V5c0-0.55-0.45-1-1-1z m-7 0H1v-1h5v1z</value> </data> <data name="file_media" xml:space="preserve"> - <value>M576 64H0v896h768V256L576 64zM704 896H64V128h448l192 192V896zM128 256v512h128c0-70.625 57.344-128 128-128-70.656 0-128-57.375-128-128 0-70.656 57.344-128 128-128 70.625 0 128 57.344 128 128 0 70.625-57.375 128-128 128 70.625 0 128 57.375 128 128h128V384L512 256H128z</value> + <value>M6 5h2v2H6V5z m6-0.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v11l3-5 2 4 2-2 3 3V5z</value> </data> <data name="file_pdf" xml:space="preserve"> - <value>M576 64H0v896h768V256L576 64zM64 128h255.812c-13.188 4.094-27.281 15.031-34.625 42.875-13.25 49.406-7.031 130.75 15.625 209.344C276.688 461.438 178.188 656.875 171.531 668.5c-15.625 4.875-65.344 23.625-107.531 59.812V128zM347.125 435.469c57.625 149.781 95 149.531 135.188 167.594C398.344 616 334.219 625.25 249.781 662.5 246.094 668.938 326.281 516.594 347.125 435.469zM704 896H65.844 64v-0.375c0.781 0.062 1.094 0.375 1.844 0.375 33.812 0 84.75-21 180.562-182.375 38.188-15.438 72.062-26.875 78.469-28.938 58.812-14.875 125-26.625 187.562-33.375C566.875 678.5 639.125 697 680.25 699.625c9.625 0.5 16.062-1.188 23.75-2V896zM704 585.375c-23.688-14.688-54-25-89.125-25-24.25 0-50.625 1.375-78.688 4.375-26.938-13-92.562-32.719-147.188-190.219 17.094-103.625 12.719-173.562 12.719-173.562 6.781-52.938-23.344-72.844-51.625-72.844 0 0-0.279-0.125-0.344-0.125H512l192 192V585.375z</value> + <value>M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1zM1 2h4c-0.11 0.03-0.2 0.09-0.31 0.2-0.09 0.09-0.17 0.25-0.23 0.47-0.11 0.39-0.14 0.89-0.09 1.47s0.17 1.17 0.34 1.8c-0.23 0.73-0.61 1.61-1.11 2.66s-0.8 1.66-0.91 1.84c-0.14 0.05-0.36 0.14-0.69 0.3-0.33 0.14-0.66 0.36-1 0.64V2z m4.42 4.8c0.45 1.13 0.84 1.83 1.17 2.09s0.64 0.45 0.94 0.53c-0.64 0.09-1.23 0.2-1.81 0.33-0.56 0.13-1.17 0.33-1.81 0.59 0.02-0.02 0.22-0.44 0.61-1.25s0.7-1.58 0.91-2.3z m5.58 7.2H1.5c-0.06 0-0.13-0.02-0.17-0.03 0.2-0.06 0.45-0.2 0.73-0.44 0.45-0.38 1.05-1.16 1.78-2.38 0.31-0.13 0.58-0.23 0.81-0.31l0.42-0.14c0.45-0.13 0.94-0.23 1.44-0.33 0.5-0.08 1-0.16 1.48-0.2 0.45 0.22 0.91 0.39 1.39 0.53 0.48 0.13 0.91 0.2 1.23 0.23 0.14 0 0.27-0.02 0.38-0.03v3.09z m0-4.86c-0.19-0.11-0.41-0.2-0.64-0.28-0.23-0.06-0.48-0.09-0.75-0.11-0.39 0-0.8 0.03-1.23 0.08-0.23-0.06-0.56-0.28-0.98-0.64s-0.86-1.14-1.31-2.33c0.13-0.83 0.19-1.48 0.2-1.97s0.02-0.73 0-0.75c0.05-0.41-0.03-0.7-0.2-0.88s-0.38-0.27-0.61-0.27h2.53l3 3v4.14z</value> </data> <data name="file_submodule" xml:space="preserve"> - <value>M832 512H640c0 0 2-64-64-64s-64 0-128 0-64 64-64 64v320h512c0 0 0-190 0-256S832 512 832 512zM576 576H448v-32c0 0 0-32 32-32h64c32 0 32 32 32 32V576zM832 256c0 0-320 0-352 0s-32-32-32-32v-32c0 0 0-64-64-64H64c-64 0-64 64-64 64v640h320V448c0-62 64-64 64-64s190 0 256 0 64 64 64 64h192V320C896 254 832 256 832 256zM384 256H64v-32c0 0 0-32 32-32h256c32 0 32 32 32 32V256z</value> + <value>M10 7H4v7h9c0.55 0 1-0.45 1-1V8H10v-1z m-1 2H5v-1h4v1z m4-5H7v-1c0-0.66-0.31-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h2V7c0-0.55 0.45-1 1-1h6c0.55 0 1 0.45 1 1h3V5c0-0.55-0.45-1-1-1z m-7 0H1v-1h5v1z</value> </data> <data name="file_symlink_directory" xml:space="preserve"> - <value>M832 192c0 0-320 0-352 0s-32-32-32-32 0-21.25 0-32c0-64-64-64-64-64s-256 0-320 0S0 128 0 128v704h896c0 0 0-510 0-576S832 192 832 192zM64 160c0 0 0-32 32-32h256c32 0 32 32 32 32v32H64V160zM448 704V576c0 0-192 5.125-256 192 0-314.875 256-320 256-320V320l256 192L448 704z</value> + <value>M13 4H7v-1c0-0.66-0.31-1-1-1H1c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V5c0-0.55-0.45-1-1-1zM1 3h5v1H1v-1z m6 9V10c-0.98-0.02-1.84 0.22-2.55 0.7s-1.19 1.25-1.45 2.3c0.02-1.64 0.39-2.88 1.13-3.73 0.73-0.84 1.69-1.27 2.88-1.27V6l4 3-4 3z</value> </data> <data name="file_symlink_file" xml:space="preserve"> - <value>M576 64H0v896h768V256L576 64zM704 896H64V128h448l192 192V896zM384 448c0 0-256 0-256 314.875C192 576 384 576 384 576v128l256-192L384 320V448z</value> + <value>M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1z m2.5 13H1V2h7l3 3v9zM6 4.5l4 3-4 3V8.5c-0.98-0.02-1.84 0.22-2.55 0.7s-1.19 1.25-1.45 2.3c0.02-1.64 0.39-2.88 1.13-3.73 0.73-0.84 1.69-1.27 2.88-1.27V4.5z</value> </data> <data name="file_text" xml:space="preserve"> - <value>M448 256H128v64h320V256zM576 64H0v896h768V256L576 64zM704 896H64V128h448l192 192V896zM128 768h512v-64H128V768zM128 640h512v-64H128V640zM128 512h512v-64H128V512z</value> + <value>M6 5H2v-1h4v1zM2 8h7v-1H2v1z m0 2h7v-1H2v1z m0 2h7v-1H2v1z m10-7.5v9.5c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V2c0-0.55 0.45-1 1-1h7.5l3.5 3.5z m-1 0.5L8 2H1v12h10V5z</value> </data> <data name="file_zip" xml:space="preserve"> - <value>M320 576v-64h-64v64H320zM320 448v-64h-64v64H320zM320 320v-64h-64v64H320zM192 384h64v-64h-64V384zM576 64H0v896h768V256L576 64zM704 896H64V128h192v64h64v-64h192l192 192V896zM192 256h64v-64h-64V256zM192 512h64v-64h-64V512zM192 640l-64 64v128h256V704l-64-64h-64v-64h-64V640zM320 704v64H192v-64H320z</value> + <value>M8.5 1H1C0.45 1 0 1.45 0 2v12c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V4.5L8.5 1z m2.5 13H1V2h3v1h1v-1h3l3 3v9zM5 4v-1h1v1h-1z m-1 0h1v1h-1v-1z m1 2v-1h1v1h-1z m-1 0h1v1h-1v-1z m1 2v-1h1v1h-1z m-1 1.28c-0.59 0.34-1 0.98-1 1.72v1h4v-1c0-1.11-0.89-2-2-2v-1h-1v1.28z m2 0.72v1H4v-1h2z</value> </data> <data name="flame" xml:space="preserve"> - <value>M433 45c105 282-127 292-254.918 471.315C88.999 641.192 74 916 395 988c-135-71-164-277-18-406-37.695 125.105 31.57 205.291 118.652 175.852C581 729 636.452 790.294 635 860c-1 48-16 85-69 112 0 0 293-56 293-342 0-174-155-198.125-77-344-93 8-125.111 68.779-116 169 6 66-63 111.333-114 81.02-41.425-24.622-40-73.145-4-109.02C624.648 350.619 655 176 433 45z</value> + <value>M5.05 0.31c0.81 2.17 0.41 3.38-0.52 4.31-0.98 1.05-2.55 1.83-3.63 3.36-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-0.3-6.61-0.61 2.03 0.53 3.33 1.94 2.86 1.39-0.47 2.3 0.53 2.27 1.67-0.02 0.78-0.31 1.44-1.13 1.81 3.42-0.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52 0.13-2.03 1.13-1.89 2.75 0.09 1.08-1.02 1.8-1.86 1.33-0.67-0.41-0.66-1.19-0.06-1.78 1.25-1.23 1.75-4.09-1.88-6.22l-0.02-0.02z</value> </data> <data name="fold" xml:space="preserve"> - <value>M896 256H672l-64 64h192L672 448H224L96 320h192l-64-64H0v63.999L160 480 0 640v64h224l64-64H96l128-128h448l128 128H608l64 64h224v-64L736 480l160-160.001V256zM640 192H512V0H384v192H256l192 192L640 192zM256 768h128v192h128V768h128L448 576 256 768z</value> + <value>M7 9l3 3H8v3H6V12H4l3-3z m3-6H8V0H6v3H4l3 3 3-3z m4 2c0-0.55-0.45-1-1-1H10.5l-1 1h3L10.5 7H3.5L1.5 5h3l-1-1H1c-0.55 0-1 0.45-1 1l2.5 2.5L0 10c0 0.55 0.45 1 1 1h2.5l1-1H1.5l2-2h7l2 2H9.5l1 1h2.5c0.55 0 1-0.45 1-1L11.5 7.5l2.5-2.5z</value> </data> <data name="gear" xml:space="preserve"> - <value>M447.938 350C358.531 350 286 422.531 286 512c0 89.375 72.531 162.062 161.938 162.062 89.438 0 161.438-72.688 161.438-162.062C609.375 422.531 537.375 350 447.938 350zM772.625 605.062l-29.188 70.312 52.062 102.25 6.875 13.5-72.188 72.188L611.75 807.375l-70.312 28.875L505.75 945.5l-4.562 14.5H399.156L355 836.688l-70.312-29-102.404 51.938-13.5 6.75-72.156-72.125 55.875-118.5-28.969-70.25L14.469 569.875 0 565.188V463.219L123.406 419l28.969-70.188-51.906-102.469-6.844-13.438 72.062-72.062 118.594 55.844 70.219-29.031 35.656-109.188L394.75 64h102l44.188 123.469 70.125 29.031L713.5 164.53099999999995l13.625-6.844 72.125 72.062-55.875 118.406L772.25 418.5l109.375 35.656L896 458.75v101.938L772.625 605.062z</value> + <value>M14 8.77V7.17l-1.94-0.64-0.45-1.09 0.88-1.84-1.13-1.13-1.81 0.91-1.09-0.45-0.69-1.92H6.17l-0.63 1.94-1.11 0.45-1.84-0.88-1.13 1.13 0.91 1.81-0.45 1.09L0 7.23v1.59l1.94 0.64 0.45 1.09-0.88 1.84 1.13 1.13 1.81-0.91 1.09 0.45 0.69 1.92h1.59l0.63-1.94 1.11-0.45 1.84 0.88 1.13-1.13-0.92-1.81 0.47-1.09 1.92-0.69zM7 11c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z</value> </data> <data name="gift" xml:space="preserve"> - <value>M448 960h320V640H448V960zM64 960h320V640H64V960zM447.75 376.188c31.469-3.5 66.875-7.406 87.375-9.719C619 357.125 694.5 281.59400000000005 703.812 197.75c9.312-83.75-51-144.125-134.688-134.719C503.688 70.34400000000005 443.844 118 416 178.375 388.156 118 328.312 70.34400000000005 262.906 62.96900000000005 179.188 53.625 118.781 114 128.188 197.75c9.344 83.844 84.875 159.312 168.656 168.719 20.531 2.312 55.938 6.281 87.406 9.719C383.75 380.406 384 384 384 384h64C448 384 448.25 380.406 447.75 376.188zM555.375 140.688c45.25-5.062 78 27.562 72.875 72.875-5 45.312-45.875 86.156-91.125 91.219-45.375 5.031-78-27.594-72.938-72.906C469.249 186.56399999999996 510.125 145.71900000000005 555.375 140.688zM294.906 304.78099999999995c-45.25-5.062-86.062-45.906-91.125-91.219-5.063-45.313 27.594-77.938 72.812-72.875 45.312 5.031 86.156 45.875 91.222 91.188C372.875 277.188 340.219 309.812 294.906 304.78099999999995zM448 384v192h384V384H448zM0 576h384V384H0V576z</value> + <value>M13 4h-1.38c0.19-0.33 0.33-0.67 0.36-0.91 0.06-0.67-0.11-1.22-0.52-1.61-0.36-0.38-0.81-0.48-1.36-0.48-0.05 0-0.08 0-0.11 0-0.53 0.02-1.11 0.25-1.53 0.58s-0.73 0.72-0.97 1.2c-0.23-0.48-0.55-0.88-0.97-1.2s-1-0.58-1.53-0.58c-0.02 0-0.03 0-0.03 0-0.56 0-1.06 0.09-1.44 0.48-0.41 0.39-0.58 0.94-0.52 1.61 0.03 0.23 0.17 0.58 0.36 0.91h-1.38c-0.55 0-1 0.45-1 1v3h1v5c0 0.55 0.45 1 1 1h9c0.55 0 1-0.45 1-1V8h1V5c0-0.55-0.45-1-1-1z m-4.78-0.88c0.17-0.36 0.42-0.67 0.75-0.92 0.3-0.23 0.72-0.39 1.05-0.41h0.09c0.45 0 0.66 0.11 0.8 0.25s0.33 0.39 0.3 0.95c-0.05 0.19-0.25 0.61-0.5 1H7.81l0.41-0.88z m-4.13-1.08c0.13-0.13 0.31-0.25 0.91-0.25 0.31 0 0.72 0.17 1.03 0.41 0.33 0.25 0.58 0.55 0.75 0.92l0.42 0.88H4.3c-0.25-0.39-0.45-0.81-0.5-1-0.03-0.56 0.16-0.81 0.3-0.95z m2.91 10.95H3V8h4v5z m0-6H2V5h5v2z m5 6H8V8h4v5z m1-6H8V5h5v2z</value> </data> <data name="gist_secret" xml:space="preserve"> - <value>M193 704l128 192h-256l-65-256 257-64-64 128z m448-128l64 128-128 192h256l64-256-256-64z m-84 0h-216l44 102-64 218h256l-64-218 44-102z m84-192h-384l-128 64h640l-128-64z m-64-256l-128 64-128-64-64 192h384l-64-192z</value> + <value>M8 10.5l1 3.5H5l1-3.5-0.75-1.5h3.5l-0.75 1.5z m2-4.5H4l-2 1h10l-2-1z m-1-4l-2 1-2-1-1 3h6l-1-3z m4.03 7.75l-3.03-0.75 1 2-2 3h3.22c0.45 0 0.86-0.31 0.97-0.75l0.56-2.28c0.14-0.53-0.19-1.08-0.72-1.22z m-9.03-0.75L0.97 9.75c-0.53 0.14-0.86 0.69-0.72 1.22l0.56 2.28c0.11 0.44 0.52 0.75 0.97 0.75h3.22L3 11l1-2z</value> </data> <data name="gist" xml:space="preserve"> - <value>M416 384l96 96-96 96 64 64 160-160-160-160-64 64z m-416-320v832h768v-832h-768z m704 768h-640v-704h640v704z m-352-256l-96-96 96-96-64-64-160 160 160 160 64-64z</value> + <value>M7.5 5l2.5 2.5-2.5 2.5-0.75-0.75 1.75-1.75-1.75-1.75 0.75-0.75z m-3 0L2 7.5l2.5 2.5 0.75-0.75-1.75-1.75 1.75-1.75-0.75-0.75zM0 13V2c0-0.55 0.45-1 1-1h10c0.55 0 1 0.45 1 1v11c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1z m1 0h10V2H1v11z</value> </data> <data name="git_branch" xml:space="preserve"> - <value>M512 192c-70.625 0-128 57.344-128 128 0 47.219 25.875 88.062 64 110.281V448c0 0 0 128-128 128-53.062 0-94.656 11.375-128 28.812V302.28099999999995c38.156-22.219 64-63.062 64-110.281 0-70.656-57.344-128-128-128S0 121.34400000000005 0 192c0 47.219 25.844 88.062 64 110.281V721.75C25.844 743.938 0 784.75 0 832c0 70.625 57.344 128 128 128s128-57.375 128-128c0-33.5-13.188-63.75-34.25-86.625C240.375 722.5 270.656 704 320 704c254 0 256-256 256-256v-17.719c38.125-22.219 64-63.062 64-110.281C640 249.34400000000005 582.625 192 512 192zM128 128c35.406 0 64 28.594 64 64s-28.594 64-64 64-64-28.594-64-64S92.594 128 128 128zM128 896c-35.406 0-64-28.625-64-64 0-35.312 28.594-64 64-64s64 28.688 64 64C192 867.375 163.406 896 128 896zM512 384c-35.375 0-64-28.594-64-64s28.625-64 64-64 64 28.594 64 64S547.375 384 512 384z</value> + <value>M10 5c0-1.11-0.89-2-2-2s-2 0.89-2 2c0 0.73 0.41 1.38 1 1.72v0.3c-0.02 0.52-0.23 0.98-0.63 1.38s-0.86 0.61-1.38 0.63c-0.83 0.02-1.48 0.16-2 0.45V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v6.56C0.41 11.63 0 12.27 0 13c0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.53-0.2-1-0.53-1.36 0.09-0.06 0.48-0.41 0.59-0.47 0.25-0.11 0.56-0.17 0.94-0.17 1.05-0.05 1.95-0.45 2.75-1.25s1.2-1.98 1.25-3.02h-0.02c0.61-0.36 1.02-1 1.02-1.73zM2 1.8c0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2z m0 12.41c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m6-8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z</value> </data> <data name="git_commit" xml:space="preserve"> - <value>M694.875 448C666.375 337.781 567.125 256 448 256c-119.094 0-218.375 81.781-246.906 192H0v128h201.094C229.625 686.25 328.906 768 448 768c119.125 0 218.375-81.75 246.875-192H896V448H694.875zM448 640c-70.656 0-128-57.375-128-128 0-70.656 57.344-128 128-128 70.625 0 128 57.344 128 128C576 582.625 518.625 640 448 640z</value> + <value>M10.86 7c-0.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H0v2h3.14c0.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3h3.14V7H10.86zM7 10.2c-1.22 0-2.2-0.98-2.2-2.2s0.98-2.2 2.2-2.2 2.2 0.98 2.2 2.2-0.98 2.2-2.2 2.2z</value> </data> <data name="git_compare" xml:space="preserve"> - <value>M832 721.75V320c0-192.5-192-192-192-192h-64V0L384 192l192 192V256c0 0 26.688 0 64 0 56.438 0 64 64 64 64v401.75c-38.125 22.188-64 62.875-64 110.25 0 70.625 57.375 128 128 128s128-57.375 128-128C896 784.75 870.125 743.938 832 721.75zM768 896c-35.312 0-64-28.625-64-64 0-35.312 28.688-64 64-64 35.375 0 64 28.688 64 64C832 867.375 803.375 896 768 896zM64 315.59400000000005v401.719c0 192.5 192 192 192 192h64v128l192-192-192-192v128c0 0-26.688 0-64 0-56.438 0-64-64-64-64V315.59400000000005c38.156-22.219 64-62.906 64-110.281 0-70.656-57.344-128-128-128s-128 57.344-128 128C0 252.59400000000005 25.844 293.375 64 315.59400000000005zM128 272c-35.312 0-64-28.594-64-64 0-35.312 28.688-64 64-64 35.406 0 64 28.688 64 64C192 243.40599999999995 163.406 272 128 272z</value> + <value>M5 12h-1c-0.27-0.02-0.48-0.11-0.69-0.31s-0.3-0.42-0.31-0.69V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.73 0 6.28 0 6.28 0.03 0.78 0.34 1.47 0.94 2.06s1.28 0.91 2.06 0.94c0 0 1.02 0 1 0v2l3-3-3-3v2zM2 1.8c0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2z m11 9.48c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L6 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z</value> </data> <data name="git_merge" xml:space="preserve"> - <value>M640 448c-47.625 0-88.625 26.312-110.625 64.906C523.625 512.5 518 512 512 512c-131.062 0-255.438-99.844-300.812-223.438C238.469 265.09400000000005 256 230.71900000000005 256 192c0-70.656-57.344-128-128-128S0 121.34400000000005 0 192c0 47.219 25.844 88.062 64 110.281V721.75C25.844 743.938 0 784.75 0 832c0 70.625 57.344 128 128 128s128-57.375 128-128c0-47.25-25.844-88.062-64-110.25V491.469C276.156 580.5 392.375 640 512 640c6.375 0 11.625-0.438 17.375-0.625C551.5 677.812 592.5 704 640 704c70.625 0 128-57.375 128-128C768 505.344 710.625 448 640 448zM128 896c-35.312 0-64-28.625-64-64 0-35.312 28.688-64 64-64 35.406 0 64 28.688 64 64C192 867.375 163.406 896 128 896zM128 256c-35.312 0-64-28.594-64-64s28.688-64 64-64c35.406 0 64 28.594 64 64S163.406 256 128 256zM640 640c-35.312 0-64-28.625-64-64 0-35.406 28.688-64 64-64 35.375 0 64 28.594 64 64C704 611.375 675.375 640 640 640z</value> + <value>M10 7c-0.73 0-1.38 0.41-1.73 1.02v-0.02c-1.05-0.02-2.27-0.36-3.13-1.02-0.75-0.58-1.5-1.61-1.89-2.44 0.45-0.36 0.75-0.92 0.75-1.55 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v6.56C0.41 11.63 0 12.27 0 13c0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V7.67c0.67 0.7 1.44 1.27 2.3 1.69s2.03 0.63 2.97 0.64v-0.02c0.36 0.61 1 1.02 1.73 1.02 1.11 0 2-0.89 2-2s-0.89-2-2-2zM3.2 13c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m8 6c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z</value> </data> <data name="git_pull_request" xml:space="preserve"> - <value>M128 64C57.344 64 0 121.34400000000005 0 192c0 47.219 25.906 88.062 64 110.281V721.75C25.906 743.938 0 784.75 0 832c0 70.625 57.344 128 128 128s128-57.375 128-128c0-47.25-25.844-88.062-64-110.25V302.28099999999995c38.156-22.219 64-63.062 64-110.281C256 121.34400000000005 198.656 64 128 64zM128 896c-35.312 0-64-28.625-64-64 0-35.312 28.688-64 64-64 35.406 0 64 28.688 64 64C192 867.375 163.406 896 128 896zM128 256c-35.312 0-64-28.594-64-64s28.688-64 64-64c35.406 0 64 28.594 64 64S163.406 256 128 256zM704 721.75V320c0-192.5-192-192-192-192h-64V0L256 192l192 192V256c0 0 26.688 0 64 0 56.438 0 64 64 64 64v401.75c-38.125 22.188-64 62.938-64 110.25 0 70.625 57.375 128 128 128s128-57.375 128-128C768 784.75 742.125 743.938 704 721.75zM640 896c-35.312 0-64-28.625-64-64 0-35.312 28.688-64 64-64 35.375 0 64 28.688 64 64C704 867.375 675.375 896 640 896z</value> + <value>M11 11.28c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L4 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2zM4 3c0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.55 0 5.56 0 6.56-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V4.72c0.59-0.34 1-0.98 1-1.72z m-0.8 10c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z</value> </data> <data name="globe" xml:space="preserve"> - <value>M512 128c-212.077 0-384 171.923-384 384s171.923 384 384 384c25.953 0 51.303-2.582 75.812-7.49-9.879-4.725-10.957-40.174-1.188-60.385 10.875-22.5 45-79.5 11.25-98.625s-24.375-27.75-45-49.875-12.19-25.451-13.5-31.125c-4.5-19.5 19.875-48.75 21-51.75s1.125-14.25 0.75-17.625S545.75 566.75 542 566.375s-5.625 6-10.875 6.375-28.125-13.875-33-17.625-7.125-12.75-13.875-19.5-7.5-1.5-18-5.625-44.25-16.5-70.125-27-28.125-25.219-28.5-35.625-15.75-25.5-22.961-36.375c-7.209-10.875-8.539-25.875-11.164-22.5s13.5 42.75 10.875 43.875-8.25-10.875-15.75-20.625 7.875-4.5-16.125-51.75 7.5-71.344 9-96 20.25 9 10.5-6.75 0.75-48.75-6.75-60.75S275 230 275 230c1.125-11.625 37.5-31.5 63.75-49.875s42.281-4.125 63.375 2.625 22.5 4.5 15.375-2.25 3-10.125 19.5-7.5 21 22.5 46.125 20.625 2.625 4.875 6 11.25-3.75 5.625-20.25 16.875S469.25 233 498.5 254.375s20.25-14.25 17.25-30S537.125 221 537.125 221c18 12 14.674 0.66 27.799 4.785S613.625 260 613.625 260c-44.625 24.375-16.5 27-9 32.625s-15.375 16.5-15.375 16.5c-9.375-9.375-10.875 0.375-16.875 3.75s-0.375 12-0.375 12c-31.031 4.875-24 37.5-23.625 45.375s-19.875 19.875-25.125 31.125S536.75 437 527 438.5s-19.5-36.75-72-22.5c-15.828 4.297-51 22.5-32.25 59.625s49.875-10.5 60.375-5.25-3 28.875-0.75 29.25 29.625 1.031 31.125 33 41.625 29.25 50.25 30 37.5-23.625 41.625-24.75S626 522.875 662 543.5s54.375 17.625 66.75 26.25 3.75 25.875 15.375 31.5 58.125-1.875 69.75 17.25-48 115.125-66.75 125.625S719.75 778.625 701 794s-45 34.406-69.75 49.125c-21.908 13.027-25.85 36.365-35.609 43.732C767.496 848.68 896 695.35 896 512 896 299.923 724.077 128 512 128zM602 488.375c-5.25 1.5-16.125 11.25-42.75-4.5s-45-12.75-47.25-15.375c0 0-2.25-6.375 9.375-7.5 23.871-2.311 54 22.125 60.75 22.5s10.125-6.75 22.125-2.883C616.25 484.48 607.25 486.875 602 488.375zM476.375 166.25c-2.615-1.902 2.166-4.092 5.016-7.875 1.645-2.186 0.425-5.815 2.484-7.875 5.625-5.625 33.375-13.5 27.949 1.875C506.4 167.75 480.5 169.25 476.375 166.25zM543.5 215c-9.375-0.375-31.443-2.707-27.375-6.75 15.844-15.75-6-20.25-19.5-21.375S477.5 178.25 484.25 177.5s33.75 0.375 38.25 4.125 28.875 13.5 30.375 20.625S552.875 215.375 543.5 215zM624.875 212.375c-7.5 6-45.24-21.529-52.5-27.75-31.5-27-48.375-18-54.99-22.5-6.617-4.5-4.26-10.5 5.865-19.5s38.625 3 55.125 4.875 35.625 14.625 36 29.781C614.75 192.43600000000004 632.375 206.375 624.875 212.375z</value> + <value>M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7c0.48 0 0.94-0.05 1.38-0.14-0.17-0.08-0.2-0.73-0.02-1.09 0.19-0.41 0.81-1.45 0.2-1.8s-0.44-0.5-0.81-0.91-0.22-0.47-0.25-0.58c-0.08-0.34 0.36-0.89 0.39-0.94 0.02-0.06 0.02-0.27 0-0.33 0-0.08-0.27-0.22-0.34-0.23-0.06 0-0.11 0.11-0.2 0.13s-0.5-0.25-0.59-0.33-0.14-0.23-0.27-0.34c-0.13-0.13-0.14-0.03-0.33-0.11s-0.8-0.31-1.28-0.48c-0.48-0.19-0.52-0.47-0.52-0.66-0.02-0.2-0.3-0.47-0.42-0.67-0.14-0.2-0.16-0.47-0.2-0.41s0.25 0.78 0.2 0.81c-0.05 0.02-0.16-0.2-0.3-0.38-0.14-0.19 0.14-0.09-0.3-0.95s0.14-1.3 0.17-1.75 0.38 0.17 0.19-0.13 0-0.89-0.14-1.11c-0.13-0.22-0.88 0.25-0.88 0.25 0.02-0.22 0.69-0.58 1.16-0.92s0.78-0.06 1.16 0.05c0.39 0.13 0.41 0.09 0.28-0.05-0.13-0.13 0.06-0.17 0.36-0.13 0.28 0.05 0.38 0.41 0.83 0.36 0.47-0.03 0.05 0.09 0.11 0.22s-0.06 0.11-0.38 0.3c-0.3 0.2 0.02 0.22 0.55 0.61s0.38-0.25 0.31-0.55 0.39-0.06 0.39-0.06c0.33 0.22 0.27 0.02 0.5 0.08s0.91 0.64 0.91 0.64c-0.83 0.44-0.31 0.48-0.17 0.59s-0.28 0.3-0.28 0.3c-0.17-0.17-0.19 0.02-0.3 0.08s-0.02 0.22-0.02 0.22c-0.56 0.09-0.44 0.69-0.42 0.83 0 0.14-0.38 0.36-0.47 0.58-0.09 0.2 0.25 0.64 0.06 0.66-0.19 0.03-0.34-0.66-1.31-0.41-0.3 0.08-0.94 0.41-0.59 1.08 0.36 0.69 0.92-0.19 1.11-0.09s-0.06 0.53-0.02 0.55 0.53 0.02 0.56 0.61 0.77 0.53 0.92 0.55c0.17 0 0.7-0.44 0.77-0.45 0.06-0.03 0.38-0.28 1.03 0.09 0.66 0.36 0.98 0.31 1.2 0.47s0.08 0.47 0.28 0.58 1.06-0.03 1.28 0.31-0.88 2.09-1.22 2.28-0.48 0.64-0.84 0.92-0.81 0.64-1.27 0.91c-0.41 0.23-0.47 0.66-0.66 0.8 3.14-0.7 5.48-3.5 5.48-6.84 0-3.86-3.14-7-7-7z m1.64 6.56c-0.09 0.03-0.28 0.22-0.78-0.08-0.48-0.3-0.81-0.23-0.86-0.28 0 0-0.05-0.11 0.17-0.14 0.44-0.05 0.98 0.41 1.11 0.41s0.19-0.13 0.41-0.05 0.05 0.13-0.05 0.14zM6.34 1.7c-0.05-0.03 0.03-0.08 0.09-0.14 0.03-0.03 0.02-0.11 0.05-0.14 0.11-0.11 0.61-0.25 0.52 0.03-0.11 0.27-0.58 0.3-0.66 0.25z m1.23 0.89c-0.19-0.02-0.58-0.05-0.52-0.14 0.3-0.28-0.09-0.38-0.34-0.38-0.25-0.02-0.34-0.16-0.22-0.19s0.61 0.02 0.7 0.08c0.08 0.06 0.52 0.25 0.55 0.38 0.02 0.13 0 0.25-0.17 0.25z m1.47-0.05c-0.14 0.09-0.83-0.41-0.95-0.52-0.56-0.48-0.89-0.31-1-0.41s-0.08-0.19 0.11-0.34 0.69 0.06 1 0.09c0.3 0.03 0.66 0.27 0.66 0.55 0.02 0.25 0.33 0.5 0.19 0.63z</value> </data> <data name="graph" xml:space="preserve"> - <value>M704 256H512v640h192V256zM960 448H768v448h192V448zM64 960V832h64v-64H64V640h64v-64H64V448h64v-64H64V256h64v-64H64V64h64V0H0v1024h1024v-64H64zM448 576H256v320h192V576z</value> + <value>M16 14v1H0V0h1v14h15z m-11-1H3V8h2v5z m4 0H7V3h2v10z m4 0H11V6h2v7z</value> </data> <data name="heart" xml:space="preserve"> - <value>M384.1 864.026C783.145 550.451 768.126 438.959 768.126 352S696.149 159.97399999999993 576.1 159.97399999999993 384.1 288 384.1 288s-71.95-128.026-192-128.026S0.074 265.04099999999994 0.074 352-14.945 550.451 384.1 864.026z</value> + <value>M11.2 4c-0.52-0.63-1.25-0.95-2.2-1-0.97 0-1.69 0.42-2.2 1s-0.78 0.92-0.8 1c-0.02-0.08-0.28-0.42-0.8-1s-1.17-1-2.2-1c-0.95 0.05-1.69 0.38-2.2 1-0.52 0.61-0.78 1.28-0.8 2 0 0.52 0.09 1.52 0.67 2.67s2.34 2.94 5.33 5.33c2.98-2.39 4.77-4.17 5.34-5.33s0.66-2.17 0.66-2.67c-0.02-0.72-0.28-1.39-0.8-2.02z</value> </data> <data name="history" xml:space="preserve"> - <value>M448 64c-90.938 0-175.312 27.531-245.938 74.062L128 64v256h256l-88-88c45.438-24.688 96.688-40 152-40 176.75 0 320 143.219 320 320 0 176.75-143.25 320-320 320-176.781 0-320-143.25-320-320 0-45.562 9.781-88.781 27-128H64v-99.406C24.312 351.5 0 428.594 0 512c0 247.438 200.562 448 448 448 247.438 0 448-200.562 448-448C896 264.562 695.438 64 448 64zM447.031 831L512 768V576h128l64-64-64-64H512l-64-64L320 512l64 64v192L447.031 831z</value> + <value>M8 13H6V6h5v2H8v5zM7 1c-2.19 0-4.13 1.02-5.41 2.59L0 2v4h4l-1.5-1.5c1.05-1.33 2.67-2.2 4.5-2.2 3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8c0-0.34 0.03-0.67 0.09-1H0.08c-0.05 0.33-0.08 0.66-0.08 1 0 3.86 3.14 7 7 7s7-3.14 7-7S10.86 1 7 1z</value> </data> <data name="home" xml:space="preserve"> - <value>M192 576l64 384h192V640h128v320h192l64-384L512 256 192 576zM832 384V128H704l0.312 128.312L512 64 0 576h128l384-384 384 384h128L832 384z</value> + <value>M16 9L13 6V2H11v2L8 1 0 9h2l1 5c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1l1-5h2zM12 14H9V10H7v4H4l-1.19-6.31 5.19-5.19 5.19 5.19-1.19 6.31z</value> </data> <data name="horizontal_rule" xml:space="preserve"> - <value>M63.938 448h128v128h64V192.062h-64V384h-128V192.062H0V576h63.938V448zM639.875 576V448h-63.938v128H639.875zM639.875 384V256.062h-63.938V384H639.875zM447.938 384V256.062h128v-64h-192V576h64V448h128v-64H447.938zM0 832h639.875V704H0V832z</value> - </data> - <data name="hourglass" xml:space="preserve"> - <value>M570.625 513.906C688.312 429.375 768 274.25 768 129.90599999999995c0-70.656-172-128-384-128-212.031 0-384 57.344-384 128 0 144.344 79.688 299.469 197.375 384C79.688 598.5 0 753.625 0 897.875c0 70.688 171.969 128 384 128 212 0 384-57.312 384-128C768 753.625 688.312 598.5 570.625 513.906zM384 65.90599999999995c141.375 0 256 28.625 256 64 0 35.406-114.625 64-256 64s-256-28.594-256-64C128 94.53099999999995 242.625 65.90599999999995 384 65.90599999999995zM320 771.5c-153.719 7.125-238.281 40.25-252.688 81.688C82.875 739.625 156.031 637.75 256 577.875c0 0 64 1.062 64-63.969V771.5zM381.875 385.906c-97.469 0-144.812 16.656-167.875 32.969C144.781 364.219 92.938 289.28099999999995 73.406 204.687c69.75 32.125 182.469 53.219 310.594 53.219s240.875-21.094 310.625-53.219c-19.688 84.969-72 160.375-141.75 214.969C529.25 403.125 481.25 385.906 381.875 385.906zM448 771.5V513.906c0 61.969 64 63.969 64 63.969 100 59.875 173.125 161.75 188.688 275.312C686.25 811.75 601.75 778.625 448 771.5z</value> + <value>M1 7h2v2h1V3h-1v3H1V3H0v6h1V7z m9 2V7h-1v2h1z m0-3V4h-1v2h1z m-3 0V4h2v-1H6v6h1V7h2v-1H7zM0 13h10V11H0v2z</value> </data> <data name="hubot" xml:space="preserve"> - <value>M512 64C229.25 64 0 293.25 0 576v256c0 126.375 128 128 128 128h768c0 0 128-1.375 128-128V576C1024 293.25 794.75 64 512 64zM608 832H416c-17.719 0-32-14.375-32-32s14.281-32 32-32h192c17.625 0 32 14.375 32 32S625.625 832 608 832zM896 704c0 61.062-64 64-64 64H704c0 0 0-64-64-64H384c-64 0-64 64-64 64H192c-63.094 0-64-64-64-64V344.062C206 215.21900000000005 347.969 128 512 128c164 0 305.875 87.219 384 216V704zM768 320H256c-35.406 0-64 28.594-64 64v128c0 35.375 28.594 64 64 64h512c35.375 0 64-28.625 64-64V384C832 348.594 803.312 320 768 320zM768 448l-64 64H576l-64-64-64 64H320l-64-64v-64h64l64 64 64-64h128l64 64 64-64h64V448z</value> + <value>M3 6c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h8c0.55 0 1-0.45 1-1V7c0-0.55-0.45-1-1-1H3z m8 1.75l-1.25 1.25h-1.5l-1.25-1.25-1.25 1.25h-1.5l-1.25-1.25v-0.75h0.75l1.25 1.25 1.25-1.25h1.5l1.25 1.25 1.25-1.25h0.75v0.75zM5 11h4v1H5v-1z m2-9C3.14 2 0 4.91 0 8.5v4.5c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V8.5c0-3.59-3.14-6.5-7-6.5z m6 11H1V8.5c0-3.09 2.64-5.59 6-5.59s6 2.5 6 5.59v4.5z</value> </data> <data name="inbox" xml:space="preserve"> - <value>M704 192H64L0 576v256h768V576L704 192zM576 576l-64 128H256l-64-128H79l49-320h512l49 320H576z</value> + <value>M14 9l-1.13-7.14c-0.08-0.48-0.5-0.86-1-0.86H2.13c-0.5 0-0.92 0.38-1 0.86L0 9v5c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V9z m-3.28 0.55l-0.44 0.89c-0.17 0.34-0.52 0.56-0.91 0.56H4.61c-0.38 0-0.72-0.22-0.89-0.55l-0.44-0.91c-0.17-0.33-0.52-0.55-0.89-0.55H1l1-7h10l1 7h-1.38c-0.39 0-0.73 0.22-0.91 0.55z</value> </data> <data name="info" xml:space="preserve"> - <value>M448 384c35.375 0 64-28.594 64-64 0-35.312-28.625-64-64-64-35.406 0-64 28.688-64 64C384 355.406 412.594 384 448 384zM448 64C200.562 64 0 264.562 0 512c0 247.438 200.562 448 448 448 247.438 0 448-200.562 448-448C896 264.562 695.438 64 448 64zM448 832c-176.781 0-320-143.25-320-320 0-176.781 143.219-320 320-320 176.75 0 320 143.219 320 320C768 688.75 624.75 832 448 832zM512 512c0-66-64-64-64-64s0 0-64 0-64 64-64 64h64c0 0 0 128 0 192s64 64 64 64 0 0 64 0 64-64 64-64h-64C512 704 512 578 512 512z</value> + <value>M6.3 5.69c-0.19-0.19-0.28-0.42-0.28-0.7s0.09-0.52 0.28-0.7 0.42-0.28 0.7-0.28 0.52 0.09 0.7 0.28 0.28 0.42 0.28 0.7-0.09 0.52-0.28 0.7-0.42 0.3-0.7 0.3-0.52-0.11-0.7-0.3z m1.7 2.3c-0.02-0.25-0.11-0.48-0.31-0.69-0.2-0.19-0.42-0.3-0.69-0.31h-1c-0.27 0.02-0.48 0.13-0.69 0.31-0.2 0.2-0.3 0.44-0.31 0.69h1v3c0.02 0.27 0.11 0.5 0.31 0.69 0.2 0.2 0.42 0.31 0.69 0.31h1c0.27 0 0.48-0.11 0.69-0.31 0.2-0.19 0.3-0.42 0.31-0.69h-1V7.98z m-1-5.69C3.86 2.3 1.3 4.84 1.3 7.98s2.56 5.7 5.7 5.7 5.7-2.55 5.7-5.7-2.56-5.69-5.7-5.69m0-1.31c3.86 0 7 3.14 7 7S10.86 14.98 7 14.98 0 11.86 0 7.98 3.14 0.98 7 0.98z</value> </data> <data name="issue_closed" xml:space="preserve"> - <value>M704 316.03099999999995l-96 96L768 576l256-256-96-96L769.25 382.781 704 316.03099999999995zM512 832c-176.781 0-320-143.25-320-320 0-176.781 143.219-320 320-320 88.375 0 168.375 35.844 226.25 93.75l90.562-90.5C747.75 114.125 635.75 64 512 64 264.562 64 64 264.562 64 512c0 247.438 200.562 448 448 448 247.438 0 448-200.562 448-448L759.75 712.25C768.688 701.25 684.75 832 512 832zM576 256H448v320h128V256zM448 768h128V640H448V768z</value> + <value>M7 10h2v2H7V10z m2-6H7v5h2V4z m1.5 1.5l-1 1 2.5 2.5 4-4.5-1-1-3 3.5-1.5-1.5zM8 13.7c-3.14 0-5.7-2.56-5.7-5.7s2.56-5.7 5.7-5.7c1.83 0 3.45 0.88 4.5 2.2l0.92-0.92C12.14 2 10.19 1 8 1 4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-0.66 2.41-2.86 4.19-5.48 4.19z</value> </data> <data name="issue_opened" xml:space="preserve"> - <value>M448 64C200.562 64 0 264.562 0 512c0 247.438 200.562 448 448 448 247.438 0 448-200.562 448-448C896 264.562 695.438 64 448 64zM448 832c-176.781 0-320-143.25-320-320 0-176.781 143.219-320 320-320 176.75 0 320 143.219 320 320C768 688.75 624.75 832 448 832zM384 768h128V640H384V768zM384 576h128V256H384V576z</value> + <value>M7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m1 3H6v5h2V4z m0 6H6v2h2V10z</value> </data> <data name="issue_reopened" xml:space="preserve"> - <value>M639.125 767.25C585.75 807.375 520 832 448 832c-176.781 0-320-143.25-320-320 0-45.562 9.781-88.781 27-128H64v-99.469C24.312 351.438 0 428.594 0 512c0 247.438 200.562 448 448 448 107.375 0 204.5-39.312 281.75-102.25L768 896V704H576L639.125 767.25zM384 768h128V640H384V768zM512 256H384v320h128V256zM896 512c0-247.438-200.562-448-448-448-107.406 0-204.531 39.312-281.656 102.344L128 128v192h192l-63.156-63.156C310.281 216.688 376 192 448 192c176.75 0 320 143.219 320 320 0 45.562-9.75 88.75-27 128h91v99.5C871.688 672.562 896 595.5 896 512z</value> - </data> - <data name="jersey" xml:space="preserve"> - <value>M704 0H512.5c0 0-0.75 64-96.625 64S320.5 0 320.5 0h-192c0 128-1.598 384-128 384L0 960c0 0 0 64 64 64h704c64 0 64-64 64-64V384C705.598 384 704 128 704 0zM95.322 960c-31.946 0-31.946-31.946-31.946-31.946l0.5-480.024L63.71 448C182.875 384 192 256 192 64h64c0 0 0 191.746 160 191.746S576 64 576 64s37.583 0 64 0c0 185.875 32.431 275.776 63.998 338.875L703.999 960H95.322zM480 384l-32 32v320l32 32h128l32-32V416l-32-32H480zM576.062 704h-64V448h64V704zM224 384l-32 32v320l32 32h128l32-32V416l-32-32H224zM320.062 704h-64V448h64V704z</value> - </data> - <data name="jump_down" xml:space="preserve"> - <value>M767.75 192H0.25L384 575.75 767.75 192zM0 704v128h768V704H0z</value> - </data> - <data name="jump_left" xml:space="preserve"> - <value>M256.25 512L640 895.75v-767.5L256.25 512zM0 896h128V128H0V896z</value> + <value>M8 9H6V4h2v5zM6 12h2V10H6v2z m6.33-2H10l1.5 1.5c-1.05 1.33-2.67 2.2-4.5 2.2-3.14 0-5.7-2.56-5.7-5.7 0-0.34 0.03-0.67 0.09-1H0.08c-0.05 0.33-0.08 0.66-0.08 1 0 3.86 3.14 7 7 7 2.19 0 4.13-1.02 5.41-2.59l1.59 1.59V10H12.33zM1.67 6h2.33l-1.5-1.5c1.05-1.33 2.67-2.2 4.5-2.2 3.14 0 5.7 2.56 5.7 5.7 0 0.34-0.03 0.67-0.09 1h1.31c0.05-0.33 0.08-0.66 0.08-1 0-3.86-3.14-7-7-7-2.19 0-4.13 1.02-5.41 2.59L0 2v4h1.67z</value> </data> - <data name="jump_right" xml:space="preserve"> - <value>M0 895.75L383.75 512 0 128.188V895.75zM512 128v768h128V128H512z</value> + <data name="italic" xml:space="preserve"> + <value>M2.81 5h1.98L3 14H1l1.81-9z m0.36-2.7c0-0.7 0.58-1.3 1.33-1.3 0.56 0 1.13 0.38 1.13 1.03 0 0.75-0.59 1.3-1.33 1.3-0.58 0-1.13-0.38-1.13-1.03z</value> </data> - <data name="jump_up" xml:space="preserve"> - <value>M0.188 832h767.5L384 448.25 0.188 832zM0 192v128h768V192H0z</value> + <data name="jersey" xml:space="preserve"> + <value>M3.5 6l-0.5 0.5v5l0.5 0.5h2l0.5-0.5V6.5l-0.5-0.5H3.5z m1.5 5h-1V7h1v4z m6.27-7.25c-0.22-1.38-0.31-2.63-0.27-3.75H8.02c0 0.27-0.13 0.48-0.39 0.69-0.25 0.2-0.63 0.3-1.13 0.3s-0.88-0.09-1.13-0.3c-0.23-0.2-0.36-0.42-0.36-0.69H2c0.05 1.13-0.03 2.38-0.25 3.75-0.2 1.38-0.8 2.13-1.75 2.25v9c0.02 0.27 0.11 0.48 0.31 0.69s0.42 0.3 0.69 0.31h11c0.27-0.02 0.48-0.11 0.69-0.31s0.3-0.42 0.31-0.69V6c-0.95-0.13-1.53-0.88-1.75-2.25z m0.73 11.25H1V7c0.89-0.5 1.48-1.25 1.72-2.25s0.31-2.25 0.28-3.75h1c-0.02 0.78 0.16 1.47 0.52 2.06 0.36 0.58 1.02 0.89 2 0.94 0.98-0.02 1.64-0.33 2-0.94 0.36-0.59 0.5-1.28 0.48-2.06h1c0.02 1.42 0.13 2.55 0.33 3.38 0.2 0.81 0.69 2 1.67 2.63v8zM7.5 6l-0.5 0.5v5l0.5 0.5h2l0.5-0.5V6.5l-0.5-0.5H7.5z m1.5 5h-1V7h1v4z</value> </data> <data name="key" xml:space="preserve"> - <value>M640.9 63.89999999999998c-141.4 0-256 114.6-256 256 0 19.6 2.2 38.6 6.4 56.9L0 768v64l64 64h128l64-64v-64h64v-64h64v-64h128l70.8-70.8c18.7 4.3 38.1 6.6 58.1 6.6 141.4 0 256-114.6 256-256S782.2 63.89999999999998 640.9 63.89999999999998zM384 512L64 832v-64l320-320V512zM704 320c-35.3 0-64-28.7-64-64 0-35.3 28.7-64 64-64s64 28.7 64 64C768 291.29999999999995 739.3 320 704 320z</value> + <value>M12.83 2.17c-0.75-0.75-1.69-1.14-2.83-1.17-1.13 0.03-2.08 0.42-2.83 1.17s-1.13 1.69-1.16 2.83c0 0.3 0.03 0.59 0.09 0.89L0 12v1l1 1h2l1-1v-1h1v-1h1v-1h2l1.09-1.11c0.3 0.08 0.59 0.11 0.91 0.11 1.14-0.03 2.08-0.42 2.83-1.17s1.14-1.69 1.17-2.83c-0.03-1.14-0.42-2.08-1.17-2.83zM11 5.38c-0.77 0-1.38-0.61-1.38-1.38s0.61-1.38 1.38-1.38 1.38 0.61 1.38 1.38-0.61 1.38-1.38 1.38z</value> </data> <data name="keyboard" xml:space="preserve"> - <value>M640 576h64V448h-64V576zM768 256h-64v128h64V256zM640 256h-64v128h64V256zM512 576h64V448h-64V576zM384 768h320V640H384V768zM768 576h128V256h-64v192h-64V576zM256 768h64V640h-64V768zM768 768h128V640H768V768zM512 256h-64v128h64V256zM192 448h-64v128h64V448zM192 640h-64v128h64V640zM0 128v768h1024V128H0zM960 832H64V192h896V832zM384 576h64V448h-64V576zM256 256H128v128h128V256zM384 256h-64v128h64V256zM256 576h64V448h-64V576z</value> + <value>M10 5h-1v-1h1v1z m-7 1h-1v1h1v-1z m5-2h-1v1h1v-1z m-4 0H2v1h2v-1z m8 7h2v-1H12v1zM8 7h1v-1h-1v1zM4 10H2v1h2v-1z m8-6h-1v1h1v-1z m2 0h-1v1h1v-1zM12 9h2V6H12v3z m4-6v9c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h14c0.55 0 1 0.45 1 1z m-1 0H1v9h14V3zM6 7h1v-1h-1v1z m0-3h-1v1h1v-1zM4 7h1v-1h-1v1z m1 4h6v-1H5v1z m5-4h1v-1h-1v1z m-7 1h-1v1h1v-1z m5 0v1h1v-1h-1z m-2 0v1h1v-1h-1z m-1 0h-1v1h1v-1z m5 1h1v-1h-1v1z</value> </data> <data name="law" xml:space="preserve"> - <value>M514 192c34-1 61-28 62-62 1-37-29-67-66-66-34 1-61 28-62 62-1 37 29 67 66 66z m464 384h-18l-127-246c18-2 36-9 52-16 24-11 29-43 11-62l-1-1c-11-11-28-15-43-8-14 6-34 13-53 13-56 0-81-64-287-64s-231 64-287 64c-20 0-39-6-53-13-15-6-32-3-43 8l-1 1c-18 19-13 50 11 62 16 8 34 14 52 16l-127 246h-18c-8 0-14 7-13 15 11 64 92 113 191 113s180-49 191-113c1-8-5-15-13-15h-18l-127-245c83-7 127-49 191-49v486c-35 0-64 29-64 64h-71c-28 0-57 29-57 64h512c0-35-29-64-71-64h-57c0-35-29-64-64-64v-486c64 0 108 42 191 49l-127 245h-18c-8 0-14 7-13 15 11 64 92 113 191 113s180-49 191-113c1-8-5-15-13-15z m-658 0h-192l96-180 96 180z m384 0l96-180 96 180h-192z</value> + <value>M7 4c-0.83 0-1.5-0.67-1.5-1.5s0.67-1.5 1.5-1.5 1.5 0.67 1.5 1.5-0.67 1.5-1.5 1.5z m7 6c0 1.11-0.89 2-2 2h-1c-1.11 0-2-0.89-2-2l2-4h-1c-0.55 0-1-0.45-1-1h-1v8c0.42 0 1 0.45 1 1h1c0.42 0 1 0.45 1 1H3c0-0.55 0.58-1 1-1h1c0-0.55 0.58-1 1-1h0.03l-0.03-8h-1c0 0.55-0.45 1-1 1h-1l2 4c0 1.11-0.89 2-2 2h-1C0.89 12 0 11.11 0 10l2-4H1v-1h3c0-0.55 0.45-1 1-1h4c0.55 0 1 0.45 1 1h3v1h-1l2 4zM2.5 7L1 10h3l-1.5-3z m10.5 3l-1.5-3-1.5 3h3z</value> </data> <data name="light_bulb" xml:space="preserve"> - <value>M512 64c-176.731 0-320 143.269-320 320 0 104.69 50.278 197.633 128 256.015V832c0 35.346 28.653 64 64 64 0 35.346 28.653 64 64 64h128c35.347 0 64-28.654 64-64 35.347 0 64-28.654 64-64V640.015C781.722 581.633 832 488.69 832 384 832 207.269 688.731 64 512 64zM640 800c0 17.673-14.326 32-32 32H416c-17.674 0-32-14.327-32-32v-32h256V800zM704 553.307c-33.234 33.03-64 42.389-64 124.041V704h-64V576l128-128v-64l-64-64-64 64-64-64-64 64-64-64-64 64v64l128 128v128h-64v-26.652c0-81.652-30.766-91.011-64-124.041C280.177 508.18 256 448.918 256 384c0-141.385 114.615-256 256-256s256 114.615 256 256C768 448.918 743.823 508.18 704 553.307zM512 576L384 448v-64l64 64 64-64 64 64 64-64v64L512 576z</value> + <value>M5.5 0C2.48 0 0 2.19 0 5c0 0.92 0.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c0.22-1.22 0.66-1.75 2-4 0.45-0.75 1-2.08 1-3C11 2.19 8.52 0 5.5 0z m3.64 7.48c-0.25 0.44-0.47 0.8-0.67 1.11-0.86 1.41-1.25 2.06-1.45 3.23-0.02 0.05-0.02 0.11-0.02 0.17H4c0-0.06 0-0.13-0.02-0.17-0.2-1.17-0.59-1.83-1.45-3.23-0.2-0.31-0.42-0.67-0.67-1.11-0.42-0.7-0.86-1.83-0.86-2.48C1 2.8 3.02 1 5.5 1c1.22 0 2.36 0.42 3.22 1.19 0.83 0.75 1.28 1.75 1.28 2.81 0 0.66-0.44 1.78-0.86 2.48zM3 14h5c-0.23 1.14-1.3 2-2.5 2s-2.27-0.86-2.5-2z</value> </data> <data name="link_external" xml:space="preserve"> - <value>M640 768H128V257.90599999999995L256 256V128H0v768h768V576H640V768zM384 128l128 128L320 448l128 128 192-192 128 128V128H384z</value> + <value>M11 10h1v3c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h3v1H1v10h10V10zM6 2l2.25 2.25-3.25 3.25 1.5 1.5 3.25-3.25 2.25 2.25V2H6z</value> </data> <data name="link" xml:space="preserve"> - <value>M768 768H576c0 0-254.281 0-256-256 0-22.281 3.469-43.469 8.312-64h137.405c-11 18.875-17.719 40.562-17.719 64 0 128 128 128 128 128h192c0 0 128 0 128-128S768 384 768 384s-0.125-64-64-128h64c0 0 256 0 256 256S768 768 768 768zM695.688 576H558.25c11-18.875 17.75-40.562 17.75-64 0-128-128-128-128-128H256c0 0-128 0-128 128s128 128 128 128-3.906 64.875 65.719 128H256c0 0-256 0-256-256s256-256 256-256h192c0 0 256 0 256 256C704 534.281 700.5 555.5 695.688 576z</value> + <value>M4 9h1v1h-1c-1.5 0-3-1.69-3-3.5s1.55-3.5 3-3.5h4c1.45 0 3 1.69 3 3.5 0 1.41-0.91 2.72-2 3.25v-1.16c0.58-0.45 1-1.27 1-2.09 0-1.28-1.02-2.5-2-2.5H4c-0.98 0-2 1.22-2 2.5s1 2.5 2 2.5z m9-3h-1v1h1c1 0 2 1.22 2 2.5s-1.02 2.5-2 2.5H9c-0.98 0-2-1.22-2-2.5 0-0.83 0.42-1.64 1-2.09v-1.16c-1.09 0.53-2 1.84-2 3.25 0 1.81 1.55 3.5 3 3.5h4c1.45 0 3-1.69 3-3.5s-1.5-3.5-3-3.5z</value> </data> <data name="list_ordered" xml:space="preserve"> - <value>M0 704h64v-64h128v64L0 864v96h256v-64H64l192-160V576H0V704zM384 960h512V832H384V960zM192 128H64v64h128v256h64V64h-64V128zM384 64v128h512V64H384zM384 448h512V320H384V448zM384 704h512V576H384V704z</value> + <value>M12 13c0 0.59 0 1-0.59 1H4.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h6.81c0.59 0 0.59 0.41 0.59 1zM4.59 4h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1z m6.81 3H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1zM2 1H1.28C0.98 1.19 0.7 1.25 0.25 1.34v0.66h0.75v2.14H0.16v0.86h2.84v-0.86h-1V1z m0.25 8.13c-0.17 0-0.45 0.03-0.66 0.06 0.53-0.56 1.14-1.25 1.14-1.89-0.02-0.78-0.56-1.3-1.36-1.3-0.59 0-0.97 0.2-1.38 0.64l0.58 0.58c0.19-0.19 0.38-0.38 0.64-0.38 0.28 0 0.48 0.16 0.48 0.52 0 0.53-0.77 1.2-1.7 2.06v0.58h3l-0.09-0.88h-0.66z m-0.08 3.78v-0.03c0.44-0.19 0.64-0.47 0.64-0.86 0-0.7-0.56-1.11-1.44-1.11-0.48 0-0.89 0.19-1.28 0.52l0.55 0.64c0.25-0.2 0.44-0.31 0.69-0.31 0.27 0 0.42 0.13 0.42 0.36 0 0.27-0.2 0.44-0.86 0.44v0.75c0.83 0 0.98 0.17 0.98 0.47 0 0.25-0.23 0.38-0.58 0.38-0.28 0-0.56-0.14-0.81-0.38L0 14.44c0.3 0.36 0.77 0.56 1.41 0.56 0.83 0 1.53-0.41 1.53-1.16 0-0.5-0.31-0.81-0.77-0.94z</value> </data> <data name="list_unordered" xml:space="preserve"> - <value>M0 192h128V64H0V192zM0 704h128V576H0V704zM0 448h128V320H0V448zM0 960h128V832H0V960zM256 704h640V576H256V704zM256 448h640V320H256V448zM256 960h640V832H256V960zM256 64v128h640V64H256z</value> + <value>M2 13c0 0.59 0 1-0.59 1H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h0.81c0.59 0 0.59 0.41 0.59 1z m2.59-9h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1zM1.41 7H0.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h0.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z m0-5H0.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h0.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z m10 5H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z m0 5H4.59c-0.59 0-0.59 0.41-0.59 1s0 1 0.59 1h6.81c0.59 0 0.59-0.41 0.59-1s0-1-0.59-1z</value> </data> <data name="location" xml:space="preserve"> - <value>M320 0C143.219 0 0 143.21900000000005 0 320s320 704 320 704 320-527.219 320-704S496.75 0 320 0zM320 448c-70.656 0-128-57.344-128-128s57.344-128 128-128c70.625 0 128 57.344 128 128S390.625 448 320 448z</value> + <value>M6 0C2.69 0 0 2.5 0 5.5c0 4.52 6 10.5 6 10.5s6-5.98 6-10.5C12 2.5 9.31 0 6 0z m0 14.55C4.14 12.52 1 8.44 1 5.5 1 3.02 3.25 1 6 1c1.34 0 2.61 0.48 3.56 1.36 0.92 0.86 1.44 1.97 1.44 3.14 0 2.94-3.14 7.02-5 9.05z m2-9.05c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z</value> </data> <data name="_lock" xml:space="preserve"> - <value>M704 448h-64V256c0 0 0-256-256-256S128 256 128 256v192H64c0 0-64 0-64 64v448c0 64 64 64 64 64h640c0 0 64 0 64-64V512C768 448 704 448 704 448zM512 576H128v64h384v64H128v64h384v64H128v64h384v64H64V512h448V576zM512 448H256V256c0 0 0-128 128-128s128 128 128 128V448z</value> + <value>M4 13h-1v-1h1v1z m8-6v7c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V7c0-0.55 0.45-1 1-1h1V4C2 1.8 3.8 0 6 0s4 1.8 4 4v2h1c0.55 0 1 0.45 1 1z m-8.2-1h4.41V4c0-1.22-0.98-2.2-2.2-2.2s-2.2 0.98-2.2 2.2v2z m7.2 1H2v7h9V7z m-7 1h-1v1h1v-1z m0 2h-1v1h1v-1z</value> + </data> + <data name="logo_gist" xml:space="preserve"> + <value>M4.7 6.72h2.45v4.02c-0.55 0.27-1.64 0.34-2.53 0.34-2.56 0-3.48-2.2-3.48-5.05 0-2.83 0.92-5.05 3.48-5.05 1.27 0 2.06 0.23 3.28 0.73v-1.06c-0.64-0.33-1.66-0.66-3.28-0.66-3.5 0-4.63 2.69-4.63 6.03s1.11 6.03 4.63 6.03c1.64 0 2.8-0.27 3.59-0.64v-5.69h-3.52v0.98z m6.39 3.73v-6.39h-1.06v6.27c0 1.25 0.59 1.72 1.72 1.72v-0.89c-0.48 0-0.66-0.16-0.66-0.7z m0.25-8.73c0-0.44-0.34-0.78-0.78-0.78s-0.78 0.34-0.78 0.78 0.34 0.78 0.78 0.78 0.78-0.34 0.78-0.78z m4.33 5.69c-1.5-0.13-1.78-0.48-1.78-1.17 0-0.77 0.33-1.34 1.88-1.34 1.05 0 1.66 0.16 2.27 0.36v-0.94c-0.69-0.3-1.52-0.39-2.25-0.39-2.2 0-2.92 1.2-2.92 2.31 0 1.08 0.47 1.88 2.73 2.08 1.55 0.13 1.77 0.63 1.77 1.34 0 0.73-0.44 1.42-2.06 1.42-1.11 0-1.86-0.19-2.34-0.36v0.94c0.5 0.2 1.58 0.39 2.33 0.39 2.38 0 3.14-1.2 3.14-2.41 0-1.28-0.53-2.03-2.75-2.23z m8.59-2.47v-0.86h-2.42v-2.5l-1.09 0.31v2.11l-1.56 0.45v0.48h1.56v5c0 1.53 1.2 2.13 2.5 2.13 0.2 0 0.52-0.02 0.7-0.06v-0.88c-0.2 0.02-0.41 0.03-0.61 0.03-0.98 0-1.5-0.39-1.5-1.34v-4.88h2.42z</value> </data> <data name="logo_github" xml:space="preserve"> - <value>M552.73 332.135H311.557c-6.205 0-11.25 5.045-11.25 11.297v117.887c0 6.252 5.045 11.272 11.25 11.272h94.109v146.542c0 0-21.145 7.057-79.496 7.057-68.914 0-165.156-25.244-165.156-236.795 0-211.642 100.197-239.491 194.307-239.491 81.465 0 116.514 14.304 138.869 21.241 7.01 2.203 13.404-4.831 13.404-11.105L534.543 46.129999999999995c0-2.912-1.041-6.417-4.262-8.785C521.186 30.951999999999998 465.865 0 326.168 0 165.133 0 0 68.48699999999997 0 397.757 0 726.979 189.051 776 348.381 776c131.883 0 212.021-56.314 212.021-56.314 3.268-1.801 3.6-6.395 3.6-8.479V343.432C563.955 337.227 558.887 332.135 552.73 332.135zM1772.381 28.134000000000015h-135.695c-6.252 0-11.271 5.044-11.271 11.296v262.393h-211.619V39.42999999999995c0-6.252-5.068-11.296-11.178-11.296h-135.838c-6.111 0-11.084 5.044-11.084 11.296v710.473c0 6.299 5.021 11.32 11.084 11.32h135.838c6.203 0 11.178-5.068 11.178-11.32V446.067h211.619l-0.475 303.883c0 6.3 5.021 11.272 11.084 11.272h135.885c6.252 0 11.131-5.068 11.131-11.272l0.473-710.521C1783.607 33.178 1778.539 28.134000000000015 1772.381 28.134000000000015zM714.949 44.236999999999966c-48.357 0-87.574 39.572-87.574 88.403 0 48.855 39.217 88.428 87.574 88.428s87.527-39.572 87.527-88.428C802.477 83.80999999999995 763.307 44.236999999999966 714.949 44.236999999999966zM792.861 272.126c0-6.205-5.02-11.344-11.131-11.344H646.32c-6.348 0-11.746 6.394-11.746 12.67 0 0 0 394.654 0 469.867 0 13.735 8.572 17.903 19.703 17.903 0 0 57.688 0 121.959 0 13.311 0 16.814-6.536 16.814-18.188-0.094-25.197-0.094-123.808-0.094-142.942C792.861 581.905 792.861 272.126 792.861 272.126zM2297.973 261.84799999999996h-134.701c-6.158 0-11.084 5.092-11.084 11.344v348.31c0 0-34.244 25.197-82.934 25.197-48.547 0-61.525-22.024-61.525-69.719 0-47.553 0-303.835 0-303.835 0-6.252-5.068-11.345-11.131-11.345h-136.643c-6.252 0-11.178 5.093-11.178 11.345 0 0 0 185.521 0 326.807 0 141.284 78.766 175.906 186.99 175.906 88.854 0 160.609-49.115 160.609-49.115s3.363 25.766 5.068 28.844c1.422 3.078 5.447 6.158 9.852 6.158h86.58c6.158 0 11.178-5.069 11.178-11.321l0.379-477.278C2309.15 266.9390000000001 2304.129 261.84799999999996 2297.973 261.84799999999996zM2666.932 245.83899999999994c-76.539 0-128.592 34.148-128.592 34.148V39.42999999999995c0-6.252-5.068-11.296-11.131-11.296h-136.264c-6.109 0-11.131 5.044-11.131 11.296l-0.379 710.521c0 6.3 5.068 11.272 11.225 11.272 0 0 94.773 0 94.869 0 4.215 0 7.389-2.179 9.805-5.968 2.369-3.837 5.73-32.775 5.73-32.775s55.557 52.763 161.035 52.763c123.807 0 194.758-62.804 194.758-281.906C2856.859 274.51800000000003 2743.471 245.83899999999994 2666.932 245.83899999999994zM2613.791 646.225c-46.701-1.421-78.34-22.64-78.34-22.64v-225.07c0 0 31.307-19.206 69.672-22.593 48.547-4.31 95.438 10.326 95.438 126.13C2700.322 624.059 2679.199 648.166 2613.791 646.225zM1185.125 643.667c-5.969 0-21.219 2.368-36.85 2.368-49.92 0-66.971-23.256-66.971-53.331 0-30.218 0-199.85 0-199.85h101.926c6.252 0 11.178-5.044 11.178-11.343v-109.48c0.094-6.299-4.926-11.344-11.178-11.344h-101.926l-0.143-134.535c0-5.092-2.699-7.625-8.572-7.625H933.861c-5.352 0-8.336 2.391-8.336 7.578v139.035c0 0-69.576 16.79-74.266 18.188-4.641 1.326-8.051 5.684-8.051 10.822v87.408c0 6.252 5.068 11.344 11.178 11.344h71.139c0 0 0 91.34 0 210.222 0 156.109 109.553 171.455 183.439 171.455 33.723 0 74.076-10.988 80.848-13.356 4.074-1.421 6.395-5.637 6.395-10.136l0.047-96.101C1196.254 648.688 1190.998 643.572 1185.125 643.667z</value> + <value>M8.64 5.19H4.88c-0.11 0-0.19 0.08-0.19 0.17v1.84c0 0.09 0.08 0.17 0.19 0.17h1.47v2.3s-0.33 0.11-1.25 0.11c-1.08 0-2.58-0.39-2.58-3.7s1.58-3.73 3.05-3.73c1.27 0 1.81 0.22 2.17 0.33 0.11 0.03 0.2-0.08 0.2-0.17l0.42-1.78c0-0.05-0.02-0.09-0.06-0.14-0.14-0.09-1.02-0.58-3.2-0.58C2.58 0 0 1.06 0 6.2s2.95 5.92 5.44 5.92c2.06 0 3.31-0.89 3.31-0.89 0.05-0.02 0.06-0.09 0.06-0.13V5.36c0-0.09-0.08-0.17-0.19-0.17h0.02zM27.7 0.44h-2.13c-0.09 0-0.17 0.08-0.17 0.17v4.09h-3.31V0.61c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17v11.11c0 0.09 0.09 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V6.97h3.31l-0.02 4.75c0 0.09 0.08 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V0.61c0-0.09-0.08-0.17-0.17-0.17h0.02zM11.19 0.69c-0.77 0-1.38 0.61-1.38 1.38s0.61 1.38 1.38 1.38c0.75 0 1.36-0.61 1.36-1.38s-0.61-1.38-1.36-1.38z m1.22 3.55c0-0.09-0.08-0.17-0.17-0.17H10.11c-0.09 0-0.17 0.09-0.17 0.2 0 0 0 6.17 0 7.34 0 0.2 0.13 0.27 0.3 0.27 0 0 0.91 0 1.92 0 0.2 0 0.25-0.09 0.25-0.27 0-0.39 0-7.36 0-7.36v-0.02z m23.52-0.16h-2.09c-0.11 0-0.17 0.08-0.17 0.19v5.44s-0.55 0.39-1.3 0.39-0.97-0.34-0.97-1.09c0-0.73 0-4.75 0-4.75 0-0.09-0.08-0.17-0.17-0.17h-2.14c-0.09 0-0.17 0.08-0.17 0.17 0 0 0 2.91 0 5.11s1.23 2.75 2.92 2.75c1.39 0 2.52-0.77 2.52-0.77s0.05 0.39 0.08 0.45c0.02 0.05 0.09 0.09 0.16 0.09h1.34c0.11 0 0.17-0.08 0.17-0.17l0.02-7.47c0-0.09-0.08-0.17-0.19-0.17z m5.77-0.25c-1.2 0-2.02 0.53-2.02 0.53V0.59c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17l-0.02 11.11c0 0.09 0.09 0.17 0.19 0.17h1.48c0.06 0 0.11-0.02 0.14-0.08 0.05-0.06 0.09-0.52 0.09-0.52s0.88 0.83 2.52 0.83c1.94 0 3.05-0.98 3.05-4.41s-1.77-3.88-2.97-3.88z m-0.83 6.27c-0.73-0.02-1.22-0.36-1.22-0.36V6.22s0.48-0.3 1.08-0.34c0.77-0.08 1.5 0.16 1.5 1.97 0 1.91-0.33 2.28-1.36 2.25z m-22.33-0.05c-0.09 0-0.33 0.05-0.58 0.05-0.78 0-1.05-0.36-1.05-0.83s0-3.13 0-3.13h1.59c0.09 0 0.16-0.08 0.16-0.19V4.25c0-0.09-0.08-0.17-0.16-0.17h-1.59V1.97c0-0.08-0.05-0.13-0.14-0.13H14.61c-0.09 0-0.14 0.05-0.14 0.13v2.17s-1.09 0.27-1.16 0.28c-0.08 0.02-0.13 0.09-0.13 0.17v1.36c0 0.11 0.08 0.19 0.17 0.19h1.11s0 1.44 0 3.28c0 2.44 1.7 2.69 2.86 2.69 0.53 0 1.17-0.17 1.27-0.22 0.06-0.02 0.09-0.09 0.09-0.16v-1.5c0-0.11-0.08-0.19-0.17-0.19h0.02z</value> </data> <data name="mail_read" xml:space="preserve"> - <value>M576 384H256v64h320V384zM384 256H256v64h128V256zM768 228.53099999999995V128H627.188L448 0 268.812 128H128v100.531L0 320v640h896V320L768 228.53099999999995zM192 192h512v244.812L448 648 192 436.812V192zM64 448l252.031 191.625L64 832V448zM128 896l254-206.25L448 740l65.875-50.125L768 896H128zM832 832L579.625 639.938 832 448V832z</value> + <value>M6 5H4v-1h2v1z m3 1H4v1h5v-1z m5-0.48v8.48c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V5.52c0-0.33 0.16-0.63 0.42-0.81l1.58-1.13v-0.58c0-0.55 0.45-1 1-1h1.2L7 0l2.8 2h1.2c0.55 0 1 0.45 1 1v0.58l1.58 1.13c0.27 0.19 0.42 0.48 0.42 0.81zM3 7.5l4 2.5 4-2.5V3H3v4.5zM1 13.5l4.5-3L1 7.5v6z m11 0.5L7 11 2 14h10z m1-6.5L8.5 10.5l4.5 3V7.5z</value> </data> <data name="mail_reply" xml:space="preserve"> - <value>M384 160l-384 288 384 288v-192s288 0 384 280c0-472-384-472-384-472v-192z</value> + <value>M6 2.5l-6 4.5 6 4.5v-3c1.73 0 5.14 0.95 6 4.38 0-4.55-3.06-7.05-6-7.38v-3z</value> </data> <data name="mail" xml:space="preserve"> - <value>M0 192v640h896V192H0zM768 256L448 520 128 256H768zM64 320l252.031 191.625L64 704V320zM128 768l254-206.25L448 612l65.875-50.125L768 768H128zM832 704L579.625 511.938 832 320V704z</value> + <value>M0 4v8c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1H1c-0.55 0-1 0.45-1 1z m13 0L7 9 1 4h12zM1 5.5l4 3L1 11.5V5.5z m1 6.5l3.5-3 1.5 1.5 1.5-1.5 3.5 3H2z m11-0.5L9 8.5l4-3v6z</value> </data> <data name="mark_github" xml:space="preserve"> - <value>M512 0C229.25 0 0 229.25 0 512c0 226.25 146.688 418.125 350.156 485.812 25.594 4.688 34.938-11.125 34.938-24.625 0-12.188-0.469-52.562-0.719-95.312C242 908.812 211.906 817.5 211.906 817.5c-23.312-59.125-56.844-74.875-56.844-74.875-46.531-31.75 3.53-31.125 3.53-31.125 51.406 3.562 78.47 52.75 78.47 52.75 45.688 78.25 119.875 55.625 149 42.5 4.654-33 17.904-55.625 32.5-68.375C304.906 725.438 185.344 681.5 185.344 485.312c0-55.938 19.969-101.562 52.656-137.406-5.219-13-22.844-65.094 5.062-135.562 0 0 42.938-13.75 140.812 52.5 40.812-11.406 84.594-17.031 128.125-17.219 43.5 0.188 87.312 5.875 128.188 17.281 97.688-66.312 140.688-52.5 140.688-52.5 28 70.531 10.375 122.562 5.125 135.5 32.812 35.844 52.625 81.469 52.625 137.406 0 196.688-119.75 240-233.812 252.688 18.438 15.875 34.75 47 34.75 94.75 0 68.438-0.688 123.625-0.688 140.5 0 13.625 9.312 29.562 35.25 24.562C877.438 930 1024 738.125 1024 512 1024 229.25 794.75 0 512 0z</value> + <value>M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z</value> </data> <data name="markdown" xml:space="preserve"> - <value>M950.154 192H73.846C33.127 192 0 225.12699999999995 0 265.846v492.308C0 798.875 33.127 832 73.846 832h876.308c40.721 0 73.846-33.125 73.846-73.846V265.846C1024 225.12699999999995 990.875 192 950.154 192zM576 703.875L448 704V512l-96 123.077L256 512v192H128V320h128l96 128 96-128 128-0.125V703.875zM767.091 735.875L608 512h96V320h128v192h96L767.091 735.875z</value> + <value>M14.85 3H1.15C0.52 3 0 3.52 0 4.15v7.69C0 12.48 0.52 13 1.15 13h13.69c0.64 0 1.15-0.52 1.15-1.15V4.15C16 3.52 15.48 3 14.85 3zM9 11L7 11V8l-1.5 1.92L4 8v3H2V5h2l1.5 2 1.5-2 2 0V11zM11.99 11.5L9.5 8h1.5V5h2v3h1.5L11.99 11.5z</value> </data> <data name="megaphone" xml:space="preserve"> - <value>M832 32c-130 0-124 130-704 128C57.344 160 0 274.625 0 416s57.344 256 128 256c22.781 0 43.188 0.5 64.188 0.875L256 960l192 32 64-96-45.125-203.125C709.375 729.125 733.75 800 832 800c106 0 192-172 192-384C1024 203.96900000000005 938 32 832 32zM197 482.938c-39.188-1.469-82.188-2.25-127.562-2.625C66 460.594 64 438.906 64 416c0-88.375 28.688-192 64-192 39.031 0.125 75-0.438 109-1.406C209.656 269.562 192 338.312 192 416 192 439.312 194.062 461.438 197 482.938zM261.312 485.938C258.125 463.688 256 440.375 256 416c0-79.5 18.438-149.5 46.906-196.219 155.156-8.312 251.906-28.469 319.031-50.188C593.625 236.46900000000005 576 321.656 576 416c0 40 3.875 78 9.5 114.312C513.344 511.625 412.812 494.594 261.312 485.938zM832 704c-12.125 0-23.688-5.062-34.812-12.125-15.25-67.312-83.438-418.344 117.438-494.188C942.125 250.5 960 328.188 960 416 960 575 902.625 704 832 704z</value> + <value>M10 1c-0.17 0-0.36 0.05-0.52 0.14-1.44 0.88-4.98 3.44-6.48 3.86-1.38 0-3 0.67-3 2.5s1.63 2.5 3 2.5c0.3 0.08 0.64 0.23 1 0.41v4.59h2V11.55c1.34 0.86 2.69 1.83 3.48 2.31 0.16 0.09 0.34 0.14 0.52 0.14 0.52 0 1-0.42 1-1V2c0-0.58-0.48-1-1-1z m0 12c-0.38-0.23-0.89-0.58-1.5-1-0.16-0.11-0.33-0.22-0.5-0.34V3.31c0.16-0.11 0.31-0.2 0.47-0.31 0.61-0.41 1.16-0.77 1.53-1v11z m2-6h4v1H12v-1z m0 2l4 2v1L12 10v-1z m4-6v1L12 6v-1l4-2z</value> </data> <data name="mention" xml:space="preserve"> - <value>M466.697 99.101C238.66 71.10199999999998 31.1 233.265 3.102 461.302c-28 228.038 134.163 435.598 362.2 463.597 71.429 8.756 145.115-0.913 213.325-29.946l-0.016-0.032c24.404-10.357 35.788-38.538 25.431-62.939-10.359-24.403-38.538-35.787-62.94-25.43l-0.001-0.004c-52.472 22.339-109.15 29.799-164.1 23.067-175.413-21.538-300.153-181.2-278.616-356.613 21.538-175.413 181.199-300.154 356.613-278.616 175.412 21.538 300.154 181.199 278.617 356.612-4.309 35.083-21.542 55.725-61.6 55.725-42.5 0-64-45.889-64-81.222V400c0-26.51-21.49-48-48-48-9.699 0-18.72 2.887-26.269 7.833-25.684-20.259-57.437-33.87-94.349-38.402-105.246-12.923-201.045 61.924-213.967 167.17C212.508 593.848 287.354 689.646 392.6 702.568c57.379 7.045 116.216-14.707 157.871-53.13 24.959 28.124 59.866 47.624 100.121 52.567 87.707 10.769 167.537-51.602 178.307-139.309C856.898 334.66 694.734 127.101 466.697 99.101zM511.285 523.699c-6.462 52.623-54.361 90.047-106.985 83.585-52.623-6.461-90.046-54.36-83.585-106.984 6.461-52.623 54.361-90.046 106.984-83.585C480.322 423.177 517.746 471.076 511.285 523.699z</value> - </data> - <data name="microscope" xml:space="preserve"> - <value>M617 896c86.312-18.75 151-100 151-192 0-58.438-26.625-110.125-67.875-145.375C702.5 543.375 704 527.875 704 512c0-104.844-49.875-197.875-128-256l64-64v-64l64-64L640 0l-64 64h-64L256 320l-128 64v128l64 64h128l64-128 96-96c55.5 33.406 96 90.438 96 160-106.062 0-192 85.938-192 192H0v64h192c19.125 14.25 42.062 22.125 64 32v96H128L0 1024h768L640 896H617zM512 704c0-35.375 28.625-64 64-64s64 28.625 64 64c0 35.312-28.625 64-64 64S512 739.312 512 704z</value> + <value>M6.58 15c1.25 0 2.52-0.31 3.56-0.94l-0.42-0.94c-0.84 0.52-1.89 0.83-3.03 0.83-3.23 0-5.64-2.08-5.64-5.72C1.05 3.86 4.28 1.05 7.63 1.05c3.45 0 5.22 2.19 5.22 5.2 0 2.39-1.34 3.86-2.5 3.86-1.05 0-1.36-0.73-1.05-2.19l0.73-3.75h-1.05l-0.11 0.72c-0.41-0.63-0.94-0.83-1.56-0.83-2.19 0-3.66 2.39-3.66 4.38 0 1.67 0.94 2.61 2.3 2.61 0.84 0 1.67-0.53 2.3-1.25 0.11 0.94 0.94 1.45 1.98 1.45 1.67 0 3.77-1.67 3.77-5C14 2.61 11.59 0 7.83 0 3.66 0 0 3.33 0 8.33c0 4.38 2.92 6.67 6.58 6.67z m-0.31-5c-0.73 0-1.36-0.52-1.36-1.67 0-1.45 0.94-3.22 2.41-3.22 0.52 0 0.84 0.2 1.25 0.83l-0.52 3.02c-0.63 0.73-1.25 1.05-1.78 1.05z</value> </data> <data name="milestone" xml:space="preserve"> - <value>M704 192H0v256h704l128-128L704 192zM448 384H320V256h128V384zM448 0H320v128h128V0zM320 1024h128V512H320V1024z</value> + <value>M8 2H6V0h2v2z m4 5H2c-0.55 0-1-0.45-1-1V4c0-0.55 0.45-1 1-1h10l2 2-2 2zM8 4H6v2h2V4z m-2 12h2V8H6v8z</value> </data> <data name="mirror" xml:space="preserve"> - <value>M320 320L128 512l192 192V576h384v128l192-192L704 320v128H320V320zM512 0L0 320v704l512-256 512 256V320L512 0zM960 896L576 704v-64H448v64L64 896V384l384-256v256h128V128l384 256V896z</value> + <value>M15.5 4.7L8.5 0 1.5 4.7c-0.3 0.19-0.5 0.45-0.5 0.8v10.5l7.5-4 7.5 4V5.5c0-0.34-0.2-0.61-0.5-0.8z m-0.5 9.8L9 11.25v-1.25h-1v1.25L2 14.5V5.5L8 1.5v4.5h1V1.5l6 4v9zM6 7h5V5l3 3-3 3V9H6v2L3 8l3-3v2z</value> </data> <data name="mortar_board" xml:space="preserve"> - <value>M501 588l-245-76v160c0 53 115 96 256 96s256-43 256-96v-160l-245 76c-7 2-15 2-23 0z m507-257l-489-152c-4-1-9-1-13 0l-489 152c-21 7-21 36 0 43l111 35v113c-19 11-32 32-32 55 0 12 3 23 9 32-5 9-9 20-9 32v165c0 35 128 35 128 0v-165c0-12-3-23-9-32 5-9 9-20 9-32 0-24-13-44-32-55v-93l313 98c4 1 9 1 13 0l489-152c21-7 21-36 0-43z m-495 53c-35 0-64-14-64-32s29-32 64-32 64 14 64 32-29 32-64 32z</value> - </data> - <data name="move_down" xml:space="preserve"> - <value>M640 320H448V0H192v320H0l320 384L640 320zM0 1024h640V832H0V1024z</value> - </data> - <data name="move_left" xml:space="preserve"> - <value>M0 832h192V192H0V832zM704 384V192L320 512l384 320V640h320V384H704z</value> - </data> - <data name="move_right" xml:space="preserve"> - <value>M832 192v640h192V192H832zM320 384H0v256h320v192l384-320L320 192V384z</value> - </data> - <data name="move_up" xml:space="preserve"> - <value>M0 704h192v320h256V704h192L320 320 0 704zM0 0v192h640V0H0z</value> + <value>M7.83 9.19l-3.83-1.19s0 1.5 0 2.5 1.8 1.5 4 1.5 4-0.5 4-1.5 0-2.5 0-2.5l-3.83 1.19c-0.11 0.03-0.23 0.03-0.36 0h0.02z m0.28-6.39c-0.06-0.02-0.14-0.02-0.2 0l-7.64 2.38c-0.33 0.11-0.33 0.56 0 0.67l1.73 0.55v1.77c-0.3 0.17-0.5 0.5-0.5 0.86 0 0.19 0.05 0.36 0.14 0.5-0.08 0.14-0.14 0.31-0.14 0.5v2.58c0 0.55 2 0.55 2 0v-2.58c0-0.19-0.05-0.36-0.14-0.5 0.08-0.14 0.14-0.31 0.14-0.5 0-0.38-0.2-0.69-0.5-0.86v-1.45l4.89 1.53c0.06 0.02 0.14 0.02 0.2 0l7.64-2.38c0.33-0.11 0.33-0.56 0-0.67l-7.63-2.39z m-0.09 3.2c-0.55 0-1-0.22-1-0.5s0.45-0.5 1-0.5 1 0.22 1 0.5-0.45 0.5-1 0.5z</value> </data> <data name="mute" xml:space="preserve"> - <value>M128 384H0v256h128l256 192h64V192h-64L128 384zM864 416l-64-64-96 96-96-96-63 63.5 95 96.5-96 96 64 64 96-96 96 96 64-64-96-96L864 416z</value> + <value>M8 2.81v10.38c0 0.67-0.81 1-1.28 0.53L3 10H1c-0.55 0-1-0.45-1-1V7c0-0.55 0.45-1 1-1h2l3.72-3.72c0.47-0.47 1.28-0.14 1.28 0.53z m7.53 3.22l-1.06-1.06-1.97 1.97-1.97-1.97-1.06 1.06 1.97 1.97-1.97 1.97 1.06 1.06 1.97-1.97 1.97 1.97 1.06-1.06-1.97-1.97 1.97-1.97z</value> </data> <data name="no_newline" xml:space="preserve"> - <value>M896 320v128H768V320L576 512l192 192V576h192c0 0 64-0.375 64-64s0-192 0-192H896zM224 288C100.281 288 0 388.281 0 512c0 123.75 100.281 224 224 224s224-100.25 224-224C448 388.281 347.719 288 224 288zM96 512c0-70.656 57.344-128 128-128 18.75 0 36.406 4.219 52.469 11.531L107.531 564.5C100.219 548.375 96 530.75 96 512zM224 640c-18.75 0-36.406-4.25-52.469-11.5l168.938-168.969C347.781 475.594 352 493.25 352 512 352 582.625 294.656 640 224 640z</value> + <value>M16 5v3c0 0.55-0.45 1-1 1H12v2L9 8l3-3v2h2V5h2zM8 8c0 2.2-1.8 4-4 4S0 10.2 0 8s1.8-4 4-4 4 1.8 4 4zM1.5 9.66l4.16-4.16c-0.48-0.31-1.05-0.5-1.66-0.5-1.66 0-3 1.34-3 3 0 0.61 0.19 1.17 0.5 1.66z m5.5-1.66c0-0.61-0.19-1.17-0.5-1.66L2.34 10.5c0.48 0.31 1.05 0.5 1.66 0.5 1.66 0 3-1.34 3-3z</value> </data> <data name="octoface" xml:space="preserve"> - <value>M940.812 277.688c8.25-20.219 35.375-101.75-8.562-211.906 0 0-67.375-21.312-219.875 82.906C648.5 131.125 579.875 128.5 512 128.5c-67.906 0-136.438 2.625-200.5 20.25C159.031 44.46900000000005 91.719 65.78099999999995 91.719 65.78099999999995 47.812 176 74.938 257.46900000000005 83.188 277.688 31.5 333.562 0 404.875 0 492.344 0 821.562 213.25 896 510.844 896 808.562 896 1024 821.562 1024 492.344 1024 404.875 992.5 333.562 940.812 277.688zM512 833c-211.406 0-382.781-9.875-382.781-214.688 0-48.938 24.062-94.595 65.344-132.312 68.75-62.969 185.281-29.688 317.438-29.688 132.25 0 248.625-33.281 317.438 29.625 41.312 37.78 65.438 83.312 65.438 132.312C894.875 823.125 723.375 833 512 833zM351.156 512.438c-42.469 0-76.906 51.062-76.906 114.188s34.438 114.312 76.906 114.312c42.375 0 76.812-51.188 76.812-114.312S393.531 512.438 351.156 512.438zM672.875 512.438C630.5 512.438 596 563.5 596 626.625s34.5 114.312 76.875 114.312 76.812-51.188 76.812-114.312C749.75 563.5 715.312 512.438 672.875 512.438z</value> + <value>M14.7 4.34c0.13-0.32 0.55-1.59-0.13-3.31 0 0-1.05-0.33-3.44 1.3C10.13 2.05 9.06 2.01 8 2.01c-1.06 0-2.13 0.04-3.13 0.32C2.48 0.69 1.43 1.03 1.43 1.03 0.75 2.75 1.17 4.02 1.3 4.34 0.49 5.21 0 6.33 0 7.69 0 12.84 3.33 14 7.98 14 12.63 14 16 12.84 16 7.69 16 6.33 15.51 5.21 14.7 4.34zM8 13.02c-3.3 0-5.98-0.15-5.98-3.35 0-0.76 0.38-1.48 1.02-2.07 1.07-0.98 2.9-0.46 4.96-0.46 2.07 0 3.88-0.52 4.96 0.46 0.65 0.59 1.02 1.3 1.02 2.07C13.98 12.86 11.3 13.02 8 13.02zM5.49 8.01c-0.66 0-1.2 0.8-1.2 1.78s0.54 1.79 1.2 1.79c0.66 0 1.2-0.8 1.2-1.79S6.15 8.01 5.49 8.01zM10.51 8.01C9.85 8.01 9.31 8.8 9.31 9.79s0.54 1.79 1.2 1.79 1.2-0.8 1.2-1.79C11.71 8.8 11.18 8.01 10.51 8.01z</value> </data> <data name="organization" xml:space="preserve"> - <value>M768 384h-64H576h-64-64-64-64H192h-64C57.344 384 0 441.344 0 512v64c0 47.25 25.844 88.062 64 110.25V896h256v128h256V896h256V686.25c38.125-22.188 64-62.938 64-110.25v-64C896 441.344 838.625 384 768 384zM256 832H128V576H64v-64c0-35.312 28.688-64 64-64h81.719c-11 18.875-17.719 40.562-17.719 64v128c0 47.25 25.844 88.062 64 110.25V832zM576 704V576h-64v384H384V576h-64v128c-35.312 0-64-28.625-64-64V512c0-35.312 28.688-64 64-64h256c35.375 0 64 28.688 64 64v128C640 675.375 611.375 704 576 704zM832 576h-64v256H640v-81.75c38.125-22.188 64-62.938 64-110.25V512c0-23.438-6.75-45.125-17.75-64H768c35.375 0 64 28.688 64 64V576zM303.688 317.375C338.875 357.875 390.156 384 448 384c57.875 0 109.125-26.125 144.312-66.625C614.125 356.938 655.688 384 704 384c70.625 0 128-57.344 128-128s-57.375-128-128-128c-25.625 0-49.375 7.688-69.375 20.688C614.875 63.56200000000001 539.062 0 448 0S281.094 63.56200000000001 261.375 148.688C241.344 135.688 217.594 128 192 128c-70.656 0-128 57.344-128 128s57.344 128 128 128C240.312 384 281.844 356.938 303.688 317.375zM704 192c35.375 0 64 28.594 64 64s-28.625 64-64 64c-35.312 0-64-28.594-64-64S668.688 192 704 192zM448 64c70.625 0 128 57.344 128 128s-57.375 128-128 128c-70.656 0-128-57.344-128-128S377.344 64 448 64zM192 320c-35.312 0-64-28.594-64-64s28.688-64 64-64c35.406 0 64 28.594 64 64S227.406 320 192 320z</value> + <value>M4.75 4.95c0.55 0.64 1.34 1.05 2.25 1.05s1.7-0.41 2.25-1.05c0.34 0.63 1 1.05 1.75 1.05 1.11 0 2-0.89 2-2s-0.89-2-2-2c-0.41 0-0.77 0.13-1.08 0.33C9.61 1 8.42 0 7 0S4.39 1 4.08 2.33c-0.31-0.2-0.67-0.33-1.08-0.33-1.11 0-2 0.89-2 2s0.89 2 2 2c0.75 0 1.41-0.42 1.75-1.05z m5.2-1.52c0.2-0.38 0.59-0.64 1.05-0.64 0.66 0 1.2 0.55 1.2 1.2s-0.55 1.2-1.2 1.2-1.17-0.53-1.19-1.17c0.06-0.19 0.11-0.39 0.14-0.59zM7 0.98c1.11 0 2.02 0.91 2.02 2.02s-0.91 2.02-2.02 2.02-2.02-0.91-2.02-2.02S5.89 0.98 7 0.98zM3 5.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2c0.45 0 0.84 0.27 1.05 0.64 0.03 0.2 0.08 0.41 0.14 0.59-0.02 0.64-0.53 1.17-1.19 1.17z m10 0.8H1c-0.55 0-1 0.45-1 1v3c0 0.55 0.45 1 1 1v2c0 0.55 0.45 1 1 1h1c0.55 0 1-0.45 1-1v-1h1v3c0 0.55 0.45 1 1 1h2c0.55 0 1-0.45 1-1V12h1v1c0 0.55 0.45 1 1 1h1c0.55 0 1-0.45 1-1V11c0.55 0 1-0.45 1-1V7c0-0.55-0.45-1-1-1zM3 13h-1V10H1V7h2v6z m7-2h-1V9h-1v6H6V9h-1v2h-1V7h6v4z m3-1h-1v3h-1V7h2v3z</value> </data> <data name="package" xml:space="preserve"> - <value>M480 64L0 192v576l480 128 480-128V192L480 64zM63.875 720.934L63.5 288l384.498 102.533 0.001 432.833L63.875 720.934zM63.5 224l160.254-42.734L640 292.265v0.135l-160 42.667L63.5 224zM896.125 720.934L512.001 823.366l0.001-432.833L640 356.4v156l128-34.135V322.267L896.5 288 896.125 720.934zM768 258.26700000000005v-0.125L351.734 147.13800000000003 480 112.93399999999997 896.5 224 768 258.26700000000005z</value> + <value>M0 4.27v7.47c0 0.45 0.3 0.84 0.75 0.97l6.5 1.73c0.16 0.05 0.34 0.05 0.5 0l6.5-1.73c0.45-0.13 0.75-0.52 0.75-0.97V4.27c0-0.45-0.3-0.84-0.75-0.97L7.75 1.56c-0.16-0.03-0.34-0.03-0.5 0L0.75 3.3c-0.45 0.13-0.75 0.52-0.75 0.97z m7 9.09L1 11.77V5l6 1.61v6.75zM1 4l2.5-0.67 6.5 1.73-2.5 0.67L1 4z m13 7.77L8 13.36V6.61l2-0.55v2.44l2-0.53V5.53l2-0.53v6.77zM12 4.53L5.5 2.8l2-0.53 6.5 1.73-2 0.53z</value> </data> <data name="paintcan" xml:space="preserve"> - <value>M384 0C171.923 0 0 171.923 0 384v64c0 35.346 28.654 64 64 64v320c0 70.692 143.269 128 320 128s320-57.308 320-128V512c35.346 0 64-28.654 64-64v-64C768 171.923 596.077 0 384 0zM576 640v32c0 17.673-14.327 32-32 32s-32-14.327-32-32v-32c0-17.673-14.327-32-32-32s-32 14.327-32 32v160c0 17.673-14.327 32-32 32s-32-14.327-32-32V672c0-17.673-14.327-32-32-32s-32 14.327-32 32v32c0 35.346-28.654 64-64 64s-64-28.654-64-64v-64c-35.346 0-64-28.654-64-64V460.807C186.382 491.892 279.318 512 384 512s197.618-20.108 256-51.193V576C640 611.346 611.346 640 576 640zM384 448c-107.433 0-199.393-26.474-237.372-64 37.979-37.526 129.939-64 237.372-64s199.393 26.474 237.372 64C583.393 421.526 491.433 448 384 448zM384 256c-176.62 0-319.816 57.236-319.996 127.867-0.001-0.001-0.002-0.001-0.003-0.002C64.075 207.19600000000003 207.314 64 384 64c176.731 0 320 143.269 320 320C704 313.308 560.731 256 384 256z</value> + <value>M6 0C2.69 0 0 2.69 0 6v1c0 0.55 0.45 1 1 1v5c0 1.1 2.24 2 5 2s5-0.9 5-2V8c0.55 0 1-0.45 1-1v-1C12 2.69 9.31 0 6 0zM9 10v0.5c0 0.28-0.22 0.5-0.5 0.5s-0.5-0.22-0.5-0.5v-0.5c0-0.28-0.22-0.5-0.5-0.5s-0.5 0.22-0.5 0.5v2.5c0 0.28-0.22 0.5-0.5 0.5s-0.5-0.22-0.5-0.5V10.5c0-0.28-0.22-0.5-0.5-0.5s-0.5 0.22-0.5 0.5v0.5c0 0.55-0.45 1-1 1s-1-0.45-1-1v-1c-0.55 0-1-0.45-1-1V7.2C2.91 7.69 4.36 8 6 8s3.09-0.31 4-0.8V9C10 9.55 9.55 10 9 10zM6 7c-1.68 0-3.12-0.41-3.71-1 0.59-0.59 2.03-1 3.71-1s3.12 0.41 3.71 1C9.12 6.59 7.68 7 6 7zM6 4c-2.76 0-5 0.89-5 2 0 0 0 0 0 0C1 3.24 3.24 1 6 1c2.76 0 5 2.24 5 5C11 4.9 8.76 4 6 4z</value> </data> <data name="pencil" xml:space="preserve"> - <value>M704 64L576 192l192 192 128-128L704 64zM0 768l0.688 192.562L192 960l512-512L512 256 0 768zM192 896H64V768h64v64h64V896z</value> + <value>M0 12v3h3l8-8-3-3L0 12z m3 2H1V12h1v1h1v1z m10.3-9.3l-1.3 1.3-3-3 1.3-1.3c0.39-0.39 1.02-0.39 1.41 0l1.59 1.59c0.39 0.39 0.39 1.02 0 1.41z</value> </data> <data name="person" xml:space="preserve"> - <value>M448 192C448 86 362.062 0 256 0S64 86 64 192c0 106.062 85.938 192 192 192S448 298.062 448 192zM256 320c-70.656 0-128-57.344-128-128S185.344 64 256 64c70.625 0 128 57.344 128 128S326.625 320 256 320zM384 384H256 128C57.344 384 0 441.344 0 512v128c0 70.625 57.344 128 128 128v256h256V768c70.625 0 128-57.375 128-128V512C512 441.344 454.625 384 384 384zM448 640c0 35.375-28.625 64-64 64V576h-64v384H192V576h-64v128c-35.312 0-64-28.625-64-64V512c0-35.312 28.688-64 64-64h256c35.375 0 64 28.688 64 64V640z</value> + <value>M 12 14.002 a 0.998 0.998 0 0 1 -0.998 0.998 H 1.001 A 1 1 0 0 1 0 13.999 V 13 c 0 -2.633 4 -4 4 -4 s 0.229 -0.409 0 -1 c -0.841 -0.62 -0.944 -1.59 -1 -4 c 0.173 -2.413 1.867 -3 3 -3 s 2.827 0.586 3 3 c -0.056 2.41 -0.159 3.38 -1 4 c -0.229 0.59 0 1 0 1 s 4 1.367 4 4 v 1.002 Z</value> </data> <data name="pin" xml:space="preserve"> - <value>M196.032 704l64 320 64-320c-20.125 2-41.344 3.188-62.281 3.188C239.22 707.188 217.47 706.312 196.032 704zM450.032 404.688c-16.188-15.625-40.312-44.375-62-84.688v-64c7.562-12.406 12.25-39.438 23.375-51.969 15.25-13.375 24-28.594 24-44.875 0-53.094-61.062-95.156-175.375-95.156-114.25 0-182.469 42.062-182.469 95.094 0 16 8.469 31.062 23.375 44.312 13.438 14.844 22.719 38 31.094 52.594v64c-32.375 62.656-82 96.188-82 96.188h0.656C18.749 437.876 0 464.126 0 492.344 0.063 566.625 101.063 640.062 260.032 640c159 0.062 259.625-73.375 259.625-147.656C519.657 458.875 493.407 428.219 450.032 404.688z</value> - </data> - <data name="playback_fast_forward" xml:space="preserve"> - <value>M0 768l384-256L0 256V768zM768 512L384 256v256 256L768 512z</value> - </data> - <data name="playback_pause" xml:space="preserve"> - <value>M0 832h192V192H0V832zM320 192v640h192V192H320z</value> - </data> - <data name="playback_play" xml:space="preserve"> - <value>M0 192l512 320L0 832V192z</value> - </data> - <data name="playback_rewind" xml:space="preserve"> - <value>M384 512l384 256V256L384 512zM0 512l384 256V512 256L0 512z</value> + <value>M10 1.2v0.8l0.5 1-4.5 3H2.2c-0.44 0-0.67 0.53-0.34 0.86l3.14 3.14L1 15l5-4 3.14 3.14c0.33 0.33 0.86 0.09 0.86-0.34V10l3-4.5 1 0.5h0.8c0.44 0 0.67-0.53 0.34-0.86L10.86 0.86c-0.33-0.33-0.86-0.09-0.86 0.34z</value> </data> <data name="plug" xml:space="preserve"> - <value>M1003.386 204.664l-0.905-0.905c-24.744-24.744-64.861-24.744-89.605 0l-45.707 45.707-90.51-90.51 45.707-45.707c24.744-24.744 24.744-64.861 0-89.605l-0.905-0.905c-24.744-24.744-64.861-24.744-89.605 0l-47.973 47.973C621.76 29.553999999999974 537.237 36.34000000000003 482.502 91.07399999999996l-24.89 24.89c-109.011 109.011-121.948 277.692-38.854 400.892l-4.138 4.138c-62.392 62.392-62.484 163.493-0.275 225.999 12.41 12.469 12.642 33.327 0.121 45.683-12.509 12.343-32.655 12.292-45.101-0.153l-89.427-89.427c-62.637-62.638-164.63-63.747-227.299-1.141-62.542 62.479-62.562 163.829-0.058 226.332l8.763 8.763c24.744 24.744 64.861 24.744 89.605 0l0.905-0.905c24.744-24.744 24.744-64.861 0-89.605l-8.292-8.292c-12.329-12.329-13.085-32.418-1.098-45.081 12.437-13.138 33.174-13.353 45.882-0.645l89.328 89.328c62.92 62.92 165.504 63.814 228.081 0.553 61.793-62.468 61.65-163.161-0.431-225.451-12.55-12.592-12.777-32.866-0.207-45.437l4.151-4.151c123.2 83.095 291.881 70.158 400.892-38.854l24.89-24.89c54.734-54.735 61.52-139.258 20.362-201.382l47.973-47.973C1028.129 269.525 1028.129 229.40700000000004 1003.386 204.664zM889.796 498.368c-37.49 37.49-98.274 37.49-135.765 0L527.757 272.09400000000005c-37.49-37.49-37.49-98.274 0-135.765 29.556-29.556 73.585-35.804 109.269-18.759l-41.839 41.839c-24.744 24.744-24.744 64.861 0 89.604l0.905 0.905c24.744 24.744 64.861 24.744 89.605 0l45.707-45.707 90.51 90.51-45.707 45.707c-24.744 24.744-24.744 64.861 0 89.605l0.905 0.905c24.744 24.744 64.861 24.744 89.604 0l41.839-41.839C925.6 424.782 919.351 468.812 889.796 498.368z</value> + <value>M15 6v-1H11V3H9v1H7c-1.03 0-1.77 0.81-2 2l-1 1c-1.66 0-3 1.34-3 3v2h1V10c0-1.11 0.89-2 2-2l1 1c0.25 1.16 0.98 2 2 2h2v1h2V10h4v-1H11V6h4z</value> </data> <data name="plus" xml:space="preserve"> - <value>M384 448V192H256v256H0v128h256v256h128V576h256V448H384z</value> - </data> - <data name="podium" xml:space="preserve"> - <value>M320 0c-64 0-64 64-64 64v63.874h-64l-192 192v128h192l64 384-128 64v64h512v-64l-128-64 64-384h192v-128l-192-192H320V64h32c17.673 0 32-14.327 32-32S369.673 0 352 0H320zM320 831.874L266.688 512h118.033L384 831.874H320zM96 319.874l128-128h32v64h64v-64h224l128 128H96z</value> + <value>M12 9H7v5H5V9H0V7h5V2h2v5h5v2z</value> </data> <data name="primitive_dot" xml:space="preserve"> - <value>M-0.088 512c0-141.5 114.5-256 256-256 141.438 0 256 114.5 256 256s-114.562 256-256 256C114.413 768-0.088 653.5-0.088 512z</value> + <value>M0 8c0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4S0 10.2 0 8z</value> </data> <data name="primitive_square" xml:space="preserve"> - <value>M512 768H0V256h512V768z</value> + <value>M8 12H0V4h8V12z</value> </data> <data name="pulse" xml:space="preserve"> - <value>M736 511.938L563.188 345.594 422.406 544 352 102.40599999999995 152.438 511.938H0V640h230.406L288 524.812l57.594 345.562L576 544l102.375 96H896V511.938H736z</value> - </data> - <data name="puzzle" xml:space="preserve"> - <value>M755.75 575.15c-13.95-9.96-28.52-16.59-43.47-19.92-8.84-1.69-18.06-2.33-27.57-1.81-8.99 0.5-17.56 1.68-25.69 3.52-6.1 1.69-12.22 3.89-18.35 6.59-18.18 8.02-33.89 18.12-46.79 30.33-12.22 12.9-22.32 28.62-30.34 46.79-2.7 6.12-4.9 12.24-6.59 18.34-1.84 8.14-3.03 16.7-3.52 25.69-0.52 9.51 0.12 18.73 1.81 27.57 3.33 14.95 9.96 29.52 19.92 43.47 3.89 5.44 8.08 10.4 12.56 14.88 20.06 20.03 45.83 30.7 75.42 34.11 8.92 1.02 18.12 1.68 26.53 4.48 5.12 1.7 9.16 4.08 12.08 7.02 6.65 6.6 7.63 16.1 2.5 27.24-3.15 6.84-7.7 13.45-12.96 18.84l-2.79 2.86c-3.93 3.92-6.41 6.4-7.05 7.04-3.13 3.16-6.1 6.15-9.06 9.15l-2.96 2.92c-10.52 10.58-21.09 21.12-31.66 31.65-22.76 22.82-45.57 45.58-68.38 68.36-7.5 7.5-15 15-22.5 22.49-3.46 3.45-7.07 6.38-10.78 8.79-1.8 1.22-3.49 2.24-5.18 3.16-19.6 9.89-41.43 5.92-59.24-11.88-5.4-5.4-10.62-10.62-15.85-15.84-30.25-30.25-60.48-60.52-90.77-90.73-8.59-8.57-17.13-17.08-25.68-25.59-6.12-6.09-12.67-11.85-19.56-17.06-5.72-4.33-11.59-7.56-17.46-9.73-21.16-7.32-41.41-2.01-54.67 13.26-3.81 4.8-7 10.47-9.39 16.94-3.43 9.26-4.6 19.47-5.9 29.36-4.9 37.53-25.8 68.43-55.98 82.65-7.48 3.65-15.49 6.29-23.9 7.78-7.95 1.41-15.95 1.71-23.85 1.04-26.61-1.35-49.48-13.09-68.51-32.57-1.68-1.67-2.1-2.09-2.51-2.51-19.48-19.02-31.22-41.9-32.57-68.5-0.68-7.9-0.37-15.9 1.04-23.85 1.49-8.41 4.13-16.43 7.78-23.9 14.22-30.18 45.13-51.07 82.65-55.97 9.89-1.29 20.1-2.47 29.36-5.9 6.94-2.56 12.96-6.05 17.97-10.23 14.54-13.15 19.59-32.63 12.84-52.34-2.78-7.35-6-13.22-10.33-18.94-5.21-6.88-10.97-13.43-17.06-19.55-8.51-8.55-17.03-17.09-25.55-25.63-26.92-26.98-53.84-53.88-80.75-80.78l-10.03-10.03c-5.22-5.22-10.45-10.45-15.26-15.27-18.39-18.4-22.35-40.22-12.46-59.82 0.92-1.69 1.94-3.37 3.08-5.05 2.49-3.84 5.42-7.45 8.87-10.91 7.49-7.5 14.99-15 22.49-22.5 22.77-22.81 45.54-45.62 68.36-68.38 10.53-10.57 21.06-21.14 31.65-31.66l2.92-2.96c2.99-2.97 5.99-5.93 8.98-8.9 0.8-0.81 3.28-3.29 7.2-7.22l2.86-2.79c5.39-5.26 12-9.8 18.84-12.96 11.14-5.13 20.63-4.15 27.24 2.5 2.94 2.92 5.32 6.96 7.02 12.08 2.79 8.41 3.45 17.61 4.48 26.53 3.41 29.59 14.08 55.35 34.11 75.41 4.49 4.48 9.44 8.67 14.88 12.56 13.95 9.96 28.52 16.59 43.47 19.92 8.84 1.69 18.06 2.33 27.57 1.81 8.99-0.5 17.56-1.68 25.69-3.52 6.1-1.69 12.22-3.89 18.35-6.59 18.18-8.02 33.89-18.12 46.79-30.33 12.22-12.9 22.32-28.62 30.34-46.79 2.7-6.12 4.9-12.24 6.59-18.34 1.84-8.14 3.03-16.7 3.52-25.69 0.52-9.51-0.12-18.73-1.81-27.57-3.33-14.95-9.96-29.52-19.92-43.47-3.89-5.44-8.08-10.4-12.56-14.88-20.06-20.03-45.83-30.7-75.42-34.11-8.92-1.02-18.12-1.68-26.53-4.48-5.12-1.7-9.16-4.08-12.08-7.02-6.65-6.6-7.63-16.1-2.5-27.24 3.15-6.84 7.7-13.45 12.96-18.84l2.79-2.86c3.93-3.92 6.41-6.4 7.05-7.04 3.13-3.16 6.1-6.15 9.06-9.15l2.96-2.92c10.52-10.58 21.09-21.12 31.66-31.65 22.76-22.82 45.57-45.58 68.38-68.35 7.5-7.5 15-15 22.5-22.49 3.46-3.45 7.07-6.38 10.78-8.79 1.8-1.22 3.49-2.24 5.18-3.16 19.6-9.89 41.43-5.92 59.24 11.88 5.4 5.4 10.62 10.62 15.85 15.84 30.25 30.25 60.48 60.52 90.77 90.73 8.59 8.57 17.13 17.08 25.68 25.59 6.12 6.09 12.67 11.85 19.56 17.06 5.72 4.33 11.59 7.56 17.46 9.73 21.16 7.32 41.41 2.01 54.67-13.26 3.81-4.8 7-10.47 9.39-16.94 3.43-9.26 4.6-19.47 5.9-29.36 4.9-37.53 25.8-68.43 55.98-82.65 7.48-3.65 15.49-6.28 23.9-7.78 7.95-1.41 15.95-1.71 23.85-1.04 26.61 1.35 49.48 13.09 68.51 32.57 1.68 1.67 2.1 2.09 2.51 2.51 19.48 19.02 31.22 41.9 32.57 68.5 0.68 7.9 0.37 15.9-1.04 23.85-1.49 8.41-4.13 16.43-7.78 23.9-14.22 30.18-45.13 51.07-82.65 55.97-9.89 1.29-20.1 2.47-29.36 5.9-6.94 2.56-12.96 6.05-17.97 10.23-14.54 13.15-19.59 32.63-12.84 52.34 2.78 7.35 6 13.22 10.33 18.94 5.21 6.88 10.97 13.43 17.06 19.55 8.51 8.55 17.03 17.09 25.55 25.63 30.26 30.33 60.54 60.56 90.78 90.81 5.22 5.22 10.45 10.45 15.26 15.27 18.39 18.4 22.35 40.22 12.46 59.82-0.92 1.69-1.94 3.37-3.08 5.05-2.49 3.84-5.42 7.45-8.87 10.91-7.49 7.5-14.99 15-22.49 22.5-22.77 22.81-45.54 45.62-68.36 68.38-10.53 10.57-21.06 21.14-31.65 31.66l-2.92 2.96c-2.99 2.97-5.99 5.93-8.98 8.9-0.8 0.81-3.28 3.29-7.2 7.22l-2.86 2.79c-5.39 5.26-12 9.8-18.84 12.96-11.14 5.13-20.63 4.15-27.24-2.5-2.94-2.92-5.32-6.96-7.02-12.08-2.79-8.41-3.45-17.61-4.48-26.53-3.41-29.59-14.08-55.35-34.11-75.41C766.15 583.23 761.19 579.03 755.75 575.15z</value> + <value>M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z</value> </data> <data name="question" xml:space="preserve"> - <value>M448 768h128V640H448V768zM512 256c0 0-192 0-192 192 8 1.344 128 0 128 0s0-64 64-64 64 64 64 64c0 64-129.781 64-129.781 128H576c0 0 128 0 128-160S512 256 512 256zM512 0C229.25 0 0 229.25 0 512s229.25 512 512 512 512-229.25 512-512S794.75 0 512 0zM512 896c-212.031 0-384-172-384-384 0-212.031 171.969-384 384-384 212 0 384 171.969 384 384C896 724 724 896 512 896z</value> + <value>M6 10h2v2H6V10z m4-3.5c0 2.14-2 2.5-2 2.5H6c0-0.55 0.45-1 1-1h0.5c0.28 0 0.5-0.22 0.5-0.5v-1c0-0.28-0.22-0.5-0.5-0.5h-1c-0.28 0-0.5 0.22-0.5 0.5v0.5H4c0-1.5 1.5-3 3-3s3 1 3 2.5zM7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z</value> </data> <data name="quote" xml:space="preserve"> - <value>M0 512v256h256V512H128c0 0 0-128 128-128V256C256 256 0 256 0 512zM640 384V256c0 0-256 0-256 256v256h256V512H512C512 512 512 384 640 384z</value> + <value>M6.16 3.17C3.73 4.73 2.55 6.34 2.55 9.03c0.16-0.05 0.3-0.05 0.44-0.05 1.27 0 2.5 0.86 2.5 2.41 0 1.61-1.03 2.61-2.5 2.61C1.09 14 0 12.48 0 9.75 0 5.95 1.75 3.22 5.02 1.33l1.14 1.84z m7 0C10.73 4.73 9.55 6.34 9.55 9.03c0.16-0.05 0.3-0.05 0.44-0.05 1.27 0 2.5 0.86 2.5 2.41 0 1.61-1.03 2.61-2.5 2.61-1.89 0-2.98-1.52-2.98-4.25 0-3.8 1.75-6.53 5.02-8.42l1.14 1.84z</value> </data> <data name="radio_tower" xml:space="preserve"> - <value>M306.838 390.739c15.868-16.306 15.868-42.731 0-59.037-20.521-21.116-30.643-48.417-30.705-76.124 0.062-27.77 10.183-55.039 30.705-76.186 15.868-16.337 15.868-42.764 0-59.069-7.934-8.184-18.272-12.275-28.706-12.275-10.371 0-20.804 4.029-28.738 12.213-36.266 37.297-54.633 86.433-54.57 135.317-0.062 48.792 18.305 97.927 54.57 135.161C265.262 407.045 290.97 407.045 306.838 390.739zM149.093 33.14200000000005c-8.121-8.309-18.68-12.463-29.3-12.463-10.558 0-21.179 4.154-29.237 12.463C30.8 94.49099999999999 0.751 175.144 0.813 255.57799999999997 0.751 335.919 30.8 416.728 90.494 478.015c16.181 16.618 42.356 16.618 58.537 0 16.118-16.587 16.118-43.513 0-60.067-43.7-44.98-65.44-103.456-65.44-162.368s21.74-117.449 65.44-162.368C165.149 76.56100000000004 165.149 49.63499999999999 149.093 33.14200000000005zM513.031 359.847c57.351 0 103.956-46.574 103.956-103.956 0-57.382-46.605-103.955-103.956-103.955-57.381 0-103.956 46.573-103.956 103.955C409.076 313.273 455.65 359.847 513.031 359.847zM933.539 33.76700000000005c-16.181-16.618-42.355-16.618-58.475 0-16.181 16.587-16.181 43.513 0 60.068 43.668 44.918 65.409 103.456 65.409 162.368 0 58.85-21.805 117.387-65.473 162.306-16.117 16.618-16.117 43.575 0.062 60.068 8.059 8.309 18.616 12.463 29.237 12.463 10.558 0 21.178-4.154 29.236-12.463 59.726-61.287 89.774-142.096 89.649-222.437C1023.313 175.86199999999997 993.264 95.053 933.539 33.76700000000005zM513.281 442.873L513.281 442.873c-26.489 0.062-53.04-6.466-77.091-19.429L235.057 959.59h95.209l54.819-63.973h255.891l53.977 63.973h95.272L589.124 423.569C565.384 436.345 539.395 442.873 513.281 442.873zM512.656 473.517L577.004 703.7H449.059L512.656 473.517zM385.086 831.645l63.974-63.973h127.944l63.974 63.973H385.086zM717.194 121.04200000000003c-15.868 16.306-15.868 42.731 0 59.037 20.491 21.116 30.611 48.511 30.674 76.124-0.062 27.77-10.183 55.102-30.674 76.187-15.868 16.336-15.868 42.763 0 59.068 7.871 8.184 18.242 12.213 28.737 12.213 10.309 0 20.741-4.029 28.675-12.213 36.298-37.234 54.665-86.433 54.54-135.255 0.125-48.792-18.181-97.927-54.54-135.161C758.801 104.73599999999999 733.062 104.73599999999999 717.194 121.04200000000003z</value> + <value>M4.79 6.11c0.25-0.25 0.25-0.67 0-0.92-0.32-0.33-0.48-0.76-0.48-1.19 0-0.43 0.16-0.86 0.48-1.19 0.25-0.26 0.25-0.67 0-0.92-0.12-0.13-0.29-0.19-0.45-0.19-0.16 0-0.33 0.06-0.45 0.19-0.57 0.58-0.85 1.35-0.85 2.11 0 0.76 0.29 1.53 0.85 2.11C4.14 6.36 4.55 6.36 4.79 6.11zM2.33 0.52c-0.13-0.13-0.29-0.19-0.46-0.19-0.16 0-0.33 0.06-0.46 0.19C0.48 1.48 0.01 2.74 0.01 3.99 0.01 5.25 0.48 6.51 1.41 7.47c0.25 0.26 0.66 0.26 0.91 0 0.25-0.26 0.25-0.68 0-0.94-0.68-0.7-1.02-1.62-1.02-2.54s0.34-1.84 1.02-2.54C2.58 1.2 2.58 0.78 2.33 0.52zM8.02 5.62c0.9 0 1.62-0.73 1.62-1.62 0-0.9-0.73-1.62-1.62-1.62-0.9 0-1.62 0.73-1.62 1.62C6.39 4.89 7.12 5.62 8.02 5.62zM14.59 0.53c-0.25-0.26-0.66-0.26-0.91 0-0.25 0.26-0.25 0.68 0 0.94 0.68 0.7 1.02 1.62 1.02 2.54 0 0.92-0.34 1.83-1.02 2.54-0.25 0.26-0.25 0.68 0 0.94 0.13 0.13 0.29 0.19 0.46 0.19 0.16 0 0.33-0.06 0.46-0.19 0.93-0.96 1.4-2.22 1.4-3.48C15.99 2.75 15.52 1.49 14.59 0.53zM8.02 6.92L8.02 6.92c-0.41 0-0.83-0.1-1.2-0.3L3.67 14.99h1.49l0.86-1h4l0.84 1h1.49L9.21 6.62C8.83 6.82 8.43 6.92 8.02 6.92zM8.01 7.4L9.02 11H7.02L8.01 7.4zM6.02 12.99l1-1h2l1 1H6.02zM11.21 1.89c-0.25 0.25-0.25 0.67 0 0.92 0.32 0.33 0.48 0.76 0.48 1.19 0 0.43-0.16 0.86-0.48 1.19-0.25 0.26-0.25 0.67 0 0.92 0.12 0.13 0.29 0.19 0.45 0.19 0.16 0 0.32-0.06 0.45-0.19 0.57-0.58 0.85-1.35 0.85-2.11 0-0.76-0.28-1.53-0.85-2.11C11.86 1.64 11.45 1.64 11.21 1.89z</value> </data> <data name="repo_clone" xml:space="preserve"> - <value>M320 384h-64v64h64v-64z m-128-320h256v-64h-384s-64-1-64 64c0 293 0 768 0 768 0 10 3 64 64 64h128v128l96-96 96 96v-128h318c67 0 66-64 66-64v-192h-576v-576z m512 640s0 32 0 64c0 62-66 64-66 64s-124 0-254 0v-64h-192v64h-66c-64 0-62-64-62-64v-64h640z m-384-448h-64v64h64v-64z m-64 320h64v-64h-64v64z m704-576h-320s-64 0-64 64v384c0 64 64 64 64 64h64v64l32-32 32 32v-64h192s64 0 64-64v-384c0-64-64-64-64-64z m-256 448s0 0-32 0-32-32-32-32v-32h64v64z m256-32s0 32-32 32-160 0-160 0v-64h192v32z m0-96h-256v-256h224s32 0 32 32 0 224 0 224z m-640-192h-64v64h64v-64z</value> + <value>M15 0H9v7c0 0.55 0.45 1 1 1h1v1h1v-1h3c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1zM11 7h-1v-1h1v1z m4 0H12v-1h3v1z m0-2H11V1h4v4z m-11 0h-1v-1h1v1z m0-2h-1v-1h1v1zM2 1h6V0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h2v2l1.5-1.5 1.5 1.5V14h5c0.55 0 1-0.45 1-1V10H2V1z m9 10v2H6v-1H3v1H1V11h10zM3 8h1v1h-1v-1z m1-1h-1v-1h1v1z</value> </data> <data name="repo_force_push" xml:space="preserve"> - <value>M767.698 63.75199999999995c-1.625-64.251-63.75-63.751-63.75-63.751h-640c0 0-64-1.219-64 64 0 293.438 0 768 0 768H0.01c0.125 9.625 3.406 64 63.938 64h128v128l128-128v-128h-128v64h-66c-64 0-62-64-62-64v-64h256v-64h-128v-576h512v576h-128v64h128c0 0 0 32 0 64 0 61.625-66 64-66 64s-24.5 0-62 0v64h126c66.875 0 66-64 66-64L767.698 63.75199999999995zM495.948 384.002h144l-192-256-192 256h144l-144 192h128v448h128v-448h128L495.948 384.002z</value> + <value>M10 9H8v7H6V9H4l2.25-3H4l3-4 3 4H7.75l2.25 3zM11 0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h4v-1H1V11h4v-1H2V1h9v9H9v1h2v2H9v1h2c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1z</value> </data> <data name="repo_forked" xml:space="preserve"> - <value>M768 128c0-71-57-128-128-128s-128 57-128 128c0 47 26 89 64 111v106l-192 212-192-212v-106c38-22 64-63 64-111 0-71-57-128-128-128s-128 57-128 128c0 47 26 89 64 111v156l256 282v109c-38 22-64 63-64 111 0 71 57 128 128 128s128-57 128-128c0-47-26-89-64-111v-109l256-282v-156c38-22 64-63 64-111z m-640-63c34 0 62 28 62 62s-28 62-62 62-62-28-62-62 28-62 62-62z m256 891c-34 0-62-28-62-62s28-62 62-62 62 28 62 62-28 62-62 62z m256-891c34 0 62 28 62 62s-28 62-62 62-62-28-62-62 28-62 62-62z</value> + <value>M8 1c-1.11 0-2 0.89-2 2 0 0.73 0.41 1.38 1 1.72v1.28L5 8 3 6v-1.28c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v1.78l3 3v1.78c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V9.5l3-3V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2zM2 4.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3 10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3-10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z</value> </data> <data name="repo_pull" xml:space="preserve"> - <value>M1024 320L832 128v128H448v128h384v128L1024 320zM704 640H192V64h512v128h64V64c0-64-64-64-64-64H64C64 0 0 0 0 64v768c0 64 64 64 64 64h128v128l96-96 96 96V896h320c0 0 64-1.125 64-64V448h-64V640zM704 768c0 61.625-64 64-64 64H384v-64H192v64h-64c-64 0-64-64-64-64v-64h640V768zM320 256h-64v64h64V256zM320 128h-64v64h64V128zM320 384h-64v64h64V384zM256 576h64v-64h-64V576z</value> + <value>M13 8V6H7V4h6V2l3 3-3 3zM4 2h-1v1h1v-1z m7 5h1v6c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1v2h-1V1H2v9h9V7z m0 4H1v2h2v-1h3v1h5V11zM4 6h-1v1h1v-1z m0-2h-1v1h1v-1z m-1 5h1v-1h-1v1z</value> </data> <data name="repo_push" xml:space="preserve"> - <value>M448 320L256 576h128v448h128V576h128L448 320zM256 320h64v-64h-64V320zM320 128h-64v64h64V128zM704 0H64C64 0 0 0 0 64v768c0 64 64 64 64 64h128v128l128-128V768H192v64h-64c-64 0-64-64-64-64v-64h256v-64H192V64h513l-1 576H576v64h128v64c0 61.625-64 64-64 64h-64v64h128c0 0 64-1.125 64-64V64C768 0 704 0 704 0z</value> + <value>M4 3h-1v-1h1v1z m-1 2h1v-1h-1v1z m4 0L4 9h2v7h2V9h2L7 5zM11 0H1C0.45 0 0 0.45 0 1v12c0 0.55 0.45 1 1 1h4v-1H1V11h4v-1H2V1h9.02l-0.02 9H9v1h2v2H9v1h2c0.55 0 1-0.45 1-1V1c0-0.55-0.45-1-1-1z</value> </data> <data name="repo" xml:space="preserve"> - <value>M320 256h-64v64h64V256zM320 128h-64v64h64V128zM704 0H64C64 0 0 0 0 64v768c0 64 64 64 64 64h128v128l96-96 96 96V896h320c0 0 64-1.125 64-64V64C768 0 704 0 704 0zM704 768c0 61.625-64 64-64 64H384v-64H192v64h-64c-64 0-64-64-64-64v-64h640V768zM704 640H192V64h513L704 640zM320 512h-64v64h64V512zM320 384h-64v64h64V384z</value> + <value>M4 9h-1v-1h1v1z m0-3h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m8-1v12c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1z m-1 10H1v2h2v-1h3v1h5V11z m0-10H2v9h9V1z</value> </data> <data name="rocket" xml:space="preserve"> - <value>M716.737 124.05600000000004c-71.926 41.686-148.041 96.13-218.436 166.555-45 45.031-81.213 88.78-110.39 129.778L209.538 453.35 0.047 662.997l186.818 5.815 131.562-131.562c-46.439 96.224-50.536 160.019-50.536 160.019l58.854 58.792c0 0 65.827-6.255 162.737-53.163L355.107 837.119l5.88 186.881 209.585-209.521 33.086-179.252c41.403-29.02 85.185-65.046 129.716-109.545 70.425-70.455 124.837-146.541 166.555-218.466-45.97-9.351-88.125-28.488-121.397-61.668C745.257 212.18100000000004 725.994 170.02499999999998 716.737 124.05600000000004zM786.161 86.84299999999996c5.004 45 19.952 81.274 44.78 105.98 24.769 24.985 60.98 39.902 106.138 44.844C1003.063 104.32299999999998 1023.953 0 1023.953 0S919.63 20.857999999999947 786.161 86.84299999999996z</value> + <value>M16 0s-0.09 0.38-0.3 1.06c-0.2 0.7-0.55 1.58-1.06 2.66-0.7-0.08-1.27-0.33-1.66-0.72s-0.63-0.94-0.7-1.64c1.08-0.52 1.95-0.88 2.64-1.08 0.7-0.2 1.08-0.28 1.08-0.28zM12.17 3.83c-0.27-0.27-0.47-0.55-0.63-0.88-0.16-0.31-0.27-0.66-0.34-1.02-0.58 0.33-1.16 0.7-1.73 1.13-0.58 0.44-1.14 0.94-1.69 1.48-0.7 0.7-1.33 1.81-1.78 2.45H3L0 10h3l2-2c-0.34 0.77-1.02 2.98-1 3l1 1c0.02 0.02 2.23-0.64 3-1L6 13v3l3-3V10c0.64-0.45 1.75-1.09 2.45-1.78 0.55-0.55 1.05-1.13 1.47-1.7 0.44-0.58 0.81-1.16 1.14-1.72-0.36-0.08-0.7-0.19-1.03-0.34-0.31-0.16-0.59-0.36-0.86-0.63z</value> </data> <data name="rss" xml:space="preserve"> - <value>M128 640C57.344 640 0 697.375 0 768s57.344 128 128 128 128-57.375 128-128S198.656 640 128 640zM128 384c0 0-64 2-64 64s64 64 64 64c141.375 0 256 114.625 256 256 0 0 0 64 64 64s64-64 64-64C512 556 340.031 384 128 384zM128 128c0 0-64 0-64 64s64 64 64 64c282.75 0 512 229.25 512 512 0 0 0 64 64 64s64-64 64-64C768 414.594 481.5 128 128 128z</value> + <value>M2 13H0V11c1.11 0 2 0.89 2 2zM0 3v1c4.97 0 9 4.03 9 9h1c0-5.52-4.48-10-10-10z m0 4v1c2.75 0 5 2.25 5 5h1c0-3.31-2.69-6-6-6z</value> </data> <data name="ruby" xml:space="preserve"> - <value>M768 128H256L0 384l512 512 512-512L768 128zM128 384l192-192h384l192 192L512 768 128 384zM704 256H512v448l320-320L704 256z</value> - </data> - <data name="screen_full" xml:space="preserve"> - <value>M128 768h639.875V256H128V768zM255.938 384h384v256h-384V384zM64 192.062h191.938v-64H0V384h64V192.062zM64 640H0v255.938h255.938V832H64V640zM639.938 128.062v64h191.938V384h64V128.062H639.938zM831.875 832H639.938v63.938h255.938V640h-64V832z</value> - </data> - <data name="screen_normal" xml:space="preserve"> - <value>M127.938 191.938H0v64h191.938V64h-64V191.938zM0 832.062h127.938V960h64V768.062H0V832.062zM768.062 191.938V64h-64v191.938H896v-64H768.062zM704.062 960h64V832.062H896v-64H704.062V960zM192.062 704H704V320H192.062V704zM320 448h256v128H320V448z</value> + <value>M13 6L8 11V4h3l2 2z m3 0L8 14 0 6l4-4h8l4 4zM8 12.5l6.5-6.5-3-3H4.5L1.5 6l6.5 6.5z</value> </data> <data name="search" xml:space="preserve"> - <value>M960 832L710.875 582.875C746.438 524.812 768 457.156 768 384 768 171.96900000000005 596 0 384 0 171.969 0 0 171.96900000000005 0 384c0 212 171.969 384 384 384 73.156 0 140.812-21.562 198.875-57L832 960c17.5 17.5 46.5 17.375 64 0l64-64C977.5 878.5 977.5 849.5 960 832zM384 640c-141.375 0-256-114.625-256-256s114.625-256 256-256 256 114.625 256 256S525.375 640 384 640z</value> + <value>M15.7 14.3L11.89 10.47c0.7-0.98 1.11-2.17 1.11-3.47 0-3.31-2.69-6-6-6S1 3.69 1 7s2.69 6 6 6c1.3 0 2.48-0.41 3.47-1.11l3.83 3.81c0.19 0.2 0.45 0.3 0.7 0.3s0.52-0.09 0.7-0.3c0.39-0.39 0.39-1.02 0-1.41zM7 11.7c-2.59 0-4.7-2.11-4.7-4.7s2.11-4.7 4.7-4.7 4.7 2.11 4.7 4.7-2.11 4.7-4.7 4.7z</value> </data> <data name="server" xml:space="preserve"> - <value>M0 128v128c0 0 1.906 64 64 64s577.125 0 640 0 64-64 64-64V128c0 0-0.5-64-64-64s-588.468 0-640 0C-0.906 64 0 128 0 128zM0 448v128c0 0 1.906 64 64 64s577.125 0 640 0 64-64 64-64V448c0 0-0.5-64-64-64s-588.468 0-640 0C-0.906 384 0 448 0 448zM0 768v128c0 0 1.906 64 64 64s577.125 0 640 0 64-64 64-64V768c0 0-0.5-64-64-64s-588.468 0-640 0C-0.906 704 0 768 0 768zM64 576V448h64v128H64zM192 576V448h64v128H192zM320 576V448h64v128H320zM448 576V448h64v128H448zM640 512v-64h64v64H640zM64 256V128h64v128H64zM192 256V128h64v128H192zM320 256V128h64v128H320zM448 256V128h64v128H448zM640 192v-64h64v64H640zM64 896V768h64v128H64zM192 896V768h64v128H192zM320 896V768h64v128H320zM448 896V768h64v128H448zM640 832v-64h64v64H640z</value> + <value>M11 6H1c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V7c0-0.55-0.45-1-1-1zM2 9H1V7h1v2z m2 0h-1V7h1v2z m2 0h-1V7h1v2z m2 0h-1V7h1v2zM11 1H1C0.45 1 0 1.45 0 2v2c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1zM2 4H1V2h1v2z m2 0h-1V2h1v2z m2 0h-1V2h1v2z m2 0h-1V2h1v2z m3-1h-1v-1h1v1z m0 8H1c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h10c0.55 0 1-0.45 1-1V12c0-0.55-0.45-1-1-1zM2 14H1V12h1v2z m2 0h-1V12h1v2z m2 0h-1V12h1v2z m2 0h-1V12h1v2z</value> </data> <data name="settings" xml:space="preserve"> - <value>M64 896h128V704H64V896zM192 128H64v320h128V128zM512 128H384v128h128V128zM0 640h256V512H0V640zM384 896h128V512H384V896zM320 448h256V320H320V448zM832 128H704v384h128V128zM640 576v128h256V576H640zM704 896h128V768H704V896z</value> + <value>M3 7h-1V2h1v5z m-1 7h1V11h-1v3z m5 0h1V8h-1v6z m5 0h1V12h-1v2z m1-12h-1v6h1V2z m-5 0h-1v2h1V2zM4 8H1c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1s-0.45-1-1-1z m5-3H6c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1s-0.45-1-1-1z m5 4H11c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1s-0.45-1-1-1z</value> + </data> + <data name="shield" xml:space="preserve"> + <value>M7 0L0 2v6.02c0 4.67 5.31 7.98 7 7.98s7-3.31 7-7.98V2L7 0zM5 11l1.14-2.8c0.05-0.23-0.06-0.47-0.25-0.59-0.56-0.36-0.89-0.95-0.89-1.61 0-1.09 0.89-2 1.98-2 1.08 0 2.02 0.91 2.02 2 0 0.66-0.33 1.25-0.89 1.61-0.19 0.13-0.3 0.36-0.25 0.59l1.14 2.8H5z</value> </data> <data name="sign_in" xml:space="preserve"> - <value>M640 576L640 448 896 448 896 320 640 320 640 192 448 336 448 192 192 64 704 64 704 256 768 256 768 0 64 0 64 832 448 1024 448 832 768 832 768 512 704 512 704 768 448 768 448 432z</value> + <value>M6 6.75v5.25h4V8h1v4c0 0.55-0.45 1-1 1H6v3L0.55 13.28c-0.33-0.17-0.55-0.52-0.55-0.91V1C0 0.45 0.45 0 1 0h9c0.55 0 1 0.45 1 1v3h-1V1H2l4 2v2.25l3-2.25v2h4v2H9v2L6 6.75z</value> </data> <data name="sign_out" xml:space="preserve"> - <value>M640 768H384V192L128 64h512v192h64V0H0v832l384 192V832h320V512h-64V768zM1024 384L768 192v128H512v128h256v128L1024 384z</value> + <value>M12 9V7H8V5h4V3l4 3-4 3zM10 12H6V3L2 1h8v3h1V1c0-0.55-0.45-1-1-1H1C0.45 0 0 0.45 0 1v11.38c0 0.39 0.22 0.73 0.55 0.91l5.45 2.72V13h4c0.55 0 1-0.45 1-1V8h-1v4z</value> </data> - <data name="split" xml:space="preserve"> - <value>M448 256L192 0 0 192l310.72 300.41c6.42-20.87 13.42-39.62 19.99-55.5 19.94-48.141 55.68-117.5 112.7-174.52L448 256zM576 0l133.49 133.49L512 330.98c-45.74 45.739-75.1 103.039-91.67 143.05C403.76 514.04 384 575.32 384 640v384h256V640c0-36.44 27.25-102.23 53.02-128l197.49-197.49L1024 448V0H576z</value> + <data name="smiley" xml:space="preserve"> + <value>M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8S12.42 0 8 0z m4.81 12.81c-0.63 0.63-1.36 1.11-2.17 1.45-0.83 0.36-1.72 0.53-2.64 0.53s-1.81-0.17-2.64-0.53c-0.81-0.34-1.55-0.83-2.17-1.45s-1.11-1.36-1.45-2.17c-0.36-0.83-0.53-1.72-0.53-2.64s0.17-1.81 0.53-2.64c0.34-0.81 0.83-1.55 1.45-2.17s1.36-1.11 2.17-1.45c0.83-0.36 1.72-0.53 2.64-0.53s1.81 0.17 2.64 0.53c0.81 0.34 1.55 0.83 2.17 1.45s1.11 1.36 1.45 2.17c0.36 0.83 0.53 1.72 0.53 2.64s-0.17 1.81-0.53 2.64c-0.34 0.81-0.83 1.55-1.45 2.17zM4 5.8v-0.59c0-0.66 0.53-1.19 1.2-1.19h0.59c0.66 0 1.19 0.53 1.19 1.19v0.59c0 0.67-0.53 1.2-1.19 1.2h-0.59c-0.67 0-1.2-0.53-1.2-1.2z m5 0v-0.59c0-0.66 0.53-1.19 1.2-1.19h0.59c0.66 0 1.19 0.53 1.19 1.19v0.59c0 0.67-0.53 1.2-1.19 1.2h-0.59c-0.67 0-1.2-0.53-1.2-1.2z m4 4.2c-0.72 1.88-2.91 3-5 3s-4.28-1.13-5-3c-0.14-0.39 0.23-1 0.66-1h8.59c0.41 0 0.89 0.61 0.75 1z</value> </data> <data name="squirrel" xml:space="preserve"> - <value>M768 64c-141.385 0-256 83.75-256 186.875C512 374.75 544 445 512 640c0-288-177-405.783-256-405.783 3.266-32.17-30.955-42.217-30.955-42.217s-14 7.124-19.354 21.583c-17.231-20.053-36.154-17.54-36.154-17.54l-8.491 37.081c0 0-117.045 40.876-118.635 206.292C56 461 141.311 478.102 201.887 467.118c57.157 2.956 42.991 50.648 30.193 63.446C178.083 584.562 128 512 64 512s-64 64 0 64 64 64 192 64c-198 77 0 256 0 256h-64c-64 0-64 64-64 64s256 0 384 0c192 0 320-64 320-222.182 0-54.34-27.699-114.629-64-162.228C697.057 482.567 782.453 404.434 832 448s192 64 192-128C1024 178.615 909.385 64 768 64zM160 384c-17.674 0-32-14.327-32-32 0-17.674 14.326-32 32-32 17.673 0 32 14.326 32 32C192 369.673 177.673 384 160 384z</value> + <value>M12 1c-2.21 0-4 1.31-4 2.92C8 5.86 8.5 6.95 8 10c0-4.5-2.77-6.34-4-6.34 0.05-0.5-0.48-0.66-0.48-0.66s-0.22 0.11-0.3 0.34c-0.27-0.31-0.56-0.27-0.56-0.27l-0.13 0.58c0 0-1.83 0.64-1.85 3.22C0.88 7.2 2.21 7.47 3.15 7.3c0.89 0.05 0.67 0.79 0.47 0.99C2.78 9.13 2 8 1 8s-1 1 0 1 1 1 3 1c-3.09 1.2 0 4 0 4h-1c-1 0-1 1-1 1s4 0 6 0c3 0 5-1 5-3.47 0-0.85-0.43-1.79-1-2.53C10.89 7.54 12.23 6.32 13 7s3 1 3-2C16 2.79 14.21 1 12 1zM2.5 6c-0.28 0-0.5-0.22-0.5-0.5 0-0.28 0.22-0.5 0.5-0.5 0.28 0 0.5 0.22 0.5 0.5C3 5.78 2.78 6 2.5 6z</value> </data> <data name="star" xml:space="preserve"> - <value>M896 384l-313.5-40.781L448 64 313.469 343.219 0 384l230.469 208.875L171 895.938l277-148.812 277.062 148.812L665.5 592.875 896 384z</value> - </data> - <data name="steps" xml:space="preserve"> - <value>M136 64C60.89 64 0 164.28999999999996 0 288c0 68.83 17.02 141.84 34 254.54C47.3 630.83 79.67 704 136 704s94.08-48.79 94.08-137.97c0-30.37-24.97-78.75-26.08-120.03-2.02-74.46 49.93-104.17 49.93-173C253.93 149.28999999999996 211.1 64 136 64zM502.97 320c-75.1 0-117.93 85.29-117.93 209 0 68.83 51.95 98.54 49.93 173-1.109 41.28-26.08 89.66-26.08 120.03 0 89.18 37.75 137.97 94.08 137.97s88.7-73.17 102-161.46c16.98-112.7 34-185.71 34-254.54C638.97 420.29 578.08 320 502.97 320z</value> + <value>M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z</value> </data> <data name="stop" xml:space="preserve"> - <value>M704 0H320L0 320v384l320 320h384l320-320V320L704 0zM896 640L640 896H384L128 640V384l256-256h256l256 256V640zM448 576h128V256H448V576zM448 768h128V640H448V768z</value> + <value>M10 1H4L0 5v6l4 4h6l4-4V5L10 1z m3 9.5L9.5 14H4.5L1 10.5V5.5l3.5-3.5h5l3.5 3.5v5zM6 4h2v5H6V4z m0 6h2v2H6V10z</value> </data> <data name="sync" xml:space="preserve"> - <value>M655.461 473.469c11.875 81.719-13.062 167.781-76.812 230.594-94.188 92.938-239.5 104.375-346.375 34.562l74.875-73L31.96 627.25 70.367 896l84.031-80.5c150.907 111.25 364.938 100.75 502.063-34.562 79.5-78.438 115.75-182.562 111.25-285.312L655.461 473.469zM189.46 320.062c94.156-92.938 239.438-104.438 346.313-34.562l-75 72.969 275.188 38.406L697.586 128l-83.938 80.688C462.711 97.34400000000005 248.742 107.96900000000005 111.585 243.25 32.085 321.656-4.133 425.781 0.335 528.5l112.25 22.125C100.71 468.875 125.71 382.906 189.46 320.062z</value> + <value>M10.24 7.4c0.19 1.28-0.2 2.62-1.2 3.6-1.47 1.45-3.74 1.63-5.41 0.54l1.17-1.14L0.5 9.8 1.1 14l1.31-1.26c2.36 1.74 5.7 1.57 7.84-0.54 1.24-1.23 1.81-2.85 1.74-4.46L10.24 7.4zM2.96 5c1.47-1.45 3.74-1.63 5.41-0.54l-1.17 1.14 4.3 0.6L10.9 2l-1.31 1.26C7.23 1.52 3.89 1.69 1.74 3.8 0.5 5.03-0.06 6.65 0.01 8.26l1.75 0.35C1.57 7.33 1.96 5.98 2.96 5z</value> </data> <data name="tag" xml:space="preserve"> - <value>M384 64H128L0 192v256l512 512 384-384L384 64zM64 416V224l96-96h192l448 448L512 864 64 416zM448 320L256 512l256 256 192-192L448 320zM352 512l96-96 160 160-96 96L352 512zM320 288c0-53-43-96-96-96s-96 43-96 96 43 96 96 96S320 341 320 288zM224 320c-17.656 0-32-14.344-32-32s14.344-32 32-32 32 14.344 32 32S241.656 320 224 320z</value> + <value>M6.73 2.73c-0.47-0.47-1.11-0.73-1.77-0.73H2.5C1.13 2 0 3.13 0 4.5v2.47c0 0.66 0.27 1.3 0.73 1.77l6.06 6.06c0.39 0.39 1.02 0.39 1.41 0l4.59-4.59c0.39-0.39 0.39-1.02 0-1.41L6.73 2.73zM1.38 8.09c-0.31-0.3-0.47-0.7-0.47-1.13V4.5c0-0.88 0.72-1.59 1.59-1.59h2.47c0.42 0 0.83 0.16 1.13 0.47l6.14 6.13-4.73 4.73L1.38 8.09z m0.63-4.09h2v2H2V4z</value> + </data> + <data name="tasklist" xml:space="preserve"> + <value>M15.41 9H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM9.59 4c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h5.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H9.59zM0 3.91l1.41-1.3 1.59 1.59L7.09 0l1.41 1.41-5.5 5.5L0 3.91z m7.59 8.09h7.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H7.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z</value> </data> <data name="telescope" xml:space="preserve"> - <value>M77.771 425.469l1.281-3.062 225.906-57.812C304.083 371 303.052 384 303.052 384c0 128 128 128 128 128s128 0 128-128c0-10.812-8.438-22.094-19.062-32.125l48.938 2.938c0 0 7.375-1.969 30.875-8.312-51.25 13.75-107.625-30.531-125.875-98.75-18.25-68.281 8.439-134.781 59.625-148.5-23.5 6.281-30.875 8.281-30.875 8.281L354.24 217.59400000000005c-34.094 9.156-54.904 45.625-45.75 79.812 2.375 8.844 6.596 16.594 11.971 23.219-7.031 12.312-12.346 25.5-15.031 39.812-27-1.344-51.406-18.938-58.75-46.438-9.094-34.188 11.125-69.25 45.25-78.344L47.052 300.812c-34.125 9.125-54 42.688-44.812 76.781C11.333 411.719 43.646 434.625 77.771 425.469zM495.052 576h-128v64l-320 320h128l192-128v128h128V832l192 128h128l-320-320V576zM923.865 128c-18.25-68.219-70.439-109.75-121.625-96-68.688 18.375-98.312 27.781-185.562 51.188-51.25 13.719-79.188 79.688-60.875 147.969 18.25 68.312 73.625 115.406 124.875 101.656 87.25-23.375 116.875-32.781 185.562-51.219C917.427 267.84400000000005 942.177 196.28099999999995 923.865 128zM853.427 217.59400000000005c-17.062 4.562-41.25-18.562-50.375-52.719l-0.812 1.531c-9.125-34.125-4.312-65.875 12.812-70.406s42.062 17.094 51.188 51.188C875.427 181.375 870.552 213.062 853.427 217.59400000000005z</value> + <value>M8 9l3 6h-1L8 11v5h-1V10L5 15h-1l2-5 2-1zM7 0h-1v1h1V0zM5 3h-1v1h1v-1zM2 1H1v1h1V1zM0.63 9c-0.22 0.16-0.28 0.44-0.16 0.67l0.55 0.92c0.13 0.23 0.41 0.31 0.64 0.2l1.39-0.66-1.16-2-1.27 0.86z m7.89-5.39L2.72 7.56l1.23 2.14 6.33-3.03-1.77-3.06z m4.22 1.28l-1.47-2.52c-0.14-0.25-0.47-0.33-0.72-0.17l-1.2 0.83 1.84 3.2 1.33-0.64c0.27-0.13 0.36-0.44 0.22-0.7z</value> </data> <data name="terminal" xml:space="preserve"> - <value>M831 127H63c-35.35 0-64 28.65-64 64v640c0 35.35 28.65 64 64 64h768c35.35 0 64-28.65 64-64V191C895 155.64999999999998 866.35 127 831 127zM127 575l128-128L127 319l64-64 192 192L191 639 127 575zM639 639H383v-64h256V639z</value> + <value>M7 10h4v1H7v-1z m-3 1l3-3-3-3-0.75 0.75 2.25 2.25-2.25 2.25 0.75 0.75z m10-8v10c0 0.55-0.45 1-1 1H1c-0.55 0-1-0.45-1-1V3c0-0.55 0.45-1 1-1h12c0.55 0 1 0.45 1 1z m-1 0H1v10h12V3z</value> + </data> + <data name="text_size" xml:space="preserve"> + <value>M17.97 14h-2.25l-0.95-3.25H10.7l-0.95 3.25H7.5l-0.69-2.33H3.53l-0.7 2.33H0.66l3.3-9.59h2.5l2.17 6.34 2.89-8.75h2.52l3.94 12zM6.36 10.13s-1.02-3.61-1.17-4.11h-0.08l-1.13 4.11h2.38z m7.92-1.05l-1.52-5.42h-0.06l-1.5 5.42h3.08z</value> </data> <data name="three_bars" xml:space="preserve"> - <value>M0 192v128h640V192H0zM0 576h640V448H0V576zM0 832h640V704H0V832z</value> + <value>M11.41 9H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h10.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1z m0-4H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1h10.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1zM0.59 11h10.81c0.59 0 0.59 0.41 0.59 1s0 1-0.59 1H0.59c-0.59 0-0.59-0.41-0.59-1s0-1 0.59-1z</value> + </data> + <data name="thumbsdown" xml:space="preserve"> + <value>M15.98 7.83l-0.97-5.95C14.84 0.5 13.13 0 12 0H5.69c-0.2 0-0.38 0.05-0.53 0.14l-1.44 0.86H2C0.94 1 0 1.94 0 3v4c0 1.06 0.94 2.02 2 2h2c0.91 0 1.39 0.45 2.39 1.55 0.91 1 0.88 1.8 0.63 3.27-0.08 0.5 0.06 1 0.42 1.42 0.39 0.47 0.98 0.77 1.56 0.77 1.83 0 3-3.72 3-5.02l-0.02-0.98c0.02 0 0.02 0 0.02 0h2.02c1.16 0 1.95-0.8 1.98-1.97 0-0.06 0.02-0.13-0.02-0.2z m-1.97 1.19H12.02c-0.7 0-1.03 0.28-1.03 0.97l0.03 1.03c0 1.27-1.17 4-2 4-0.5 0-1.08-0.5-1-1 0.25-1.58 0.34-2.78-0.89-4.14-1.02-1.13-1.77-1.88-3.13-1.88V2l1.67-1h6.33c0.73 0 1.95 0.31 2 1l0.02 0.02 1 6c-0.03 0.64-0.38 1-1 1z</value> + </data> + <data name="thumbsup" xml:space="preserve"> + <value>M14 6H12s0 0-0.02 0l0.02-0.98c0-1.3-1.17-5.02-3-5.02-0.58 0-1.17 0.3-1.56 0.77-0.36 0.41-0.5 0.91-0.42 1.41 0.25 1.48 0.28 2.28-0.63 3.28-1 1.09-1.48 1.55-2.39 1.55H2C0.94 7 0 7.94 0 9v4c0 1.06 0.94 2 2 2h1.72l1.44 0.86c0.16 0.09 0.33 0.14 0.52 0.14h6.33c1.13 0 2.84-0.5 3-1.88l0.98-5.95c0.02-0.08 0.02-0.14 0.02-0.2-0.03-1.17-0.84-1.97-2-1.97z m0 8c-0.05 0.69-1.27 1-2 1H5.67l-1.67-1V8c1.36 0 2.11-0.75 3.13-1.88 1.23-1.36 1.14-2.56 0.88-4.13-0.08-0.5 0.5-1 1-1 0.83 0 2 2.73 2 4l-0.02 1.03c0 0.69 0.33 0.97 1.02 0.97h2c0.63 0 0.98 0.36 1 1l-1 6z</value> </data> <data name="tools" xml:space="preserve"> - <value>M286.547 465.016c16.843 16.812 81.716 85.279 81.716 85.279l35.968-37.093-56.373-58.248L456.072 340.02c0 0-48.842-47.623-27.468-28.655 20.438-75.903 1.812-160.589-55.842-220.243C315.608 31.936000000000035 234.392 12.529999999999973 161.425 32.903999999999996l123.653 127.715-32.53 125.309-121.06 33.438L7.898 191.61799999999994c-19.718 75.436-0.969 159.339 56.311 218.556C124.302 472.297 210.83 490.547 286.547 465.016zM698.815 589.231L549.694 736.539l245.932 254.805c20.062 20.812 46.498 31.188 72.872 31.188 26.25 0 52.624-10.375 72.811-31.188 40.249-41.624 40.249-108.997 0-150.62L698.815 589.231zM1023.681 161.83799999999997L867.06-0.0009999999999763531 405.387 477.297l56.373 58.248L185.425 821.161l-63.154 33.749-89.217 145.559 22.719 23.562 140.839-92.247 32.655-65.312 276.336-285.554 56.404 58.248L1023.681 161.83799999999997z</value> + <value>M4.48 7.27c0.26 0.26 1.28 1.33 1.28 1.33l0.56-0.58-0.88-0.91L7.13 5.31c0 0-0.76-0.74-0.43-0.45 0.32-1.19 0.03-2.51-0.87-3.44C4.93 0.5 3.66 0.2 2.52 0.51l1.93 2-0.51 1.96-1.89 0.52L0.12 2.99c-0.31 1.18-0.02 2.49 0.88 3.41C1.94 7.38 3.29 7.66 4.48 7.27zM10.92 9.21L8.59 11.51l3.84 3.98c0.31 0.33 0.73 0.49 1.14 0.49 0.41 0 0.82-0.16 1.14-0.49 0.63-0.65 0.63-1.7 0-2.35L10.92 9.21zM16 2.53L13.55 0 6.33 7.46l0.88 0.91L2.9 12.83l-0.99 0.53-1.39 2.27 0.35 0.37 2.2-1.44 0.51-1.02 4.32-4.46 0.88 0.91L16 2.53z</value> </data> <data name="trashcan" xml:space="preserve"> - <value>M704 128H448c0 0 0-24.057 0-32 0-17.673-14.327-32-32-32s-32 14.327-32 32c0 17.673 0 32 0 32H128c-35.346 0-64 28.654-64 64v64c0 35.346 28.654 64 64 64v576c0 35.346 28.654 64 64 64h448c35.346 0 64-28.654 64-64V320c35.346 0 64-28.654 64-64v-64C768 156.654 739.346 128 704 128zM640 864c0 17.673-14.327 32-32 32H224c-17.673 0-32-14.327-32-32V320h64v480c0 17.673 14.327 32 32 32s32-14.327 32-32l0.387-480H384v480c0 17.673 14.327 32 32 32s32-14.327 32-32l0.387-480h64L512 800c0 17.673 14.327 32 32 32s32-14.327 32-32V320h64V864zM704 240c0 8.837-7.163 16-16 16H144c-8.837 0-16-7.163-16-16v-32c0-8.837 7.163-16 16-16h544c8.837 0 16 7.163 16 16V240z</value> + <value>M10 2H8c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1H1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1v9c0 0.55 0.45 1 1 1h7c0.55 0 1-0.45 1-1V5c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1z m-1 12H2V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9z m1-10H1v-1h9v1z</value> </data> <data name="triangle_down" xml:space="preserve"> - <value>M0 384l383.75 383.75L767.5 384H0z</value> + <value>M0 5l6 6 6-6H0z</value> </data> <data name="triangle_left" xml:space="preserve"> - <value>M0 511.875l383.75 383.75v-767.5L0 511.875z</value> + <value>M6 2L0 8l6 6V2z</value> </data> <data name="triangle_right" xml:space="preserve"> - <value>M0.062 128.25L383.812 512 0.062 895.75V128.25z</value> + <value>M0 14l6-6L0 2v12z</value> </data> <data name="triangle_up" xml:space="preserve"> - <value>M383.75 256L0 639.75h767.5L383.75 256z</value> + <value>M12 11L6 5 0 11h12z</value> </data> <data name="unfold" xml:space="preserve"> - <value>M384 384h128V192h128L448 0 256 192h128V384zM576 256v64h224L672 448H224L96 320h224v-64H0v63.999L160 480 0 640v64h320v-64H96l128-128h448l128 128H576v64h320v-64L736 480l160-160.001V256H576zM512 576H384v192H256l192 192 192-192H512V576z</value> + <value>M11.5 8.5l2.5 2.5c0 0.55-0.45 1-1 1H9v-1h3.5L10.5 9H3.5L1.5 11h3.5v1H1c-0.55 0-1-0.45-1-1l2.5-2.5L0 6c0-0.55 0.45-1 1-1h4v1H1.5l2 2h7l2-2H9v-1h4c0.55 0 1 0.45 1 1L11.5 8.5z m-5.5-1.5h2V4h2L7 1 4 4h2v3z m2 3H6v3H4l3 3 3-3H8V10z</value> </data> <data name="unmute" xml:space="preserve"> - <value>M128 384H0v256h128l256 192h64V192h-64L128 384zM538.51 421.49c-12.496-12.497-32.758-12.497-45.255 0-12.496 12.496-12.496 32.758 0 45.255 24.994 24.993 24.994 65.516 0 90.51-12.496 12.496-12.496 32.758 0 45.255 12.497 12.496 32.759 12.496 45.255 0C588.497 552.521 588.497 471.477 538.51 421.49zM629.02 330.981c-12.495-12.497-32.758-12.497-45.255 0-12.495 12.496-12.495 32.758 0 45.255 74.981 74.98 74.981 196.548 0 271.528-12.495 12.497-12.495 32.76 0 45.256 12.497 12.496 32.76 12.496 45.255 0C728.994 593.046 728.994 430.955 629.02 330.981zM719.529 240.471c-12.497-12.497-32.76-12.497-45.255 0-12.496 12.496-12.496 32.758 0 45.255 124.968 124.968 124.968 327.58 0 452.548-12.496 12.497-12.496 32.759 0 45.255 12.495 12.497 32.758 12.497 45.255 0C869.49 633.567 869.49 390.432 719.529 240.471z</value> + <value>M11 8.02c0 1.09-0.45 2.09-1.17 2.83l-0.67-0.67c0.55-0.56 0.89-1.31 0.89-2.16s-0.34-1.61-0.89-2.16l0.67-0.67c0.72 0.72 1.17 1.72 1.17 2.83zM6.72 2.28L3 6H1c-0.55 0-1 0.45-1 1v2c0 0.55 0.45 1 1 1h2l3.72 3.72c0.47 0.47 1.28 0.14 1.28-0.53V2.81c0-0.67-0.81-1-1.28-0.53z m5.94 0.08l-0.67 0.67c1.28 1.28 2.06 3.03 2.06 4.98 0 1.94-0.78 3.7-2.06 4.98l0.67 0.67c1.45-1.45 2.34-3.45 2.34-5.66 0-2.22-0.89-4.22-2.34-5.66z m-1.41 1.41l-0.69 0.67c0.92 0.92 1.48 2.19 1.48 3.58s-0.56 2.66-1.48 3.56l0.69 0.67c1.08-1.08 1.75-2.58 1.75-4.23s-0.67-3.16-1.75-4.25z</value> + </data> + <data name="verified" xml:space="preserve"> + <value>M15.66 7.36l-1.31-1c-0.47-0.34-0.78-1.11-0.7-1.69l0.22-1.63c0.08-0.58-0.33-0.98-0.91-0.91l-1.63 0.22c-0.58 0.08-1.34-0.23-1.69-0.7l-1-1.31c-0.36-0.45-0.92-0.45-1.28 0l-1 1.31c-0.34 0.47-1.11 0.78-1.69 0.7l-1.63-0.22c-0.58-0.08-0.98 0.33-0.91 0.91l0.22 1.63c0.08 0.58-0.23 1.34-0.7 1.69l-1.31 1c-0.45 0.36-0.45 0.92 0 1.28l1.31 1c0.47 0.34 0.78 1.11 0.7 1.69l-0.22 1.63c-0.08 0.58 0.33 0.98 0.91 0.91l1.63-0.22c0.58-0.08 1.34 0.23 1.69 0.7l1 1.31c0.36 0.45 0.92 0.45 1.28 0l1-1.31c0.34-0.47 1.11-0.78 1.69-0.7l1.63 0.22c0.58 0.08 0.98-0.33 0.91-0.91l-0.22-1.63c-0.08-0.58 0.23-1.34 0.7-1.69l1.31-1c0.45-0.36 0.45-0.92 0-1.28z m-3.66 0.14c0 0.83-0.67 1.5-1.5 1.5H8v1l-1-0.5v1.5l-1-0.5v1.5H4V10l3-3v-1.5c0-0.83 0.67-1.5 1.5-1.5h2.5c0.55 0 1 0.45 1 1v2.5z m-1-1.44v0.44c0 0.28-0.22 0.5-0.5 0.5h-1c-0.28 0-0.5-0.22-0.5-0.5v-1c0-0.28 0.22-0.5 0.5-0.5h0.44c0.58 0 1.06 0.48 1.06 1.06z</value> </data> <data name="versions" xml:space="preserve"> - <value>M0 704h128v-64H64V384h64v-64H0V704zM384 192v640h512V192H384zM768 704H512V320h256V704zM192 768h128v-64h-64V320h64v-64H192V768z</value> + <value>M13 3H7c-0.55 0-1 0.45-1 1v8c0 0.55 0.45 1 1 1h6c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1z m-1 8H8V5h4v6zM4 4h1v1h-1v6h1v1h-1c-0.55 0-1-0.45-1-1V5c0-0.55 0.45-1 1-1zM1 5h1v1H1v4h1v1H1c-0.55 0-1-0.45-1-1V6c0-0.55 0.45-1 1-1z</value> + </data> + <data name="watch" xml:space="preserve"> + <value>M6 8h2v1H5V5h1v3z m6 0c0 2.22-1.2 4.16-3 5.19v1.81c0 0.55-0.45 1-1 1H4c-0.55 0-1-0.45-1-1V13.19C1.2 12.16 0 10.22 0 8s1.2-4.16 3-5.19V1c0-0.55 0.45-1 1-1h4c0.55 0 1 0.45 1 1v1.81c1.8 1.03 3 2.97 3 5.19z m-1 0c0-2.77-2.23-5-5-5S1 5.23 1 8s2.23 5 5 5 5-2.23 5-5z</value> </data> <data name="x" xml:space="preserve"> - <value>M640 320L512 192 320 384 128 192 0 320l192 192L0 704l128 128 192-192 192 192 128-128L448 512 640 320z</value> + <value>M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z</value> </data> <data name="zap" xml:space="preserve"> - <value>M640 448H384L576 0 0 576h256L64 1024 640 448z</value> + <value>M10 7H6L9 0 0 9h4L1 16 10 7z</value> </data> </root> \ No newline at end of file diff --git a/src/GitHub.UI/Controls/Panels/FixedAspectRatioPanel.cs b/src/GitHub.UI/Controls/Panels/FixedAspectRatioPanel.cs index fc3db35062..5800458903 100644 --- a/src/GitHub.UI/Controls/Panels/FixedAspectRatioPanel.cs +++ b/src/GitHub.UI/Controls/Panels/FixedAspectRatioPanel.cs @@ -2,7 +2,6 @@ using System.ComponentModel; using System.Windows; using System.Windows.Controls; -using NullGuard; namespace GitHub.UI { @@ -31,14 +30,12 @@ public double AspectRatio public HorizontalAlignment HorizontalContentAlignment { - [return: AllowNull] get { return (HorizontalAlignment)GetValue(Control.HorizontalContentAlignmentProperty); } set { SetValue(Control.HorizontalContentAlignmentProperty, value); } } public VerticalAlignment VerticalContentAlignment { - [return: AllowNull] get { return (VerticalAlignment)GetValue(Control.VerticalContentAlignmentProperty); } set { SetValue(Control.VerticalContentAlignmentProperty, value); } } diff --git a/src/GitHub.UI/Controls/PromptRichTextBox.cs b/src/GitHub.UI/Controls/PromptRichTextBox.cs index b67505b750..08546672b1 100644 --- a/src/GitHub.UI/Controls/PromptRichTextBox.cs +++ b/src/GitHub.UI/Controls/PromptRichTextBox.cs @@ -7,7 +7,6 @@ using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; -using NullGuard; namespace GitHub.UI { @@ -41,7 +40,6 @@ public PromptRichTextBox() public Brush PromptForeground { - [return: AllowNull] get { return (Brush)GetValue(PromptForegroundProperty); } set { SetValue(PromptForegroundProperty, value); } } @@ -50,7 +48,6 @@ public Brush PromptForeground [DefaultValue("")] public string PromptText { - [return: AllowNull] get { return (string)GetValue(PromptTextProperty); } set { SetValue(PromptTextProperty, value); } } @@ -59,7 +56,6 @@ public string PromptText [DefaultValue("")] public string Text { - [return: AllowNull] get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value ?? ""); } } @@ -68,7 +64,6 @@ public string Text [DefaultValue("")] public string CharacterLimitToolTipWarning { - [return: AllowNull] get { return (string)GetValue(CharacterLimitToolTipWarningProperty); } set { SetValue(CharacterLimitToolTipWarningProperty, value); } } @@ -116,8 +111,12 @@ static void OnPaste(object sender, DataObjectPastingEventArgs e) var content = SetDocumentContent(document, rtf, DataFormats.Rtf); + if (content.Text == null) + { + throw new GitHubLogicException("WPF ensures this is not null. WPF failed on the job"); + } + var d = new DataObject(); - Debug.Assert(content.Text != null, "WPF ensures this is not null. WPF failed on the job"); var textContent = content.Text; if (!richTextBox.AcceptsReturn) { diff --git a/src/GitHub.UI/Controls/PromptTextBox.cs b/src/GitHub.UI/Controls/PromptTextBox.cs index 279fe3d1d8..5fbcd75812 100644 --- a/src/GitHub.UI/Controls/PromptTextBox.cs +++ b/src/GitHub.UI/Controls/PromptTextBox.cs @@ -1,20 +1,34 @@ using System.ComponentModel; using System.Windows; using System.Windows.Controls; -using NullGuard; namespace GitHub.UI { public class PromptTextBox : TextBox, IShortcutContainer { + public static readonly DependencyProperty IconContentProperty = + DependencyProperty.RegisterAttached(nameof(IconContent), typeof(object), typeof(PromptTextBox)); + public static readonly DependencyProperty IconContentTemplateProperty = + DependencyProperty.RegisterAttached(nameof(IconContentTemplate), typeof(DataTemplate), typeof(PromptTextBox)); public static readonly DependencyProperty PromptTextProperty = - DependencyProperty.Register("PromptText", typeof(string), typeof(PromptTextBox), new UIPropertyMetadata("")); + DependencyProperty.Register(nameof(PromptText), typeof(string), typeof(PromptTextBox), new UIPropertyMetadata("")); + + public object IconContent + { + get { return GetValue(IconContentProperty); } + set { SetValue(IconContentProperty, value); } + } + + public object IconContentTemplate + { + get { return GetValue(IconContentTemplateProperty); } + set { SetValue(IconContentTemplateProperty, value); } + } [Localizability(LocalizationCategory.Text)] [DefaultValue("")] public string PromptText { - [return: AllowNull] get { return (string)GetValue(PromptTextProperty); } set { SetValue(PromptTextProperty, value); } } diff --git a/src/GitHub.UI/Controls/ScrollingVerticalStackPanel.cs b/src/GitHub.UI/Controls/ScrollingVerticalStackPanel.cs new file mode 100644 index 0000000000..5ca669ca68 --- /dev/null +++ b/src/GitHub.UI/Controls/ScrollingVerticalStackPanel.cs @@ -0,0 +1,224 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; + +namespace GitHub.UI.Controls +{ + /// <summary> + /// A vertical stack panel which implements its own logical scrolling, allowing controls to be + /// fixed horizontally in the scroll area. + /// </summary> + /// <remarks> + /// This panel is needed by the PullRequestDetailsView because of #1698: there is no default + /// panel in WPF which allows the horizontal scrollbar to always be present at the bottom while + /// also making the PR description etc be fixed horizontally (non-scrollable) in the viewport. + /// </remarks> + public class ScrollingVerticalStackPanel : Panel, IScrollInfo + { + const int lineSize = 16; + const int mouseWheelSize = 48; + + /// <summary> + /// Attached property which when set to True on a child control, will cause it to be fixed + /// horizontally within the scrollable viewport. + /// </summary> + public static readonly DependencyProperty IsFixedProperty = + DependencyProperty.RegisterAttached( + "IsFixed", + typeof(bool), + typeof(ScrollingVerticalStackPanel), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public bool CanHorizontallyScroll + { + get { return true; } + set { } + } + + public bool CanVerticallyScroll + { + get { return true; } + set { } + } + + public double ExtentHeight { get; private set; } + public double ExtentWidth { get; private set; } + public double HorizontalOffset { get; private set; } + public double VerticalOffset { get; private set; } + public double ViewportHeight { get; private set; } + public double ViewportWidth { get; private set; } + public ScrollViewer ScrollOwner { get; set; } + + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Can only be applied to controls")] + public static bool GetIsFixed(FrameworkElement control) + { + return (bool)control.GetValue(IsFixedProperty); + } + + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Can only be applied to controls")] + public static void SetIsFixed(FrameworkElement control, bool value) + { + control.SetValue(IsFixedProperty, value); + } + + public void LineDown() => SetVerticalOffset(VerticalOffset + lineSize); + public void LineLeft() => SetHorizontalOffset(HorizontalOffset - lineSize); + public void LineRight() => SetHorizontalOffset(HorizontalOffset + lineSize); + public void LineUp() => SetVerticalOffset(VerticalOffset - lineSize); + public void MouseWheelDown() => SetVerticalOffset(VerticalOffset + mouseWheelSize); + public void MouseWheelLeft() => SetHorizontalOffset(HorizontalOffset - mouseWheelSize); + public void MouseWheelRight() => SetHorizontalOffset(HorizontalOffset + mouseWheelSize); + public void MouseWheelUp() => SetVerticalOffset(VerticalOffset - mouseWheelSize); + public void PageDown() => SetVerticalOffset(VerticalOffset + ViewportHeight); + public void PageLeft() => SetHorizontalOffset(HorizontalOffset - ViewportWidth); + public void PageRight() => SetHorizontalOffset(HorizontalOffset + ViewportWidth); + public void PageUp() => SetVerticalOffset(VerticalOffset - ViewportHeight); + + public Rect MakeVisible(Visual visual, Rect rectangle) + { + var transform = visual.TransformToVisual(this); + var rect = transform.TransformBounds(rectangle); + var offsetX = HorizontalOffset; + var offsetY = VerticalOffset; + + if (rect.Bottom > ViewportHeight) + { + var delta = rect.Bottom - ViewportHeight; + offsetY += delta; + rect.Y -= delta; + } + + if (rect.Y < 0) + { + offsetY += rect.Y; + } + + // We technially should be trying to also show the right-hand side of the rect here + // using the same technique that we just used to show the bottom of the rect above, + // but in the case of the PR details view, the left hand side of the item is much + // more important than the right hand side and it actually feels better to not do + // this. If this control is used elsewhere and this behavior is required, we could + // put in a switch to enable it. + + if (rect.X < 0) + { + offsetX += rect.X; + } + + SetHorizontalOffset(offsetX); + SetVerticalOffset(offsetY); + + return rect; + } + + public void SetHorizontalOffset(double offset) + { + var value = Math.Max(0, Math.Min(offset, ExtentWidth - ViewportWidth)); + + if (value != HorizontalOffset) + { + HorizontalOffset = value; + InvalidateArrange(); + } + } + + public void SetVerticalOffset(double offset) + { + var value = Math.Max(0, Math.Min(offset, ExtentHeight - ViewportHeight)); + + if (value != VerticalOffset) + { + VerticalOffset = value; + InvalidateArrange(); + } + } + + protected override void ParentLayoutInvalidated(UIElement child) + { + base.ParentLayoutInvalidated(child); + } + + protected override Size MeasureOverride(Size availableSize) + { + var maxWidth = 0.0; + var height = 0.0; + + foreach (FrameworkElement child in Children) + { + var isFixed = GetIsFixed(child); + var childConstraint = new Size( + isFixed ? availableSize.Width : double.PositiveInfinity, + double.PositiveInfinity); + child.Measure(childConstraint); + + if (height - VerticalOffset < availableSize.Height) + { + maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); + } + + height += child.DesiredSize.Height; + } + + UpdateScrollInfo(new Size(maxWidth, height), availableSize); + + return new Size( + Math.Min(maxWidth, availableSize.Width), + Math.Min(height, availableSize.Height)); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var y = -VerticalOffset; + var thisRect = new Rect(finalSize); + var visibleMaxWidth = 0.0; + + foreach (FrameworkElement child in Children) + { + var isFixed = GetIsFixed(child); + var x = isFixed ? 0 : -HorizontalOffset; + var width = child.DesiredSize.Width; + + if (isFixed) + { + switch (child.HorizontalAlignment) + { + case HorizontalAlignment.Stretch: + width = finalSize.Width; + break; + case HorizontalAlignment.Right: + x = finalSize.Width - child.DesiredSize.Width; + break; + case HorizontalAlignment.Center: + x = (finalSize.Width - child.DesiredSize.Width) / 2; + break; + } + } + + var childRect = new Rect(x, y, width, child.DesiredSize.Height); + child.Arrange(childRect); + y += child.DesiredSize.Height; + + if (childRect.IntersectsWith(thisRect) && childRect.Right > visibleMaxWidth) + { + visibleMaxWidth = childRect.Right; + } + } + + UpdateScrollInfo(new Size(visibleMaxWidth, ExtentHeight), new Size(finalSize.Width, finalSize.Height)); + return finalSize; + } + + void UpdateScrollInfo(Size extent, Size viewport) + { + ExtentWidth = extent.Width; + ExtentHeight = extent.Height; + ScrollOwner?.InvalidateScrollInfo(); + ViewportWidth = viewport.Width; + ViewportHeight = viewport.Height; + ScrollOwner?.InvalidateScrollInfo(); + } + } +} diff --git a/src/GitHub.UI/Controls/SectionControl.cs b/src/GitHub.UI/Controls/SectionControl.cs new file mode 100644 index 0000000000..8ebb76a9ad --- /dev/null +++ b/src/GitHub.UI/Controls/SectionControl.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections; +using System.Windows; +using System.Windows.Controls; + +namespace GitHub.UI +{ + public class SectionControl : ContentControl + { + public static readonly DependencyProperty ButtonsProperty = DependencyProperty.Register( + "Buttons", + typeof(IList), + typeof(SectionControl)); + + public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register( + "HeaderText", + typeof(string), + typeof(SectionControl)); + + public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register( + "IsExpanded", + typeof(bool), + typeof(SectionControl), + new FrameworkPropertyMetadata(true)); + + static SectionControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SectionControl), new FrameworkPropertyMetadata(typeof(SectionControl))); + } + + public SectionControl() + { + Buttons = new ArrayList(); + } + + public IList Buttons + { + get { return (IList)GetValue(ButtonsProperty); } + set { SetValue(ButtonsProperty, value); } + } + + public string HeaderText + { + get { return (string)GetValue(HeaderTextProperty); } + set { SetValue(HeaderTextProperty, value); } + } + + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set { SetValue(IsExpandedProperty, value); } + } + } +} diff --git a/src/GitHub.UI/Controls/SecurePasswordBox.cs b/src/GitHub.UI/Controls/SecurePasswordBox.cs index e3dd556964..c49dfb0528 100644 --- a/src/GitHub.UI/Controls/SecurePasswordBox.cs +++ b/src/GitHub.UI/Controls/SecurePasswordBox.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Globalization; using System.Windows.Controls; -using NullGuard; namespace GitHub.UI { @@ -30,7 +29,6 @@ public class SecurePasswordBox : PromptTextBox /// </summary> protected string BaseText { - [return: AllowNull] get { return base.Text; } set { @@ -43,10 +41,8 @@ protected string BaseText /// <summary> /// Clean Password /// </summary> - [AllowNull] public new string Text { - [return: AllowNull] get { return password; } set { @@ -79,13 +75,22 @@ protected override void OnTextChanged(TextChangedEventArgs e) { if (currentText[i] != pwdChar) { - Debug.Assert(password != null, "Password can't be null here"); + if (password == null) + { + throw new GitHubLogicException("Password can't be null here"); + } + // Replace or insert char string currentCharacter = currentText[i].ToString(CultureInfo.InvariantCulture); password = BaseText.Length == password.Length ? password.Remove(i, 1).Insert(i, currentCharacter) : password.Insert(i, currentCharacter); } } - Debug.Assert(password != null, "Password can't be null here"); + + if (password == null) + { + throw new GitHubLogicException("Password can't be null here"); + } + BaseText = new string(pwdChar, password.Length); SelectionStart = selStart; } diff --git a/src/GitHub.UI/Controls/Spinner.xaml b/src/GitHub.UI/Controls/Spinner.xaml new file mode 100644 index 0000000000..295fe45f2d --- /dev/null +++ b/src/GitHub.UI/Controls/Spinner.xaml @@ -0,0 +1,12 @@ +<UserControl x:Class="GitHub.UI.Controls.Spinner" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + d:DesignHeight="24" + d:DesignWidth="24" + mc:Ignorable="d"> + <Viewbox> + <ProgressBar IsIndeterminate="True" Style="{DynamicResource GitHubSyncProgressBar}" /> + </Viewbox> +</UserControl> \ No newline at end of file diff --git a/src/GitHub.UI/Controls/Spinner.xaml.cs b/src/GitHub.UI/Controls/Spinner.xaml.cs new file mode 100644 index 0000000000..21c53253f3 --- /dev/null +++ b/src/GitHub.UI/Controls/Spinner.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace GitHub.UI.Controls +{ + public partial class Spinner : UserControl + { + public Spinner() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.UI/Controls/ToggleButtons/OcticonLinkToggleButton.xaml b/src/GitHub.UI/Controls/ToggleButtons/OcticonLinkToggleButton.xaml index a3e8df8d9e..148eb9d183 100644 --- a/src/GitHub.UI/Controls/ToggleButtons/OcticonLinkToggleButton.xaml +++ b/src/GitHub.UI/Controls/ToggleButtons/OcticonLinkToggleButton.xaml @@ -98,7 +98,7 @@ </Style> <Style TargetType="{x:Type Path}"> - <Setter Property="Height" Value="1024" /> + <Setter Property="Height" Value="16" /> <Setter Property="Fill" Value="#666" /> <Setter Property="Opacity" Value="0" /> </Style> diff --git a/src/GitHub.UI/Controls/ToggleButtons/OcticonToggleButton.cs b/src/GitHub.UI/Controls/ToggleButtons/OcticonToggleButton.cs index 96a6011661..2697dc25ba 100644 --- a/src/GitHub.UI/Controls/ToggleButtons/OcticonToggleButton.cs +++ b/src/GitHub.UI/Controls/ToggleButtons/OcticonToggleButton.cs @@ -1,7 +1,6 @@ using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Media; -using NullGuard; namespace GitHub.UI { @@ -53,42 +52,36 @@ public abstract class OcticonToggleButton: ToggleButton public Octicon IconChecked { - [return: AllowNull] get { return (Octicon)GetValue(IconCheckedProperty); } set { SetValue(IconCheckedProperty, value); } } public Geometry PathChecked { - [return: AllowNull] get { return (Geometry)GetValue(PathCheckedProperty); } set { SetValue(PathCheckedProperty, value); } } public Octicon IconUnchecked { - [return: AllowNull] get { return (Octicon)GetValue(IconUncheckedProperty); } set { SetValue(IconUncheckedProperty, value); } } public Geometry PathUnchecked { - [return: AllowNull] get { return (Geometry)GetValue(PathUncheckedProperty); } set { SetValue(PathUncheckedProperty, value); } } public Octicon IconIndeterminate { - [return: AllowNull] get { return (Octicon)GetValue(IconIndeterminateProperty); } set { SetValue(IconIndeterminateProperty, value); } } public Geometry PathIndeterminate { - [return: AllowNull] get { return (Geometry)GetValue(PathIndeterminateProperty); } set { SetValue(PathIndeterminateProperty, value); } } diff --git a/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs b/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs new file mode 100644 index 0000000000..84b000c8e6 --- /dev/null +++ b/src/GitHub.UI/Controls/TrimmedPathTextBlock.cs @@ -0,0 +1,166 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace GitHub.UI +{ + /// <summary> + /// TextBlock that displays a path and intelligently trims with ellipsis when the path doesn't + /// fit in the allocated size. + /// </summary> + /// <remarks> + /// When displaying a path that is too long for its allocated space, we need to trim the path + /// with ellipses intelligently instead of simply trimming the end (as this is the filename + /// which is the most important part!). This control trims a path in the following manner with + /// decreasing allocated space: + /// + /// - VisualStudio\src\GitHub.UI\Controls\TrimmedPathTextBlock.cs + /// - VisualStudio\...\GitHub.UI\Controls\TrimmedPathTextBlock.cs + /// - VisualStudio\...\...\Controls\TrimmedPathTextBlock.cs + /// - VisualStudio\...\...\...\TrimmedPathTextBlock.cs + /// - ...\...\...\...\TrimmedPathTextBlock.cs + /// </remarks> + public class TrimmedPathTextBlock : FrameworkElement + { + public static readonly DependencyProperty FontFamilyProperty = + TextBlock.FontFamilyProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontSizeProperty = + TextBlock.FontSizeProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontStretchProperty = + TextBlock.FontStretchProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontStyleProperty = + TextBlock.FontStyleProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty FontWeightProperty = + TextBlock.FontWeightProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty ForegroundProperty = + TextBlock.ForegroundProperty.AddOwner(typeof(TrimmedPathTextBlock)); + public static readonly DependencyProperty TextProperty = + TextBlock.TextProperty.AddOwner( + typeof(TrimmedPathTextBlock), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, + TextChanged)); + + FormattedText formattedText; + FormattedText renderText; + + public FontFamily FontFamily + { + get { return (FontFamily)GetValue(FontFamilyProperty); } + set { SetValue(FontFamilyProperty, value); } + } + + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public FontStretch FontStretch + { + get { return (FontStretch)GetValue(FontStretchProperty); } + set { SetValue(FontStretchProperty, value); } + } + + public FontStyle FontStyle + { + get { return (FontStyle)GetValue(FontStyleProperty); } + set { SetValue(FontStyleProperty, value); } + } + + public FontWeight FontWeight + { + get { return (FontWeight)GetValue(FontWeightProperty); } + set { SetValue(FontWeightProperty, value); } + } + + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + protected FormattedText FormattedText + { + get + { + if (formattedText == null && Text != null) + { + formattedText = CreateFormattedText(Text); + } + + return formattedText; + } + } + + protected override Size MeasureOverride(Size availableSize) + { + if (Text == null) + { + return new Size(); + } + + var parts = Text + .Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) + .ToList(); + var nextPart = Math.Min(1, parts.Count - 1); + + while (true) + { + renderText = CreateFormattedText(string.Join(Path.DirectorySeparatorChar.ToString(), parts)); + + if (renderText.Width <= availableSize.Width || nextPart == -1) + break; + + parts[nextPart] = "\u2026"; + + if (nextPart == 0) + nextPart = -1; + else if (nextPart == parts.Count - 2) + nextPart = 0; + else + nextPart++; + }; + + return new Size(renderText.Width, renderText.Height); + } + + protected override void OnRender(DrawingContext drawingContext) + { + drawingContext.DrawText(renderText, new Point()); + } + + FormattedText CreateFormattedText(string text) + { + return new FormattedText( + text, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), + FontSize, + Foreground); + } + + static void TextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + var textBlock = sender as TrimmedPathTextBlock; + + if (textBlock != null) + { + textBlock.formattedText = null; + textBlock.renderText = null; + } + } + } +} diff --git a/src/GitHub.UI/Converters/AllCapsConverter.cs b/src/GitHub.UI/Converters/AllCapsConverter.cs new file mode 100644 index 0000000000..5d06f88dab --- /dev/null +++ b/src/GitHub.UI/Converters/AllCapsConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace GitHub.UI +{ + public class AllCapsConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString().ToUpper(CultureInfo.CurrentCulture); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.UI/Converters/BooleanToHiddenVisibilityConverter.cs b/src/GitHub.UI/Converters/BooleanToHiddenVisibilityConverter.cs index 1b175a3a5d..46c059bd11 100644 --- a/src/GitHub.UI/Converters/BooleanToHiddenVisibilityConverter.cs +++ b/src/GitHub.UI/Converters/BooleanToHiddenVisibilityConverter.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Windows; -using NullGuard; namespace GitHub.UI { @@ -10,17 +9,17 @@ public sealed class BooleanToHiddenVisibilityConverter : ValueConverterMarkupExt { public override object Convert( object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return value is bool && (bool)value ? Visibility.Visible : Visibility.Hidden; } public override object ConvertBack(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return value is Visibility && (Visibility)value == Visibility.Visible; } diff --git a/src/GitHub.UI/Converters/BooleanToInverseHiddenVisibilityConverter.cs b/src/GitHub.UI/Converters/BooleanToInverseHiddenVisibilityConverter.cs index b2e5a1137e..f1f62d8dbd 100644 --- a/src/GitHub.UI/Converters/BooleanToInverseHiddenVisibilityConverter.cs +++ b/src/GitHub.UI/Converters/BooleanToInverseHiddenVisibilityConverter.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Windows; -using NullGuard; namespace GitHub.UI { @@ -9,17 +8,17 @@ namespace GitHub.UI public sealed class BooleanToInverseHiddenVisibilityConverter : ValueConverterMarkupExtension<BooleanToInverseHiddenVisibilityConverter> { public override object Convert(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return value is bool && (bool)value ? Visibility.Hidden : Visibility.Visible; } public override object ConvertBack(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return value is Visibility && (Visibility)value != Visibility.Visible; } diff --git a/src/GitHub.UI/Converters/BooleanToInverseVisibilityConverter.cs b/src/GitHub.UI/Converters/BooleanToInverseVisibilityConverter.cs index c29360926a..5a3ac549dd 100644 --- a/src/GitHub.UI/Converters/BooleanToInverseVisibilityConverter.cs +++ b/src/GitHub.UI/Converters/BooleanToInverseVisibilityConverter.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Windows; -using NullGuard; namespace GitHub.UI { @@ -9,17 +8,17 @@ namespace GitHub.UI public sealed class BooleanToInverseVisibilityConverter : ValueConverterMarkupExtension<BooleanToInverseVisibilityConverter> { public override object Convert(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return value is bool && (bool)value ? Visibility.Collapsed : Visibility.Visible; } public override object ConvertBack(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return value is Visibility && (Visibility)value != Visibility.Visible; } diff --git a/src/GitHub.UI/Converters/BooleanToVisibilityConverter.cs b/src/GitHub.UI/Converters/BooleanToVisibilityConverter.cs index 3355a05f35..77b8905181 100644 --- a/src/GitHub.UI/Converters/BooleanToVisibilityConverter.cs +++ b/src/GitHub.UI/Converters/BooleanToVisibilityConverter.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Windows; -using NullGuard; namespace GitHub.UI { @@ -11,17 +10,17 @@ public sealed class BooleanToVisibilityConverter : ValueConverterMarkupExtension readonly System.Windows.Controls.BooleanToVisibilityConverter converter = new System.Windows.Controls.BooleanToVisibilityConverter(); public override object Convert(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return converter.Convert(value, targetType, parameter, culture); } public override object ConvertBack(object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + Type targetType, + object parameter, + CultureInfo culture) { return converter.ConvertBack(value, targetType, parameter, culture); } diff --git a/src/GitHub.UI/Converters/BranchNameConverter.cs b/src/GitHub.UI/Converters/BranchNameConverter.cs new file mode 100644 index 0000000000..0db0f51c3e --- /dev/null +++ b/src/GitHub.UI/Converters/BranchNameConverter.cs @@ -0,0 +1,44 @@ +using GitHub.Models; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace GitHub.UI.Converters +{ + public class BranchNameConverter : IMultiValueConverter + { + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var branch = values.OfType<IBranch>().FirstOrDefault(); + var activeRepo = values.OfType<IRepositoryModel>().FirstOrDefault(); + + if (branch != null && activeRepo != null) + { + var repo = (IRemoteRepositoryModel)branch.Repository; + + if (repo.Parent == null && activeRepo.Owner != repo.Owner) + { + return repo.Owner + ":" + branch.Name; + } + + return branch.DisplayName; + } + else + { + return values.FirstOrDefault()?.ToString(); + } + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + return null; + } + + } +} diff --git a/src/GitHub.UI/Converters/CountToVisibilityConverter.cs b/src/GitHub.UI/Converters/CountToVisibilityConverter.cs new file mode 100644 index 0000000000..366e5d0da0 --- /dev/null +++ b/src/GitHub.UI/Converters/CountToVisibilityConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace GitHub.UI +{ + /// <summary> + /// Convert a count to visibility based on the following rule: + /// * If count == 0, return Visibility.Visible + /// * If count > 0, return Visibility.Collapsed + /// </summary> + public class CountToVisibilityConverter : ValueConverterMarkupExtension<CountToVisibilityConverter> + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return ((int)value > 0) ? Visibility.Visible : Visibility.Collapsed; + } + } +} diff --git a/src/GitHub.UI/Converters/DefaultValueConverter.cs b/src/GitHub.UI/Converters/DefaultValueConverter.cs new file mode 100644 index 0000000000..cc8267c684 --- /dev/null +++ b/src/GitHub.UI/Converters/DefaultValueConverter.cs @@ -0,0 +1,14 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +namespace GitHub.UI +{ + public class DefaultValueConverter : ValueConverterMarkupExtension<DefaultValueConverter> + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value ?? parameter; + } + } +} diff --git a/src/GitHub.UI/Converters/DurationToStringConverter.cs b/src/GitHub.UI/Converters/DurationToStringConverter.cs new file mode 100644 index 0000000000..dbebe9f3fa --- /dev/null +++ b/src/GitHub.UI/Converters/DurationToStringConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; + +namespace GitHub.UI +{ + public class DurationToStringConverter : ValueConverterMarkupExtension<DurationToStringConverter> + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + TimeSpan duration; + if (value is TimeSpan span) + duration = span; + else if (value is DateTime time) + duration = DateTime.UtcNow - time; + else if (value is DateTimeOffset offset) + duration = DateTimeOffset.UtcNow - offset; + else + return value; + + if (duration.Ticks <= 0) + { + return Resources.JustNow; + } + + const int year = 365; + const int month = 30; + const int day = 24; + const int hour = 60; + const int minute = 60; + + if (duration.TotalDays >= year) + return string.Format(culture, (int)(duration.TotalDays / year) > 1 ? Resources.years : Resources.year, (int)(duration.TotalDays / year)); + else if (duration.TotalDays >= 360) + return string.Format(culture, Resources.months, 11); + else if (duration.TotalDays >= month) + return string.Format(culture, (int)(duration.TotalDays / (month)) > 1 ? Resources.months : Resources.month, (int)(duration.TotalDays / (month))); + else if (duration.TotalHours >= day) + return string.Format(culture, (int)(duration.TotalHours / day) > 1 ? Resources.days : Resources.day, (int)(duration.TotalHours / day)); + else if (duration.TotalMinutes >= hour) + return string.Format(culture, (int)(duration.TotalMinutes / hour) > 1 ? Resources.hours : Resources.hour, (int)(duration.TotalMinutes / hour)); + else if (duration.TotalSeconds >= minute) + return string.Format(culture, (int)(duration.TotalSeconds / minute) > 1 ? Resources.minutes : Resources.minute, (int)(duration.TotalSeconds / minute)); + return string.Format(culture, duration.TotalSeconds > 1 || duration.Ticks == 0 ? Resources.seconds : Resources.second, duration.TotalSeconds); + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Converters/EqualityConverter.cs b/src/GitHub.UI/Converters/EqualityConverter.cs new file mode 100644 index 0000000000..4750aa65f2 --- /dev/null +++ b/src/GitHub.UI/Converters/EqualityConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace GitHub.UI +{ + [Localizability(LocalizationCategory.NeverLocalize)] + public sealed class EqualityConverter : MultiValueConverterMarkupExtension<EqualityConverter> + { + public override object Convert( + object[] value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (value.Length == 2) + { + return Equals(value[0], value[1]); + } + + return false; + } + + public override object[] ConvertBack( + object value, + Type[] targetType, + object parameter, + CultureInfo culture) + { + return null; + } + } +} diff --git a/src/GitHub.UI/Converters/EqualsToVisibilityConverter.cs b/src/GitHub.UI/Converters/EqualsToVisibilityConverter.cs new file mode 100644 index 0000000000..e26403b0da --- /dev/null +++ b/src/GitHub.UI/Converters/EqualsToVisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace GitHub.UI +{ + public class EqualsToVisibilityConverter : MarkupExtension, IValueConverter + { + readonly string visibleValue; + + public EqualsToVisibilityConverter(string visibleValue) + { + this.visibleValue = visibleValue; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString() == visibleValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) => this; + } +} diff --git a/src/GitHub.UI/Converters/InverseBooleanConverter.cs b/src/GitHub.UI/Converters/InverseBooleanConverter.cs index 13dc95c879..660430f1c3 100644 --- a/src/GitHub.UI/Converters/InverseBooleanConverter.cs +++ b/src/GitHub.UI/Converters/InverseBooleanConverter.cs @@ -2,7 +2,6 @@ using System.Globalization; using System.Windows; using System.Windows.Data; -using NullGuard; namespace GitHub.UI { @@ -12,8 +11,8 @@ public class InverseBooleanConverter : ValueConverterMarkupExtension<InverseBool { public override object Convert(object value, Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + object parameter, + CultureInfo culture) { if (targetType != typeof(bool)) throw new InvalidOperationException("The target must be a boolean"); @@ -22,8 +21,8 @@ public override object Convert(object value, } public override object ConvertBack(object value, Type targetType, - [AllowNull]object parameter, - [AllowNull]CultureInfo culture) + object parameter, + CultureInfo culture) { if (targetType != typeof(bool)) throw new InvalidOperationException("The target must be a boolean"); diff --git a/src/GitHub.UI/Converters/IsLastItemInContainerConverter.cs b/src/GitHub.UI/Converters/IsLastItemInContainerConverter.cs index 90e4c1f5e2..ded361511b 100644 --- a/src/GitHub.UI/Converters/IsLastItemInContainerConverter.cs +++ b/src/GitHub.UI/Converters/IsLastItemInContainerConverter.cs @@ -6,20 +6,20 @@ namespace GitHub.UI { - // http://stackoverflow.com/questions/12125764/change-style-of-last-item-in-listbox - public class IsLastItemInContainerConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - DependencyObject item = (DependencyObject)value; - ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item); + // http://stackoverflow.com/questions/12125764/change-style-of-last-item-in-listbox + public class IsLastItemInContainerConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + DependencyObject item = (DependencyObject)value; + ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item); - return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1; - } + return ic != null ? ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1 : false; + } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/src/GitHub.UI/Converters/MultiBooleanToVisibilityConverter.cs b/src/GitHub.UI/Converters/MultiBooleanToVisibilityConverter.cs new file mode 100644 index 0000000000..1416e85a33 --- /dev/null +++ b/src/GitHub.UI/Converters/MultiBooleanToVisibilityConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows; + +namespace GitHub.UI +{ + [Localizability(LocalizationCategory.NeverLocalize)] + public sealed class MultiBooleanToVisibilityConverter : MultiValueConverterMarkupExtension<MultiBooleanToVisibilityConverter> + { + public override object Convert( + object[] value, + Type targetType, + object parameter, + CultureInfo culture) + { + return value.OfType<bool>().All(x => x) ? Visibility.Visible : Visibility.Collapsed; + } + + public override object[] ConvertBack( + object value, + Type[] targetType, + object parameter, + CultureInfo culture) + { + return null; + } + } +} diff --git a/src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs b/src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs new file mode 100644 index 0000000000..42d186302b --- /dev/null +++ b/src/GitHub.UI/Converters/NotEqualsToVisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace GitHub.UI +{ + public class NotEqualsToVisibilityConverter : MarkupExtension, IValueConverter + { + readonly string collapsedValue; + + public NotEqualsToVisibilityConverter(string collapsedValue) + { + this.collapsedValue = collapsedValue; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.ToString() != collapsedValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) => this; + } +} diff --git a/src/GitHub.UI/Converters/NullToVisibilityConverter.cs b/src/GitHub.UI/Converters/NullToVisibilityConverter.cs new file mode 100644 index 0000000000..3f54a541f1 --- /dev/null +++ b/src/GitHub.UI/Converters/NullToVisibilityConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace GitHub.UI +{ + [Localizability(LocalizationCategory.NeverLocalize)] + public sealed class NullToVisibilityConverter : ValueConverterMarkupExtension<NullToVisibilityConverter> + { + readonly System.Windows.Controls.BooleanToVisibilityConverter converter = new System.Windows.Controls.BooleanToVisibilityConverter(); + + public override object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + return converter.Convert(value != null, targetType, parameter, culture); + } + + public override object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + return converter.ConvertBack(value != null, targetType, parameter, culture); + } + } +} diff --git a/src/GitHub.UI/Converters/StickieListItemConverter.cs b/src/GitHub.UI/Converters/StickieListItemConverter.cs new file mode 100644 index 0000000000..9652f1e208 --- /dev/null +++ b/src/GitHub.UI/Converters/StickieListItemConverter.cs @@ -0,0 +1,13 @@ +using System; +using System.Globalization; + +namespace GitHub.UI +{ + public class StickieListItemConverter : MultiValueConverterMarkupExtension<StickieListItemConverter> + { + public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture) + { + return value[1]; + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Converters/StringConverter.cs b/src/GitHub.UI/Converters/StringConverter.cs index 5289b6279b..958650f3a6 100644 --- a/src/GitHub.UI/Converters/StringConverter.cs +++ b/src/GitHub.UI/Converters/StringConverter.cs @@ -10,7 +10,6 @@ public override object Convert(object value, Type targetType, object parameter, { var text = value as string; if (String.IsNullOrEmpty(text)) return null; - Debug.Assert(text != null, "This should not be null. Possible error with IsNullOrEmpty extension method"); var conversionType = GetConversionTypeFromParameter(parameter); diff --git a/src/GitHub.UI/Converters/ThicknessConverter.cs b/src/GitHub.UI/Converters/ThicknessConverter.cs index 5121d32949..2edad6a235 100644 --- a/src/GitHub.UI/Converters/ThicknessConverter.cs +++ b/src/GitHub.UI/Converters/ThicknessConverter.cs @@ -1,7 +1,6 @@ using System; using System.Windows; using System.Windows.Data; -using NullGuard; namespace GitHub.UI { @@ -9,9 +8,9 @@ public class ThicknessConverter : IValueConverter { public object Convert( object value, - [AllowNull]Type targetType, - [AllowNull]object parameter, - [AllowNull]System.Globalization.CultureInfo culture) + Type targetType, + object parameter, + System.Globalization.CultureInfo culture) { var t = ((Thickness)value); diff --git a/src/GitHub.UI/Converters/TrimNewlinesConverter.cs b/src/GitHub.UI/Converters/TrimNewlinesConverter.cs new file mode 100644 index 0000000000..d7dee09fd7 --- /dev/null +++ b/src/GitHub.UI/Converters/TrimNewlinesConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace GitHub.UI +{ + /// <summary> + /// An <see cref="IValueConverter"/> that trims newlines and tabs from a string and replaces them + /// with spaces. + /// </summary> + public class TrimNewlinesConverter : ValueConverterMarkupExtension<TrimNewlinesConverter> + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var text = value as string; + if (String.IsNullOrEmpty(text)) return null; + return Regex.Replace(text, @"\t|\n|\r", " "); + } + } +} diff --git a/src/GitHub.UI/FodyWeavers.xml b/src/GitHub.UI/FodyWeavers.xml deleted file mode 100644 index 95494fb843..0000000000 --- a/src/GitHub.UI/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard ExcludeRegex="^GitHub.UI.*$" /> -</Weavers> \ No newline at end of file diff --git a/src/GitHub.UI/GitHub.UI.csproj b/src/GitHub.UI/GitHub.UI.csproj index 47647dd1a3..041f14a08d 100644 --- a/src/GitHub.UI/GitHub.UI.csproj +++ b/src/GitHub.UI/GitHub.UI.csproj @@ -5,56 +5,69 @@ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{346384DD-2445-4A28-AF22-B45F3957BD89}</ProjectGuid> + <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub.UI</RootNamespace> <AssemblyName>GitHub.UI</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <NuGetPackageImportStamp>6e7ecf50</NuGetPackageImportStamp> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <RunCodeAnalysis>false</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>false</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> <ItemGroup> - <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> + <Reference Include="Microsoft.Expression.Interactions, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\Microsoft.Expression.Interactions.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> + <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\System.Windows.Interactivity.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System.Xaml" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> @@ -64,15 +77,49 @@ <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> + <Compile Include="Behaviours\ClosePopupAction.cs" /> + <Compile Include="Behaviours\OpenPopupAction.cs" /> + <Compile Include="Controls\ComboBoxes\LinkDropDown.cs" /> + <Compile Include="Controls\DropDownButton.cs" /> <Compile Include="Controls\Octicons\OcticonPaths.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>OcticonPaths.resx</DependentUpon> </Compile> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> + <Compile Include="Controls\ScrollingVerticalStackPanel.cs" /> + <Compile Include="Controls\SectionControl.cs" /> + <Compile Include="Controls\Spinner.xaml.cs"> + <DependentUpon>Spinner.xaml</DependentUpon> + </Compile> + <Compile Include="Controls\TrimmedPathTextBlock.cs" /> + <Compile Include="Converters\AllCapsConverter.cs" /> + <Compile Include="Converters\EqualityConverter.cs" /> + <Compile Include="Converters\EqualsToVisibilityConverter.cs" /> + <Compile Include="Converters\MultiBooleanToVisibilityConverter.cs" /> + <Compile Include="Converters\NotEqualsToVisibilityConverter.cs" /> + <Compile Include="Converters\NullToVisibilityConverter.cs" /> + <Compile Include="Converters\BranchNameConverter.cs" /> + <Compile Include="Converters\CountToVisibilityConverter.cs" /> + <Compile Include="Converters\DefaultValueConverter.cs" /> + <Compile Include="Converters\StickieListItemConverter.cs" /> + <Compile Include="Converters\TrimNewlinesConverter.cs" /> + <Compile Include="Helpers\LoadingResourceDictionary.cs" /> + <Compile Include="Helpers\ScrollViewerUtilities.cs" /> + <Compile Include="Helpers\SharedDictionaryManager.cs" /> + <Compile Include="Helpers\TreeViewExtensions.cs" /> + <Compile Include="Helpers\VisualTreeExtensions.cs" /> + <Compile Include="Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="TestAutomation\AutomationIDs.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>AutomationIDs.resx</DependentUpon> + </Compile> <Compile Include="Controls\GitHubProgressBar.cs" /> + <Compile Include="Converters\DurationToStringConverter.cs" /> <Compile Include="Converters\HasItemsVisibilityConverter.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="..\common\SolutionInfo.cs"> @@ -95,6 +142,7 @@ <Compile Include="Controls\Buttons\OcticonButton.cs" /> <Compile Include="Controls\Buttons\OcticonCircleButton.cs" /> <Compile Include="Controls\Buttons\OcticonLinkButton.cs" /> + <Compile Include="Controls\Buttons\GitHubActionLink.cs" /> <Compile Include="Controls\ImageButton.cs" /> <Compile Include="Controls\IShortcutContainer.cs" /> <Compile Include="Controls\Panels\FixedAspectRatioPanel.cs" /> @@ -121,7 +169,6 @@ <Compile Include="Converters\VerticalOffsetToVisibilityConverter.cs" /> <Compile Include="Helpers\BindingProxy.cs" /> <Compile Include="Helpers\GitHubBrushes.cs" /> - <Compile Include="Helpers\SharedDictionaryManager.cs" /> </ItemGroup> <ItemGroup> <Page Include="Assets\Controls\GitHubLinkButton.xaml"> @@ -145,6 +192,9 @@ <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> + <Page Include="Assets\Markdown.xaml"> + <SubType>Designer</SubType> + </Page> <Page Include="Assets\TextBlocks.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> @@ -165,10 +215,18 @@ <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> + <Page Include="Controls\Buttons\OcticonButton.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> <Page Include="Controls\Buttons\OcticonLinkButton.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> + <Page Include="Controls\DropDownButton.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> <Page Include="Controls\HorizontalShadowDivider.xaml"> <Generator>MSBuild:Compile</Generator> </Page> @@ -176,6 +234,10 @@ <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> + <Page Include="Controls\Spinner.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> <Page Include="Controls\ToggleButtons\OcticonCircleToggleButton.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> @@ -187,6 +249,10 @@ <Page Include="SharedDictionary.xaml"> <Generator>MSBuild:Compile</Generator> </Page> + <Page Include="Themes\Generic.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> </ItemGroup> <ItemGroup> <EmbeddedResource Include="Controls\Octicons\OcticonPaths.resx"> @@ -194,24 +260,34 @@ <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>OcticonPaths.Designer.cs</LastGenOutput> </EmbeddedResource> + <EmbeddedResource Include="Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + <SubType>Designer</SubType> + </EmbeddedResource> + <EmbeddedResource Include="TestAutomation\AutomationIDs.resx"> + <Generator>PublicResXFileCodeGenerator</Generator> + <LastGenOutput>AutomationIDs.Designer.cs</LastGenOutput> + </EmbeddedResource> </ItemGroup> <ItemGroup> <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> <Name>GitHub.Exports</Name> </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> </ItemGroup> <ItemGroup> - <Resource Include="FodyWeavers.xml" /> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>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}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> - </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/src/GitHub.UI/Helpers/BindingProxy.cs b/src/GitHub.UI/Helpers/BindingProxy.cs index 175db5c710..3f616ce065 100644 --- a/src/GitHub.UI/Helpers/BindingProxy.cs +++ b/src/GitHub.UI/Helpers/BindingProxy.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Windows; -using NullGuard; namespace GitHub.UI { @@ -16,7 +15,6 @@ protected override Freezable CreateInstanceCore() public object Data { - [return: AllowNull] get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } diff --git a/src/GitHub.UI/Helpers/GitHubBrushes.cs b/src/GitHub.UI/Helpers/GitHubBrushes.cs index a6cb6a399b..966d5e28d1 100644 --- a/src/GitHub.UI/Helpers/GitHubBrushes.cs +++ b/src/GitHub.UI/Helpers/GitHubBrushes.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Windows.Media; +using GitHub.Extensions; namespace GitHub.UI { @@ -59,9 +60,9 @@ public static SolidColorBrush CreateBrush(Color color) public static SolidColorBrush CreateBrush(string color) { + Guard.ArgumentNotNull(color, nameof(color)); + var colorObject = ColorConverter.ConvertFromString(color); - Debug.Assert(colorObject != null, - string.Format(CultureInfo.InvariantCulture, "Cannot convert string '{0}' to a Color instance.", color)); return CreateBrush((Color)colorObject); } } diff --git a/src/GitHub.UI/Helpers/LoadingResourceDictionary.cs b/src/GitHub.UI/Helpers/LoadingResourceDictionary.cs new file mode 100644 index 0000000000..1fd4a02422 --- /dev/null +++ b/src/GitHub.UI/Helpers/LoadingResourceDictionary.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Windows; +using System.Reflection; +using System.Collections.Generic; +using System.Diagnostics; + +namespace GitHub +{ + public class LoadingResourceDictionary : ResourceDictionary + { + static Dictionary<string, Assembly> assemblyDicts = new Dictionary<string, Assembly>(); + + public new Uri Source + { + get { return base.Source; } + set + { + EnsureAssemblyLoaded(value); + base.Source = value; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] + void EnsureAssemblyLoaded(Uri value) + { + try + { + var assemblyName = FindAssemblyNameFromPackUri(value); + if (assemblyName == null) + { + Trace.WriteLine($"Couldn't find assembly name in '{value}'"); + return; + } + + var baseDir = Path.GetDirectoryName(GetType().Assembly.Location); + var assemblyFile = Path.Combine(baseDir, assemblyName + ".dll"); + if (assemblyDicts.ContainsKey(assemblyFile)) + { + return; + } + + if (!File.Exists(assemblyFile)) + { + Trace.WriteLine($"Couldn't find assembly at '{assemblyFile}'"); + return; + } + + var assembly = Assembly.LoadFrom(assemblyFile); + assemblyDicts.Add(assemblyFile, assembly); + } + catch (Exception) + { + Trace.WriteLine($"Error loading assembly for '{value}"); + } + } + + static string FindAssemblyNameFromPackUri(Uri packUri) + { + var path = packUri.LocalPath; + if (!path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var component = ";component/"; + int componentIndex = path.IndexOf(component, 1, StringComparison.OrdinalIgnoreCase); + if (componentIndex == -1) + { + return null; + } + + return path.Substring(1, componentIndex - 1); + } + } +} diff --git a/src/GitHub.UI/Helpers/ScrollViewerUtilities.cs b/src/GitHub.UI/Helpers/ScrollViewerUtilities.cs new file mode 100644 index 0000000000..334d8d46a1 --- /dev/null +++ b/src/GitHub.UI/Helpers/ScrollViewerUtilities.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using GitHub.UI.Helpers; + +namespace GitHub.VisualStudio.UI.Helpers +{ + /// <summary> + /// Utilities for fixing WPF's broken ScrollViewer. + /// </summary> + public static class ScrollViewerUtilities + { + /// <summary> + /// Fixes mouse wheel scrolling in controls that have a ScrollViewer. + /// </summary> + /// <param name="sender">The sender.</param> + /// <param name="e">The event arguments.</param> + /// <remarks> + /// WPF's ScrollViewer is broken in that it doesn't pass scroll events to the parent + /// control when it can't scroll any more. Add this method as a PreviewMouseWheel event + /// handler to a ScrollViewer or a control which has a ScrollViewer in its template to + /// fix this. + /// </remarks> + public static void FixMouseWheelScroll(object sender, MouseWheelEventArgs e) + { + try + { + if (!e.Handled) + { + var control = sender as FrameworkElement; + var parent = control.Parent as UIElement; + var scrollViewer = control.GetSelfAndVisualDescendents() + .OfType<ScrollViewer>() + .FirstOrDefault(); + + if (scrollViewer != null && parent != null) + { + var offset = scrollViewer.ContentVerticalOffset; + + if ((offset == scrollViewer.ScrollableHeight && e.Delta < 0) || + (offset == 0 && e.Delta > 0)) + { + e.Handled = true; + parent.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) + { + RoutedEvent = UIElement.MouseWheelEvent, + Source = control, + }); + } + } + } + } + catch + { + } + } + } +} diff --git a/src/GitHub.UI/Helpers/SharedDictionaryManager.cs b/src/GitHub.UI/Helpers/SharedDictionaryManager.cs index 4ce7d81442..b9a5c0fe77 100644 --- a/src/GitHub.UI/Helpers/SharedDictionaryManager.cs +++ b/src/GitHub.UI/Helpers/SharedDictionaryManager.cs @@ -1,29 +1,153 @@ using System; -using System.Collections.Generic; using System.Windows; +using System.Collections.Generic; +using System.Globalization; namespace GitHub.UI.Helpers { public class SharedDictionaryManager : ResourceDictionary { - static readonly Dictionary<Uri, ResourceDictionary> resourceDicts = new Dictionary<Uri, ResourceDictionary>(); + CachingFactory factory; + Uri source; + + public SharedDictionaryManager() : this(CachingFactory.GetInstanceForDomain()) + { + } + + public SharedDictionaryManager(CachingFactory factory) + { + this.factory = factory; + } - Uri sourceUri; - public new Uri Source + public virtual new Uri Source { - get { return sourceUri; } + // Just in case the designer checks this property. + get + { + return source; + } + set { - sourceUri = value; - ResourceDictionary ret; - if (resourceDicts.TryGetValue(value, out ret)) + source = value; + + value = FixDesignTimeUri(value); + var rd = factory.GetOrCreateResourceDictionary(this, value); + MergedDictionaries.Clear(); + MergedDictionaries.Add(rd); + } + } + + public class CachingFactory : IDisposable + { + internal static string DataName = typeof(CachingFactory).FullName; + + IDictionary<Uri, ResourceDictionary> sharedDictionaries; + ISet<IDisposable> disposables; + + public CachingFactory() + { + sharedDictionaries = new Dictionary<Uri, ResourceDictionary>(); + disposables = new HashSet<IDisposable>(); + + AppDomain.CurrentDomain.SetData(DataName, this); + } + + public static CachingFactory GetInstanceForDomain() + { + var data = AppDomain.CurrentDomain.GetData(DataName); + + var cachingFactory = data as CachingFactory; + if (cachingFactory != null) { - MergedDictionaries.Add(ret); - return; + return cachingFactory; } - base.Source = value; - resourceDicts.Add(value, this); + + var disposable = data as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + + return new CachingFactory(); } + + public ResourceDictionary GetOrCreateResourceDictionary(ResourceDictionary owner, Uri uri) + { + TryAddDisposable(owner); + + ResourceDictionary rd; + if (!sharedDictionaries.TryGetValue(uri, out rd)) + { + rd = new LoadingResourceDictionary { Source = uri }; + sharedDictionaries[uri] = rd; + } + + return rd; + } + + // Remember subtypes that need disposing of. + public void TryAddDisposable(object owner) + { + var disposable = owner as IDisposable; + if (disposable != null) + { + disposables.Add(disposable); + } + } + + bool disposed; + void Dispose(bool disposing) + { + if (disposed) return; + if (disposing) + { + disposed = true; + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + + disposables.Clear(); + sharedDictionaries.Clear(); + } + + AppDomain.CurrentDomain.SetData(DataName, null); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } + + public static Uri FixDesignTimeUri(Uri inUri) + { + if (inUri.Scheme != "file") + { + return inUri; + } + + var url = inUri.ToString(); + var assemblyPrefix = "/src/"; + var assemblyIndex = url.LastIndexOf(assemblyPrefix, StringComparison.OrdinalIgnoreCase); + if (assemblyIndex == -1) + { + return inUri; + } + + assemblyIndex += assemblyPrefix.Length; + var pathIndex = url.IndexOf('/', assemblyIndex); + if (pathIndex == -1) + { + return inUri; + } + + var assemblyName = url.Substring(assemblyIndex, pathIndex - assemblyIndex); + var path = url.Substring(pathIndex + 1); + + return new Uri(String.Format(CultureInfo.InvariantCulture, "pack://application:,,,/{0};component/{1}", assemblyName, path)); } } } diff --git a/src/GitHub.UI/Helpers/TreeViewExtensions.cs b/src/GitHub.UI/Helpers/TreeViewExtensions.cs new file mode 100644 index 0000000000..b78f5d67f5 --- /dev/null +++ b/src/GitHub.UI/Helpers/TreeViewExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.UI.Helpers +{ + /// <summary> + /// Extensions for TreeView control. + /// </summary> + public static class TreeViewExtensions + { + /// <summary> + /// Gets the TreeViewItem for an item in the TreeView. + /// </summary> + /// <param name="tree">The tree view.</param> + /// <param name="item">The item to search for.</param> + /// <returns>The TreeViewItem or null if the item was null or not found.</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] + public static TreeViewItem GetTreeViewItem(this TreeView tree, object item) + { + if (item == null) + { + return null; + } + + return GetTreeViewItem(tree.ItemContainerGenerator, item); + } + + static TreeViewItem GetTreeViewItem(ItemContainerGenerator itemContainerGenerator, object item) + { + var container = (TreeViewItem)itemContainerGenerator.ContainerFromItem(item); + + if (container == null) + { + foreach (var childItem in itemContainerGenerator.Items) + { + var node = (TreeViewItem)itemContainerGenerator.ContainerFromItem(childItem); + container = GetTreeViewItem(node.ItemContainerGenerator, item); + + if (container != null) + { + return container; + } + } + } + + return container; + } + } +} diff --git a/src/GitHub.UI/Helpers/VisualTreeExtensions.cs b/src/GitHub.UI/Helpers/VisualTreeExtensions.cs new file mode 100644 index 0000000000..4d8eee26ee --- /dev/null +++ b/src/GitHub.UI/Helpers/VisualTreeExtensions.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows.Media; +using GitHub.Extensions; + +namespace GitHub.UI.Helpers +{ + public static class VisualTreeExtensions + { + public static IEnumerable<Visual> GetSelfAndVisualAncestors(this Visual visual) + { + Guard.ArgumentNotNull(visual, nameof(visual)); + + return Enumerable.Repeat(visual, 1).Concat(GetVisualAncestors(visual)); + } + + public static IEnumerable<Visual> GetVisualAncestors(this Visual visual) + { + Guard.ArgumentNotNull(visual, nameof(visual)); + + while (true) + { + visual = VisualTreeHelper.GetParent(visual) as Visual; + + if (visual != null) + yield return visual; + else + break; + } + } + + public static IEnumerable<Visual> GetSelfAndVisualDescendents(this Visual visual) + { + Guard.ArgumentNotNull(visual, nameof(visual)); + + return Enumerable.Repeat(visual, 1).Concat(GetVisualDescendents(visual)); + } + + public static IEnumerable<Visual> GetVisualDescendents(this Visual visual) + { + Guard.ArgumentNotNull(visual, nameof(visual)); + + var count = VisualTreeHelper.GetChildrenCount(visual); + + for (var i = 0; i < count; ++i) + { + var child = (Visual)VisualTreeHelper.GetChild(visual, i); + yield return child; + + foreach (var descendent in child.GetVisualDescendents()) + { + yield return descendent; + } + } + } + } +} diff --git a/src/GitHub.UI/Properties/AssemblyInfo.cs b/src/GitHub.UI/Properties/AssemblyInfo.cs index d275386622..d4827fb54e 100644 --- a/src/GitHub.UI/Properties/AssemblyInfo.cs +++ b/src/GitHub.UI/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Windows; +using System.Windows.Markup; [assembly: AssemblyTitle("GitHub.UI")] [assembly: AssemblyDescription("GitHub flavored WPF styles and controls")] @@ -13,4 +14,11 @@ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) - )] \ No newline at end of file + )] + +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.Helpers")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.UI")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.UI.Controls")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.UI.Converters")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.UI.Helpers")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.UI.TestAutomation")] diff --git a/src/GitHub.UI/Resources.Designer.cs b/src/GitHub.UI/Resources.Designer.cs new file mode 100644 index 0000000000..75d9c09db5 --- /dev/null +++ b/src/GitHub.UI/Resources.Designer.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace GitHub.UI { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GitHub.UI.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} day ago. + /// </summary> + internal static string day { + get { + return ResourceManager.GetString("day", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} days ago. + /// </summary> + internal static string days { + get { + return ResourceManager.GetString("days", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} hour ago. + /// </summary> + internal static string hour { + get { + return ResourceManager.GetString("hour", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} hours ago. + /// </summary> + internal static string hours { + get { + return ResourceManager.GetString("hours", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to just now. + /// </summary> + internal static string JustNow { + get { + return ResourceManager.GetString("JustNow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} minute ago. + /// </summary> + internal static string minute { + get { + return ResourceManager.GetString("minute", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} minutes ago. + /// </summary> + internal static string minutes { + get { + return ResourceManager.GetString("minutes", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} month ago. + /// </summary> + internal static string month { + get { + return ResourceManager.GetString("month", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} months ago. + /// </summary> + internal static string months { + get { + return ResourceManager.GetString("months", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} second ago. + /// </summary> + internal static string second { + get { + return ResourceManager.GetString("second", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} seconds ago. + /// </summary> + internal static string seconds { + get { + return ResourceManager.GetString("seconds", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} year ago. + /// </summary> + internal static string year { + get { + return ResourceManager.GetString("year", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0:N0} years ago. + /// </summary> + internal static string years { + get { + return ResourceManager.GetString("years", resourceCulture); + } + } + } +} diff --git a/src/GitHub.UI/Resources.resx b/src/GitHub.UI/Resources.resx new file mode 100644 index 0000000000..2b61694a8f --- /dev/null +++ b/src/GitHub.UI/Resources.resx @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="day" xml:space="preserve"> + <value>{0:N0} day ago</value> + </data> + <data name="days" xml:space="preserve"> + <value>{0:N0} days ago</value> + </data> + <data name="hour" xml:space="preserve"> + <value>{0:N0} hour ago</value> + </data> + <data name="hours" xml:space="preserve"> + <value>{0:N0} hours ago</value> + </data> + <data name="JustNow" xml:space="preserve"> + <value>just now</value> + </data> + <data name="minute" xml:space="preserve"> + <value>{0:N0} minute ago</value> + </data> + <data name="minutes" xml:space="preserve"> + <value>{0:N0} minutes ago</value> + </data> + <data name="month" xml:space="preserve"> + <value>{0:N0} month ago</value> + </data> + <data name="months" xml:space="preserve"> + <value>{0:N0} months ago</value> + </data> + <data name="second" xml:space="preserve"> + <value>{0:N0} second ago</value> + </data> + <data name="seconds" xml:space="preserve"> + <value>{0:N0} seconds ago</value> + </data> + <data name="year" xml:space="preserve"> + <value>{0:N0} year ago</value> + </data> + <data name="years" xml:space="preserve"> + <value>{0:N0} years ago</value> + </data> +</root> \ No newline at end of file diff --git a/src/GitHub.UI/SharedDictionary.xaml b/src/GitHub.UI/SharedDictionary.xaml index d4bd2f9102..14cfd8d63c 100644 --- a/src/GitHub.UI/SharedDictionary.xaml +++ b/src/GitHub.UI/SharedDictionary.xaml @@ -5,5 +5,5 @@ <ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/Controls.xaml" /> <ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/Buttons.xaml" /> <ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Assets/TextBlocks.xaml" /> - </ResourceDictionary.MergedDictionaries> + </ResourceDictionary.MergedDictionaries> </ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.UI/TestAutomation/AutomationIDs.Designer.cs b/src/GitHub.UI/TestAutomation/AutomationIDs.Designer.cs new file mode 100644 index 0000000000..d8bd417d15 --- /dev/null +++ b/src/GitHub.UI/TestAutomation/AutomationIDs.Designer.cs @@ -0,0 +1,1260 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace GitHub.UI.TestAutomation { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class AutomationIDs { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AutomationIDs() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GitHub.UI.TestAutomation.AutomationIDs", typeof(AutomationIDs).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to AccountComboBox. + /// </summary> + public static string AccountComboBox { + get { + return ResourceManager.GetString("AccountComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to AccountListBoxItem. + /// </summary> + public static string AccountListBoxItem { + get { + return ResourceManager.GetString("AccountListBoxItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneAGitHubRepositoryWindow. + /// </summary> + public static string CloneAGitHubRepositoryWindow { + get { + return ResourceManager.GetString("CloneAGitHubRepositoryWindow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneHyperlink. + /// </summary> + public static string CloneHyperlink { + get { + return ResourceManager.GetString("CloneHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneRepositoryButton. + /// </summary> + public static string CloneRepositoryButton { + get { + return ResourceManager.GetString("CloneRepositoryButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneRepositoryCloseButton. + /// </summary> + public static string CloneRepositoryCloseButton { + get { + return ResourceManager.GetString("CloneRepositoryCloseButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneRepositoryLocalPathBrowsePathButton. + /// </summary> + public static string CloneRepositoryLocalPathBrowsePathButton { + get { + return ResourceManager.GetString("CloneRepositoryLocalPathBrowsePathButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneRepositoryLocalPathCustom. + /// </summary> + public static string CloneRepositoryLocalPathCustom { + get { + return ResourceManager.GetString("CloneRepositoryLocalPathCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CloneRepositoryTitleBar. + /// </summary> + public static string CloneRepositoryTitleBar { + get { + return ResourceManager.GetString("CloneRepositoryTitleBar", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to ConnectToGitHubCloseButton. + /// </summary> + public static string ConnectToGitHubCloseButton { + get { + return ResourceManager.GetString("ConnectToGitHubCloseButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to ConnectToGitHubMenuItem. + /// </summary> + public static string ConnectToGitHubMenuItem { + get { + return ResourceManager.GetString("ConnectToGitHubMenuItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to ConnectToGitHubTitleBar. + /// </summary> + public static string ConnectToGitHubTitleBar { + get { + return ResourceManager.GetString("ConnectToGitHubTitleBar", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to ConnectToGitHubWindow. + /// </summary> + public static string ConnectToGitHubWindow { + get { + return ResourceManager.GetString("ConnectToGitHubWindow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateAGitHubGistControl. + /// </summary> + public static string CreateAGitHubGistControl { + get { + return ResourceManager.GetString("CreateAGitHubGistControl", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateAGitHubGistTitleBar. + /// </summary> + public static string CreateAGitHubGistTitleBar { + get { + return ResourceManager.GetString("CreateAGitHubGistTitleBar", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateAGitHubRepositoryWindow. + /// </summary> + public static string CreateAGitHubRepositoryWindow { + get { + return ResourceManager.GetString("CreateAGitHubRepositoryWindow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreatedPullRequestAuthorImage. + /// </summary> + public static string CreatedPullRequestAuthorImage { + get { + return ResourceManager.GetString("CreatedPullRequestAuthorImage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreatedPullRequestDetailsTextBlock. + /// </summary> + public static string CreatedPullRequestDetailsTextBlock { + get { + return ResourceManager.GetString("CreatedPullRequestDetailsTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreatedPullRequestListItem. + /// </summary> + public static string CreatedPullRequestListItem { + get { + return ResourceManager.GetString("CreatedPullRequestListItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreatedPullRequestNumberHyperlink. + /// </summary> + public static string CreatedPullRequestNumberHyperlink { + get { + return ResourceManager.GetString("CreatedPullRequestNumberHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreatedPullRequestTitleHyperlink. + /// </summary> + public static string CreatedPullRequestTitleHyperlink { + get { + return ResourceManager.GetString("CreatedPullRequestTitleHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateGistButton. + /// </summary> + public static string CreateGistButton { + get { + return ResourceManager.GetString("CreateGistButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateHyperlink. + /// </summary> + public static string CreateHyperlink { + get { + return ResourceManager.GetString("CreateHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateNewHyperlink. + /// </summary> + public static string CreateNewHyperlink { + get { + return ResourceManager.GetString("CreateNewHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateRepositoryButton. + /// </summary> + public static string CreateRepositoryButton { + get { + return ResourceManager.GetString("CreateRepositoryButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateRepositoryCloseButton. + /// </summary> + public static string CreateRepositoryCloseButton { + get { + return ResourceManager.GetString("CreateRepositoryCloseButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateRepositoryLocalPathBrowseButton. + /// </summary> + public static string CreateRepositoryLocalPathBrowseButton { + get { + return ResourceManager.GetString("CreateRepositoryLocalPathBrowseButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateRepositoryLocalPathTextBox. + /// </summary> + public static string CreateRepositoryLocalPathTextBox { + get { + return ResourceManager.GetString("CreateRepositoryLocalPathTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to CreateRepositoryTitleBar. + /// </summary> + public static string CreateRepositoryTitleBar { + get { + return ResourceManager.GetString("CreateRepositoryTitleBar", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DontHaveDotcomAccountTextBlock. + /// </summary> + public static string DontHaveDotcomAccountTextBlock { + get { + return ResourceManager.GetString("DontHaveDotcomAccountTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DontHaveEnterpriseTextBlock. + /// </summary> + public static string DontHaveEnterpriseTextBlock { + get { + return ResourceManager.GetString("DontHaveEnterpriseTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DotcomPasswordTextBox. + /// </summary> + public static string DotcomPasswordTextBox { + get { + return ResourceManager.GetString("DotcomPasswordTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DotcomSignInButton. + /// </summary> + public static string DotcomSignInButton { + get { + return ResourceManager.GetString("DotcomSignInButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DotcomSignUpHyperlink. + /// </summary> + public static string DotcomSignUpHyperlink { + get { + return ResourceManager.GetString("DotcomSignUpHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to DotcomUsernameEmailTextBox. + /// </summary> + public static string DotcomUsernameEmailTextBox { + get { + return ResourceManager.GetString("DotcomUsernameEmailTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to EnterpriseLearnMoreHyperlink. + /// </summary> + public static string EnterpriseLearnMoreHyperlink { + get { + return ResourceManager.GetString("EnterpriseLearnMoreHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to EnterprisePasswordTextBox. + /// </summary> + public static string EnterprisePasswordTextBox { + get { + return ResourceManager.GetString("EnterprisePasswordTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to EnterpriseServerAddressTextBox. + /// </summary> + public static string EnterpriseServerAddressTextBox { + get { + return ResourceManager.GetString("EnterpriseServerAddressTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to EnterpriseSignInButton. + /// </summary> + public static string EnterpriseSignInButton { + get { + return ResourceManager.GetString("EnterpriseSignInButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to EnterpriseUsernameEmailTextBox. + /// </summary> + public static string EnterpriseUsernameEmailTextBox { + get { + return ResourceManager.GetString("EnterpriseUsernameEmailTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistAccountImage. + /// </summary> + public static string GistAccountImage { + get { + return ResourceManager.GetString("GistAccountImage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistAccountNameTextBlock. + /// </summary> + public static string GistAccountNameTextBlock { + get { + return ResourceManager.GetString("GistAccountNameTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistCreationControlCustom. + /// </summary> + public static string GistCreationControlCustom { + get { + return ResourceManager.GetString("GistCreationControlCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistDescriptionTextBlock. + /// </summary> + public static string GistDescriptionTextBlock { + get { + return ResourceManager.GetString("GistDescriptionTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistDescriptionTextBox. + /// </summary> + public static string GistDescriptionTextBox { + get { + return ResourceManager.GetString("GistDescriptionTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistErrorMessageTextBlock. + /// </summary> + public static string GistErrorMessageTextBlock { + get { + return ResourceManager.GetString("GistErrorMessageTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistFileNameTextBlock. + /// </summary> + public static string GistFileNameTextBlock { + get { + return ResourceManager.GetString("GistFileNameTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GistFileNameTextBox. + /// </summary> + public static string GistFileNameTextBox { + get { + return ResourceManager.GetString("GistFileNameTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubConnectContentCustom. + /// </summary> + public static string GitHubConnectContentCustom { + get { + return ResourceManager.GetString("GitHubConnectContentCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubHomeContentCustom. + /// </summary> + public static string GitHubHomeContentCustom { + get { + return ResourceManager.GetString("GitHubHomeContentCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubInfoPanel. + /// </summary> + public static string GitHubInfoPanel { + get { + return ResourceManager.GetString("GitHubInfoPanel", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubInfoPanelCancelButton. + /// </summary> + public static string GitHubInfoPanelCancelButton { + get { + return ResourceManager.GetString("GitHubInfoPanelCancelButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubInfoPanelMessageDocument. + /// </summary> + public static string GitHubInfoPanelMessageDocument { + get { + return ResourceManager.GetString("GitHubInfoPanelMessageDocument", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubLoggedOutCreateAnAccountHyperlink. + /// </summary> + public static string GitHubLoggedOutCreateAnAccountHyperlink { + get { + return ResourceManager.GetString("GitHubLoggedOutCreateAnAccountHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubLoggedOutSignInHyperlink. + /// </summary> + public static string GitHubLoggedOutSignInHyperlink { + get { + return ResourceManager.GetString("GitHubLoggedOutSignInHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubPaneView. + /// </summary> + public static string GitHubPaneView { + get { + return ResourceManager.GetString("GitHubPaneView", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubTabItem. + /// </summary> + public static string GitHubTabItem { + get { + return ResourceManager.GetString("GitHubTabItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubToolBar. + /// </summary> + public static string GitHubToolBar { + get { + return ResourceManager.GetString("GitHubToolBar", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubToolBarBackImage. + /// </summary> + public static string GitHubToolBarBackImage { + get { + return ResourceManager.GetString("GitHubToolBarBackImage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubToolBarForwardImage. + /// </summary> + public static string GitHubToolBarForwardImage { + get { + return ResourceManager.GetString("GitHubToolBarForwardImage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubToolBarPullRequestImage. + /// </summary> + public static string GitHubToolBarPullRequestImage { + get { + return ResourceManager.GetString("GitHubToolBarPullRequestImage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHubToolBarRefreshImage. + /// </summary> + public static string GitHubToolBarRefreshImage { + get { + return ResourceManager.GetString("GitHubToolBarRefreshImage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitignoreComboBox. + /// </summary> + public static string GitignoreComboBox { + get { + return ResourceManager.GetString("GitignoreComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitignoreFilterTextBox. + /// </summary> + public static string GitignoreFilterTextBox { + get { + return ResourceManager.GetString("GitignoreFilterTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitignoreListBoxItem. + /// </summary> + public static string GitignoreListBoxItem { + get { + return ResourceManager.GetString("GitignoreListBoxItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitignorePopupWindow. + /// </summary> + public static string GitignorePopupWindow { + get { + return ResourceManager.GetString("GitignorePopupWindow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitignoreTextBlock. + /// </summary> + public static string GitignoreTextBlock { + get { + return ResourceManager.GetString("GitignoreTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to LicenseComboBox. + /// </summary> + public static string LicenseComboBox { + get { + return ResourceManager.GetString("LicenseComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to LicenseFilterTextBox. + /// </summary> + public static string LicenseFilterTextBox { + get { + return ResourceManager.GetString("LicenseFilterTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to LicenseListBoxItem. + /// </summary> + public static string LicenseListBoxItem { + get { + return ResourceManager.GetString("LicenseListBoxItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to LicensePopupWindow. + /// </summary> + public static string LicensePopupWindow { + get { + return ResourceManager.GetString("LicensePopupWindow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to LicenseTextBlock. + /// </summary> + public static string LicenseTextBlock { + get { + return ResourceManager.GetString("LicenseTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to LoggedOutViewCustom. + /// </summary> + public static string LoggedOutViewCustom { + get { + return ResourceManager.GetString("LoggedOutViewCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PowerfulCollaborationTextBlock. + /// </summary> + public static string PowerfulCollaborationTextBlock { + get { + return ResourceManager.GetString("PowerfulCollaborationTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PrivateGistCheckBox. + /// </summary> + public static string PrivateGistCheckBox { + get { + return ResourceManager.GetString("PrivateGistCheckBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PrivateRepositoryCheckBox. + /// </summary> + public static string PrivateRepositoryCheckBox { + get { + return ResourceManager.GetString("PrivateRepositoryCheckBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestCreationCancelButton. + /// </summary> + public static string PullRequestCreationCancelButton { + get { + return ResourceManager.GetString("PullRequestCreationCancelButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestCreationCreateButton. + /// </summary> + public static string PullRequestCreationCreateButton { + get { + return ResourceManager.GetString("PullRequestCreationCreateButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestCreationDescriptionTextBox. + /// </summary> + public static string PullRequestCreationDescriptionTextBox { + get { + return ResourceManager.GetString("PullRequestCreationDescriptionTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestCreationDetailPane. + /// </summary> + public static string PullRequestCreationDetailPane { + get { + return ResourceManager.GetString("PullRequestCreationDetailPane", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestCreationTitleTextBox. + /// </summary> + public static string PullRequestCreationTitleTextBox { + get { + return ResourceManager.GetString("PullRequestCreationTitleTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestCreationViewCustom. + /// </summary> + public static string PullRequestCreationViewCustom { + get { + return ResourceManager.GetString("PullRequestCreationViewCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestListAssigneeFilterComboBox. + /// </summary> + public static string PullRequestListAssigneeFilterComboBox { + get { + return ResourceManager.GetString("PullRequestListAssigneeFilterComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestListAuthorFilterComboBox. + /// </summary> + public static string PullRequestListAuthorFilterComboBox { + get { + return ResourceManager.GetString("PullRequestListAuthorFilterComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestListFilterAssigneesDropDown. + /// </summary> + public static string PullRequestListFilterAssigneesDropDown { + get { + return ResourceManager.GetString("PullRequestListFilterAssigneesDropDown", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestListStatusFilterComboBox. + /// </summary> + public static string PullRequestListStatusFilterComboBox { + get { + return ResourceManager.GetString("PullRequestListStatusFilterComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestListViewCustom. + /// </summary> + public static string PullRequestListViewCustom { + get { + return ResourceManager.GetString("PullRequestListViewCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestRepositoryNameTextBlock. + /// </summary> + public static string PullRequestRepositoryNameTextBlock { + get { + return ResourceManager.GetString("PullRequestRepositoryNameTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestSourceBranchHyperlink. + /// </summary> + public static string PullRequestSourceBranchHyperlink { + get { + return ResourceManager.GetString("PullRequestSourceBranchHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to PullRequestTargetBranchComboBox. + /// </summary> + public static string PullRequestTargetBranchComboBox { + get { + return ResourceManager.GetString("PullRequestTargetBranchComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryCloneControlCustom. + /// </summary> + public static string RepositoryCloneControlCustom { + get { + return ResourceManager.GetString("RepositoryCloneControlCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryCreationControlCustom. + /// </summary> + public static string RepositoryCreationControlCustom { + get { + return ResourceManager.GetString("RepositoryCreationControlCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryDescriptionTextBlock. + /// </summary> + public static string RepositoryDescriptionTextBlock { + get { + return ResourceManager.GetString("RepositoryDescriptionTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryDescriptionTextBox. + /// </summary> + public static string RepositoryDescriptionTextBox { + get { + return ResourceManager.GetString("RepositoryDescriptionTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryGroupItem. + /// </summary> + public static string RepositoryGroupItem { + get { + return ResourceManager.GetString("RepositoryGroupItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryListBoxItem. + /// </summary> + public static string RepositoryListBoxItem { + get { + return ResourceManager.GetString("RepositoryListBoxItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryNameTextBlock. + /// </summary> + public static string RepositoryNameTextBlock { + get { + return ResourceManager.GetString("RepositoryNameTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to RepositoryNameTextBox. + /// </summary> + public static string RepositoryNameTextBox { + get { + return ResourceManager.GetString("RepositoryNameTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SearchRepositoryTextBox. + /// </summary> + public static string SearchRepositoryTextBox { + get { + return ResourceManager.GetString("SearchRepositoryTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SelectABranchComboBox. + /// </summary> + public static string SelectABranchComboBox { + get { + return ResourceManager.GetString("SelectABranchComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignInCustom. + /// </summary> + public static string SignInCustom { + get { + return ResourceManager.GetString("SignInCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignInDotcomHostTabItem. + /// </summary> + public static string SignInDotcomHostTabItem { + get { + return ResourceManager.GetString("SignInDotcomHostTabItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignInEnterpriseHostTabItem. + /// </summary> + public static string SignInEnterpriseHostTabItem { + get { + return ResourceManager.GetString("SignInEnterpriseHostTabItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignInHostTab. + /// </summary> + public static string SignInHostTab { + get { + return ResourceManager.GetString("SignInHostTab", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignInHyperlink. + /// </summary> + public static string SignInHyperlink { + get { + return ResourceManager.GetString("SignInHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignInToGitHubTextBlock. + /// </summary> + public static string SignInToGitHubTextBlock { + get { + return ResourceManager.GetString("SignInToGitHubTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to SignOutHyperlink. + /// </summary> + public static string SignOutHyperlink { + get { + return ResourceManager.GetString("SignOutHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerConnectGitHubSectionButton. + /// </summary> + public static string TeamExplorerConnectGitHubSectionButton { + get { + return ResourceManager.GetString("TeamExplorerConnectGitHubSectionButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerConnectGitHubSectionTextBlock. + /// </summary> + public static string TeamExplorerConnectGitHubSectionTextBlock { + get { + return ResourceManager.GetString("TeamExplorerConnectGitHubSectionTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerHomeGitHubSectionButton. + /// </summary> + public static string TeamExplorerHomeGitHubSectionButton { + get { + return ResourceManager.GetString("TeamExplorerHomeGitHubSectionButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerHomeGitHubSectionTextBlock. + /// </summary> + public static string TeamExplorerHomeGitHubSectionTextBlock { + get { + return ResourceManager.GetString("TeamExplorerHomeGitHubSectionTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerPrivateRepositoryCheckBox. + /// </summary> + public static string TeamExplorerPrivateRepositoryCheckBox { + get { + return ResourceManager.GetString("TeamExplorerPrivateRepositoryCheckBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerPublishAccountComboBox. + /// </summary> + public static string TeamExplorerPublishAccountComboBox { + get { + return ResourceManager.GetString("TeamExplorerPublishAccountComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerPublishHostComboBox. + /// </summary> + public static string TeamExplorerPublishHostComboBox { + get { + return ResourceManager.GetString("TeamExplorerPublishHostComboBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerPublishRepositoryButton. + /// </summary> + public static string TeamExplorerPublishRepositoryButton { + get { + return ResourceManager.GetString("TeamExplorerPublishRepositoryButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerPublishRepositoryDescriptionTextBox. + /// </summary> + public static string TeamExplorerPublishRepositoryDescriptionTextBox { + get { + return ResourceManager.GetString("TeamExplorerPublishRepositoryDescriptionTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerPublishRepositoryNameTextBox. + /// </summary> + public static string TeamExplorerPublishRepositoryNameTextBox { + get { + return ResourceManager.GetString("TeamExplorerPublishRepositoryNameTextBox", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerRepositoryListBoxItem. + /// </summary> + public static string TeamExplorerRepositoryListBoxItem { + get { + return ResourceManager.GetString("TeamExplorerRepositoryListBoxItem", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerRepositoryListView. + /// </summary> + public static string TeamExplorerRepositoryListView { + get { + return ResourceManager.GetString("TeamExplorerRepositoryListView", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerRepositoryNameTextBlock. + /// </summary> + public static string TeamExplorerRepositoryNameTextBlock { + get { + return ResourceManager.GetString("TeamExplorerRepositoryNameTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerRepositoryURLTextBlock. + /// </summary> + public static string TeamExplorerRepositoryURLTextBlock { + get { + return ResourceManager.GetString("TeamExplorerRepositoryURLTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerSyncGitHubRepositoryPublishCustom. + /// </summary> + public static string TeamExplorerSyncGitHubRepositoryPublishCustom { + get { + return ResourceManager.GetString("TeamExplorerSyncGitHubRepositoryPublishCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerSyncGitHubSectionButton. + /// </summary> + public static string TeamExplorerSyncGitHubSectionButton { + get { + return ResourceManager.GetString("TeamExplorerSyncGitHubSectionButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TeamExplorerSyncGitHubSectionTextBlock. + /// </summary> + public static string TeamExplorerSyncGitHubSectionTextBlock { + get { + return ResourceManager.GetString("TeamExplorerSyncGitHubSectionTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationCloseButton. + /// </summary> + public static string TwoFactorAuthenticationCloseButton { + get { + return ResourceManager.GetString("TwoFactorAuthenticationCloseButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationCustom. + /// </summary> + public static string TwoFactorAuthenticationCustom { + get { + return ResourceManager.GetString("TwoFactorAuthenticationCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationInformationTextBlock. + /// </summary> + public static string TwoFactorAuthenticationInformationTextBlock { + get { + return ResourceManager.GetString("TwoFactorAuthenticationInformationTextBlock", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationInputStackPanel. + /// </summary> + public static string TwoFactorAuthenticationInputStackPanel { + get { + return ResourceManager.GetString("TwoFactorAuthenticationInputStackPanel", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationLearnMoreHyperlink. + /// </summary> + public static string TwoFactorAuthenticationLearnMoreHyperlink { + get { + return ResourceManager.GetString("TwoFactorAuthenticationLearnMoreHyperlink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationTitleBar. + /// </summary> + public static string TwoFactorAuthenticationTitleBar { + get { + return ResourceManager.GetString("TwoFactorAuthenticationTitleBar", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticationWindow. + /// </summary> + public static string TwoFactorAuthenticationWindow { + get { + return ResourceManager.GetString("TwoFactorAuthenticationWindow", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticatonInputCustom. + /// </summary> + public static string TwoFactorAuthenticatonInputCustom { + get { + return ResourceManager.GetString("TwoFactorAuthenticatonInputCustom", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to TwoFactorAuthenticatonVerifyButton. + /// </summary> + public static string TwoFactorAuthenticatonVerifyButton { + get { + return ResourceManager.GetString("TwoFactorAuthenticatonVerifyButton", resourceCulture); + } + } + } +} diff --git a/src/GitHub.UI/TestAutomation/AutomationIDs.resx b/src/GitHub.UI/TestAutomation/AutomationIDs.resx new file mode 100644 index 0000000000..0ba6b768fc --- /dev/null +++ b/src/GitHub.UI/TestAutomation/AutomationIDs.resx @@ -0,0 +1,519 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="CreateAGitHubRepositoryWindow" xml:space="preserve"> + <value>CreateAGitHubRepositoryWindow</value> + </data> + <data name="GitHubInfoPanel" xml:space="preserve"> + <value>GitHubInfoPanel</value> + </data> + <data name="GitHubTabItem" xml:space="preserve"> + <value>GitHubTabItem</value> + </data> + <data name="PullRequestCreationViewCustom" xml:space="preserve"> + <value>PullRequestCreationViewCustom</value> + </data> + <data name="PullRequestCreationDetailPane" xml:space="preserve"> + <value>PullRequestCreationDetailPane</value> + </data> + <data name="PullRequestListViewCustom" xml:space="preserve"> + <value>PullRequestListViewCustom</value> + </data> + <data name="PullRequestCreationCancelButton" xml:space="preserve"> + <value>PullRequestCreationCancelButton</value> + </data> + <data name="PullRequestCreationCreateButton" xml:space="preserve"> + <value>PullRequestCreationCreateButton</value> + </data> + <data name="PullRequestTargetBranchComboBox" xml:space="preserve"> + <value>PullRequestTargetBranchComboBox</value> + </data> + <data name="PullRequestCreationDescriptionTextBox" xml:space="preserve"> + <value>PullRequestCreationDescriptionTextBox</value> + </data> + <data name="PullRequestCreationTitleTextBox" xml:space="preserve"> + <value>PullRequestCreationTitleTextBox</value> + </data> + <data name="PullRequestListFilterAssigneesDropDown" xml:space="preserve"> + <value>PullRequestListFilterAssigneesDropDown</value> + </data> + <data name="PullRequestSourceBranchHyperlink" xml:space="preserve"> + <value>PullRequestSourceBranchHyperlink</value> + </data> + <data name="CreatedPullRequestAuthorImage" xml:space="preserve"> + <value>CreatedPullRequestAuthorImage</value> + </data> + <data name="CreatedPullRequestDetailsTextBlock" xml:space="preserve"> + <value>CreatedPullRequestDetailsTextBlock</value> + </data> + <data name="CreatedPullRequestListItem" xml:space="preserve"> + <value>CreatedPullRequestListItem</value> + </data> + <data name="CreatedPullRequestNumberHyperlink" xml:space="preserve"> + <value>CreatedPullRequestNumberHyperlink</value> + </data> + <data name="CreatedPullRequestTitleHyperlink" xml:space="preserve"> + <value>CreatedPullRequestTitleHyperlink</value> + </data> + <data name="CreateNewHyperlink" xml:space="preserve"> + <value>CreateNewHyperlink</value> + </data> + <data name="PullRequestListAssigneeFilterComboBox" xml:space="preserve"> + <value>PullRequestListAssigneeFilterComboBox</value> + </data> + <data name="PullRequestListAuthorFilterComboBox" xml:space="preserve"> + <value>PullRequestListAuthorFilterComboBox</value> + </data> + <data name="PullRequestListStatusFilterComboBox" xml:space="preserve"> + <value>PullRequestListStatusFilterComboBox</value> + </data> + <data name="SelectABranchComboBox" xml:space="preserve"> + <value>SelectABranchComboBox</value> + </data> + <data name="PullRequestRepositoryNameTextBlock" xml:space="preserve"> + <value>PullRequestRepositoryNameTextBlock</value> + </data> + <data name="AccountComboBox" xml:space="preserve"> + <value>AccountComboBox</value> + </data> + <data name="AccountListBoxItem" xml:space="preserve"> + <value>AccountListBoxItem</value> + </data> + <data name="CloneAGitHubRepositoryWindow" xml:space="preserve"> + <value>CloneAGitHubRepositoryWindow</value> + </data> + <data name="CloneRepositoryButton" xml:space="preserve"> + <value>CloneRepositoryButton</value> + </data> + <data name="CloneRepositoryCloseButton" xml:space="preserve"> + <value>CloneRepositoryCloseButton</value> + </data> + <data name="CloneRepositoryLocalPathBrowsePathButton" xml:space="preserve"> + <value>CloneRepositoryLocalPathBrowsePathButton</value> + </data> + <data name="CloneRepositoryLocalPathCustom" xml:space="preserve"> + <value>CloneRepositoryLocalPathCustom</value> + </data> + <data name="CloneRepositoryTitleBar" xml:space="preserve"> + <value>CloneRepositoryTitleBar</value> + </data> + <data name="ConnectToGitHubCloseButton" xml:space="preserve"> + <value>ConnectToGitHubCloseButton</value> + </data> + <data name="ConnectToGitHubTitleBar" xml:space="preserve"> + <value>ConnectToGitHubTitleBar</value> + </data> + <data name="ConnectToGitHubWindow" xml:space="preserve"> + <value>ConnectToGitHubWindow</value> + </data> + <data name="CreateAGitHubGistControl" xml:space="preserve"> + <value>CreateAGitHubGistControl</value> + </data> + <data name="CreateAGitHubGistTitleBar" xml:space="preserve"> + <value>CreateAGitHubGistTitleBar</value> + </data> + <data name="CreateGistButton" xml:space="preserve"> + <value>CreateGistButton</value> + </data> + <data name="CreateRepositoryButton" xml:space="preserve"> + <value>CreateRepositoryButton</value> + </data> + <data name="CreateRepositoryCloseButton" xml:space="preserve"> + <value>CreateRepositoryCloseButton</value> + </data> + <data name="CreateRepositoryLocalPathBrowseButton" xml:space="preserve"> + <value>CreateRepositoryLocalPathBrowseButton</value> + </data> + <data name="CreateRepositoryLocalPathTextBox" xml:space="preserve"> + <value>CreateRepositoryLocalPathTextBox</value> + </data> + <data name="CreateRepositoryTitleBar" xml:space="preserve"> + <value>CreateRepositoryTitleBar</value> + </data> + <data name="DontHaveDotcomAccountTextBlock" xml:space="preserve"> + <value>DontHaveDotcomAccountTextBlock</value> + </data> + <data name="DontHaveEnterpriseTextBlock" xml:space="preserve"> + <value>DontHaveEnterpriseTextBlock</value> + </data> + <data name="DotcomPasswordTextBox" xml:space="preserve"> + <value>DotcomPasswordTextBox</value> + </data> + <data name="DotcomSignInButton" xml:space="preserve"> + <value>DotcomSignInButton</value> + </data> + <data name="DotcomSignUpHyperlink" xml:space="preserve"> + <value>DotcomSignUpHyperlink</value> + </data> + <data name="DotcomUsernameEmailTextBox" xml:space="preserve"> + <value>DotcomUsernameEmailTextBox</value> + </data> + <data name="EnterpriseLearnMoreHyperlink" xml:space="preserve"> + <value>EnterpriseLearnMoreHyperlink</value> + </data> + <data name="EnterprisePasswordTextBox" xml:space="preserve"> + <value>EnterprisePasswordTextBox</value> + </data> + <data name="EnterpriseServerAddressTextBox" xml:space="preserve"> + <value>EnterpriseServerAddressTextBox</value> + </data> + <data name="EnterpriseSignInButton" xml:space="preserve"> + <value>EnterpriseSignInButton</value> + </data> + <data name="EnterpriseUsernameEmailTextBox" xml:space="preserve"> + <value>EnterpriseUsernameEmailTextBox</value> + </data> + <data name="GistAccountImage" xml:space="preserve"> + <value>GistAccountImage</value> + </data> + <data name="GistAccountNameTextBlock" xml:space="preserve"> + <value>GistAccountNameTextBlock</value> + </data> + <data name="GistCreationControlCustom" xml:space="preserve"> + <value>GistCreationControlCustom</value> + </data> + <data name="GistDescriptionTextBlock" xml:space="preserve"> + <value>GistDescriptionTextBlock</value> + </data> + <data name="GistDescriptionTextBox" xml:space="preserve"> + <value>GistDescriptionTextBox</value> + </data> + <data name="GistErrorMessageTextBlock" xml:space="preserve"> + <value>GistErrorMessageTextBlock</value> + </data> + <data name="GistFileNameTextBlock" xml:space="preserve"> + <value>GistFileNameTextBlock</value> + </data> + <data name="GistFileNameTextBox" xml:space="preserve"> + <value>GistFileNameTextBox</value> + </data> + <data name="GitignoreComboBox" xml:space="preserve"> + <value>GitignoreComboBox</value> + </data> + <data name="GitignoreFilterTextBox" xml:space="preserve"> + <value>GitignoreFilterTextBox</value> + </data> + <data name="GitignoreListBoxItem" xml:space="preserve"> + <value>GitignoreListBoxItem</value> + </data> + <data name="GitignorePopupWindow" xml:space="preserve"> + <value>GitignorePopupWindow</value> + </data> + <data name="GitignoreTextBlock" xml:space="preserve"> + <value>GitignoreTextBlock</value> + </data> + <data name="LicenseComboBox" xml:space="preserve"> + <value>LicenseComboBox</value> + </data> + <data name="LicenseFilterTextBox" xml:space="preserve"> + <value>LicenseFilterTextBox</value> + </data> + <data name="LicenseListBoxItem" xml:space="preserve"> + <value>LicenseListBoxItem</value> + </data> + <data name="LicensePopupWindow" xml:space="preserve"> + <value>LicensePopupWindow</value> + </data> + <data name="LicenseTextBlock" xml:space="preserve"> + <value>LicenseTextBlock</value> + </data> + <data name="PrivateGistCheckBox" xml:space="preserve"> + <value>PrivateGistCheckBox</value> + </data> + <data name="PrivateRepositoryCheckBox" xml:space="preserve"> + <value>PrivateRepositoryCheckBox</value> + </data> + <data name="RepositoryCloneControlCustom" xml:space="preserve"> + <value>RepositoryCloneControlCustom</value> + </data> + <data name="RepositoryCreationControlCustom" xml:space="preserve"> + <value>RepositoryCreationControlCustom</value> + </data> + <data name="RepositoryDescriptionTextBlock" xml:space="preserve"> + <value>RepositoryDescriptionTextBlock</value> + </data> + <data name="RepositoryDescriptionTextBox" xml:space="preserve"> + <value>RepositoryDescriptionTextBox</value> + </data> + <data name="RepositoryGroupItem" xml:space="preserve"> + <value>RepositoryGroupItem</value> + </data> + <data name="RepositoryListBoxItem" xml:space="preserve"> + <value>RepositoryListBoxItem</value> + </data> + <data name="RepositoryNameTextBlock" xml:space="preserve"> + <value>RepositoryNameTextBlock</value> + </data> + <data name="RepositoryNameTextBox" xml:space="preserve"> + <value>RepositoryNameTextBox</value> + </data> + <data name="SearchRepositoryTextBox" xml:space="preserve"> + <value>SearchRepositoryTextBox</value> + </data> + <data name="SignInCustom" xml:space="preserve"> + <value>SignInCustom</value> + </data> + <data name="SignInDotcomHostTabItem" xml:space="preserve"> + <value>SignInDotcomHostTabItem</value> + </data> + <data name="SignInEnterpriseHostTabItem" xml:space="preserve"> + <value>SignInEnterpriseHostTabItem</value> + </data> + <data name="SignInHostTab" xml:space="preserve"> + <value>SignInHostTab</value> + </data> + <data name="TwoFactorAuthenticationCloseButton" xml:space="preserve"> + <value>TwoFactorAuthenticationCloseButton</value> + </data> + <data name="TwoFactorAuthenticationCustom" xml:space="preserve"> + <value>TwoFactorAuthenticationCustom</value> + </data> + <data name="TwoFactorAuthenticationInformationTextBlock" xml:space="preserve"> + <value>TwoFactorAuthenticationInformationTextBlock</value> + </data> + <data name="TwoFactorAuthenticationLearnMoreHyperlink" xml:space="preserve"> + <value>TwoFactorAuthenticationLearnMoreHyperlink</value> + </data> + <data name="TwoFactorAuthenticationTitleBar" xml:space="preserve"> + <value>TwoFactorAuthenticationTitleBar</value> + </data> + <data name="TwoFactorAuthenticationWindow" xml:space="preserve"> + <value>TwoFactorAuthenticationWindow</value> + </data> + <data name="TwoFactorAuthenticatonInputCustom" xml:space="preserve"> + <value>TwoFactorAuthenticatonInputCustom</value> + </data> + <data name="TwoFactorAuthenticatonVerifyButton" xml:space="preserve"> + <value>TwoFactorAuthenticatonVerifyButton</value> + </data> + <data name="CloneHyperlink" xml:space="preserve"> + <value>CloneHyperlink</value> + </data> + <data name="ConnectToGitHubMenuItem" xml:space="preserve"> + <value>ConnectToGitHubMenuItem</value> + </data> + <data name="CreateHyperlink" xml:space="preserve"> + <value>CreateHyperlink</value> + </data> + <data name="GitHubConnectContentCustom" xml:space="preserve"> + <value>GitHubConnectContentCustom</value> + </data> + <data name="GitHubHomeContentCustom" xml:space="preserve"> + <value>GitHubHomeContentCustom</value> + </data> + <data name="SignInHyperlink" xml:space="preserve"> + <value>SignInHyperlink</value> + </data> + <data name="SignOutHyperlink" xml:space="preserve"> + <value>SignOutHyperlink</value> + </data> + <data name="TeamExplorerConnectGitHubSectionButton" xml:space="preserve"> + <value>TeamExplorerConnectGitHubSectionButton</value> + </data> + <data name="TeamExplorerConnectGitHubSectionTextBlock" xml:space="preserve"> + <value>TeamExplorerConnectGitHubSectionTextBlock</value> + </data> + <data name="TeamExplorerHomeGitHubSectionButton" xml:space="preserve"> + <value>TeamExplorerHomeGitHubSectionButton</value> + </data> + <data name="TeamExplorerHomeGitHubSectionTextBlock" xml:space="preserve"> + <value>TeamExplorerHomeGitHubSectionTextBlock</value> + </data> + <data name="TeamExplorerRepositoryListBoxItem" xml:space="preserve"> + <value>TeamExplorerRepositoryListBoxItem</value> + </data> + <data name="TeamExplorerRepositoryListView" xml:space="preserve"> + <value>TeamExplorerRepositoryListView</value> + </data> + <data name="TeamExplorerRepositoryNameTextBlock" xml:space="preserve"> + <value>TeamExplorerRepositoryNameTextBlock</value> + </data> + <data name="TeamExplorerRepositoryURLTextBlock" xml:space="preserve"> + <value>TeamExplorerRepositoryURLTextBlock</value> + </data> + <data name="TeamExplorerPrivateRepositoryCheckBox" xml:space="preserve"> + <value>TeamExplorerPrivateRepositoryCheckBox</value> + </data> + <data name="TeamExplorerPublishAccountComboBox" xml:space="preserve"> + <value>TeamExplorerPublishAccountComboBox</value> + </data> + <data name="TeamExplorerPublishHostComboBox" xml:space="preserve"> + <value>TeamExplorerPublishHostComboBox</value> + </data> + <data name="TeamExplorerPublishRepositoryButton" xml:space="preserve"> + <value>TeamExplorerPublishRepositoryButton</value> + </data> + <data name="TeamExplorerPublishRepositoryDescriptionTextBox" xml:space="preserve"> + <value>TeamExplorerPublishRepositoryDescriptionTextBox</value> + </data> + <data name="TeamExplorerPublishRepositoryNameTextBox" xml:space="preserve"> + <value>TeamExplorerPublishRepositoryNameTextBox</value> + </data> + <data name="TeamExplorerSyncGitHubRepositoryPublishCustom" xml:space="preserve"> + <value>TeamExplorerSyncGitHubRepositoryPublishCustom</value> + </data> + <data name="TeamExplorerSyncGitHubSectionButton" xml:space="preserve"> + <value>TeamExplorerSyncGitHubSectionButton</value> + </data> + <data name="TeamExplorerSyncGitHubSectionTextBlock" xml:space="preserve"> + <value>TeamExplorerSyncGitHubSectionTextBlock</value> + </data> + <data name="GitHubInfoPanelCancelButton" xml:space="preserve"> + <value>GitHubInfoPanelCancelButton</value> + </data> + <data name="GitHubInfoPanelMessageDocument" xml:space="preserve"> + <value>GitHubInfoPanelMessageDocument</value> + </data> + <data name="GitHubToolBar" xml:space="preserve"> + <value>GitHubToolBar</value> + </data> + <data name="GitHubToolBarBackImage" xml:space="preserve"> + <value>GitHubToolBarBackImage</value> + </data> + <data name="GitHubToolBarForwardImage" xml:space="preserve"> + <value>GitHubToolBarForwardImage</value> + </data> + <data name="GitHubToolBarPullRequestImage" xml:space="preserve"> + <value>GitHubToolBarPullRequestImage</value> + </data> + <data name="GitHubToolBarRefreshImage" xml:space="preserve"> + <value>GitHubToolBarRefreshImage</value> + </data> + <data name="GitHubLoggedOutCreateAnAccountHyperlink" xml:space="preserve"> + <value>GitHubLoggedOutCreateAnAccountHyperlink</value> + </data> + <data name="GitHubLoggedOutSignInHyperlink" xml:space="preserve"> + <value>GitHubLoggedOutSignInHyperlink</value> + </data> + <data name="LoggedOutViewCustom" xml:space="preserve"> + <value>LoggedOutViewCustom</value> + </data> + <data name="PowerfulCollaborationTextBlock" xml:space="preserve"> + <value>PowerfulCollaborationTextBlock</value> + </data> + <data name="SignInToGitHubTextBlock" xml:space="preserve"> + <value>SignInToGitHubTextBlock</value> + </data> + <data name="GitHubPaneView" xml:space="preserve"> + <value>GitHubPaneView</value> + </data> + <data name="TwoFactorAuthenticationInputStackPanel" xml:space="preserve"> + <value>TwoFactorAuthenticationInputStackPanel</value> + </data> +</root> \ No newline at end of file diff --git a/src/GitHub.UI/Themes/Generic.xaml b/src/GitHub.UI/Themes/Generic.xaml new file mode 100644 index 0000000000..41baf6c6ff --- /dev/null +++ b/src/GitHub.UI/Themes/Generic.xaml @@ -0,0 +1,7 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:GitHub.UI"> + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="pack://application:,,,/GitHub.UI;component/Controls/DropDownButton.xaml"/> + </ResourceDictionary.MergedDictionaries> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.UI/packages.config b/src/GitHub.UI/packages.config index 2a53434606..ea78bb8317 100644 --- a/src/GitHub.UI/packages.config +++ b/src/GitHub.UI/packages.config @@ -1,5 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Fody" version="1.28.0" targetFramework="net45" developmentDependency="true" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net45" developmentDependency="true" /> + <package id="Expression.Blend.Sdk.WPF" version="1.0.1" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> </packages> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Base/TeamExplorerBase.cs b/src/GitHub.VisualStudio.UI/Base/TeamExplorerBase.cs new file mode 100644 index 0000000000..7f022105c5 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Base/TeamExplorerBase.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Extensions; + +namespace GitHub.VisualStudio.Base +{ + public abstract class TeamExplorerBase : NotificationAwareObject, IDisposable + { + public static readonly Guid TeamExplorerConnectionsSectionId = new Guid("ef6a7a99-f01f-4c91-ad31-183c1354dd97"); + + protected IServiceProvider TEServiceProvider + { + get; set; + } + + protected IGitHubServiceProvider ServiceProvider + { + get; + } + + protected TeamExplorerBase(IGitHubServiceProvider serviceProvider) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + + ServiceProvider = serviceProvider; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + + protected static void OpenInBrowser(Lazy<IVisualStudioBrowser> browser, Uri uri) + { + Guard.ArgumentNotNull(browser, nameof(browser)); + Guard.ArgumentNotNull(uri, nameof(uri)); + + OpenInBrowser(browser.Value, uri); + } + + protected static void OpenInBrowser(IVisualStudioBrowser browser, Uri uri) + { + Guard.ArgumentNotNull(browser, nameof(browser)); + Guard.ArgumentNotNull(uri, nameof(uri)); + + browser?.OpenUrl(uri); + } + } +} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerGitRepoInfo.cs b/src/GitHub.VisualStudio.UI/Base/TeamExplorerGitRepoInfo.cs similarity index 76% rename from src/GitHub.VisualStudio/Base/TeamExplorerGitRepoInfo.cs rename to src/GitHub.VisualStudio.UI/Base/TeamExplorerGitRepoInfo.cs index 176028ca14..f8e0ca43ed 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerGitRepoInfo.cs +++ b/src/GitHub.VisualStudio.UI/Base/TeamExplorerGitRepoInfo.cs @@ -2,25 +2,21 @@ using GitHub.Primitives; using GitHub.Services; using GitHub.VisualStudio.Helpers; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; -using NullGuard; namespace GitHub.VisualStudio.Base { public class TeamExplorerGitRepoInfo : TeamExplorerBase, IGitAwareItem { - public TeamExplorerGitRepoInfo() + public TeamExplorerGitRepoInfo(IGitHubServiceProvider serviceProvider) : base(serviceProvider) { activeRepo = null; activeRepoUri = null; activeRepoName = string.Empty; } - ISimpleRepositoryModel activeRepo; - [AllowNull] - public ISimpleRepositoryModel ActiveRepo + ILocalRepositoryModel activeRepo; + public ILocalRepositoryModel ActiveRepo { - [return: AllowNull] get { return activeRepo; } set { @@ -35,10 +31,9 @@ public ISimpleRepositoryModel ActiveRepo /// <summary> /// Represents the web URL of the repository on GitHub.com, even if the origin is an SSH address. /// </summary> - [AllowNull] public UriString ActiveRepoUri { - [return: AllowNull] get { return activeRepoUri; } + get { return activeRepoUri; } set { activeRepoUri = value; this.RaisePropertyChange(); } } diff --git a/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs b/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs new file mode 100644 index 0000000000..917a0b7fee --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs @@ -0,0 +1,203 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.VisualStudio.Helpers; +using GitHub.ViewModels; +using GitHub.VisualStudio.UI; +using GitHub.Extensions; + +namespace GitHub.VisualStudio.Base +{ + public class TeamExplorerItemBase : TeamExplorerGitRepoInfo, IServiceProviderAware + { + readonly ISimpleApiClientFactory apiFactory; + protected ITeamExplorerServiceHolder holder; + + ISimpleApiClient simpleApiClient; + public ISimpleApiClient SimpleApiClient + { + get { return simpleApiClient; } + set + { + if (simpleApiClient != value && value == null) + apiFactory.ClearFromCache(simpleApiClient); + simpleApiClient = value; + } + } + + protected ISimpleApiClientFactory ApiFactory => apiFactory; + + public TeamExplorerItemBase(IGitHubServiceProvider serviceProvider, ITeamExplorerServiceHolder holder) + : base(serviceProvider) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(holder, nameof(holder)); + + this.holder = holder; + } + + public TeamExplorerItemBase(IGitHubServiceProvider serviceProvider, + ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder) + : base(serviceProvider) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + Guard.ArgumentNotNull(apiFactory, nameof(apiFactory)); + Guard.ArgumentNotNull(holder, nameof(holder)); + + this.apiFactory = apiFactory; + this.holder = holder; + } + + public virtual void Initialize(IServiceProvider serviceProvider) + { + Guard.ArgumentNotNull(serviceProvider, nameof(serviceProvider)); + + TEServiceProvider = serviceProvider; + Debug.Assert(holder != null, "Could not get an instance of TeamExplorerServiceHolder"); + if (holder == null) + return; + holder.ServiceProvider = TEServiceProvider; + SubscribeToRepoChanges(); + } + + + public virtual void Execute() + { + } + + public virtual void Invalidate() + { + } + + void SubscribeToRepoChanges() + { + holder.Subscribe(this, (ILocalRepositoryModel repo) => + { + var changed = !Equals(ActiveRepo, repo); + ActiveRepo = repo; + RepoChanged(changed); + }); + } + + void Unsubscribe() + { + holder.Unsubscribe(this); + if (TEServiceProvider != null) + holder.ClearServiceProvider(TEServiceProvider); + } + + protected virtual void RepoChanged(bool changed) + { + var repo = ActiveRepo; + if (repo != null) + { + var uri = repo.CloneUrl; + if (uri?.RepositoryName != null) + { + ActiveRepoUri = uri; + ActiveRepoName = uri.NameWithOwner; + } + } + } + + protected async Task<RepositoryOrigin> GetRepositoryOrigin() + { + if (ActiveRepo == null) + return RepositoryOrigin.NonGitRepository; + + var uri = ActiveRepoUri; + if (uri == null) + return RepositoryOrigin.Other; + + Debug.Assert(apiFactory != null, "apiFactory cannot be null. Did you call the right constructor?"); + SimpleApiClient = await apiFactory.Create(uri); + + var isdotcom = HostAddress.IsGitHubDotComUri(uri.ToRepositoryUrl()); + + if (isdotcom) + { + return RepositoryOrigin.DotCom; + } + else + { + var repo = await SimpleApiClient.GetRepository(); + + if ((repo.FullName == ActiveRepoName || repo.Id == 0) && await SimpleApiClient.IsEnterprise()) + { + return RepositoryOrigin.Enterprise; + } + } + + return RepositoryOrigin.Other; + } + + protected async Task<bool> IsAGitHubRepo() + { + var origin = await GetRepositoryOrigin(); + return origin == RepositoryOrigin.DotCom || origin == RepositoryOrigin.Enterprise; + } + + protected async Task<bool> IsAGitHubDotComRepo() + { + var origin = await GetRepositoryOrigin(); + return origin == RepositoryOrigin.DotCom; + } + + protected async Task<bool> IsUserAuthenticated() + { + if (SimpleApiClient == null) + { + if (ActiveRepo == null) + return false; + + var uri = ActiveRepoUri; + if (uri == null) + return false; + + SimpleApiClient = await apiFactory.Create(uri); + } + + return SimpleApiClient?.IsAuthenticated() ?? false; + } + + bool disposed; + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!disposed) + { + Unsubscribe(); + disposed = true; + } + } + base.Dispose(disposing); + } + + bool isEnabled; + public bool IsEnabled + { + get { return isEnabled; } + set { isEnabled = value; this.RaisePropertyChange(); } + } + + bool isVisible; + public bool IsVisible + { + get { return isVisible; } + set { isVisible = value; this.RaisePropertyChange(); } + } + + string text; + public string Text + { + get { return text; } + set { text = value; this.RaisePropertyChange(); } + } + + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Colors.cs b/src/GitHub.VisualStudio.UI/Colors.cs new file mode 100644 index 0000000000..f9966c2d5d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Colors.cs @@ -0,0 +1,58 @@ +using Microsoft.VisualStudio.PlatformUI; +using System; +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 Color LightThemeNavigationItem = Color.FromRgb(66, 66, 66); + public static Color DarkThemeNavigationItem = Color.FromRgb(200, 200, 200); + + public static int ToInt32(this Color color) + { + return BitConverter.ToInt32(new byte[]{ color.B, color.G, color.R, color.A }, 0); + } + + public static Color ToColor(this System.Drawing.Color color) + { + return Color.FromArgb(color.A, color.R, color.G, color.B); + } + + + static Color AccentMediumDarkTheme = Color.FromRgb(45, 45, 48); + static Color AccentMediumLightTheme = Color.FromRgb(238, 238, 242); + static Color AccentMediumBlueTheme = Color.FromRgb(255, 236, 181); + + public static string DetectTheme() + { + try + { + 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 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"; + } + } + } +} diff --git a/src/GitHub.VisualStudio.UI/Constants.cs b/src/GitHub.VisualStudio.UI/Constants.cs new file mode 100644 index 0000000000..0378a5e916 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Constants.cs @@ -0,0 +1,15 @@ +namespace GitHub.VisualStudio.UI +{ + public static class Constants + { + public const string NoAngleBracketsErrorMessage = "Failed to parse signature - Neither `name` nor `email` should contain angle brackets chars."; + public const int MaxRepositoryNameLength = 100; + public const int MaxDirectoryLength = 200; // Windows allows 248, but we need to allow room for subdirectories. + public const int MaxFilePathLength = 260; + + public const string Notification_RepoCreated = "[{0}](u:{1}) has been successfully created."; + public const string Notification_RepoCloned = "[{0}](u:{1}) has been successfully cloned."; + public const string Notification_CreateNewProject = "[Create a new project or solution](c:{0})."; + public const string Notification_OpenProject = "[Open an existing project or solution](o:{0})."; + } +} diff --git a/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj b/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj new file mode 100644 index 0000000000..3e15e52e4e --- /dev/null +++ b/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj @@ -0,0 +1,275 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{D1DFBB0C-B570-4302-8F1E-2E3A19C41961}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.VisualStudio.UI</RootNamespace> + <AssemblyName>GitHub.VisualStudio.UI</AssemblyName> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>CODE_ANALYSIS;DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <OutputPath>bin\Release\</OutputPath> + </PropertyGroup> + <Import Project="$(SolutionDir)\src\common\signing.props" /> + <ItemGroup> + <Reference Include="Markdig, Version=0.13.0.0, Culture=neutral, PublicKeyToken=870da25a133885f8, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Markdig.Signed.0.13.0\lib\net40\Markdig.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Markdig.Wpf, Version=0.2.1.0, Culture=neutral, PublicKeyToken=a0d0cdbebd8d164b, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Markdig.Wpf.Signed.0.2.1\lib\net452\Markdig.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Windows.Forms" /> + <Reference Include="System.Xaml" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Base\TeamExplorerBase.cs" /> + <Compile Include="Base\TeamExplorerGitRepoInfo.cs" /> + <Compile Include="Base\TeamExplorerItemBase.cs" /> + <Compile Include="Colors.cs" /> + <Compile Include="Constants.cs" /> + <Compile Include="Helpers\ThemeDictionaryManager.cs" /> + <Compile Include="RepositoryOrigin.cs" /> + <Compile Include="Resources.Designer.cs"> + <DependentUpon>Resources.resx</DependentUpon> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + </Compile> + <Compile Include="SharedResources.cs" /> + <Compile Include="TeamFoundationColors.cs" /> + <Compile Include="UI\Controls\AccountAvatar.xaml.cs"> + <DependentUpon>AccountAvatar.xaml</DependentUpon> + </Compile> + <Compile Include="UI\Controls\InfoPanel.xaml.cs"> + <DependentUpon>InfoPanel.xaml</DependentUpon> + </Compile> + <Compile Include="UI\DrawingExtensions.cs" /> + <Compile Include="UI\Views\GitHubConnectContent.xaml.cs"> + <DependentUpon>GitHubConnectContent.xaml</DependentUpon> + </Compile> + <Compile Include="UI\Views\GitHubHomeContent.xaml.cs"> + <DependentUpon>GitHubHomeContent.xaml</DependentUpon> + </Compile> + <Compile Include="UI\Views\GitHubInvitationContent.xaml.cs"> + <DependentUpon>GitHubInvitationContent.xaml</DependentUpon> + </Compile> + <Compile Include="..\common\SolutionInfo.cs"> + <Link>Properties\SolutionInfo.cs</Link> + </Compile> + <Compile Include="Properties\AssemblyInfo.cs" /> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <Page Include="SharedDictionary.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\SectionControl.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\ItemsControls.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\ActionLinkButton.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\Buttons.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\GitHubActionLink.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\GitHubComboBox.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\GitHubTabControl.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\LinkDropDown.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\TextBlocks.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\ThemeBlue.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\ThemeDark.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\ThemeDesignTime.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\ThemeLight.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsBrush.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsBrushesBlue.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsBrushesDark.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsBrushesLight.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsColorsBlue.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsColorsDark.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Styles\VsColorsLight.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="UI\Controls\AccountAvatar.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="UI\Controls\InfoPanel.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="UI\Views\GitHubConnectContent.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="UI\Views\GitHubHomeContent.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="UI\Views\GitHubInvitationContent.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1CE2D235-8072-4649-BA5A-CFB1AF8776E0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> + <Project>{1a1da411-8d1f-4578-80a6-04576bea2dc5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> + <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> + <Name>GitHub.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Resources.resx"> + <Generator>PublicResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + <SubType>Designer</SubType> + </EmbeddedResource> + </ItemGroup> + <ItemGroup /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + </Target> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs b/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs new file mode 100644 index 0000000000..4ff54b5818 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs @@ -0,0 +1,81 @@ +using System; +using System.Windows; +using System.ComponentModel; +using Microsoft.VisualStudio.PlatformUI; +using GitHub.VisualStudio.Helpers; +using GitHub.UI.Helpers; + +namespace GitHub.VisualStudio.UI.Helpers +{ + public class ThemeDictionaryManager : SharedDictionaryManager, IDisposable + { + static bool isInDesignMode; + Uri baseThemeUri; + + static ThemeDictionaryManager() + { + isInDesignMode = DesignerProperties.GetIsInDesignMode(new DependencyObject()); + } + + public override Uri Source + { + get { return base.Source; } + set + { + InitTheme(value); + base.Source = GetCurrentThemeUri(); + } + } + + void InitTheme(Uri themeUri) + { + if (baseThemeUri == null) + { + baseThemeUri = themeUri; + if (!isInDesignMode) + { + VSColorTheme.ThemeChanged += OnThemeChange; + } + } + } + + void OnThemeChange(ThemeChangedEventArgs e) + { + base.Source = GetCurrentThemeUri(); + } + + Uri GetCurrentThemeUri() + { + if (isInDesignMode) + { + return baseThemeUri; + } + + var currentTheme = Colors.DetectTheme(); + return new Uri(baseThemeUri, "Theme" + currentTheme + ".xaml"); + } + + bool disposed; + private void Dispose(bool disposing) + { + if (disposed) return; + if (disposing) + { + disposed = true; + if (baseThemeUri != null) + { + baseThemeUri = null; + if (!isInDesignMode) + { + VSColorTheme.ThemeChanged -= OnThemeChange; + } + } + } + } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Properties/AssemblyInfo.cs b/src/GitHub.VisualStudio.UI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0d5801ea70 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows.Markup; + +[assembly: AssemblyTitle("GitHub.VisualStudio.UI")] +[assembly: AssemblyDescription("GitHub.VisualStudio.UI")] +[assembly: Guid("d1dfbb0c-b570-4302-8f1e-2e3a19c41961")] + +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.UI")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.UI.Controls")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.UI.Views")] diff --git a/src/GitHub.VisualStudio.UI/RepositoryOrigin.cs b/src/GitHub.VisualStudio.UI/RepositoryOrigin.cs new file mode 100644 index 0000000000..0cec4559a9 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/RepositoryOrigin.cs @@ -0,0 +1,11 @@ +namespace GitHub.VisualStudio.UI +{ + public enum RepositoryOrigin + { + Unknown, + DotCom, + Enterprise, + Other, + NonGitRepository, + } +} diff --git a/src/GitHub.VisualStudio.UI/Resources.Designer.cs b/src/GitHub.VisualStudio.UI/Resources.Designer.cs new file mode 100644 index 0000000000..c497412cab --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Resources.Designer.cs @@ -0,0 +1,1046 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace GitHub.VisualStudio.UI { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GitHub.VisualStudio.UI.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to Add review comment. + /// </summary> + public static string AddReviewComment { + get { + return ResourceManager.GetString("AddReviewComment", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Add a single comment. + /// </summary> + public static string AddSingleComment { + get { + return ResourceManager.GetString("AddSingleComment", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Add your review. + /// </summary> + public static string AddYourReview { + get { + return ResourceManager.GetString("AddYourReview", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Invalid authentication code. + /// </summary> + public static string authenticationFailedLabelContent { + get { + return ResourceManager.GetString("authenticationFailedLabelContent", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Try entering the code again or clicking the resend button to get a new authentication code.. + /// </summary> + public static string authenticationFailedLabelMessage { + get { + return ResourceManager.GetString("authenticationFailedLabelMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Authentication code sent!. + /// </summary> + public static string authenticationSentLabelContent { + get { + return ResourceManager.GetString("authenticationSentLabelContent", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to If you do not receive the authentication code, contact support@github.com.. + /// </summary> + public static string authenticationSentLabelMessage { + get { + return ResourceManager.GetString("authenticationSentLabelMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The GitHub extension is not available inside Blend. + /// </summary> + public static string BlendDialogText { + get { + return ResourceManager.GetString("BlendDialogText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Powerful collaboration, code review, and code management for open source and private projects.. + /// </summary> + public static string BlurbText { + get { + return ResourceManager.GetString("BlurbText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Browse. + /// </summary> + public static string browsePathButtonContent { + get { + return ResourceManager.GetString("browsePathButtonContent", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Cancel. + /// </summary> + public static string CancelLink { + get { + return ResourceManager.GetString("CancelLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Changes ({0}). + /// </summary> + public static string ChangesCountFormat { + get { + return ResourceManager.GetString("ChangesCountFormat", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Clone. + /// </summary> + public static string CloneLink { + get { + return ResourceManager.GetString("CloneLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Compare File as Default Action. + /// </summary> + public static string CompareFileAsDefaultAction { + get { + return ResourceManager.GetString("CompareFileAsDefaultAction", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Continue your review. + /// </summary> + public static string ContinueYourReview { + get { + return ResourceManager.GetString("ContinueYourReview", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Could not connect to github.com. + /// </summary> + public static string couldNotConnectToGitHubText { + get { + return ResourceManager.GetString("couldNotConnectToGitHubText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Could not connect to the server.. + /// </summary> + public static string couldNotConnectToTheServerText { + get { + return ResourceManager.GetString("couldNotConnectToTheServerText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Create an account. + /// </summary> + public static string CreateAccountLink { + get { + return ResourceManager.GetString("CreateAccountLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Create. + /// </summary> + public static string CreateLink { + get { + return ResourceManager.GetString("CreateLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Are you sure you want to delete this comment?. + /// </summary> + public static string DeleteCommentConfirmation { + get { + return ResourceManager.GetString("DeleteCommentConfirmation", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Delete Comment. + /// </summary> + public static string DeleteCommentConfirmationCaption { + get { + return ResourceManager.GetString("DeleteCommentConfirmationCaption", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Description. + /// </summary> + public static string Description { + get { + return ResourceManager.GetString("Description", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Description (Optional). + /// </summary> + public static string DescriptionOptional { + get { + return ResourceManager.GetString("DescriptionOptional", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Don’t have an account? . + /// </summary> + public static string dontHaveAnAccountText { + get { + return ResourceManager.GetString("dontHaveAnAccountText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Don’t have GitHub Enterprise? . + /// </summary> + public static string dontHaveGitHubEnterpriseText { + get { + return ResourceManager.GetString("dontHaveGitHubEnterpriseText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Please check your internet connection and try again.. + /// </summary> + public static string dotComConnectionFailedMessageMessage { + get { + return ResourceManager.GetString("dotComConnectionFailedMessageMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The host isn't available or is not a GitHub Enterprise server. Check the address and try again.. + /// </summary> + public static string enterpriseConnectingFailedMessage { + get { + return ResourceManager.GetString("enterpriseConnectingFailedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to GitHub Enterprise server address. + /// </summary> + public static string enterpriseUrlPromptText { + get { + return ResourceManager.GetString("enterpriseUrlPromptText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Could not copy to the clipboard. Please try again.. + /// </summary> + public static string Error_FailedToCopyToClipboard { + get { + return ResourceManager.GetString("Error_FailedToCopyToClipboard", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to File Name. + /// </summary> + public static string fileNameText { + get { + return ResourceManager.GetString("fileNameText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Filter branches. + /// </summary> + public static string filterBranchesText { + get { + return ResourceManager.GetString("filterBranchesText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Search repositories. + /// </summary> + public static string filterTextPromptText { + get { + return ResourceManager.GetString("filterTextPromptText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to (forgot your password?). + /// </summary> + public static string ForgotPasswordLink { + get { + return ResourceManager.GetString("ForgotPasswordLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Fork. + /// </summary> + public static string ForkNavigationItemText { + get { + return ResourceManager.GetString("ForkNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Get Started. + /// </summary> + public static string GetStartedText { + get { + return ResourceManager.GetString("GetStartedText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Gist created. + /// </summary> + public static string gistCreatedMessage { + get { + return ResourceManager.GetString("gistCreatedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Failed to create gist. + /// </summary> + public static string gistCreationFailedMessage { + get { + return ResourceManager.GetString("gistCreationFailedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Connect…. + /// </summary> + public static string GitHubInvitationSectionConnectLabel { + get { + return ResourceManager.GetString("GitHubInvitationSectionConnectLabel", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Publish to GitHub. + /// </summary> + public static string GitHubPublishSectionTitle { + get { + return ResourceManager.GetString("GitHubPublishSectionTitle", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Graphs. + /// </summary> + public static string GraphsNavigationItemText { + get { + return ResourceManager.GetString("GraphsNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Git ignore. + /// </summary> + public static string ignoreTemplateListText { + get { + return ResourceManager.GetString("ignoreTemplateListText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Issues. + /// </summary> + public static string IssuesNavigationItemText { + get { + return ResourceManager.GetString("IssuesNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Learn more. + /// </summary> + public static string learnMoreLink { + get { + return ResourceManager.GetString("learnMoreLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to License. + /// </summary> + public static string licenseListText { + get { + return ResourceManager.GetString("licenseListText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Link copied to clipboard. + /// </summary> + public static string LinkCopiedToClipboardMessage { + get { + return ResourceManager.GetString("LinkCopiedToClipboardMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Some or all repositories may not have loaded. Close the dialog and try again.. + /// </summary> + public static string loadingFailedMessageContent { + get { + return ResourceManager.GetString("loadingFailedMessageContent", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An error occurred while loading repositories. + /// </summary> + public static string loadingFailedMessageMessage { + get { + return ResourceManager.GetString("loadingFailedMessageMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Local branch up to date. + /// </summary> + public static string LocalBranchUpToDate { + get { + return ResourceManager.GetString("LocalBranchUpToDate", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Local path. + /// </summary> + public static string localPathText { + get { + return ResourceManager.GetString("localPathText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Check your username and password, then try again. + /// </summary> + public static string LoginFailedMessage { + get { + return ResourceManager.GetString("LoginFailedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sign in failed.. + /// </summary> + public static string LoginFailedText { + get { + return ResourceManager.GetString("LoginFailedText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sign in. + /// </summary> + public static string LoginLink { + get { + return ResourceManager.GetString("LoginLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Private Repository. + /// </summary> + public static string makePrivateContent { + get { + return ResourceManager.GetString("makePrivateContent", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Private Gist. + /// </summary> + public static string makePrivateGist { + get { + return ResourceManager.GetString("makePrivateGist", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Name. + /// </summary> + public static string nameText { + get { + return ResourceManager.GetString("nameText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No repositories. + /// </summary> + public static string noRepositoriesMessageText { + get { + return ResourceManager.GetString("noRepositoriesMessageText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This repository is not on GitHub. + /// </summary> + public static string NotAGitHubRepository { + get { + return ResourceManager.GetString("NotAGitHubRepository", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Publish this repository to GitHub and get powerful collaboration, code review, and code management for open source and private projects.. + /// </summary> + public static string NotAGitHubRepositoryMessage { + get { + return ResourceManager.GetString("NotAGitHubRepositoryMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No repository. + /// </summary> + public static string NotAGitRepository { + get { + return ResourceManager.GetString("NotAGitRepository", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to We couldn't find a git repository here. Open a git project or click "File -> Add to Source Control" in a project to get started.. + /// </summary> + public static string NotAGitRepositoryMessage { + get { + return ResourceManager.GetString("NotAGitRepositoryMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to You are not signed in to {0}, so certain git operations may fail. [Sign in now]({1}). + /// </summary> + public static string NotLoggedInMessage { + get { + return ResourceManager.GetString("NotLoggedInMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Open. + /// </summary> + public static string Open { + get { + return ResourceManager.GetString("Open", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Open File as Default Action. + /// </summary> + public static string OpenFileAsDefaultAction { + get { + return ResourceManager.GetString("OpenFileAsDefaultAction", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Open File in Solution. + /// </summary> + public static string OpenFileInSolution { + get { + return ResourceManager.GetString("OpenFileInSolution", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Open in Browser. + /// </summary> + public static string openInBrowser { + get { + return ResourceManager.GetString("openInBrowser", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to View Pull Request on GitHub. + /// </summary> + public static string OpenPROnGitHubToolTip { + get { + return ResourceManager.GetString("OpenPROnGitHubToolTip", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Open the two-factor authentication app on your device to view your authentication code.. + /// </summary> + public static string openTwoFactorAuthAppText { + get { + return ResourceManager.GetString("openTwoFactorAuthAppText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Debugging. + /// </summary> + public static string Options_DebuggingTitle { + get { + return ResourceManager.GetString("Options_DebuggingTitle", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Show PR comments on editor margin. + /// </summary> + public static string Options_EditorCommentsLabel { + get { + return ResourceManager.GetString("Options_EditorCommentsLabel", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Enable Trace Logging. + /// </summary> + public static string Options_EnableTraceLoggingText { + get { + return ResourceManager.GetString("Options_EnableTraceLoggingText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to These features might change in a future version. + /// </summary> + public static string Options_ExperimentalNote { + get { + return ResourceManager.GetString("Options_ExperimentalNote", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Experimental features. + /// </summary> + public static string Options_ExperimentalTitle { + get { + return ResourceManager.GetString("Options_ExperimentalTitle", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Show Fork button in Team Explorer. + /// </summary> + public static string Options_ForkButtonLabel { + get { + return ResourceManager.GetString("Options_ForkButtonLabel", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Help us improve by sending anonymous usage data. + /// </summary> + public static string Options_MetricsLabel { + get { + return ResourceManager.GetString("Options_MetricsLabel", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Privacy. + /// </summary> + public static string Options_PrivacyTitle { + get { + return ResourceManager.GetString("Options_PrivacyTitle", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to or. + /// </summary> + public static string orText { + get { + return ResourceManager.GetString("orText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Password. + /// </summary> + public static string PasswordPrompt { + get { + return ResourceManager.GetString("PasswordPrompt", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Path. + /// </summary> + public static string pathText { + get { + return ResourceManager.GetString("pathText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to by. + /// </summary> + public static string prUpdatedByText { + get { + return ResourceManager.GetString("prUpdatedByText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Updated. + /// </summary> + public static string prUpdatedText { + get { + return ResourceManager.GetString("prUpdatedText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Publish. + /// </summary> + public static string publishText { + get { + return ResourceManager.GetString("publishText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Publish to GitHub. + /// </summary> + public static string PublishToGitHubButton { + get { + return ResourceManager.GetString("PublishToGitHubButton", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Pull Requests. + /// </summary> + public static string PullRequestsNavigationItemText { + get { + return ResourceManager.GetString("PullRequestsNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Pulse. + /// </summary> + public static string PulseNavigationItemText { + get { + return ResourceManager.GetString("PulseNavigationItemText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to This repository does not have a remote. Fill out the form to publish it to GitHub.. + /// </summary> + public static string RepoDoesNotHaveRemoteText { + get { + return ResourceManager.GetString("RepoDoesNotHaveRemoteText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Repository Name. + /// </summary> + public static string RepoNameText { + get { + return ResourceManager.GetString("RepoNameText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Repository created successfully.. + /// </summary> + public static string RepositoryPublishedMessage { + get { + return ResourceManager.GetString("RepositoryPublishedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Resend. + /// </summary> + public static string resendCodeButtonContent { + get { + return ResourceManager.GetString("resendCodeButtonContent", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Send the code to your registered SMS Device again. + /// </summary> + public static string resendCodeButtonToolTip { + get { + return ResourceManager.GetString("resendCodeButtonToolTip", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Retry. + /// </summary> + public static string Retry { + get { + return ResourceManager.GetString("Retry", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Reviewers. + /// </summary> + public static string Reviewers { + get { + return ResourceManager.GetString("Reviewers", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sign in.... + /// </summary> + public static string SignInCallToAction { + get { + return ResourceManager.GetString("SignInCallToAction", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sign in. + /// </summary> + public static string SignInLink { + get { + return ResourceManager.GetString("SignInLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sign out. + /// </summary> + public static string SignOutLink { + get { + return ResourceManager.GetString("SignOutLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Sign up. + /// </summary> + public static string SignUpLink { + get { + return ResourceManager.GetString("SignUpLink", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Switch to List View. + /// </summary> + public static string SwitchToListView { + get { + return ResourceManager.GetString("SwitchToListView", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Switch to Tree View. + /// </summary> + public static string SwitchToTreeView { + get { + return ResourceManager.GetString("SwitchToTreeView", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Welcome to GitHub for Visual Studio! Why not take a look at our [training](show-training) or [documentation](show-docs)? + /// + ///[Don't show this again](dont-show-again). + /// </summary> + public static string TeamExplorerWelcomeMessage { + get { + return ResourceManager.GetString("TeamExplorerWelcomeMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Title (required). + /// </summary> + public static string TitleRequired { + get { + return ResourceManager.GetString("TitleRequired", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Token. + /// </summary> + public static string TokenPrompt { + get { + return ResourceManager.GetString("TokenPrompt", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Two-factor authentication. + /// </summary> + public static string twoFactorAuthText { + get { + return ResourceManager.GetString("twoFactorAuthText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Update comment. + /// </summary> + public static string UpdateComment { + get { + return ResourceManager.GetString("UpdateComment", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to updated {0}. + /// </summary> + public static string UpdatedFormat { + get { + return ResourceManager.GetString("UpdatedFormat", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Username or email. + /// </summary> + public static string UserNameOrEmailPromptText { + get { + return ResourceManager.GetString("UserNameOrEmailPromptText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Verify. + /// </summary> + public static string verifyText { + get { + return ResourceManager.GetString("verifyText", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to View Changes. + /// </summary> + public static string ViewChanges { + get { + return ResourceManager.GetString("ViewChanges", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to View Changes in Solution. + /// </summary> + public static string ViewChangesInSolution { + get { + return ResourceManager.GetString("ViewChangesInSolution", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to View File. + /// </summary> + public static string ViewFile { + get { + return ResourceManager.GetString("ViewFile", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Wiki. + /// </summary> + public static string WikiNavigationItemText { + get { + return ResourceManager.GetString("WikiNavigationItemText", resourceCulture); + } + } + } +} diff --git a/src/GitHub.VisualStudio.UI/Resources.resx b/src/GitHub.VisualStudio.UI/Resources.resx new file mode 100644 index 0000000000..864d1f0dd1 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Resources.resx @@ -0,0 +1,449 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="authenticationFailedLabelContent" xml:space="preserve"> + <value>Invalid authentication code</value> + </data> + <data name="authenticationFailedLabelMessage" xml:space="preserve"> + <value>Try entering the code again or clicking the resend button to get a new authentication code.</value> + </data> + <data name="authenticationSentLabelContent" xml:space="preserve"> + <value>Authentication code sent!</value> + </data> + <data name="authenticationSentLabelMessage" xml:space="preserve"> + <value>If you do not receive the authentication code, contact support@github.com.</value> + </data> + <data name="browsePathButtonContent" xml:space="preserve"> + <value>Browse</value> + </data> + <data name="couldNotConnectToGitHubText" xml:space="preserve"> + <value>Could not connect to github.com</value> + </data> + <data name="couldNotConnectToTheServerText" xml:space="preserve"> + <value>Could not connect to the server.</value> + </data> + <data name="CreateLink" xml:space="preserve"> + <value>Create</value> + </data> + <data name="DescriptionOptional" xml:space="preserve"> + <value>Description (Optional)</value> + </data> + <data name="openInBrowser" xml:space="preserve"> + <value>Open in Browser</value> + </data> + <data name="CancelLink" xml:space="preserve"> + <value>Cancel</value> + </data> + <data name="gistCreatedMessage" xml:space="preserve"> + <value>Gist created</value> + </data> + <data name="gistCreationFailedMessage" xml:space="preserve"> + <value>Failed to create gist</value> + </data> + <data name="prUpdatedByText" xml:space="preserve"> + <value>by</value> + </data> + <data name="Options_PrivacyTitle" xml:space="preserve"> + <value>Privacy</value> + </data> + <data name="Options_MetricsLabel" xml:space="preserve"> + <value>Help us improve by sending anonymous usage data</value> + </data> + <data name="Error_FailedToCopyToClipboard" xml:space="preserve"> + <value>Could not copy to the clipboard. Please try again.</value> + </data> + <data name="LinkCopiedToClipboardMessage" xml:space="preserve"> + <value>Link copied to clipboard</value> + </data> + <data name="RepositoryPublishedMessage" xml:space="preserve"> + <value>Repository created successfully.</value> + </data> + <data name="makePrivateGist" xml:space="preserve"> + <value>Private Gist</value> + </data> + <data name="fileNameText" xml:space="preserve"> + <value>File Name</value> + </data> + <data name="NotLoggedInMessage" xml:space="preserve"> + <value>You are not signed in to {0}, so certain git operations may fail. [Sign in now]({1})</value> + </data> + <data name="WikiNavigationItemText" xml:space="preserve"> + <value>Wiki</value> + </data> + <data name="PulseNavigationItemText" xml:space="preserve"> + <value>Pulse</value> + </data> + <data name="PullRequestsNavigationItemText" xml:space="preserve"> + <value>Pull Requests</value> + </data> + <data name="pathText" xml:space="preserve"> + <value>Path</value> + </data> + <data name="IssuesNavigationItemText" xml:space="preserve"> + <value>Issues</value> + </data> + <data name="GraphsNavigationItemText" xml:space="preserve"> + <value>Graphs</value> + </data> + <data name="GitHubPublishSectionTitle" xml:space="preserve"> + <value>Publish to GitHub</value> + </data> + <data name="BlurbText" xml:space="preserve"> + <value>Powerful collaboration, code review, and code management for open source and private projects.</value> + </data> + <data name="GitHubInvitationSectionConnectLabel" xml:space="preserve"> + <value>Connect…</value> + </data> + <data name="CloneLink" xml:space="preserve"> + <value>Clone</value> + </data> + <data name="verifyText" xml:space="preserve"> + <value>Verify</value> + </data> + <data name="twoFactorAuthText" xml:space="preserve"> + <value>Two-factor authentication</value> + </data> + <data name="SignUpLink" xml:space="preserve"> + <value>Sign up</value> + </data> + <data name="SignOutLink" xml:space="preserve"> + <value>Sign out</value> + </data> + <data name="resendCodeButtonToolTip" xml:space="preserve"> + <value>Send the code to your registered SMS Device again</value> + </data> + <data name="resendCodeButtonContent" xml:space="preserve"> + <value>Resend</value> + </data> + <data name="RepoNameText" xml:space="preserve"> + <value>Repository Name</value> + </data> + <data name="RepoDoesNotHaveRemoteText" xml:space="preserve"> + <value>This repository does not have a remote. Fill out the form to publish it to GitHub.</value> + </data> + <data name="publishText" xml:space="preserve"> + <value>Publish</value> + </data> + <data name="orText" xml:space="preserve"> + <value>or</value> + </data> + <data name="openTwoFactorAuthAppText" xml:space="preserve"> + <value>Open the two-factor authentication app on your device to view your authentication code.</value> + </data> + <data name="noRepositoriesMessageText" xml:space="preserve"> + <value>No repositories</value> + </data> + <data name="nameText" xml:space="preserve"> + <value>Name</value> + </data> + <data name="makePrivateContent" xml:space="preserve"> + <value>Private Repository</value> + </data> + <data name="LoginFailedText" xml:space="preserve"> + <value>Sign in failed.</value> + </data> + <data name="localPathText" xml:space="preserve"> + <value>Local path</value> + </data> + <data name="licenseListText" xml:space="preserve"> + <value>License</value> + </data> + <data name="learnMoreLink" xml:space="preserve"> + <value>Learn more</value> + </data> + <data name="ignoreTemplateListText" xml:space="preserve"> + <value>Git ignore</value> + </data> + <data name="filterTextPromptText" xml:space="preserve"> + <value>Search repositories</value> + </data> + <data name="loadingFailedMessageContent" xml:space="preserve"> + <value>Some or all repositories may not have loaded. Close the dialog and try again.</value> + </data> + <data name="loadingFailedMessageMessage" xml:space="preserve"> + <value>An error occurred while loading repositories</value> + </data> + <data name="enterpriseUrlPromptText" xml:space="preserve"> + <value>GitHub Enterprise server address</value> + </data> + <data name="enterpriseConnectingFailedMessage" xml:space="preserve"> + <value>The host isn't available or is not a GitHub Enterprise server. Check the address and try again.</value> + </data> + <data name="UserNameOrEmailPromptText" xml:space="preserve"> + <value>Username or email</value> + </data> + <data name="PasswordPrompt" xml:space="preserve"> + <value>Password</value> + </data> + <data name="LoginFailedMessage" xml:space="preserve"> + <value>Check your username and password, then try again</value> + </data> + <data name="LoginLink" xml:space="preserve"> + <value>Sign in</value> + </data> + <data name="ForgotPasswordLink" xml:space="preserve"> + <value>(forgot your password?)</value> + </data> + <data name="dotComConnectionFailedMessageMessage" xml:space="preserve"> + <value>Please check your internet connection and try again.</value> + </data> + <data name="dontHaveGitHubEnterpriseText" xml:space="preserve"> + <value>Don’t have GitHub Enterprise? </value> + </data> + <data name="dontHaveAnAccountText" xml:space="preserve"> + <value>Don’t have an account? </value> + </data> + <data name="TitleRequired" xml:space="preserve"> + <value>Title (required)</value> + </data> + <data name="Description" xml:space="preserve"> + <value>Description</value> + </data> + <data name="NotAGitHubRepositoryMessage" xml:space="preserve"> + <value>Publish this repository to GitHub and get powerful collaboration, code review, and code management for open source and private projects.</value> + </data> + <data name="NotAGitHubRepository" xml:space="preserve"> + <value>This repository is not on GitHub</value> + </data> + <data name="NotAGitRepository" xml:space="preserve"> + <value>No repository</value> + </data> + <data name="NotAGitRepositoryMessage" xml:space="preserve"> + <value>We couldn't find a git repository here. Open a git project or click "File -> Add to Source Control" in a project to get started.</value> + </data> + <data name="CreateAccountLink" xml:space="preserve"> + <value>Create an account</value> + </data> + <data name="filterBranchesText" xml:space="preserve"> + <value>Filter branches</value> + </data> + <data name="PublishToGitHubButton" xml:space="preserve"> + <value>Publish to GitHub</value> + </data> + <data name="GetStartedText" xml:space="preserve"> + <value>Get Started</value> + </data> + <data name="SignInLink" xml:space="preserve"> + <value>Sign in</value> + </data> + <data name="SignInCallToAction" xml:space="preserve"> + <value>Sign in...</value> + </data> + <data name="LocalBranchUpToDate" xml:space="preserve"> + <value>Local branch up to date</value> + </data> + <data name="ChangesCountFormat" xml:space="preserve"> + <value>Changes ({0})</value> + </data> + <data name="ViewChanges" xml:space="preserve"> + <value>View Changes</value> + </data> + <data name="CompareFileAsDefaultAction" xml:space="preserve"> + <value>Compare File as Default Action</value> + </data> + <data name="ViewFile" xml:space="preserve"> + <value>View File</value> + </data> + <data name="OpenFileAsDefaultAction" xml:space="preserve"> + <value>Open File as Default Action</value> + </data> + <data name="SwitchToListView" xml:space="preserve"> + <value>Switch to List View</value> + </data> + <data name="SwitchToTreeView" xml:space="preserve"> + <value>Switch to Tree View</value> + </data> + <data name="UpdatedFormat" xml:space="preserve"> + <value>updated {0}</value> + </data> + <data name="OpenPROnGitHubToolTip" xml:space="preserve"> + <value>View Pull Request on GitHub</value> + </data> + <data name="TeamExplorerWelcomeMessage" xml:space="preserve"> + <value>Welcome to GitHub for Visual Studio! Why not take a look at our [training](show-training) or [documentation](show-docs)? + +[Don't show this again](dont-show-again)</value> + </data> + <data name="prUpdatedText" xml:space="preserve"> + <value>Updated</value> + </data> + <data name="Options_EditorCommentsLabel" xml:space="preserve"> + <value>Show PR comments on editor margin</value> + </data> + <data name="Options_ExperimentalTitle" xml:space="preserve"> + <value>Experimental features</value> + </data> + <data name="Options_ExperimentalNote" xml:space="preserve"> + <value>These features might change in a future version</value> + </data> + <data name="ViewChangesInSolution" xml:space="preserve"> + <value>View Changes in Solution</value> + </data> + <data name="OpenFileInSolution" xml:space="preserve"> + <value>Open File in Solution</value> + </data> + <data name="TokenPrompt" xml:space="preserve"> + <value>Token</value> + </data> + <data name="ContinueYourReview" xml:space="preserve"> + <value>Continue your review</value> + </data> + <data name="AddYourReview" xml:space="preserve"> + <value>Add your review</value> + </data> + <data name="Reviewers" xml:space="preserve"> + <value>Reviewers</value> + </data> + <data name="AddReviewComment" xml:space="preserve"> + <value>Add review comment</value> + </data> + <data name="AddSingleComment" xml:space="preserve"> + <value>Add a single comment</value> + </data> + <data name="ForkNavigationItemText" xml:space="preserve"> + <value>Fork</value> + </data> + <data name="Options_DebuggingTitle" xml:space="preserve"> + <value>Debugging</value> + </data> + <data name="Options_EnableTraceLoggingText" xml:space="preserve"> + <value>Enable Trace Logging</value> + </data> + <data name="BlendDialogText" xml:space="preserve"> + <value>The GitHub extension is not available inside Blend</value> + </data> + <data name="Options_ForkButtonLabel" xml:space="preserve"> + <value>Show Fork button in Team Explorer</value> + </data> + <data name="UpdateComment" xml:space="preserve"> + <value>Update comment</value> + </data> + <data name="Open" xml:space="preserve"> + <value>Open</value> + </data> + <data name="DeleteCommentConfirmation" xml:space="preserve"> + <value>Are you sure you want to delete this comment?</value> + </data> + <data name="DeleteCommentConfirmationCaption" xml:space="preserve"> + <value>Delete Comment</value> + </data> + <data name="Retry" xml:space="preserve"> + <value>Retry</value> + </data> +</root> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/SharedDictionary.xaml b/src/GitHub.VisualStudio.UI/SharedDictionary.xaml new file mode 100644 index 0000000000..12da65ad03 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/SharedDictionary.xaml @@ -0,0 +1,58 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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"> + + <ResourceDictionary.MergedDictionaries> + <theme:ThemeDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/ThemeDesignTime.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/ActionLinkButton.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/GitHubComboBox.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/Buttons.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/GitHubTabControl.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/TextBlocks.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/LinkDropDown.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/GitHubActionLink.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/ItemsControls.xaml"/> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/SectionControl.xaml"/> + </ResourceDictionary.MergedDictionaries> + + <DrawingBrush x:Key="ConnectArrowBrush"> + <DrawingBrush.Drawing> + <DrawingGroup> + <DrawingGroup.Children> + <GeometryDrawing Brush="{DynamicResource GitHubActionLinkItemBrush}" Geometry="F1 M 9,11 L 7,11 9,9 4,9 4,7 9,7 7,5 9,5 12,8 Z"/> + <GeometryDrawing Brush="{DynamicResource GitHubActionLinkItemBrush}" Geometry="F1 M 7.9741,1.0698 C 4.1461,1.0698 1.0441,4.1728 1.0441,7.9998 1.0441,11.8268 4.1461,14.9298 7.9741,14.9298 11.8011,14.9298 14.9041,11.8268 14.9041,7.9998 14.9041,4.1728 11.8011,1.0698 7.9741,1.0698 M 7.9741,2.0598 C 11.2501,2.0598 13.9151,4.7248 13.9151,7.9998 13.9151,11.2758 11.2501,13.9408 7.9741,13.9408 4.6991,13.9408 2.0341,11.2758 2.0341,7.9998 2.0341,4.7248 4.6991,2.0598 7.9741,2.0598 "/> + </DrawingGroup.Children> + </DrawingGroup> + </DrawingBrush.Drawing> + </DrawingBrush> + + <Style x:Key="TitleVerticalSeparator" TargetType="{x:Type Separator}"> + <Setter Property="Foreground" Value="#BBBBBB"/> + <Setter Property="Width" Value="2"/> + <Setter Property="Margin" Value="3,0,3,0"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Separator}"> + <Border Width="{TemplateBinding Width}" + Background="{TemplateBinding Foreground}" + BorderBrush="{TemplateBinding Foreground}" + BorderThickness="{TemplateBinding BorderThickness}" + SnapsToDevicePixels="True"/> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="VerticalSeparator" TargetType="{x:Type Separator}"> + <Setter Property="Background" Value="{DynamicResource GitHubHeaderSeparatorBrush}"/> + <Setter Property="Margin" Value="3,0,3,0"/> + <Setter Property="LayoutTransform"> + <Setter.Value> + <RotateTransform Angle="90"/> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Services/SharedResources.cs b/src/GitHub.VisualStudio.UI/SharedResources.cs similarity index 95% rename from src/GitHub.VisualStudio/Services/SharedResources.cs rename to src/GitHub.VisualStudio.UI/SharedResources.cs index 0c128a3435..b7dff21cb7 100644 --- a/src/GitHub.VisualStudio/Services/SharedResources.cs +++ b/src/GitHub.VisualStudio.UI/SharedResources.cs @@ -27,7 +27,6 @@ public static DrawingBrush GetDrawingForIcon(Octicon icon, Brush colorBrush, str Drawing = new GeometryDrawing() { Brush = colorBrush, - Pen = new Pen(colorBrush, 1.0).FreezeThis(), Geometry = OcticonPath.GetGeometryForIcon(icon).FreezeThis() } .FreezeThis(), diff --git a/src/GitHub.VisualStudio.UI/Styles/ActionLinkButton.xaml b/src/GitHub.VisualStudio.UI/Styles/ActionLinkButton.xaml new file mode 100644 index 0000000000..3d31e44cf7 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/ActionLinkButton.xaml @@ -0,0 +1,111 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + > + + <Style x:Key="ActionLinkButton" TargetType="{x:Type Button}"> + <Setter Property="Background" Value="Transparent" /> + <Setter Property="Focusable" Value="True" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubActionLinkItemBrush}" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="Button"> + <TextBlock> + <Hyperlink Command="{TemplateBinding Command}" + CommandParameter="{TemplateBinding CommandParameter}" + Foreground="{TemplateBinding Foreground}"> + <Hyperlink.Resources> + <Style TargetType="{x:Type Hyperlink}"> + <Style.Triggers> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="UIElement.IsMouseOver" Value="false" /> + <Condition Property="IsEnabled" Value="true" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="None" /> + <Setter Property="FrameworkElement.Cursor" Value="None" /> + </MultiTrigger.Setters> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsMouseOver" Value="true" /> + <Condition Property="IsEnabled" Value="true" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="Underline" /> + <Setter Property="FrameworkElement.Cursor" Value="Hand" /> + </MultiTrigger.Setters> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsEnabled" Value="false" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="None" /> + </MultiTrigger.Setters> + </MultiTrigger> + </Style.Triggers> + </Style> + </Hyperlink.Resources> + <Run Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" /> + </Hyperlink> + </TextBlock> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="HashtagActionLink" TargetType="{x:Type Button}"> + <Setter Property="Background" Value="Transparent" /> + <Setter Property="Focusable" Value="True" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubActionLinkItemBrush}" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="Button"> + <TextBlock> + <Hyperlink Command="{TemplateBinding Command}" + CommandParameter="{TemplateBinding CommandParameter}" + Foreground="{TemplateBinding Foreground}"> + <Hyperlink.Resources> + <Style TargetType="{x:Type Hyperlink}"> + <Style.Triggers> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="UIElement.IsMouseOver" Value="false" /> + <Condition Property="IsEnabled" Value="true" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="None" /> + <Setter Property="FrameworkElement.Cursor" Value="None" /> + </MultiTrigger.Setters> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsMouseOver" Value="true" /> + <Condition Property="IsEnabled" Value="true" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="Underline" /> + <Setter Property="FrameworkElement.Cursor" Value="Hand" /> + </MultiTrigger.Setters> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsEnabled" Value="false" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="None" /> + </MultiTrigger.Setters> + </MultiTrigger> + </Style.Triggers> + </Style> + </Hyperlink.Resources> + <Run Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, StringFormat=#{0}}" /> + </Hyperlink> + </TextBlock> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/Buttons.xaml b/src/GitHub.VisualStudio.UI/Styles/Buttons.xaml new file mode 100644 index 0000000000..5dc263d17d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/Buttons.xaml @@ -0,0 +1,191 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:GitHub.VisualStudio.Styles"> + + <!-- Button --> + <Style x:Key="GitHubVsButton" TargetType="{x:Type Button}"> + <Setter Property="Background" Value="{DynamicResource GitHubButtonBackgroundBrush}" /> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubButtonBorderBrush}" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubButtonForegroundBrush}" /> + <Setter Property="FontFamily" Value="{DynamicResource GitHubFontFamilyNormal}" /> + <Setter Property="Padding" Value="12,5" /> + <Setter Property="Margin" Value="0" /> + <Setter Property="MinWidth" Value="76" /> + <Setter Property="FocusVisualStyle" Value="{x:Null}" /> + <Setter Property="BorderThickness" Value="1" /> + <Setter Property="HorizontalAlignment" Value="Left" /> + <Setter Property="ToolTipService.ShowDuration" Value="30000" /> + <Setter Property="ToolTipService.ShowOnDisabled" Value="True" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Button}"> + <Grid> + <VisualStateManager.VisualStateGroups> + <VisualStateGroup x:Name="CommonStates"> + <VisualState x:Name="Normal" /> + <VisualState x:Name="MouseOver"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" + Storyboard.TargetName="MouseOverBorder"> + <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1" /> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Pressed"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" + Storyboard.TargetName="PressedBorder"> + <EasingDoubleKeyFrame KeyTime="0" Value="1" /> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Disabled"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="DisabledVisualElement"> + <SplineDoubleKeyFrame KeyTime="0" Value="0.5" /> + </DoubleAnimationUsingKeyFrames> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" + Storyboard.TargetName="contentPresenter"> + <EasingDoubleKeyFrame KeyTime="0" Value="0.5" /> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + </VisualStateGroup> + <VisualStateGroup x:Name="FocusStates"> + <VisualState x:Name="Focused" /> + <VisualState x:Name="Unfocused" /> + </VisualStateGroup> + </VisualStateManager.VisualStateGroups> + <Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" + Background="{TemplateBinding Background}" /> + <Rectangle x:Name="DisabledVisualElement" + Fill="{DynamicResource GitHubButtonBackgroundDisabledBrush}" SnapsToDevicePixels="True" IsHitTestVisible="false" + Opacity="0" /> + <Border x:Name="MouseOverBorder" BorderBrush="{DynamicResource GitHubButtonBorderMouseOverBrush}" + Background="{DynamicResource GitHubButtonBackgroundMouseOverBrush}" + BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> + <Border x:Name="PressedBorder" BorderBrush="{DynamicResource GitHubButtonBorderPressedBrush}" + Background="{DynamicResource GitHubButtonBackgroundPressedBrush}" + BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> + <Border x:Name="DefaultVisualElement" BorderBrush="{DynamicResource GitHubAccentBrush}" + Background="Transparent" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> + <ContentPresenter x:Name="contentPresenter" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentTemplate="{TemplateBinding ContentTemplate}" + Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> + </Grid> + <ControlTemplate.Triggers> + <Trigger Property="IsDefaulted" Value="True"> + <Setter Property="Opacity" Value="1" TargetName="DefaultVisualElement" /> + </Trigger> + <Trigger Property="IsKeyboardFocused" Value="True"> + <Setter Property="Opacity" Value="1" TargetName="DefaultVisualElement" /> + </Trigger> + + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.6" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + <!-- End Button --> + + <!-- PrimaryActionButton --> + <Style x:Key="GitHubVsPrimaryActionButton" TargetType="{x:Type Button}"> + <Setter Property="Background" Value="{DynamicResource GitHubPrimaryButtonBackgroundBrush}" /> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubPrimaryButtonBorderBrush}" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubPrimaryButtonForegroundBrush}" /> + <Setter Property="FontFamily" Value="{DynamicResource GitHubFontFamilyNormal}" /> + <Setter Property="Padding" Value="12,5" /> + <Setter Property="Margin" Value="0" /> + <Setter Property="MinWidth" Value="76" /> + <Setter Property="FocusVisualStyle" Value="{x:Null}" /> + <Setter Property="BorderThickness" Value="1" /> + <Setter Property="HorizontalAlignment" Value="Left" /> + <Setter Property="ToolTipService.ShowDuration" Value="30000" /> + <Setter Property="ToolTipService.ShowOnDisabled" Value="True" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Button}"> + <Grid> + <VisualStateManager.VisualStateGroups> + <VisualStateGroup x:Name="CommonStates"> + <VisualState x:Name="Normal" /> + <VisualState x:Name="MouseOver"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" + Storyboard.TargetName="MouseOverBorder"> + <EasingDoubleKeyFrame KeyTime="00:00:00" Value="1" /> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Pressed"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" + Storyboard.TargetName="PressedBorder"> + <EasingDoubleKeyFrame KeyTime="0" Value="1" /> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + <VisualState x:Name="Disabled"> + <Storyboard> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="DisabledVisualElement"> + <SplineDoubleKeyFrame KeyTime="0" Value="0.5" /> + </DoubleAnimationUsingKeyFrames> + <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" + Storyboard.TargetName="contentPresenter"> + <EasingDoubleKeyFrame KeyTime="0" Value="0.5" /> + </DoubleAnimationUsingKeyFrames> + </Storyboard> + </VisualState> + </VisualStateGroup> + <VisualStateGroup x:Name="FocusStates"> + <VisualState x:Name="Focused" /> + <VisualState x:Name="Unfocused" /> + </VisualStateGroup> + </VisualStateManager.VisualStateGroups> + <Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" + Background="{TemplateBinding Background}" /> + <Rectangle x:Name="DisabledVisualElement" + Fill="{DynamicResource GitHubPrimaryButtonBackgroundDisabledBrush}" SnapsToDevicePixels="True" IsHitTestVisible="false" + Opacity="0" /> + <Border x:Name="MouseOverBorder" BorderBrush="{DynamicResource GitHubPrimaryButtonBorderMouseOverBrush}" + Background="{DynamicResource GitHubPrimaryButtonBackgroundMouseOverBrush}" + BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> + <Border x:Name="PressedBorder" BorderBrush="{DynamicResource GitHubPrimaryButtonBorderPressedBrush}" + Background="#FF55A532" + BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> + <Border x:Name="DefaultVisualElement" BorderBrush="{DynamicResource GitHubAccentBrush}" + Background="Transparent" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" Opacity="0" /> + <ContentPresenter x:Name="contentPresenter" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentTemplate="{TemplateBinding ContentTemplate}" + Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> + </Grid> + <ControlTemplate.Triggers> + <Trigger Property="IsDefaulted" Value="True"> + <Setter Property="Opacity" Value="1" TargetName="DefaultVisualElement" /> + </Trigger> + <Trigger Property="IsKeyboardFocused" Value="True"> + <Setter Property="Opacity" Value="1" TargetName="DefaultVisualElement" /> + </Trigger> + + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubPrimaryButtonBorderDisabledBrush}" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubPrimaryButtonForegroundDisabledBrush}" /> + <Setter Property="Background" Value="{DynamicResource GitHubPrimaryButtonBackgroundDisabledBrush}" /> + <Setter Property="FontWeight" Value="Normal" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + <!-- End PrimaryActionButton --> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml b/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml new file mode 100644 index 0000000000..60218fc5ff --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/GitHubActionLink.xaml @@ -0,0 +1,71 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + <PathGeometry x:Key="arrow" Figures="M 0,448 224,768 448,448" /> + + <Style TargetType="{x:Type ui:GitHubActionLink}"> + <Setter Property="Background" Value="Transparent" /> + <Setter Property="Focusable" Value="True" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubActionLinkItemBrush}" /> + <Setter Property="TextTrimming" Value="None" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ui:GitHubActionLink}"> + <TextBlock TextTrimming="{TemplateBinding TextTrimming}" TextWrapping="{TemplateBinding TextWrapping}"> + <TextBlock.Style> + <Style TargetType="TextBlock"> + <Style.Triggers> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.5"/> + </Trigger> + </Style.Triggers> + </Style> + </TextBlock.Style> + <Hyperlink Command="{TemplateBinding Command}" + CommandParameter="{TemplateBinding CommandParameter}" + Foreground="{TemplateBinding Foreground}"> + <Hyperlink.Resources> + <Style TargetType="{x:Type Hyperlink}"> + <Style.Triggers> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="UIElement.IsMouseOver" Value="false" /> + <Condition Property="IsEnabled" Value="true" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="None" /> + <Setter Property="FrameworkElement.Cursor" Value="None" /> + </MultiTrigger.Setters> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsMouseOver" Value="true" /> + <Condition Property="IsEnabled" Value="true" /> + </MultiTrigger.Conditions> + <MultiTrigger.Setters> + <Setter Property="TextDecorations" Value="Underline" /> + <Setter Property="FrameworkElement.Cursor" Value="Hand" /> + </MultiTrigger.Setters> + </MultiTrigger> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="TextDecorations" Value="None" /> + </Trigger> + </Style.Triggers> + </Style> + </Hyperlink.Resources> + <Run Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" /> + <Polygon Margin="2,0,0,1" + Fill="{TemplateBinding Foreground}" + Points="0,0 8,0 4,4 0,0" + Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, + Path=HasDropDown, + Converter={ui:BooleanToVisibilityConverter}}" /> + + </Hyperlink> + </TextBlock> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/GitHubComboBox.xaml b/src/GitHub.VisualStudio.UI/Styles/GitHubComboBox.xaml new file mode 100644 index 0000000000..b3cbd32e7b --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/GitHubComboBox.xaml @@ -0,0 +1,100 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml"/> + </ResourceDictionary.MergedDictionaries> + + <Style x:Key="GitHubComboBoxBorder" TargetType="{x:Type Border}"> + <Setter Property="Margin" Value="10"/> + <Setter Property="BorderThickness" Value="1"/> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubComboBoxBorderBrush}"/> + <Setter Property="Effect"> + <Setter.Value> + <DropShadowEffect BlurRadius="5" + Direction="315" + Opacity="0.25" + ShadowDepth="5"/> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="GitHubComboBoxGridContainer" TargetType="{x:Type Grid}"> + <Setter Property="Background" Value="{DynamicResource GitHubComboBoxBackgroundBrush}"/> + + <Style.Resources> + <Style BasedOn="{StaticResource {x:Type TextBlock}}" TargetType="{x:Type TextBlock}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ui:OcticonImage}}" TargetType="{x:Type ui:OcticonImage}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type Separator}}" TargetType="{x:Type Separator}"> + <Setter Property="Background" Value="{DynamicResource GitHubComboBoxBorderBrush}"/> + <Setter Property="Margin" Value="0"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ui:FilterTextBox}}" TargetType="{x:Type ui:FilterTextBox}"> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubComboBoxBorderBrush}"/> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsWindowText}"/> + <Setter Property="Background" Value="{DynamicResource GitHubVsBrandedUIBackground}"/> + <Setter Property="Margin" Value="5"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ListBox}}" TargetType="{x:Type ListBox}"> + <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> + <Setter Property="BorderThickness" Value="0"/> + <Setter Property="Background" Value="Transparent"/> + <Setter Property="MaxHeight" Value="100"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ListBoxItem}}" TargetType="{x:Type ListBoxItem}"> + <Setter Property="Padding" Value="3"/> + <Setter Property="HorizontalContentAlignment" Value="Stretch"/> + </Style> + </Style.Resources> + </Style> + + <Style x:Key="GitHubComboBoxDockPanelContainer" TargetType="{x:Type DockPanel}"> + <Setter Property="Background" Value="{DynamicResource GitHubComboBoxBackgroundBrush}"/> + + <Style.Resources> + <Style BasedOn="{StaticResource {x:Type TextBlock}}" TargetType="{x:Type TextBlock}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ui:OcticonImage}}" TargetType="{x:Type ui:OcticonImage}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type Separator}}" TargetType="{x:Type Separator}"> + <Setter Property="Background" Value="{DynamicResource GitHubComboBoxBorderBrush}"/> + <Setter Property="Margin" Value="0"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ui:FilterTextBox}}" TargetType="{x:Type ui:FilterTextBox}"> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubComboBoxBorderBrush}"/> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsWindowText}"/> + <Setter Property="Background" Value="{DynamicResource GitHubVsBrandedUIBackground}"/> + <Setter Property="Margin" Value="5"/> + <Setter Property="Height" Value="25"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ListBox}}" TargetType="{x:Type ListBox}"> + <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> + <Setter Property="BorderThickness" Value="0"/> + <Setter Property="Background" Value="Transparent"/> + <Setter Property="MaxHeight" Value="100"/> + </Style> + + <Style BasedOn="{StaticResource {x:Type ListBoxItem}}" TargetType="{x:Type ListBoxItem}"> + <Setter Property="Padding" Value="3"/> + <Setter Property="HorizontalContentAlignment" Value="Stretch"/> + </Style> + </Style.Resources> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml b/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml new file mode 100644 index 0000000000..5b9b6c0dc3 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/GitHubTabControl.xaml @@ -0,0 +1,107 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:GitHub.VisualStudio.Styles"> + + <Style x:Key="GitHubPRDetailsTabControl" TargetType="{x:Type TabControl}"> + <Setter Property="Padding" Value="0,5" /> + <Setter Property="HorizontalContentAlignment" Value="Center"/> + <Setter Property="VerticalContentAlignment" Value="Center"/> + <Setter Property="Background" Value="{DynamicResource GitHubVsToolWindowBackground}"/> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubTabItemSelectedBorder}"/> + <Setter Property="BorderThickness" Value="0,1,0,0"/> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type TabControl}"> + <Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local"> + <Grid.ColumnDefinitions> + <ColumnDefinition x:Name="ColumnDefinition0"/> + <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition x:Name="RowDefinition0" Height="Auto"/> + <RowDefinition x:Name="RowDefinition1" Height="*"/> + </Grid.RowDefinitions> + <TabPanel x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,-1" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1"/> + <Border x:Name="contentPanel" BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + Background="{TemplateBinding Background}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local"> + <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> + </Border> + </Grid> + <ControlTemplate.Triggers> + <Trigger Property="TabStripPlacement" Value="Bottom"> + <Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/> + <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/> + <Setter Property="Height" TargetName="RowDefinition0" Value="*"/> + <Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/> + <Setter Property="Margin" TargetName="headerPanel" Value="2,0,2,2"/> + </Trigger> + <Trigger Property="TabStripPlacement" Value="Left"> + <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/> + <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/> + <Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/> + <Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/> + <Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/> + <Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/> + <Setter Property="Height" TargetName="RowDefinition0" Value="*"/> + <Setter Property="Height" TargetName="RowDefinition1" Value="0"/> + <Setter Property="Margin" TargetName="headerPanel" Value="2,2,0,2"/> + </Trigger> + <Trigger Property="TabStripPlacement" Value="Right"> + <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/> + <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/> + <Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/> + <Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/> + <Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/> + <Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/> + <Setter Property="Height" TargetName="RowDefinition0" Value="*"/> + <Setter Property="Height" TargetName="RowDefinition1" Value="0"/> + <Setter Property="Margin" TargetName="headerPanel" Value="0,2,2,2"/> + </Trigger> + <Trigger Property="IsEnabled" Value="false"> + <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="GitHubPRDetailsTabItem" TargetType="{x:Type TabItem}"> + <Setter Property="FocusVisualStyle" Value="{StaticResource NoMarginFocusVisual}" /> + <Setter Property="Width" Value="Auto" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type TabItem}"> + <Border Name="Border" CornerRadius="2,2,0,0" + BorderThickness="1,1,1,0" + Padding="10,5"> + <DockPanel HorizontalAlignment="Center"> + <ContentPresenter + x:Name="ContentSite" + VerticalAlignment="Center" + ContentSource="Header" /> + </DockPanel> + </Border> + + <ControlTemplate.Triggers> + <Trigger Property="IsSelected" Value="True"> + <Setter TargetName="Border" Property="Background" Value="{DynamicResource GitHubVsToolWindowBackground}" /> + <Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource GitHubTabItemSelectedBorder}" /> + <Setter TargetName="ContentSite" Property="Control.Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + </Trigger> + + <Trigger Property="IsSelected" Value="False"> + <Setter TargetName="ContentSite" Property="Control.Foreground" Value="{DynamicResource GitHubVsGrayText}" /> + </Trigger> + + <Trigger Property="IsMouseOver" Value="True"> + <Setter TargetName="ContentSite" Property="Control.Foreground" Value="{DynamicResource GitHubVsWindowText}" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ItemsControls.xaml b/src/GitHub.VisualStudio.UI/Styles/ItemsControls.xaml new file mode 100644 index 0000000000..1433547dac --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/ItemsControls.xaml @@ -0,0 +1,224 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:vsp="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0"> + + <Style TargetType="ListBox"> + <Setter Property="Background" Value="{DynamicResource {x:Static vsp:TreeViewColors.BackgroundBrushKey}}" /> + <Setter Property="BorderThickness" Value="0"/> + <Setter Property="Foreground" Value="{DynamicResource {x:Static vsp:EnvironmentColors.CommandBarTextActiveBrushKey}}" /> + <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> + <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> + <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> + <Setter Property="ScrollViewer.PanningMode" Value="Both"/> + <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> + <Setter Property="VerticalContentAlignment" Value="Center"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ListBox}"> + <Border x:Name="border" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + Background="{TemplateBinding Background}" + Padding="1" + SnapsToDevicePixels="true"> + <ScrollViewer Focusable="false" + Padding="{TemplateBinding Padding}"> + <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> + </ScrollViewer> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style TargetType="ListBoxItem"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static vsp:EnvironmentColors.CommandBarTextActiveBrushKey}}" /> + <Setter Property="Padding" Value="4,1"/> + <Setter Property="SnapsToDevicePixels" Value="True"/> + <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> + <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ListBoxItem}"> + <Border x:Name="border" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + Background="{TemplateBinding Background}" + Padding="{TemplateBinding Padding}" + SnapsToDevicePixels="true"> + <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> + </Border> + <ControlTemplate.Triggers> + <Trigger Property="IsMouseOver" Value="True"> + <Setter Property="Background" TargetName="border" Value="{DynamicResource VsBrush.CommandBarHover}"/> + </Trigger> + <Trigger Property="IsSelected" Value="True"> + <Setter Property="Background" TargetName="border" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemActiveBrushKey}}" /> + <Setter Property="TextElement.Foreground" TargetName="border" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemActiveTextBrushKey}}" /> + </Trigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsSelected" Value="True" /> + <Condition Property="Selector.IsSelectionActive" Value="False" /> + </MultiTrigger.Conditions> + <Setter Property="Background" TargetName="border" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemInactiveBrushKey}}" /> + <Setter Property="TextElement.Foreground" TargetName="border" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemInactiveTextBrushKey}}" /> + </MultiTrigger> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="TextElement.Foreground" TargetName="border" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <PathGeometry x:Key="TreeArrow" Figures="M0.5,0.5 L0.5,8.5 L4.5,4.5 z"/> + + <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}"> + <Style.Resources> + <PathGeometry x:Key="ArrowCollapsed"> + <PathGeometry.Figures> + <PathFigureCollection> + <PathFigure IsClosed="True" IsFilled="True" StartPoint="0.5 0.5"> + <PathFigure.Segments> + <PathSegmentCollection> + <LineSegment Point="4.5 4.5" /> + <LineSegment Point="0.5 8.5" /> + </PathSegmentCollection> + </PathFigure.Segments> + </PathFigure> + </PathFigureCollection> + </PathGeometry.Figures> + </PathGeometry> + <PathGeometry x:Key="ArrowExpanded"> + <PathGeometry.Figures> + <PathFigureCollection> + <PathFigure IsClosed="True" IsFilled="True" StartPoint="5.5 0.5"> + <PathFigure.Segments> + <PathSegmentCollection> + <LineSegment Point="0.5 5.5" /> + <LineSegment Point="5.5 5.5" /> + </PathSegmentCollection> + </PathFigure.Segments> + </PathFigure> + </PathFigureCollection> + </PathGeometry.Figures> + </PathGeometry> + </Style.Resources> + <Setter Property="Focusable" Value="False"/> + <Setter Property="Width" Value="16"/> + <Setter Property="Height" Value="16"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ToggleButton}"> + <Border Width="16" Height="16" Background="Transparent"> + <Path Name="ExpandPath" + SnapsToDevicePixels="True" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Data="{StaticResource ArrowCollapsed}" + Stroke="{DynamicResource {x:Static vsp:TreeViewColors.GlyphBrushKey}}"/> + </Border> + <ControlTemplate.Triggers> + <Trigger Property="IsChecked" Value="True"> + <Setter Property="Data" TargetName="ExpandPath" Value="{StaticResource ArrowExpanded}"/> + <Setter Property="Fill" TargetName="ExpandPath" Value="{DynamicResource {x:Static vsp:TreeViewColors.GlyphBrushKey}}"/> + </Trigger> + <Trigger Property="IsMouseOver" Value="True"> + <Setter Property="Stroke" TargetName="ExpandPath" Value="{DynamicResource {x:Static vsp:TreeViewColors.GlyphMouseOverBrushKey}}"/> + </Trigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsChecked" Value="True"/> + <Condition Property="IsMouseOver" Value="True"/> + </MultiTrigger.Conditions> + <Setter Property="Fill" TargetName="ExpandPath" Value="{DynamicResource {x:Static vsp:TreeViewColors.GlyphMouseOverBrushKey}}"/> + <Setter Property="Stroke" TargetName="ExpandPath" Value="{DynamicResource {x:Static vsp:TreeViewColors.GlyphMouseOverBrushKey}}"/> + </MultiTrigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="TreeViewItemFocusVisual"> + <Setter Property="Control.Template"> + <Setter.Value> + <ControlTemplate> + <Rectangle/> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style TargetType="TreeViewItem"> + <Setter Property="Background" Value="{DynamicResource {x:Static vsp:TreeViewColors.BackgroundBrushKey}}"/> + <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> + <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> + <Setter Property="Padding" Value="1,0,0,0"/> + <Setter Property="Foreground" Value="{DynamicResource {x:Static vsp:TreeViewColors.BackgroundTextBrushKey}}"/> + <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type TreeViewItem}"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition MinWidth="19" Width="Auto"/> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition/> + </Grid.RowDefinitions> + <ToggleButton x:Name="Expander" + ClickMode="Press" + IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" + Style="{StaticResource ExpandCollapseToggleStyle}"/> + <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"> + <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> + </Border> + <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/> + </Grid> + <ControlTemplate.Triggers> + <Trigger Property="IsExpanded" Value="false"> + <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/> + </Trigger> + <Trigger Property="HasItems" Value="false"> + <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/> + </Trigger> + <Trigger Property="IsSelected" Value="true"> + <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemActiveBrushKey}}"/> + <Setter Property="Foreground" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemActiveTextBrushKey}}"/> + </Trigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsSelected" Value="true"/> + <Condition Property="IsSelectionActive" Value="false"/> + </MultiTrigger.Conditions> + <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemInactiveBrushKey}}"/> + <Setter Property="Foreground" Value="{DynamicResource {x:Static vsp:TreeViewColors.SelectedItemInactiveTextBrushKey}}"/> + </MultiTrigger> + <Trigger Property="IsEnabled" Value="false"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + <Style.Triggers> + <Trigger Property="VirtualizingPanel.IsVirtualizing" Value="true"> + <Setter Property="ItemsPanel"> + <Setter.Value> + <ItemsPanelTemplate> + <VirtualizingStackPanel/> + </ItemsPanelTemplate> + </Setter.Value> + </Setter> + </Trigger> + </Style.Triggers> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/LinkDropDown.xaml b/src/GitHub.VisualStudio.UI/Styles/LinkDropDown.xaml new file mode 100644 index 0000000000..5a1eab240f --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/LinkDropDown.xaml @@ -0,0 +1,109 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml"/> + </ResourceDictionary.MergedDictionaries> + + <Style x:Key="HyperLinkToggleButton" TargetType="ToggleButton"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="ToggleButton"> + <Grid> + <Border Background="Transparent" + BorderBrush="{TemplateBinding Foreground}"> + <Border.Style> + <Style TargetType="Border"> + <Style.Triggers> + <MultiDataTrigger> + <MultiDataTrigger.Conditions> + <Condition Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/> + <Condition Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}}" Value="true"/> + <Condition Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource TemplatedParent}}" Value="false"/> + </MultiDataTrigger.Conditions> + <MultiDataTrigger.Setters> + <Setter Property="BorderThickness" Value="0,0,0,1"/> + <Setter Property="FrameworkElement.Cursor" Value="Hand"/> + </MultiDataTrigger.Setters> + </MultiDataTrigger> + <DataTrigger Binding="{Binding Path=IsPressed, RelativeSource={RelativeSource TemplatedParent}}" Value="true"> + <Setter Property="BorderThickness" Value="0,0,0,1"/> + <Setter Property="FrameworkElement.Cursor" Value="Hand"/> + </DataTrigger> + </Style.Triggers> + </Style> + </Border.Style> + </Border> + <Polygon Margin="2,0,0,3" + HorizontalAlignment="Right" + VerticalAlignment="Bottom" + Fill="{TemplateBinding Foreground}" + Points="0,0 8,0 4,4 0,0"/> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style TargetType="{x:Type ui:LinkDropDown}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubActionLinkItemBrush}"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="ui:LinkDropDown"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="14"/> + </Grid.ColumnDefinitions> + + <ToggleButton Grid.ColumnSpan="2" + Foreground="{TemplateBinding Foreground}" + IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" + Style="{StaticResource HyperLinkToggleButton}" + Margin="0,0,0,1"/> + <ContentPresenter ContentTemplate="{TemplateBinding LinkItemTemplate}" + Content="{TemplateBinding LinkItem}" + ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + IsHitTestVisible="false" + Margin="{TemplateBinding Padding}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"> + <ContentPresenter.Style> + <Style TargetType="ContentPresenter"> + <Style.Triggers> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.5"/> + </Trigger> + </Style.Triggers> + </Style> + </ContentPresenter.Style> + </ContentPresenter> + <Popup Name="PART_Popup" + AllowsTransparency="True" + IsOpen="{TemplateBinding IsDropDownOpen}" + Placement="Bottom"> + <Popup.Resources> + <Style BasedOn="{StaticResource {x:Type ComboBoxItem}}" TargetType="{x:Type ComboBoxItem}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubButtonForegroundBrush}"/> + <Setter Property="Padding" Value="3"/> + <Setter Property="HorizontalContentAlignment" Value="Stretch"/> + </Style> + </Popup.Resources> + <Border Style="{DynamicResource GitHubComboBoxBorder}"> + <DockPanel MinWidth="100" Style="{DynamicResource GitHubComboBoxDockPanelContainer}"> + <ScrollViewer VerticalScrollBarVisibility="Auto"> + <ItemsPresenter/> + </ScrollViewer> + </DockPanel> + </Border> + </Popup> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/SectionControl.xaml b/src/GitHub.VisualStudio.UI/Styles/SectionControl.xaml new file mode 100644 index 0000000000..ff1dde984d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/SectionControl.xaml @@ -0,0 +1,67 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + + <Style TargetType="{x:Type ui:SectionControl}"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ui:SectionControl}"> + <Grid> + <Grid.Resources> + <BooleanToVisibilityConverter x:Key="b2v"/> + </Grid.Resources> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition/> + </Grid.RowDefinitions> + + <ToggleButton Grid.Column="0" Grid.Row="0" + IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" + VerticalAlignment="Stretch" + Margin="0 0 4 0" + Width="10"> + <ToggleButton.Template> + <ControlTemplate TargetType="ToggleButton"> + <Border Background="Transparent"> + <Path Name="Arrow" + Fill="{DynamicResource VsBrush.WindowText}" + Height="7" + Width="7" + Stretch="UniformToFill" + Data="M7 1l-.025 5H2z" + VerticalAlignment="Center" + HorizontalAlignment="Center"/> + </Border> + + <ControlTemplate.Triggers> + <Trigger Property="IsChecked" Value="False"> + <Setter TargetName="Arrow" Property="LayoutTransform"> + <Setter.Value> + <RotateTransform Angle="-45" /> + </Setter.Value> + </Setter> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </ToggleButton.Template> + </ToggleButton> + + <TextBlock Grid.Column="1" Grid.Row="0" + FontWeight="SemiBold" + Text="{TemplateBinding HeaderText}"/> + + <ItemsControl Grid.Column="2" Grid.Row="0" ItemsSource="{TemplateBinding Buttons}"/> + + <ContentPresenter Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" + Visibility="{TemplateBinding IsExpanded, Converter={StaticResource b2v}}"/> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/TextBlocks.xaml b/src/GitHub.VisualStudio.UI/Styles/TextBlocks.xaml new file mode 100644 index 0000000000..58b18dedba --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/TextBlocks.xaml @@ -0,0 +1,134 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:local="clr-namespace:GitHub.VisualStudio.Styles" + xmlns:vsui="clr-namespace:GitHub.VisualStudio.UI"> + + <Style x:Key="GitHubVsPromptTextBox" TargetType="{x:Type ui:PromptTextBox}"> + <Setter Property="Background" Value="{DynamicResource GitHubVsBrandedUIBackground}"/> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + <Setter Property="BorderBrush" Value="{DynamicResource GitHubVsBrandedUIBorder}"/> + <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/> + <Setter Property="HorizontalContentAlignment" Value="Left"/> + <Setter Property="FocusVisualStyle" Value="{x:Null}"/> + <Setter Property="AllowDrop" Value="true"/> + <Setter Property="Padding" Value="5"/> + <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> + <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ui:PromptTextBox}"> + <Grid> + <Border x:Name="Bd" CornerRadius="0" ClipToBounds="True" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> + <Border CornerRadius="0" BorderBrush="Black" BorderThickness="0,1,0,0" Margin="0,-2,0,0" Opacity="0.4"> + <Border.Effect> + <DropShadowEffect Direction="270" ShadowDepth="0" BlurRadius="4" Opacity="1" /> + </Border.Effect> + </Border> + </Border> + + <Grid> + <ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Top" Margin="0"/> + <Label x:Name="PromptLabel" HorizontalAlignment="Left" + Foreground="{DynamicResource GitHubVsGrayText}" + FontSize="{TemplateBinding FontSize}" Padding="{TemplateBinding Padding}" Opacity="0" + Target="{Binding ElementName=Bd}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Focusable="False" IsHitTestVisible="False" + VerticalAlignment="Top"> + <TextBlock Text="{TemplateBinding PromptText}" Margin="2,0,0,0" TextTrimming="CharacterEllipsis" /> + </Label> + </Grid> + </Grid> + + <ControlTemplate.Triggers> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.5" /> + </Trigger> + <Trigger Property="IsMouseOver" Value="True"> + <Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubVsBrandedUIBorder}" /> + </Trigger> + <Trigger Property="IsKeyboardFocusWithin" Value="True"> + <Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GitHubAccentBrush}" /> + </Trigger> + <DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0"> + <Setter Property="Opacity" TargetName="PromptLabel" Value="0.7" /> + <Setter Property="Foreground" Value="Transparent" /> + </DataTrigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="FlatReadOnlyTextBox" TargetType="TextBox"> + <Setter Property="IsReadOnly" Value="True"/> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type TextBox}"> + <Grid> + <ScrollViewer Margin="0" x:Name="PART_ContentHost" /> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="TeamExplorerPromptTextBox" TargetType="{x:Type ui:PromptTextBox}"> + <Setter Property="TextBoxBase.CaretBrush" Value="{DynamicResource GitHubVsWindowText}" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsWindowText}" /> + <Setter Property="Background" Value="{DynamicResource TFTextBoxBrushKey}" /> + <Setter Property="BorderBrush" Value="{DynamicResource TFTextBoxBorderBrushKey}" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ui:PromptTextBox}"> + <Border Name="Border" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="1" + Background="{TemplateBinding Background}" + SnapsToDevicePixels="True"> + <Grid> + <Border x:Name="PromptBorder" + Background="{DynamicResource WorkItemInvalidControlBackground}" + Focusable="False" + Padding="2 0" + IsHitTestVisible="False" + Visibility="Collapsed"> + <TextBlock HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + Focusable="False" + Foreground="{DynamicResource TFTextBoxHintTextBrushKey}" + FontSize="{TemplateBinding FontSize}" + IsHitTestVisible="False" + Padding="{TemplateBinding Padding}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" + Text="{TemplateBinding PromptText}" + TextTrimming="CharacterEllipsis" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> + </Border> + <ScrollViewer x:Name="PART_ContentHost" + Padding="{TemplateBinding Padding}" + Focusable="false" + HorizontalScrollBarVisibility="Hidden" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + VerticalScrollBarVisibility="Hidden" + Margin="0"/> + </Grid> + </Border> + + <ControlTemplate.Triggers> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.5" /> + </Trigger> + <DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource Self}}" Value="0"> + <Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource TFRequiredTextBoxBorderBrushKey}" /> + <Setter Property="Foreground" Value="Transparent" /> + <Setter Property="Visibility" TargetName="PromptBorder" Value="Visible" /> + </DataTrigger> + </ControlTemplate.Triggers> + + </ControlTemplate> + + </Setter.Value> + </Setter> + </Style> + +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml new file mode 100644 index 0000000000..02e1b632f4 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeBlue.xaml @@ -0,0 +1,69 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"> + + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsBrush.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Color x:Key="GitHubContextMenuIconColor">#424242</Color> + <SolidColorBrush x:Key="GitHubContextMenuIconBrush" Color="{StaticResource GitHubContextMenuIconColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubActionLinkItemColor">#FF0E70C0</Color> + <SolidColorBrush x:Key="GitHubActionLinkItemBrush" Color="{StaticResource GitHubActionLinkItemColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubHeaderSeparatorColor">#FFCFD6E5</Color> + <SolidColorBrush x:Key="GitHubHeaderSeparatorBrush" Color="{StaticResource GitHubHeaderSeparatorColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubPaneTitleColor">#FF1B293E</Color> + <SolidColorBrush x:Key="GitHubPaneTitleBrush" Color="{StaticResource GitHubPaneTitleColor}" /> + + <SolidColorBrush x:Key="GitHubDeletedFileIconBrush" Color="{DynamicResource VsColor.GrayText}"></SolidColorBrush> + <SolidColorBrush x:Key="GitHubDeletedFileBrush" Color="{DynamicResource VsColor.GrayText}"></SolidColorBrush> + + <SolidColorBrush x:Key="GitHubPrimaryGreenBrush" Color="#FF6CC644" /> + <SolidColorBrush x:Key="GitHubPrimaryOrangeBrush" Color="#FFC9510C" /> + + <SolidColorBrush x:Key="GitHubButtonBackgroundBrush" Color="#FFECECF1" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundMouseOverBrush" Color="#FFFFFF" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundPressedBrush" Color="#FFCFCFDB" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundDisabledBrush" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="GitHubButtonForegroundBrush" Color="#1E1E1E" /> + <SolidColorBrush x:Key="GitHubButtonBorderBrush" Color="#FFCACACA" /> + <SolidColorBrush x:Key="GitHubButtonBorderMouseOverBrush" Color="#FFCACACA" /> + <SolidColorBrush x:Key="GitHubButtonBorderPressedBrush" Color="#FFBFBFBF" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundBrush" Color="#FF6FBF00" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundMouseOverBrush" Color="#FF7ED900" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundPressedBrush" Color="#FF7FBF62" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundDisabledBrush" Color="#FFFAFAFA" Opacity="0.3" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonForegroundBrush" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonForegroundDisabledBrush" Color="#FF9B9B9B" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderBrush" Color="#FF54B02C" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderMouseOverBrush" Color="#FF68B34D" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderPressedBrush" Color="#FF599942" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderDisabledBrush" Color="#FFD4D4D4" /> + + <SolidColorBrush x:Key="GitHubComboBoxBackgroundBrush" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="GitHubComboBoxBorderBrush" Color="#FFCCCEDC" /> + + <SolidColorBrush x:Key="GitHubTabItemSelectedBorder" Color="#FFD0D7E4" /> + + <SolidColorBrush x:Key="GitHubDirectoryIconForeground" Color="#FFF5D367" /> + + <SolidColorBrush x:Key="GitHubBranchNameBackgroundBrush" Color="#FFE8F0F8" /> + + <SolidColorBrush x:Key="GitHubDiffChangeBackground.Add" Color="#FFF2F3F9" /> + <SolidColorBrush x:Key="GitHubDiffChangeBackground.Delete" Color="#FFF2F3F9" /> + <SolidColorBrush x:Key="GitHubDiffChangeBackground.None" Color="{DynamicResource Color.Window}" /> + + <SolidColorBrush x:Key="GitHubDiffGlyphFill.None" Color="#9EC7FF" /> + + <SolidColorBrush x:Key="GitHubPeekViewBackground" Color="#F5F5F5" /> + <SolidColorBrush x:Key="GitHubPendingReviewBackground" Color="#FFF8C7" /> + <SolidColorBrush x:Key="GitHubMultilineListItemActiveBrush" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="GitHubFileExpanderHeaderBackgroundBrush" Color="#FFD6DBE9" /> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml new file mode 100644 index 0000000000..b9994994e7 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeDark.xaml @@ -0,0 +1,69 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"> + + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsBrush.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Color x:Key="GitHubContextMenuIconColor">#424242</Color> + <SolidColorBrush x:Key="GitHubContextMenuIconBrush" Color="{StaticResource GitHubContextMenuIconColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubActionLinkItemColor">#FF0097FB</Color> + <SolidColorBrush x:Key="GitHubActionLinkItemBrush" Color="{StaticResource GitHubActionLinkItemColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubHeaderSeparatorColor">#FF2D2D30</Color> + <SolidColorBrush x:Key="GitHubHeaderSeparatorBrush" Color="{StaticResource GitHubHeaderSeparatorColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubPaneTitleColor">#FFFFFFFF</Color> + <SolidColorBrush x:Key="GitHubPaneTitleBrush" Color="{StaticResource GitHubPaneTitleColor}" /> + + <SolidColorBrush x:Key="GitHubDeletedFileIconBrush" Color="{DynamicResource VsColor.GrayText}"></SolidColorBrush> + <SolidColorBrush x:Key="GitHubDeletedFileBrush" Color="{DynamicResource VsColor.GrayText}"></SolidColorBrush> + + <SolidColorBrush x:Key="GitHubPrimaryGreenBrush" Color="#FF6CC644" /> + <SolidColorBrush x:Key="GitHubPrimaryOrangeBrush" Color="#FFC9510C" /> + + <SolidColorBrush x:Key="GitHubButtonBackgroundBrush" Color="#FF3F404B" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundMouseOverBrush" Color="#54545C" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundPressedBrush" Color="#FF2F3038" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundDisabledBrush" Color="#FF3F404B" Opacity="0.3"/> + <SolidColorBrush x:Key="GitHubButtonForegroundBrush" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="GitHubButtonBorderBrush" Color="#FF464646" /> + <SolidColorBrush x:Key="GitHubButtonBorderMouseOverBrush" Color="#65656F" /> + <SolidColorBrush x:Key="GitHubButtonBorderPressedBrush" Color="#FF2F3038" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundBrush" Color="#FF709946" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundMouseOverBrush" Color="#FF82b352" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundPressedBrush" Color="#FF82b352" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundDisabledBrush" Color="#FF709946" Opacity="0.3" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonForegroundBrush" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonForegroundDisabledBrush" Color="#FF9B9B9B" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderBrush" Color="#FF7BA84D" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderMouseOverBrush" Color="#FF95CC5E" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderPressedBrush" Color="#FF7BA84D" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderDisabledBrush" Color="#FFD4D4D4" /> + + <SolidColorBrush x:Key="GitHubComboBoxBackgroundBrush" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="GitHubComboBoxBorderBrush" Color="#FF434346" /> + + <SolidColorBrush x:Key="GitHubTabItemSelectedBorder" Color="#FF3F3F46" /> + + <SolidColorBrush x:Key="GitHubDirectoryIconForeground" Color="#FF87C3FC" /> + + <SolidColorBrush x:Key="GitHubBranchNameBackgroundBrush" Color="#FF4B4D50" /> + + <SolidColorBrush x:Key="GitHubDiffChangeBackground.Add" Color="#FF333333" /> + <SolidColorBrush x:Key="GitHubDiffChangeBackground.Delete" Color="#FF333333" /> + <SolidColorBrush x:Key="GitHubDiffChangeBackground.None" Color="{DynamicResource VsColor.Window}" /> + + <SolidColorBrush x:Key="GitHubDiffGlyphFill.None" Color="#569CD6" /> + + <SolidColorBrush x:Key="GitHubPeekViewBackground" Color="#252526" /> + <SolidColorBrush x:Key="GitHubPendingReviewBackground" Color="#26384D" /> + <SolidColorBrush x:Key="GitHubMultilineListItemActiveBrush" Color="#FF3F3F46"/> + <SolidColorBrush x:Key="GitHubFileExpanderHeaderBackgroundBrush" Color="#FF2D2D30" /> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeDesignTime.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeDesignTime.xaml new file mode 100644 index 0000000000..ca96be1e63 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeDesignTime.xaml @@ -0,0 +1,51 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"> + + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsColorsBlue.xaml" /> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsBrushesBlue.xaml" /> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/ThemeBlue.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <!-- Design time colors taken from the VS dark theme + <SolidColorBrush x:Key="GitHubVsToolWindowText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubVsToolWindowBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="GitHubVsGrayText" Color="#FF999999" /> + <SolidColorBrush x:Key="GitHubVsCommandBarHover" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="GitHubVsCommandBarSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="GitHubVsSearchBoxBackground" Color="#FF333337" /> + <SolidColorBrush x:Key="GitHubVsWindowText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="GitHubBranchNameBackgroundBrush" Color="#FF4B4D50" /> + --> + + <!-- Design time colors taken from the VS light theme + --> + <SolidColorBrush x:Key="GitHubVsToolWindowText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="GitHubVsToolWindowBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="GitHubVsGrayText" Color="#FF717171" /> + <SolidColorBrush x:Key="GitHubVsCommandBarHover" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="GitHubVsCommandBarSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="GitHubVsSearchBoxBackground" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="GitHubVsWindowText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBorder" Color="#FFCCCEBD" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="GitHubDirectoryIconForeground" Color="#FFF5D367" /> + <SolidColorBrush x:Key="GitHubBranchNameBackgroundBrush" Color="#FFDAE8F6" /> + + <!-- Design time colors taken from the VS blue theme + <SolidColorBrush x:Key="GitHubVsToolWindowText" Color="#FF000000" /> + <SolidColorBrush x:Key="GitHubVsToolWindowBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubVsGrayText" Color="#FF6d6d6d" /> + <SolidColorBrush x:Key="GitHubVsCommandBarHover" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="GitHubVsCommandBarSelectedBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="GitHubVsSearchBoxBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubVsWindowText" Color="#FF000000" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBorder" Color="#FF8591A2" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubDirectoryIconForeground" Color="#FFF5D367" /> + <SolidColorBrush x:Key="GitHubBranchNameBackgroundBrush" Color="#FFE8F0F8" /> + --> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml b/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml new file mode 100644 index 0000000000..85fcdf79b5 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/ThemeLight.xaml @@ -0,0 +1,69 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"> + + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/VsBrush.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Color x:Key="GitHubContextMenuIconColor">#424242</Color> + <SolidColorBrush x:Key="GitHubContextMenuIconBrush" Color="{StaticResource GitHubContextMenuIconColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubActionLinkItemColor">#FF0E70C0</Color> + <SolidColorBrush x:Key="GitHubActionLinkItemBrush" Color="{StaticResource GitHubActionLinkItemColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubHeaderSeparatorColor">#FFEEEEF2</Color> + <SolidColorBrush x:Key="GitHubHeaderSeparatorBrush" Color="{StaticResource GitHubHeaderSeparatorColor}" PresentationOptions:Freeze="true" /> + + <Color x:Key="GitHubPaneTitleColor">#FF1B293E</Color> + <SolidColorBrush x:Key="GitHubPaneTitleBrush" Color="{StaticResource GitHubPaneTitleColor}" /> + + <SolidColorBrush x:Key="GitHubDeletedFileIconBrush" Color="{DynamicResource VsColor.GrayText}"></SolidColorBrush> + <SolidColorBrush x:Key="GitHubDeletedFileBrush" Color="{DynamicResource VsColor.GrayText}"></SolidColorBrush> + + <SolidColorBrush x:Key="GitHubPrimaryGreenBrush" Color="#FF6CC644" /> + <SolidColorBrush x:Key="GitHubPrimaryOrangeBrush" Color="#FFC9510C" /> + + <SolidColorBrush x:Key="GitHubButtonBackgroundBrush" Color="#FFECECF1" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundMouseOverBrush" Color="#CADEF4" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundPressedBrush" Color="#FFCFCFDB" /> + <SolidColorBrush x:Key="GitHubButtonBackgroundDisabledBrush" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="GitHubButtonForegroundBrush" Color="#1E1E1E" /> + <SolidColorBrush x:Key="GitHubButtonBorderBrush" Color="#D1D3DE" /> + <SolidColorBrush x:Key="GitHubButtonBorderMouseOverBrush" Color="#D1D3DE" /> + <SolidColorBrush x:Key="GitHubButtonBorderPressedBrush" Color="#FFBFBFBF" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundBrush" Color="#FF7ED321" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundMouseOverBrush" Color="#FF90D96F" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundPressedBrush" Color="#FF7FBF62" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBackgroundDisabledBrush" Color="#FF7ED321" Opacity="0.3" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonForegroundBrush" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonForegroundDisabledBrush" Color="#FF9B9B9B" /> + + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderBrush" Color="#FF599942" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderMouseOverBrush" Color="#FF68B34D" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderPressedBrush" Color="#FF599942" /> + <SolidColorBrush x:Key="GitHubPrimaryButtonBorderDisabledBrush" Color="#FFD4D4D4" /> + + <SolidColorBrush x:Key="GitHubComboBoxBackgroundBrush" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="GitHubComboBoxBorderBrush" Color="#FFCCCEDC" /> + + <SolidColorBrush x:Key="GitHubTabItemSelectedBorder" Color="#FFCCCEBD" /> + + <SolidColorBrush x:Key="GitHubDirectoryIconForeground" Color="#FFF5D367" /> + + <SolidColorBrush x:Key="GitHubBranchNameBackgroundBrush" Color="#FFDAE8F6" /> + + <SolidColorBrush x:Key="GitHubDiffChangeBackground.Add" Color="#FFE4E6F2" /> + <SolidColorBrush x:Key="GitHubDiffChangeBackground.Delete" Color="#FFE4E6F2" /> + <SolidColorBrush x:Key="GitHubDiffChangeBackground.None" Color="{DynamicResource Color.Window}" /> + + <SolidColorBrush x:Key="GitHubDiffGlyphFill.None" Color="#9EC7FF" /> + + <SolidColorBrush x:Key="GitHubPeekViewBackground" Color="#F5F5F5" /> + <SolidColorBrush x:Key="GitHubPendingReviewBackground" Color="#FFF8C7" /> + <SolidColorBrush x:Key="GitHubMultilineListItemActiveBrush" Color="#FFCCCEDB"/> + <SolidColorBrush x:Key="GitHubFileExpanderHeaderBackgroundBrush" Color="#FFEEEEF2" /> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/VsBrush.xaml b/src/GitHub.VisualStudio.UI/Styles/VsBrush.xaml new file mode 100644 index 0000000000..1e965a6910 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsBrush.xaml @@ -0,0 +1,25 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:vsui="clr-namespace:GitHub.VisualStudio.UI"> + + <SolidColorBrush x:Key="GitHubVsToolWindowText" Color="{DynamicResource VsColor.ToolWindowText}" /> + <SolidColorBrush x:Key="GitHubVsToolWindowBackground" Color="{DynamicResource VsColor.ToolWindowBackground}" /> + <SolidColorBrush x:Key="GitHubVsGrayText" Color="{DynamicResource VsColor.GrayText}" /> + <SolidColorBrush x:Key="GitHubVsCommandBarHover" Color="{DynamicResource VsColor.CommandBarHover}" /> + <SolidColorBrush x:Key="GitHubVsCommandBarSelectedBorder" Color="{DynamicResource VsColor.CommandBarSelectedBorder}" /> + <SolidColorBrush x:Key="GitHubVsSearchBoxBackground" Color="{DynamicResource VsColor.SearchBoxBackground}" /> + <SolidColorBrush x:Key="GitHubVsWindowText" Color="{DynamicResource VsColor.WindowText}" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBorder" Color="{DynamicResource VsColor.BrandedUIBorder}" /> + <SolidColorBrush x:Key="GitHubVsBrandedUIBackground" Color="{DynamicResource VsColor.BrandedUIBackground}" /> + <SolidColorBrush x:Key="GitHubVsComboBoxPopupBorder" Color="{DynamicResource VsColor.ComboBoxPopupBorder}" /> + + <SolidColorBrush x:Key="GitHubGlyphMarginCommentableBackground" Color="{DynamicResource VsColor.SearchBoxBackground}" /> + + <!-- Team Foundation Colors --> + <SolidColorBrush x:Key="TFRequiredTextBoxBorderBrushKey" Color="{Binding Source={x:Static vsui:TeamFoundationColors.Instance}, Path=RequiredTextBoxBorderColor, Mode=OneWay}" /> + <SolidColorBrush x:Key="TFTextBoxBorderBrushKey" Color="{Binding Source={x:Static vsui:TeamFoundationColors.Instance}, Path=TextBoxBorderColor, Mode=OneWay}" /> + <SolidColorBrush x:Key="TFTextBoxBrushKey" Color="{Binding Source={x:Static vsui:TeamFoundationColors.Instance}, Path=TextBoxColor, Mode=OneWay}" /> + <SolidColorBrush x:Key="TFTextBoxHintTextBrushKey" Color="{Binding Source={x:Static vsui:TeamFoundationColors.Instance}, Path=TextBoxHintTextColor, Mode=OneWay}" /> +</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Styles/VsBrushesBlue.xaml b/src/GitHub.VisualStudio.UI/Styles/VsBrushesBlue.xaml new file mode 100644 index 0000000000..ff1fc64ccd --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsBrushesBlue.xaml @@ -0,0 +1,510 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <SolidColorBrush x:Key="VsBrush.AccentBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.AccentDark" Color="#FFC0A776" /> + <SolidColorBrush x:Key="VsBrush.AccentLight" Color="#FFFFF0D0" /> + <SolidColorBrush x:Key="VsBrush.AccentMedium" Color="#FFFFECB5" /> + <SolidColorBrush x:Key="VsBrush.AccentPale" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.ActiveBorder" Color="#FFB4B4B4" /> + <SolidColorBrush x:Key="VsBrush.ActiveCaption" Color="#FF99B4D1" /> + <SolidColorBrush x:Key="VsBrush.AppWorkspace" Color="#FFABABAB" /> + <SolidColorBrush x:Key="VsBrush.AutoHideResizeGrip" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBackgroundBegin" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBackgroundEnd" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBorder" Color="#FF465A7D" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBackgroundBegin" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBackgroundEnd" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBorder" Color="#FF9BA7B7" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.Background" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIBorder" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIFill" Color="#FFC8D5E8" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.BrandedUITitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ButtonFace" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ButtonHighlight" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ButtonShadow" Color="#FFA0A0A0" /> + <SolidColorBrush x:Key="VsBrush.ButtonText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.CaptionText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerClassCompartment" Color="#FFF0F2F9" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerClassHeaderBackground" Color="#FFD3DCEF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentBorder" Color="#FFCCCC66" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentShapeBackground" Color="#FFFFFFCC" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCompartmentSeparator" Color="#FFD2D2D2" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerConnectionRouteBorder" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultConnection" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeBorder" Color="#FF00008B" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeSubtitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeTitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeTitleBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDelegateCompartment" Color="#FFF7F0F0" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDelegateHeader" Color="#FFEDDADC" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDiagramBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerEmphasisBorder" Color="#FF0054E3" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerEnumHeader" Color="#FFDDD6EF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerFieldAssociation" Color="#FF266035" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerGradientEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInheritance" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInterfaceCompartment" Color="#FFF3F7F0" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInterfaceHeader" Color="#FFE6F0DB" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerLasso" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerLollipop" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerPropertyAssociation" Color="#FFB0764F" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerReferencedAssemblyBorder" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerResizingShapeBorder" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerShapeBorder" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerShapeShadow" Color="#FFD8D8D8" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTempConnection" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTypedef" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTypedefHeader" Color="#FFD6ECEF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerUnresolvedText" Color="#FFFF0000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerVBModuleCompartment" Color="#FFF8F4E9" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerVBModuleHeader" Color="#FFF0E9D2" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxBackground" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxBorder" Color="#FFBDC5D8" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledBackground" Color="#FFD5DCE8" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledBorder" Color="#FFBDC5D8" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledGlyph" Color="#FFA4ADBA" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxGlyph" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseDownBackground" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseDownBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundBegin" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundEnd" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundMiddle1" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundMiddle2" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBackgroundBegin" Color="#FFEFEFEF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBackgroundEnd" Color="#FFEFEFEF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBorder" Color="#FF9BA7B7" /> + <SolidColorBrush x:Key="VsBrush.CommandBarBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.CommandBarCheckBox" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.CommandBarDragHandle" Color="#FF60728C" /> + <SolidColorBrush x:Key="VsBrush.CommandBarDragHandleShadow" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientBegin" Color="#FFCFD6E5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientEnd" Color="#FFCFD6E5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientMiddle" Color="#FFCFD6E5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHover" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelected" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelectedIcon" Color="#FFFFFCF4" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelectedIconBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBackgroundGradientBegin" Color="#FFEAF0FF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBackgroundGradientEnd" Color="#FFEAF0FF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBorder" Color="#FF9BA7B7" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuIconBackground" Color="#FFF2F4FE" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuMouseOverSubmenuGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuSeparator" Color="#FFBEC3CB" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuSubmenuGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundBegin" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundEnd" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundMiddle" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundBegin" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundEnd" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundMiddle1" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundMiddle2" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsBackground" Color="#FFDCE0EC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsGlyph" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundBegin" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundEnd" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundMiddle" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundBegin" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundEnd" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundMiddle1" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundMiddle2" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverGlyph" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.CommandBarSelected" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarSelectedBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.CommandBarShadow" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextActive" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextHover" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextInactive" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextSelected" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.CommandBarToolBarBorder" Color="#FFDCE0EC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarToolBarSeparator" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientBegin" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientEnd" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientMiddle" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientBegin" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientEnd" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientMiddle" Color="#FFD6DBE9" /> + <SolidColorBrush x:Key="VsBrush.ControlEditHintText" Color="#FFA0A0A0" /> + <SolidColorBrush x:Key="VsBrush.ControlEditRequiredBackground" Color="#FFFFFAC8" /> + <SolidColorBrush x:Key="VsBrush.ControlEditRequiredHintText" Color="#FF3C7FB1" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkText" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkTextHover" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkTextPressed" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ControlOutline" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.Dark" Color="#00CCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveBorder" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveHighlight" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveHighlightText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveSeparator" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveBorder" Color="#FFA4ADBA" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveHighlight" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveHighlightText" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveSeparator" Color="#FFA4ADBA" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveText" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.DesignerBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DesignerSelectionDots" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.DesignerTray" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.DesignerWatermark" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.DiagReportBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageHeader" Color="#FFF2F4F8" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageSubtitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageTitle" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageHeader" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageSubtitle" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageTitle" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DiagReportText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DockTargetBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DockTargetBorder" Color="#FF636871" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBackgroundBegin" Color="#FFF5F8FB" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBackgroundEnd" Color="#FFDEE2E9" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBorder" Color="#FF8A919C" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphArrow" Color="#FF445879" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBackgroundBegin" Color="#FFFDE8A7" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBackgroundEnd" Color="#FFF7C570" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBorder" Color="#FF445879" /> + <SolidColorBrush x:Key="VsBrush.DropDownBackground" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.DropDownBorder" Color="#FFBDC5D8" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledBackground" Color="#FFD5DCE8" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledBorder" Color="#FFBDC5D8" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledGlyph" Color="#FFA4ADBA" /> + <SolidColorBrush x:Key="VsBrush.DropDownGlyph" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseDownBackground" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseDownBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundBegin" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundEnd" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundMiddle1" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundMiddle2" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBackgroundBegin" Color="#FFEFEFEF" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBackgroundEnd" Color="#FFEFEFEF" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBorder" Color="#FF9BA7B7" /> + <SolidColorBrush x:Key="VsBrush.DropShadowBackground" Color="#72000000" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionBorder" Color="#FFC0A776" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionFill" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackground" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientBegin" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientEnd" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientMiddle1" Color="#FF35496A" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientMiddle2" Color="#FF35496A" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture" Color="#00FFFFFF" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture1" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture2" Color="#FF35496A" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarHighlight1" Color="#FFFFFF00" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarHighlight2" Color="#FFFF9200" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarInactive1" Color="#FFF0F0EE" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarInactive2" Color="#FFA9A9A9" /> + <SolidColorBrush x:Key="VsBrush.FileTabBorder" Color="#FF364E6F" /> + <SolidColorBrush x:Key="VsBrush.FileTabChannelBackground" Color="#FF364E6F" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderBackground" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderHighlight" Color="#FF364E6F" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderShadow" Color="#FF364E6F" /> + <SolidColorBrush x:Key="VsBrush.FileTabGradientDark" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.FileTabGradientLight" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotBorder" Color="#FF5B7199" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGlyph" Color="#FFCED4DD" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGradientBottom" Color="#FF5B7199" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGradientTop" Color="#FF5B7199" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveDocumentBorderBackground" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveDocumentBorderEdge" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveGradientBottom" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveGradientTop" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveDocumentBorderBackground" Color="#FFCED4DF" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveDocumentBorderEdge" Color="#FFC0C9D9" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGlyph" Color="#FF5F6673" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientBottom" Color="#FFD5DAE3" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientMiddle1" Color="#FFD5DAE3" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientMiddle2" Color="#FFD5DAE3" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientTop" Color="#FFD5DAE3" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedBackground" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedBorder" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientBottom" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientMiddle1" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientMiddle2" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientTop" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FileTabText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagActionTagBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagActionTagFill" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagObjectTagBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagObjectTagFill" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.GrayText" Color="#FF6D6D6D" /> + <SolidColorBrush x:Key="VsBrush.GridHeadingBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.GridHeadingText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.GridLine" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFrameBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFrameText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchPanelRules" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderIcon" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderSelectedBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderUnselectedBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderUnselectedText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultLinkSelected" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultLinkUnselected" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultSelectedBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Highlight" Color="#FF0078D7" /> + <SolidColorBrush x:Key="VsBrush.HighlightText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.InactiveBorder" Color="#FFF4F7FC" /> + <SolidColorBrush x:Key="VsBrush.InactiveCaption" Color="#FFBFCDDB" /> + <SolidColorBrush x:Key="VsBrush.InactiveCaptionText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.InfoBackground" Color="#FFFFFFE1" /> + <SolidColorBrush x:Key="VsBrush.InfoText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Light" Color="#00F6F6F6" /> + <SolidColorBrush x:Key="VsBrush.LightCaption" Color="#001E1E1E" /> + <SolidColorBrush x:Key="VsBrush.MdiClientBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.Medium" Color="#00EFEFF2" /> + <SolidColorBrush x:Key="VsBrush.Menu" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.MenuText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.NewProjectBackground" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveBegin" Color="#FFEEEDED" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveBorder" Color="#FFCFCFCF" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveEnd" Color="#FFDDDDDD" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemSelected" Color="#FFFFE8A6" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemSelectedBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverBegin" Color="#FFFFFCF4" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverEnd" Color="#FFFFECB5" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverForeground" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverMiddle1" Color="#FFFFF3CD" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverMiddle2" Color="#FFFFECB5" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveBegin" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveEnd" Color="#FF3D5277" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveForeground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.PageContentExpanderChevron" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.PageContentExpanderSeparator" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderBody" Color="#FFF2F4F8" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderChevron" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeader" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeaderHover" Color="#FFFFF3CD" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeaderPressed" Color="#FFFFECB5" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderSeparator" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.PanelBorder" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.PanelGradientDark" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.PanelGradientLight" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.PanelHoverOverCloseBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.PanelHoverOverCloseFill" Color="#FFFFFCF4" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlinkHover" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlinkPressed" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.PanelSeparator" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.PanelSubGroupSeparator" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.PanelText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBar" Color="#FFCDD4DF" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBarText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBarUnselected" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBackgroundGradientBegin" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBackgroundGradientEnd" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBorderInside" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBorderOutside" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerContentsBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabBackgroundGradientBegin" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabBackgroundGradientEnd" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedHighlight1" Color="#FFC0A776" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedHighlight2" Color="#FFFFE8A6" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedInsideBorder" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepBottomGradientBegin" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepBottomGradientEnd" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepTopGradientBegin" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepTopGradientEnd" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipBackground" Color="#FFFFFFE1" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ScrollBar" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowBackground" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowDisabledBackground" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowMouseOverBackground" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowPressedBackground" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarBackground" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarDisabledBackground" Color="#FFE8E8EC" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbBackground" Color="#FFC2C3C9" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbBorder" Color="#FFC2C3C9" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbGlyph" Color="#FFC2C3C9" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbMouseOverBackground" Color="#FF686868" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbPressedBackground" Color="#FF5B5B5B" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxBorder" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundBegin" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundEnd" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundMiddle1" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundMiddle2" Color="#FFFDF4BF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxPressedBackground" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxPressedBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SideBarBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.SideBarGradientDark" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.SideBarGradientLight" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.SideBarText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.SmartTagBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SmartTagFill" Color="#FFFFF0D0" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverFill" Color="#FFFFECB5" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.SmartTagText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Snaplines" Color="#FF4169E1" /> + <SolidColorBrush x:Key="VsBrush.SnaplinesPadding" Color="#FF96A9DD" /> + <SolidColorBrush x:Key="VsBrush.SnaplinesTextBaseline" Color="#FFE122DF" /> + <SolidColorBrush x:Key="VsBrush.SortBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.SortText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.SplashScreenBorder" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.StartPageBackgroundGradientBegin" Color="#FF162030" /> + <SolidColorBrush x:Key="VsBrush.StartPageBackgroundGradientEnd" Color="#FF162030" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonBorder" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundBegin" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundEnd" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundMiddle1" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundMiddle2" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinDown" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinHover" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinned" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonText" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonTextHover" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonUnpinned" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageSelectedItemBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageSelectedItemStroke" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageSeparator" Color="#FF363639" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabMouseOverBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabMouseOverBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBody" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBodySelected" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBodyUnselected" Color="#FF555555" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextControlLinkSelected" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextControlLinkSelectedHover" Color="#FF77AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextDate" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeading" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeadingMouseOver" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeadingSelected" Color="#FF555555" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeading" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeadingMouseOver" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeadingSelected" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemBackgroundBegin" Color="#FF3F3F3F" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemBackgroundEnd" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemStroke" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StatusBarText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.TaskListGridLines" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ThreeDDarkShadow" Color="#FF696969" /> + <SolidColorBrush x:Key="VsBrush.ThreeDFace" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ThreeDHighlight" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ThreeDLightShadow" Color="#FFE3E3E3" /> + <SolidColorBrush x:Key="VsBrush.ThreeDShadow" Color="#FFA0A0A0" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActive" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientBegin" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientEnd" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientMiddle1" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientMiddle2" Color="#FFFFF29D" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactive" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveGradientBegin" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveGradientEnd" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolboxBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolboxDivider" Color="#FF8591A2" /> + <SolidColorBrush x:Key="VsBrush.ToolboxGradientDark" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolboxGradientLight" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingAccent" Color="#FFCED4DF" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingBegin" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingEnd" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ToolboxIconHighlight" Color="#FFF7F7FF" /> + <SolidColorBrush x:Key="VsBrush.ToolboxIconShadow" Color="#FFA0A0A0" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingBegin" Color="#FFFFFBF0" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingEnd" Color="#FFFFF2CB" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingMiddle1" Color="#FFFFF7DA" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingMiddle2" Color="#FFFFF2CB" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowBorder" Color="#FF8E9BBC" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonActiveGlyph" Color="#FF75633D" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDown" Color="#FFFFE8A6" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownActiveGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownInactiveGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActive" Color="#FFFFFCF4" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActiveBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActiveGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactive" Color="#FFFFFCF4" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactiveBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactiveGlyph" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactive" Color="#FF2F405E" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactiveBorder" Color="#FF707E96" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactiveGlyph" Color="#FFCED4DD" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowContentTabGradientBegin" Color="#FFFBFCFD" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowContentTabGradientEnd" Color="#FFFBFCFD" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowFloatingFrame" Color="#FF293955" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabBorder" Color="#FF4B5C74" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabGradientBegin" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabGradientEnd" Color="#FF4D6082" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBackgroundBegin" Color="#FF5B7199" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBackgroundEnd" Color="#FF5B7199" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBorder" Color="#FF5B7199" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabSelectedTab" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownDark" Color="#FF705829" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownLight" Color="#FFB0A781" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownMedium" Color="#FFA19667" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldDark" Color="#FFA79432" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldLight" Color="#FFD0D4B7" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldMedium" Color="#FFBFC749" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldDark" Color="#FFCAB22D" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldLight" Color="#FFFBF7C8" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldMedium" Color="#FFE2E442" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenDark" Color="#FF5D8039" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenLight" Color="#FFB1C97B" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenMedium" Color="#FF9FB861" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumDark" Color="#FF8E5478" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumLight" Color="#FFE2B1CD" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumMedium" Color="#FFCB98B6" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedDark" Color="#FFAD1C2B" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedLight" Color="#FFFF9F99" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedMedium" Color="#FFFF7971" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueDark" Color="#FF779AB6" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueLight" Color="#FFC6D4DF" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueMedium" Color="#FFB8CCD7" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueDark" Color="#FF427094" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueLight" Color="#FFA0B7C9" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueMedium" Color="#FF89ABBD" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueDark" Color="#FF5386BF" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueLight" Color="#FFB9D4EE" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueMedium" Color="#FFA1C7E7" /> + <SolidColorBrush x:Key="VsBrush.Window" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.WindowFrame" Color="#FF646464" /> + <SolidColorBrush x:Key="VsBrush.WindowText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.WizardOrientationPanelBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.WizardOrientationPanelText" Color="#FF000000" /> +</ResourceDictionary> diff --git a/src/GitHub.VisualStudio.UI/Styles/VsBrushesDark.xaml b/src/GitHub.VisualStudio.UI/Styles/VsBrushesDark.xaml new file mode 100644 index 0000000000..b069dd81af --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsBrushesDark.xaml @@ -0,0 +1,510 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <SolidColorBrush x:Key="VsBrush.AccentBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.AccentDark" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.AccentLight" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.AccentMedium" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AccentPale" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ActiveBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ActiveCaption" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.AppWorkspace" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AutoHideResizeGrip" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBackgroundBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBackgroundEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBackgroundBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBackgroundEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverText" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabText" Color="#FFD0D0D0" /> + <SolidColorBrush x:Key="VsBrush.Background" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIFill" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.BrandedUITitle" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.ButtonFace" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ButtonHighlight" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.ButtonShadow" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ButtonText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.CaptionText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerClassCompartment" Color="#FFF0F2F9" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerClassHeaderBackground" Color="#FFD3DCEF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentBorder" Color="#FFCCCC66" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentShapeBackground" Color="#FFFFFFCC" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCompartmentSeparator" Color="#FFD2D2D2" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerConnectionRouteBorder" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultConnection" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeBorder" Color="#FF00008B" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeSubtitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeTitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeTitleBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDelegateCompartment" Color="#FFF7F0F0" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDelegateHeader" Color="#FFEDDADC" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDiagramBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerEmphasisBorder" Color="#FF0054E3" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerEnumHeader" Color="#FFDDD6EF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerFieldAssociation" Color="#FF266035" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerGradientEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInheritance" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInterfaceCompartment" Color="#FFF3F7F0" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInterfaceHeader" Color="#FFE6F0DB" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerLasso" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerLollipop" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerPropertyAssociation" Color="#FFB0764F" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerReferencedAssemblyBorder" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerResizingShapeBorder" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerShapeBorder" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerShapeShadow" Color="#FFD8D8D8" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTempConnection" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTypedef" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTypedefHeader" Color="#FFD6ECEF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerUnresolvedText" Color="#FFFF0000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerVBModuleCompartment" Color="#FFF8F4E9" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerVBModuleHeader" Color="#FFF0E9D2" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxBackground" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledGlyph" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxGlyph" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseDownBackground" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseDownBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundBegin" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundEnd" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundMiddle1" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundMiddle2" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverGlyph" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBackgroundBegin" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBackgroundEnd" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.CommandBarBorder" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarCheckBox" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.CommandBarDragHandle" Color="#FF46464A" /> + <SolidColorBrush x:Key="VsBrush.CommandBarDragHandleShadow" Color="#FF46464A" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientMiddle" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHover" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelected" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelectedIcon" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelectedIconBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBackgroundGradientBegin" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBackgroundGradientEnd" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuIconBackground" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuMouseOverSubmenuGlyph" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuSeparator" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuSubmenuGlyph" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundBegin" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundEnd" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundMiddle" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundBegin" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundEnd" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundMiddle1" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundMiddle2" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsGlyph" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundBegin" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundEnd" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundMiddle" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundBegin" Color="#72555555" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundEnd" Color="#72555555" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundMiddle1" Color="#72555555" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundMiddle2" Color="#72555555" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverGlyph" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarSelected" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarShadow" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextActive" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextHover" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextInactive" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextSelected" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.CommandBarToolBarBorder" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandBarToolBarSeparator" Color="#FF222222" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientMiddle" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientMiddle" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ControlEditHintText" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.ControlEditRequiredBackground" Color="#FFFEFCC8" /> + <SolidColorBrush x:Key="VsBrush.ControlEditRequiredHintText" Color="#FF555555" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkText" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkTextHover" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkTextPressed" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.ControlOutline" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.Dark" Color="#003F3F46" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveBackground" Color="#FF424245" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveBorder" Color="#FF4D4D50" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveHighlight" Color="#FF505051" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveHighlightText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveSeparator" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveBackground" Color="#FF2C2C2F" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveBorder" Color="#FF37373A" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveHighlight" Color="#FF3D3D3F" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveHighlightText" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveSeparator" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveText" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.DesignerBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.DesignerSelectionDots" Color="#FF46464A" /> + <SolidColorBrush x:Key="VsBrush.DesignerTray" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.DesignerWatermark" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.DiagReportBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageHeader" Color="#FFF2F4F8" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageSubtitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageTitle" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageHeader" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageSubtitle" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageTitle" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DiagReportText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DockTargetBackground" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.DockTargetBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBackgroundBegin" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBackgroundEnd" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBorder" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphArrow" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBackgroundBegin" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBackgroundEnd" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.DropDownBackground" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.DropDownBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledGlyph" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.DropDownGlyph" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseDownBackground" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseDownBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundBegin" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundEnd" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundMiddle1" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundMiddle2" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverGlyph" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBackgroundBegin" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBackgroundEnd" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.DropShadowBackground" Color="#72000000" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionFill" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionLink" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientMiddle1" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientMiddle2" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture" Color="#00FFFFFF" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture1" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture2" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarHighlight1" Color="#FFFF8C00" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarHighlight2" Color="#FFFF8C00" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarInactive1" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarInactive2" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.FileTabBorder" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.FileTabChannelBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderHighlight" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderShadow" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabGradientDark" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.FileTabGradientLight" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotBorder" Color="#FF1C97EA" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGlyph" Color="#FFD0E6F5" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGradientBottom" Color="#FF1C97EA" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGradientTop" Color="#FF1C97EA" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveDocumentBorderBackground" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveDocumentBorderEdge" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveGradientBottom" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveGradientTop" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveDocumentBorderBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveDocumentBorderEdge" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGlyph" Color="#FFD0E6F5" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientBottom" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientMiddle1" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientMiddle2" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientTop" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientBottom" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientMiddle1" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientMiddle2" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientTop" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagActionTagBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagActionTagFill" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagObjectTagBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagObjectTagFill" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.GrayText" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.GridHeadingBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.GridHeadingText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.GridLine" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFrameBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFrameText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchPanelRules" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderIcon" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderSelectedBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderUnselectedBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderUnselectedText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultLinkSelected" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultLinkUnselected" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultSelectedBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Highlight" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.HighlightText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.InactiveBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.InactiveCaption" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.InactiveCaptionText" Color="#FF656565" /> + <SolidColorBrush x:Key="VsBrush.InfoBackground" Color="#FFFEFCC8" /> + <SolidColorBrush x:Key="VsBrush.InfoText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.Light" Color="#001E1E1E" /> + <SolidColorBrush x:Key="VsBrush.LightCaption" Color="#00F1F1F1" /> + <SolidColorBrush x:Key="VsBrush.MdiClientBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.Medium" Color="#002D2D30" /> + <SolidColorBrush x:Key="VsBrush.Menu" Color="#FF1B1B1C" /> + <SolidColorBrush x:Key="VsBrush.MenuText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.NewProjectBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveBegin" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveEnd" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemSelected" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverBegin" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverEnd" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverForeground" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverMiddle1" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverMiddle2" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveBegin" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveEnd" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveForeground" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.PageContentExpanderChevron" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.PageContentExpanderSeparator" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderBody" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderChevron" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeader" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeaderHover" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeaderPressed" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderSeparator" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.PanelBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.PanelGradientDark" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.PanelGradientLight" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.PanelHoverOverCloseBorder" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.PanelHoverOverCloseFill" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlink" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlinkHover" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlinkPressed" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.PanelSeparator" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.PanelSubGroupSeparator" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.PanelText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBar" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBarText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBarUnselected" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBackgroundGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBackgroundGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBorderInside" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBorderOutside" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerContentsBackground" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabBackgroundGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabBackgroundGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedBackground" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedHighlight1" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedHighlight2" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedInsideBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepBottomGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepBottomGradientEnd" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepTopGradientBegin" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepTopGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipBackground" Color="#FFFEFCC8" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipBorder" Color="#FFFEFCC8" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipText" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ScrollBar" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowBackground" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowDisabledBackground" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowMouseOverBackground" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowPressedBackground" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarBackground" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarDisabledBackground" Color="#FF3E3E42" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbBackground" Color="#FF686868" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbBorder" Color="#FF686868" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbGlyph" Color="#FF686868" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbMouseOverBackground" Color="#FF9E9E9E" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbPressedBackground" Color="#FFEFEBEF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxBackground" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxBorder" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundBegin" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundEnd" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundMiddle1" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundMiddle2" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxPressedBackground" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxPressedBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.SideBarBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.SideBarGradientDark" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.SideBarGradientLight" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.SideBarText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.SmartTagBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SmartTagFill" Color="#FFFFEFBB" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverFill" Color="#FFFEFCC8" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.SmartTagText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Snaplines" Color="#FF4169E1" /> + <SolidColorBrush x:Key="VsBrush.SnaplinesPadding" Color="#FF96A9DD" /> + <SolidColorBrush x:Key="VsBrush.SnaplinesTextBaseline" Color="#FFE122DF" /> + <SolidColorBrush x:Key="VsBrush.SortBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.SortText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.SplashScreenBorder" Color="#FF434346" /> + <SolidColorBrush x:Key="VsBrush.StartPageBackgroundGradientBegin" Color="#FF1F1F22" /> + <SolidColorBrush x:Key="VsBrush.StartPageBackgroundGradientEnd" Color="#FF1F1F22" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonBorder" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundBegin" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundEnd" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundMiddle1" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundMiddle2" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinDown" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinHover" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinned" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonText" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonTextHover" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonUnpinned" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageSelectedItemBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageSelectedItemStroke" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageSeparator" Color="#FF363639" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabBackgroundBegin" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabBackgroundEnd" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabMouseOverBackgroundBegin" Color="#FF28282B" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabMouseOverBackgroundEnd" Color="#FF28282B" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBody" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBodySelected" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBodyUnselected" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextControlLinkSelected" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextControlLinkSelectedHover" Color="#FF88CCFE" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextDate" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeading" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeadingMouseOver" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeadingSelected" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeading" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeadingMouseOver" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeadingSelected" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemBackgroundBegin" Color="#FF3F3F3F" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemBackgroundEnd" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemStroke" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StatusBarText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.TaskListGridLines" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ThreeDDarkShadow" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ThreeDFace" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ThreeDHighlight" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.ThreeDLightShadow" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ThreeDShadow" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActive" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientBegin" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientEnd" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientMiddle1" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientMiddle2" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactive" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveText" Color="#FFD0D0D0" /> + <SolidColorBrush x:Key="VsBrush.ToolboxBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxDivider" Color="#FF333337" /> + <SolidColorBrush x:Key="VsBrush.ToolboxGradientDark" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxGradientLight" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingAccent" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingBegin" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingEnd" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxIconHighlight" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxIconShadow" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingMiddle1" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingMiddle2" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowBackground" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowBorder" Color="#FF3F3F46" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonActiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDown" Color="#FF0E6198" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownActiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownBorder" Color="#FF0E6198" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownInactiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActive" Color="#FF52B0EF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActiveBorder" Color="#FF52B0EF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactive" Color="#FF393939" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactiveBorder" Color="#FF393939" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactiveGlyph" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactive" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactiveBorder" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactiveGlyph" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowContentTabGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowContentTabGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowFloatingFrame" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabBorder" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBackgroundBegin" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBackgroundEnd" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBorder" Color="#FF3E3E40" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverText" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabSelectedTab" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabSelectedText" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabText" Color="#FFD0D0D0" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownDark" Color="#FF705829" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownLight" Color="#FFB0A781" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownMedium" Color="#FFA19667" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldDark" Color="#FFA79432" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldLight" Color="#FFD0D4B7" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldMedium" Color="#FFBFC749" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldDark" Color="#FFCAB22D" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldLight" Color="#FFFBF7C8" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldMedium" Color="#FFE2E442" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenDark" Color="#FF5D8039" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenLight" Color="#FFB1C97B" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenMedium" Color="#FF9FB861" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumDark" Color="#FF8E5478" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumLight" Color="#FFE2B1CD" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumMedium" Color="#FFCB98B6" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedDark" Color="#FFAD1C2B" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedLight" Color="#FFFF9F99" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedMedium" Color="#FFFF7971" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueDark" Color="#FF779AB6" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueLight" Color="#FFC6D4DF" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueMedium" Color="#FFB8CCD7" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueDark" Color="#FF427094" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueLight" Color="#FFA0B7C9" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueMedium" Color="#FF89ABBD" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueDark" Color="#FF5386BF" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueLight" Color="#FFB9D4EE" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueMedium" Color="#FFA1C7E7" /> + <SolidColorBrush x:Key="VsBrush.Window" Color="#FF252526" /> + <SolidColorBrush x:Key="VsBrush.WindowFrame" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.WindowText" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.WizardOrientationPanelBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.WizardOrientationPanelText" Color="#FF000000" /> +</ResourceDictionary> diff --git a/src/GitHub.VisualStudio.UI/Styles/VsBrushesLight.xaml b/src/GitHub.VisualStudio.UI/Styles/VsBrushesLight.xaml new file mode 100644 index 0000000000..23baf3a4c3 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsBrushesLight.xaml @@ -0,0 +1,510 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <SolidColorBrush x:Key="VsBrush.AccentBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.AccentDark" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.AccentLight" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.AccentMedium" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AccentPale" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ActiveBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ActiveCaption" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.AppWorkspace" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AutoHideResizeGrip" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBackgroundBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBackgroundEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBackgroundBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBackgroundEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabMouseOverText" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.AutoHideTabText" Color="#FF444444" /> + <SolidColorBrush x:Key="VsBrush.Background" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIBorder" Color="#FFCCCEBD" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIFill" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.BrandedUIText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.BrandedUITitle" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.ButtonFace" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ButtonHighlight" Color="#FFD8D8E0" /> + <SolidColorBrush x:Key="VsBrush.ButtonShadow" Color="#FFCCCEBD" /> + <SolidColorBrush x:Key="VsBrush.ButtonText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.CaptionText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerClassCompartment" Color="#FFF0F2F9" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerClassHeaderBackground" Color="#FFD3DCEF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentBorder" Color="#FFCCCC66" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentShapeBackground" Color="#FFFFFFCC" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCommentText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerCompartmentSeparator" Color="#FFD2D2D2" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerConnectionRouteBorder" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultConnection" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeBorder" Color="#FF00008B" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeSubtitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeTitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDefaultShapeTitleBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDelegateCompartment" Color="#FFF7F0F0" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDelegateHeader" Color="#FFEDDADC" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerDiagramBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerEmphasisBorder" Color="#FF0054E3" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerEnumHeader" Color="#FFDDD6EF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerFieldAssociation" Color="#FF266035" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerGradientEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInheritance" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInterfaceCompartment" Color="#FFF3F7F0" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerInterfaceHeader" Color="#FFE6F0DB" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerLasso" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerLollipop" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerPropertyAssociation" Color="#FFB0764F" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerReferencedAssemblyBorder" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerResizingShapeBorder" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerShapeBorder" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerShapeShadow" Color="#FFD8D8D8" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTempConnection" Color="#FF808080" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTypedef" Color="#FF716F64" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerTypedefHeader" Color="#FFD6ECEF" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerUnresolvedText" Color="#FFFF0000" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerVBModuleCompartment" Color="#FFF8F4E9" /> + <SolidColorBrush x:Key="VsBrush.ClassDesignerVBModuleHeader" Color="#FFF0E9D2" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxDisabledGlyph" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxGlyph" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseDownBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseDownBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundMiddle1" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBackgroundMiddle2" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxMouseOverGlyph" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBackgroundBegin" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBackgroundEnd" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.ComboBoxPopupBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.CommandBarBorder" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarCheckBox" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.CommandBarDragHandle" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.CommandBarDragHandleShadow" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarGradientMiddle" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHover" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelected" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelectedIcon" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarHoverOverSelectedIconBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBackgroundGradientBegin" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBackgroundGradientEnd" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuIconBackground" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuMouseOverSubmenuGlyph" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuSeparator" Color="#FFE0E3E6" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMenuSubmenuGlyph" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundBegin" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundEnd" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBackgroundMiddle" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseDownBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundBegin" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundEnd" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundMiddle1" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarMouseOverBackgroundMiddle2" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsGlyph" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundBegin" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundEnd" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseDownBackgroundMiddle" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundBegin" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundEnd" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundMiddle1" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverBackgroundMiddle2" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarOptionsMouseOverGlyph" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.CommandBarSelected" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.CommandBarShadow" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextActive" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextHover" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextInactive" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.CommandBarTextSelected" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.CommandBarToolBarBorder" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandBarToolBarSeparator" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfBackgroundGradientMiddle" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.CommandShelfHighlightGradientMiddle" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ControlEditHintText" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.ControlEditRequiredBackground" Color="#FFFDFBAC" /> + <SolidColorBrush x:Key="VsBrush.ControlEditRequiredHintText" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkText" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkTextHover" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.ControlLinkTextPressed" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.ControlOutline" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.Dark" Color="#00CCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveBackground" Color="#FFE7E8EC" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveHighlight" Color="#FFEDEEF0" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveHighlightText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveSeparator" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipActiveText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveBackground" Color="#FFD6D8DC" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveHighlight" Color="#FFEDEEF0" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveHighlightText" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveSeparator" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DebuggerDataTipInactiveText" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.DesignerBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.DesignerSelectionDots" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.DesignerTray" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DesignerWatermark" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.DiagReportBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageHeader" Color="#FFF2F4F8" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageSubtitle" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSecondaryPageTitle" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageHeader" Color="#FF4A6184" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageSubtitle" Color="#FFBCC7D8" /> + <SolidColorBrush x:Key="VsBrush.DiagReportSummaryPageTitle" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DiagReportText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.DockTargetBackground" Color="#FFE7E8EC" /> + <SolidColorBrush x:Key="VsBrush.DockTargetBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBackgroundBegin" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBackgroundEnd" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.DockTargetButtonBorder" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphArrow" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBackgroundBegin" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBackgroundEnd" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.DockTargetGlyphBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.DropDownBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DropDownBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DropDownDisabledGlyph" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DropDownGlyph" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseDownBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseDownBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundMiddle1" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBackgroundMiddle2" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.DropDownMouseOverGlyph" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBackgroundBegin" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBackgroundEnd" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.DropDownPopupBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.DropShadowBackground" Color="#72000000" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionFill" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionLink" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.EditorExpansionText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientMiddle1" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundGradientMiddle2" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture" Color="#00FFFFFF" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture1" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.EnvironmentBackgroundTexture2" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarHighlight1" Color="#FFFFA300" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarHighlight2" Color="#FFFFA300" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarInactive1" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.ExtensionManagerStarInactive2" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.FileTabBorder" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.FileTabChannelBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderHighlight" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabDocumentBorderShadow" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabGradientDark" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.FileTabGradientLight" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotBorder" Color="#FF1C97EA" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGlyph" Color="#FFD0E6F5" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGradientBottom" Color="#FF1C97EA" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotGradientTop" Color="#FF1C97EA" /> + <SolidColorBrush x:Key="VsBrush.FileTabHotText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveDocumentBorderBackground" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveDocumentBorderEdge" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveGradientBottom" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveGradientTop" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.FileTabInactiveText" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveDocumentBorderBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveDocumentBorderEdge" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGlyph" Color="#FFD0E6F5" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientBottom" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientMiddle1" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientMiddle2" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveGradientTop" Color="#FF0E639C" /> + <SolidColorBrush x:Key="VsBrush.FileTabLastActiveText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientBottom" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientMiddle1" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientMiddle2" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedGradientTop" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.FileTabSelectedText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FileTabText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagActionTagBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagActionTagFill" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagObjectTagBorder" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.FormSmartTagObjectTagFill" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.GrayText" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.GridHeadingBackground" Color="#FFEFEFE2" /> + <SolidColorBrush x:Key="VsBrush.GridHeadingText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.GridLine" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoIPaneText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskLink" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpHowDoITaskText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterBorder" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFilterText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFrameBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchFrameText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchPanelRules" Color="#FFA8B3C2" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderIcon" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderSelectedBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderUnselectedBackground" Color="#FFDEE1E7" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchProviderUnselectedText" Color="#FF1B293E" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultLinkSelected" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultLinkUnselected" Color="#FF0066CC" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultSelectedBackground" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchResultSelectedText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.HelpSearchText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Highlight" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.HighlightText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.InactiveBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.InactiveCaption" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.InactiveCaptionText" Color="#FFA2A4A5" /> + <SolidColorBrush x:Key="VsBrush.InfoBackground" Color="#FFFDFBAC" /> + <SolidColorBrush x:Key="VsBrush.InfoText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.Light" Color="#00F5F5F5" /> + <SolidColorBrush x:Key="VsBrush.LightCaption" Color="#001E1E1E" /> + <SolidColorBrush x:Key="VsBrush.MdiClientBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.Medium" Color="#00EEEEF2" /> + <SolidColorBrush x:Key="VsBrush.Menu" Color="#FFF6F6F6" /> + <SolidColorBrush x:Key="VsBrush.MenuText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.NewProjectBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveBegin" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemInactiveEnd" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemSelected" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.NewProjectItemSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverBegin" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverEnd" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverForeground" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverMiddle1" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderHoverMiddle2" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveBegin" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveEnd" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.NewProjectProviderInactiveForeground" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.PageContentExpanderChevron" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.PageContentExpanderSeparator" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderBody" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderChevron" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeader" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeaderHover" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderHeaderPressed" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderSeparator" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.PageSideBarExpanderText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.PanelBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.PanelGradientDark" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.PanelGradientLight" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.PanelHoverOverCloseBorder" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.PanelHoverOverCloseFill" Color="#FFFEFEFE" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlink" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlinkHover" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.PanelHyperlinkPressed" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.PanelSeparator" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.PanelSubGroupSeparator" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.PanelText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBar" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBarText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.PanelTitleBarUnselected" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBackgroundGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBackgroundGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBorderInside" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerBorderOutside" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerContentsBackground" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabBackgroundGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabBackgroundGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedBackground" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedHighlight1" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedHighlight2" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSelectedInsideBorder" Color="#FF3399FF" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepBottomGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepBottomGradientEnd" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepTopGradientBegin" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ProjectDesignerTabSepTopGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipBackground" Color="#FFFDFBAC" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipBorder" Color="#FFFDFBAC" /> + <SolidColorBrush x:Key="VsBrush.ScreenTipText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.ScrollBar" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowDisabledBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowMouseOverBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarArrowPressedBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarDisabledBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbBackground" Color="#FFC2C3C9" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbBorder" Color="#FFC2C3C9" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbGlyph" Color="#FFC2C3C9" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbMouseOverBackground" Color="#FF686868" /> + <SolidColorBrush x:Key="VsBrush.ScrollBarThumbPressedBackground" Color="#FF5B5B5B" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxBackground" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxBorder" Color="#FFFCFCFC" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundMiddle1" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBackgroundMiddle2" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxMouseOverBorder" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxPressedBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.SearchBoxPressedBorder" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.SideBarBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.SideBarGradientDark" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.SideBarGradientLight" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.SideBarText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.SmartTagBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SmartTagFill" Color="#FFFFEFBB" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverBorder" Color="#FFE5C365" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverFill" Color="#FFFDFBAC" /> + <SolidColorBrush x:Key="VsBrush.SmartTagHoverText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.SmartTagText" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.Snaplines" Color="#FF4169E1" /> + <SolidColorBrush x:Key="VsBrush.SnaplinesPadding" Color="#FF96A9DD" /> + <SolidColorBrush x:Key="VsBrush.SnaplinesTextBaseline" Color="#FFE122DF" /> + <SolidColorBrush x:Key="VsBrush.SortBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.SortText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.SplashScreenBorder" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageBackgroundGradientBegin" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.StartPageBackgroundGradientEnd" Color="#FF2D2D30" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonBorder" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundBegin" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundEnd" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundMiddle1" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonMouseOverBackgroundMiddle2" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinDown" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinHover" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonPinned" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonText" Color="#FF0097FB" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonTextHover" Color="#FF55AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageButtonUnpinned" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageSelectedItemBackground" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageSelectedItemStroke" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageSeparator" Color="#FF363639" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabMouseOverBackgroundBegin" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTabMouseOverBackgroundEnd" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBody" Color="#FFF1F1F1" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBodySelected" Color="#FFF30506" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextBodyUnselected" Color="#FF555555" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextControlLinkSelected" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextControlLinkSelectedHover" Color="#FF77AAFF" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextDate" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeading" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeadingMouseOver" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextHeadingSelected" Color="#FF555555" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeading" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeadingMouseOver" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.StartPageTextSubHeadingSelected" Color="#FF000000" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemBackgroundBegin" Color="#FF3F3F3F" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemBackgroundEnd" Color="#FF464646" /> + <SolidColorBrush x:Key="VsBrush.StartPageUnselectedItemStroke" Color="#FF999999" /> + <SolidColorBrush x:Key="VsBrush.StatusBarText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.TaskListGridLines" Color="#FFF0F0F0" /> + <SolidColorBrush x:Key="VsBrush.ThreeDDarkShadow" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ThreeDFace" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ThreeDHighlight" Color="#FFD8D8E0" /> + <SolidColorBrush x:Key="VsBrush.ThreeDLightShadow" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ThreeDShadow" Color="#FFCCCEBD" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActive" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientBegin" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientEnd" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientMiddle1" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveGradientMiddle2" Color="#FF007ACC" /> + <SolidColorBrush x:Key="VsBrush.TitleBarActiveText" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactive" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.TitleBarInactiveText" Color="#FF444444" /> + <SolidColorBrush x:Key="VsBrush.ToolboxBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxDivider" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ToolboxGradientDark" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxGradientLight" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingAccent" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingBegin" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxHeadingEnd" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxIconHighlight" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxIconShadow" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingMiddle1" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolboxSelectedHeadingMiddle2" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowBackground" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowBorder" Color="#FFCCCEDB" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonActiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDown" Color="#FF0E6198" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownActiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownBorder" Color="#FF0E6198" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonDownInactiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActive" Color="#FF52B0EF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActiveBorder" Color="#FF52B0EF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverActiveGlyph" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactive" Color="#FFF7F7F9" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactiveBorder" Color="#FFF7F7F9" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonHoverInactiveGlyph" Color="#FF717171" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactive" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactiveBorder" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowButtonInactiveGlyph" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowContentTabGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowContentTabGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowFloatingFrame" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabBorder" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabGradientBegin" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabGradientEnd" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBackgroundBegin" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBackgroundEnd" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverBorder" Color="#FFC9DEF5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabMouseOverText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabSelectedTab" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabSelectedText" Color="#FF0E70C0" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowTabText" Color="#FF444444" /> + <SolidColorBrush x:Key="VsBrush.ToolWindowText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownDark" Color="#FF705829" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownLight" Color="#FFB0A781" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceBrownMedium" Color="#FFA19667" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldDark" Color="#FFA79432" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldLight" Color="#FFD0D4B7" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceDarkGoldMedium" Color="#FFBFC749" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldDark" Color="#FFCAB22D" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldLight" Color="#FFFBF7C8" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGoldMedium" Color="#FFE2E442" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenDark" Color="#FF5D8039" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenLight" Color="#FFB1C97B" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceGreenMedium" Color="#FF9FB861" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumDark" Color="#FF8E5478" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumLight" Color="#FFE2B1CD" /> + <SolidColorBrush x:Key="VsBrush.VizSurfacePlumMedium" Color="#FFCB98B6" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedDark" Color="#FFAD1C2B" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedLight" Color="#FFFF9F99" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceRedMedium" Color="#FFFF7971" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueDark" Color="#FF779AB6" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueLight" Color="#FFC6D4DF" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSoftBlueMedium" Color="#FFB8CCD7" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueDark" Color="#FF427094" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueLight" Color="#FFA0B7C9" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceSteelBlueMedium" Color="#FF89ABBD" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueDark" Color="#FF5386BF" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueLight" Color="#FFB9D4EE" /> + <SolidColorBrush x:Key="VsBrush.VizSurfaceStrongBlueMedium" Color="#FFA1C7E7" /> + <SolidColorBrush x:Key="VsBrush.Window" Color="#FFF5F5F5" /> + <SolidColorBrush x:Key="VsBrush.WindowFrame" Color="#FFEEEEF2" /> + <SolidColorBrush x:Key="VsBrush.WindowText" Color="#FF1E1E1E" /> + <SolidColorBrush x:Key="VsBrush.WizardOrientationPanelBackground" Color="#FFFFFFFF" /> + <SolidColorBrush x:Key="VsBrush.WizardOrientationPanelText" Color="#FF000000" /> +</ResourceDictionary> diff --git a/src/GitHub.VisualStudio.UI/Styles/VsColorsBlue.xaml b/src/GitHub.VisualStudio.UI/Styles/VsColorsBlue.xaml new file mode 100644 index 0000000000..e5f28cb840 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsColorsBlue.xaml @@ -0,0 +1,509 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <Color x:Key="VsColor.AccentBorder">#FFE5C365</Color> + <Color x:Key="VsColor.AccentDark">#FFC0A776</Color> + <Color x:Key="VsColor.AccentLight">#FFFFF0D0</Color> + <Color x:Key="VsColor.AccentMedium">#FFFFECB5</Color> + <Color x:Key="VsColor.AccentPale">#FFDEE1E7</Color> + <Color x:Key="VsColor.ActiveBorder">#FFB4B4B4</Color> + <Color x:Key="VsColor.ActiveCaption">#FF99B4D1</Color> + <Color x:Key="VsColor.AppWorkspace">#FFABABAB</Color> + <Color x:Key="VsColor.AutoHideResizeGrip">#FFE8E8EC</Color> + <Color x:Key="VsColor.AutoHideTabBackgroundBegin">#FF293955</Color> + <Color x:Key="VsColor.AutoHideTabBackgroundEnd">#FF293955</Color> + <Color x:Key="VsColor.AutoHideTabBorder">#FF465A7D</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBackgroundBegin">#FF293955</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBackgroundEnd">#FF293955</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBorder">#FF9BA7B7</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverText">#FFFFFFFF</Color> + <Color x:Key="VsColor.AutoHideTabText">#FFFFFFFF</Color> + <Color x:Key="VsColor.Background">#FF000000</Color> + <Color x:Key="VsColor.BrandedUIBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.BrandedUIBorder">#FF8591A2</Color> + <Color x:Key="VsColor.BrandedUIFill">#FFC8D5E8</Color> + <Color x:Key="VsColor.BrandedUIText">#FF1B293E</Color> + <Color x:Key="VsColor.BrandedUITitle">#FF000000</Color> + <Color x:Key="VsColor.ButtonFace">#FFF0F0F0</Color> + <Color x:Key="VsColor.ButtonHighlight">#FFFFFFFF</Color> + <Color x:Key="VsColor.ButtonShadow">#FFA0A0A0</Color> + <Color x:Key="VsColor.ButtonText">#FF000000</Color> + <Color x:Key="VsColor.CaptionText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerClassCompartment">#FFF0F2F9</Color> + <Color x:Key="VsColor.ClassDesignerClassHeaderBackground">#FFD3DCEF</Color> + <Color x:Key="VsColor.ClassDesignerCommentBorder">#FFCCCC66</Color> + <Color x:Key="VsColor.ClassDesignerCommentShapeBackground">#FFFFFFCC</Color> + <Color x:Key="VsColor.ClassDesignerCommentText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerCompartmentSeparator">#FFD2D2D2</Color> + <Color x:Key="VsColor.ClassDesignerConnectionRouteBorder">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerDefaultConnection">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeBorder">#FF00008B</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeSubtitle">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeTitle">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeTitleBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerDelegateCompartment">#FFF7F0F0</Color> + <Color x:Key="VsColor.ClassDesignerDelegateHeader">#FFEDDADC</Color> + <Color x:Key="VsColor.ClassDesignerDiagramBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerEmphasisBorder">#FF0054E3</Color> + <Color x:Key="VsColor.ClassDesignerEnumHeader">#FFDDD6EF</Color> + <Color x:Key="VsColor.ClassDesignerFieldAssociation">#FF266035</Color> + <Color x:Key="VsColor.ClassDesignerGradientEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerInheritance">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerInterfaceCompartment">#FFF3F7F0</Color> + <Color x:Key="VsColor.ClassDesignerInterfaceHeader">#FFE6F0DB</Color> + <Color x:Key="VsColor.ClassDesignerLasso">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerLollipop">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerPropertyAssociation">#FFB0764F</Color> + <Color x:Key="VsColor.ClassDesignerReferencedAssemblyBorder">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerResizingShapeBorder">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerShapeBorder">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerShapeShadow">#FFD8D8D8</Color> + <Color x:Key="VsColor.ClassDesignerTempConnection">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerTypedef">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerTypedefHeader">#FFD6ECEF</Color> + <Color x:Key="VsColor.ClassDesignerUnresolvedText">#FFFF0000</Color> + <Color x:Key="VsColor.ClassDesignerVBModuleCompartment">#FFF8F4E9</Color> + <Color x:Key="VsColor.ClassDesignerVBModuleHeader">#FFF0E9D2</Color> + <Color x:Key="VsColor.ComboBoxBackground">#FFFCFCFC</Color> + <Color x:Key="VsColor.ComboBoxBorder">#FFBDC5D8</Color> + <Color x:Key="VsColor.ComboBoxDisabledBackground">#FFD5DCE8</Color> + <Color x:Key="VsColor.ComboBoxDisabledBorder">#FFBDC5D8</Color> + <Color x:Key="VsColor.ComboBoxDisabledGlyph">#FFA4ADBA</Color> + <Color x:Key="VsColor.ComboBoxGlyph">#FF1B293E</Color> + <Color x:Key="VsColor.ComboBoxMouseDownBackground">#FFFCFCFC</Color> + <Color x:Key="VsColor.ComboBoxMouseDownBorder">#FFE5C365</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundBegin">#FFFCFCFC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundEnd">#FFFCFCFC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundMiddle1">#FFFCFCFC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundMiddle2">#FFFCFCFC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBorder">#FFE5C365</Color> + <Color x:Key="VsColor.ComboBoxMouseOverGlyph">#FF000000</Color> + <Color x:Key="VsColor.ComboBoxPopupBackgroundBegin">#FFEFEFEF</Color> + <Color x:Key="VsColor.ComboBoxPopupBackgroundEnd">#FFEFEFEF</Color> + <Color x:Key="VsColor.ComboBoxPopupBorder">#FF9BA7B7</Color> + <Color x:Key="VsColor.CommandBarBorder">#FFE5C365</Color> + <Color x:Key="VsColor.CommandBarCheckBox">#FF000000</Color> + <Color x:Key="VsColor.CommandBarDragHandle">#FF60728C</Color> + <Color x:Key="VsColor.CommandBarDragHandleShadow">#FFBCC7D8</Color> + <Color x:Key="VsColor.CommandBarGradientBegin">#FFCFD6E5</Color> + <Color x:Key="VsColor.CommandBarGradientEnd">#FFCFD6E5</Color> + <Color x:Key="VsColor.CommandBarGradientMiddle">#FFCFD6E5</Color> + <Color x:Key="VsColor.CommandBarHover">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelected">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelectedIcon">#FFFFFCF4</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelectedIconBorder">#FFE5C365</Color> + <Color x:Key="VsColor.CommandBarMenuBackgroundGradientBegin">#FFEAF0FF</Color> + <Color x:Key="VsColor.CommandBarMenuBackgroundGradientEnd">#FFEAF0FF</Color> + <Color x:Key="VsColor.CommandBarMenuBorder">#FF9BA7B7</Color> + <Color x:Key="VsColor.CommandBarMenuIconBackground">#FFF2F4FE</Color> + <Color x:Key="VsColor.CommandBarMenuMouseOverSubmenuGlyph">#FF000000</Color> + <Color x:Key="VsColor.CommandBarMenuSeparator">#FFBEC3CB</Color> + <Color x:Key="VsColor.CommandBarMenuSubmenuGlyph">#FF000000</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundBegin">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundEnd">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundMiddle">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarMouseDownBorder">#FFE5C365</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundBegin">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundEnd">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundMiddle1">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundMiddle2">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarOptionsBackground">#FFDCE0EC</Color> + <Color x:Key="VsColor.CommandBarOptionsGlyph">#FF1B293E</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundBegin">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundEnd">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundMiddle">#FFFFF29D</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundBegin">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundEnd">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundMiddle1">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundMiddle2">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverGlyph">#FF1B293E</Color> + <Color x:Key="VsColor.CommandBarSelected">#FFFDF4BF</Color> + <Color x:Key="VsColor.CommandBarSelectedBorder">#FFE5C365</Color> + <Color x:Key="VsColor.CommandBarShadow">#FFD6DBE9</Color> + <Color x:Key="VsColor.CommandBarTextActive">#FF1B293E</Color> + <Color x:Key="VsColor.CommandBarTextHover">#FF000000</Color> + <Color x:Key="VsColor.CommandBarTextInactive">#FF808080</Color> + <Color x:Key="VsColor.CommandBarTextSelected">#FF000000</Color> + <Color x:Key="VsColor.CommandBarToolBarBorder">#FFDCE0EC</Color> + <Color x:Key="VsColor.CommandBarToolBarSeparator">#FF8591A2</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientBegin">#FFD6DBE9</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientEnd">#FFD6DBE9</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientMiddle">#FFD6DBE9</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientBegin">#FFD6DBE9</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientEnd">#FFD6DBE9</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientMiddle">#FFD6DBE9</Color> + <Color x:Key="VsColor.ControlEditHintText">#FFA0A0A0</Color> + <Color x:Key="VsColor.ControlEditRequiredBackground">#FFFFFAC8</Color> + <Color x:Key="VsColor.ControlEditRequiredHintText">#FF3C7FB1</Color> + <Color x:Key="VsColor.ControlLinkText">#FF0066CC</Color> + <Color x:Key="VsColor.ControlLinkTextHover">#FF3399FF</Color> + <Color x:Key="VsColor.ControlLinkTextPressed">#FF3399FF</Color> + <Color x:Key="VsColor.ControlOutline">#FF8591A2</Color> + <Color x:Key="VsColor.Dark">#00CCCEDB</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveBorder">#FF8591A2</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveHighlight">#FFBCC7D8</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveHighlightText">#FF000000</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveSeparator">#FFA8B3C2</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveText">#FF000000</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveBorder">#FFA4ADBA</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveHighlight">#FFBCC7D8</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveHighlightText">#FF808080</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveSeparator">#FFA4ADBA</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveText">#FF808080</Color> + <Color x:Key="VsColor.DesignerBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DesignerSelectionDots">#FF716F64</Color> + <Color x:Key="VsColor.DesignerTray">#FFDEE1E7</Color> + <Color x:Key="VsColor.DesignerWatermark">#FF808080</Color> + <Color x:Key="VsColor.DiagReportBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageHeader">#FFF2F4F8</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageSubtitle">#FF000000</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageTitle">#FF4A6184</Color> + <Color x:Key="VsColor.DiagReportSummaryPageHeader">#FF4A6184</Color> + <Color x:Key="VsColor.DiagReportSummaryPageSubtitle">#FFBCC7D8</Color> + <Color x:Key="VsColor.DiagReportSummaryPageTitle">#FFFFFFFF</Color> + <Color x:Key="VsColor.DiagReportText">#FF000000</Color> + <Color x:Key="VsColor.DockTargetBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DockTargetBorder">#FF636871</Color> + <Color x:Key="VsColor.DockTargetButtonBackgroundBegin">#FFF5F8FB</Color> + <Color x:Key="VsColor.DockTargetButtonBackgroundEnd">#FFDEE2E9</Color> + <Color x:Key="VsColor.DockTargetButtonBorder">#FF8A919C</Color> + <Color x:Key="VsColor.DockTargetGlyphArrow">#FF445879</Color> + <Color x:Key="VsColor.DockTargetGlyphBackgroundBegin">#FFFDE8A7</Color> + <Color x:Key="VsColor.DockTargetGlyphBackgroundEnd">#FFF7C570</Color> + <Color x:Key="VsColor.DockTargetGlyphBorder">#FF445879</Color> + <Color x:Key="VsColor.DropDownBackground">#FFFCFCFC</Color> + <Color x:Key="VsColor.DropDownBorder">#FFBDC5D8</Color> + <Color x:Key="VsColor.DropDownDisabledBackground">#FFD5DCE8</Color> + <Color x:Key="VsColor.DropDownDisabledBorder">#FFBDC5D8</Color> + <Color x:Key="VsColor.DropDownDisabledGlyph">#FFA4ADBA</Color> + <Color x:Key="VsColor.DropDownGlyph">#FF1B293E</Color> + <Color x:Key="VsColor.DropDownMouseDownBackground">#FFFCFCFC</Color> + <Color x:Key="VsColor.DropDownMouseDownBorder">#FFE5C365</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundBegin">#FFFCFCFC</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundEnd">#FFFCFCFC</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundMiddle1">#FFFCFCFC</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundMiddle2">#FFFCFCFC</Color> + <Color x:Key="VsColor.DropDownMouseOverBorder">#FFE5C365</Color> + <Color x:Key="VsColor.DropDownMouseOverGlyph">#FF000000</Color> + <Color x:Key="VsColor.DropDownPopupBackgroundBegin">#FFEFEFEF</Color> + <Color x:Key="VsColor.DropDownPopupBackgroundEnd">#FFEFEFEF</Color> + <Color x:Key="VsColor.DropDownPopupBorder">#FF9BA7B7</Color> + <Color x:Key="VsColor.DropShadowBackground">#72000000</Color> + <Color x:Key="VsColor.EditorExpansionBorder">#FFC0A776</Color> + <Color x:Key="VsColor.EditorExpansionFill">#FFDEE1E7</Color> + <Color x:Key="VsColor.EditorExpansionLink">#FF0066CC</Color> + <Color x:Key="VsColor.EditorExpansionText">#FF000000</Color> + <Color x:Key="VsColor.EnvironmentBackground">#FF293955</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientBegin">#FF293955</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientEnd">#FF293955</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientMiddle1">#FF35496A</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientMiddle2">#FF35496A</Color> + <Color x:Key="VsColor.EnvironmentBackgroundTexture1">#FF293955</Color> + <Color x:Key="VsColor.EnvironmentBackgroundTexture2">#FF35496A</Color> + <Color x:Key="VsColor.ExtensionManagerStarHighlight1">#FFFFFF00</Color> + <Color x:Key="VsColor.ExtensionManagerStarHighlight2">#FFFF9200</Color> + <Color x:Key="VsColor.ExtensionManagerStarInactive1">#FFF0F0EE</Color> + <Color x:Key="VsColor.ExtensionManagerStarInactive2">#FFA9A9A9</Color> + <Color x:Key="VsColor.FileTabBorder">#FF364E6F</Color> + <Color x:Key="VsColor.FileTabChannelBackground">#FF364E6F</Color> + <Color x:Key="VsColor.FileTabDocumentBorderBackground">#FFFFF29D</Color> + <Color x:Key="VsColor.FileTabDocumentBorderHighlight">#FF364E6F</Color> + <Color x:Key="VsColor.FileTabDocumentBorderShadow">#FF364E6F</Color> + <Color x:Key="VsColor.FileTabGradientDark">#FF293955</Color> + <Color x:Key="VsColor.FileTabGradientLight">#FF293955</Color> + <Color x:Key="VsColor.FileTabHotBorder">#FF5B7199</Color> + <Color x:Key="VsColor.FileTabHotGlyph">#FFCED4DD</Color> + <Color x:Key="VsColor.FileTabHotGradientBottom">#FF5B7199</Color> + <Color x:Key="VsColor.FileTabHotGradientTop">#FF5B7199</Color> + <Color x:Key="VsColor.FileTabHotText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabInactiveDocumentBorderBackground">#FF4D6082</Color> + <Color x:Key="VsColor.FileTabInactiveDocumentBorderEdge">#FF4D6082</Color> + <Color x:Key="VsColor.FileTabInactiveGradientBottom">#FF4D6082</Color> + <Color x:Key="VsColor.FileTabInactiveGradientTop">#FF4D6082</Color> + <Color x:Key="VsColor.FileTabInactiveText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabLastActiveDocumentBorderBackground">#FFCED4DF</Color> + <Color x:Key="VsColor.FileTabLastActiveDocumentBorderEdge">#FFC0C9D9</Color> + <Color x:Key="VsColor.FileTabLastActiveGlyph">#FF5F6673</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientBottom">#FFD5DAE3</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientMiddle1">#FFD5DAE3</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientMiddle2">#FFD5DAE3</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientTop">#FFD5DAE3</Color> + <Color x:Key="VsColor.FileTabLastActiveText">#FF000000</Color> + <Color x:Key="VsColor.FileTabSelectedBackground">#FF293955</Color> + <Color x:Key="VsColor.FileTabSelectedBorder">#FFFFF29D</Color> + <Color x:Key="VsColor.FileTabSelectedGradientBottom">#FFFFF29D</Color> + <Color x:Key="VsColor.FileTabSelectedGradientMiddle1">#FFFFF29D</Color> + <Color x:Key="VsColor.FileTabSelectedGradientMiddle2">#FFFFF29D</Color> + <Color x:Key="VsColor.FileTabSelectedGradientTop">#FFFFF29D</Color> + <Color x:Key="VsColor.FileTabSelectedText">#FF000000</Color> + <Color x:Key="VsColor.FileTabText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FormSmartTagActionTagBorder">#FF000000</Color> + <Color x:Key="VsColor.FormSmartTagActionTagFill">#FFFFFFFF</Color> + <Color x:Key="VsColor.FormSmartTagObjectTagBorder">#FF000000</Color> + <Color x:Key="VsColor.FormSmartTagObjectTagFill">#FFFFFFFF</Color> + <Color x:Key="VsColor.GrayText">#FF6D6D6D</Color> + <Color x:Key="VsColor.GridHeadingBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.GridHeadingText">#FF000000</Color> + <Color x:Key="VsColor.GridLine">#FFF0F0F0</Color> + <Color x:Key="VsColor.HelpHowDoIPaneBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpHowDoIPaneLink">#FF0066CC</Color> + <Color x:Key="VsColor.HelpHowDoIPaneText">#FF000000</Color> + <Color x:Key="VsColor.HelpHowDoITaskBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpHowDoITaskLink">#FF0066CC</Color> + <Color x:Key="VsColor.HelpHowDoITaskText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchFilterBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchFilterBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchFilterText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchFrameBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpSearchFrameText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchPanelRules">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchProviderIcon">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchProviderSelectedBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchProviderSelectedText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchProviderUnselectedBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpSearchProviderUnselectedText">#FF1B293E</Color> + <Color x:Key="VsColor.HelpSearchResultLinkSelected">#FF0066CC</Color> + <Color x:Key="VsColor.HelpSearchResultLinkUnselected">#FF0066CC</Color> + <Color x:Key="VsColor.HelpSearchResultSelectedBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.HelpSearchResultSelectedText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchText">#FF000000</Color> + <Color x:Key="VsColor.Highlight">#FF0078D7</Color> + <Color x:Key="VsColor.HighlightText">#FFFFFFFF</Color> + <Color x:Key="VsColor.InactiveBorder">#FFF4F7FC</Color> + <Color x:Key="VsColor.InactiveCaption">#FFBFCDDB</Color> + <Color x:Key="VsColor.InactiveCaptionText">#FF000000</Color> + <Color x:Key="VsColor.InfoBackground">#FFFFFFE1</Color> + <Color x:Key="VsColor.InfoText">#FF000000</Color> + <Color x:Key="VsColor.Light">#00F6F6F6</Color> + <Color x:Key="VsColor.LightCaption">#001E1E1E</Color> + <Color x:Key="VsColor.MdiClientBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.Medium">#00EFEFF2</Color> + <Color x:Key="VsColor.Menu">#FFF0F0F0</Color> + <Color x:Key="VsColor.MenuText">#FF000000</Color> + <Color x:Key="VsColor.NewProjectBackground">#FFBCC7D8</Color> + <Color x:Key="VsColor.NewProjectItemInactiveBegin">#FFEEEDED</Color> + <Color x:Key="VsColor.NewProjectItemInactiveBorder">#FFCFCFCF</Color> + <Color x:Key="VsColor.NewProjectItemInactiveEnd">#FFDDDDDD</Color> + <Color x:Key="VsColor.NewProjectItemSelected">#FFFFE8A6</Color> + <Color x:Key="VsColor.NewProjectItemSelectedBorder">#FFE5C365</Color> + <Color x:Key="VsColor.NewProjectProviderHoverBegin">#FFFFFCF4</Color> + <Color x:Key="VsColor.NewProjectProviderHoverEnd">#FFFFECB5</Color> + <Color x:Key="VsColor.NewProjectProviderHoverForeground">#FF000000</Color> + <Color x:Key="VsColor.NewProjectProviderHoverMiddle1">#FFFFF3CD</Color> + <Color x:Key="VsColor.NewProjectProviderHoverMiddle2">#FFFFECB5</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveBegin">#FF4D6082</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveEnd">#FF3D5277</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveForeground">#FFFFFFFF</Color> + <Color x:Key="VsColor.PageContentExpanderChevron">#FF4A6184</Color> + <Color x:Key="VsColor.PageContentExpanderSeparator">#FFA8B3C2</Color> + <Color x:Key="VsColor.PageSideBarExpanderBody">#FFF2F4F8</Color> + <Color x:Key="VsColor.PageSideBarExpanderChevron">#FF4A6184</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeader">#FFDEE1E7</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeaderHover">#FFFFF3CD</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeaderPressed">#FFFFECB5</Color> + <Color x:Key="VsColor.PageSideBarExpanderSeparator">#FFDEE1E7</Color> + <Color x:Key="VsColor.PageSideBarExpanderText">#FF000000</Color> + <Color x:Key="VsColor.PanelBorder">#FF8591A2</Color> + <Color x:Key="VsColor.PanelGradientDark">#FFDEE1E7</Color> + <Color x:Key="VsColor.PanelGradientLight">#FFFFFFFF</Color> + <Color x:Key="VsColor.PanelHoverOverCloseBorder">#FFE5C365</Color> + <Color x:Key="VsColor.PanelHoverOverCloseFill">#FFFFFCF4</Color> + <Color x:Key="VsColor.PanelHyperlink">#FF0066CC</Color> + <Color x:Key="VsColor.PanelHyperlinkHover">#FF3399FF</Color> + <Color x:Key="VsColor.PanelHyperlinkPressed">#FF3399FF</Color> + <Color x:Key="VsColor.PanelSeparator">#FFA8B3C2</Color> + <Color x:Key="VsColor.PanelSubGroupSeparator">#FFA8B3C2</Color> + <Color x:Key="VsColor.PanelText">#FF1B293E</Color> + <Color x:Key="VsColor.PanelTitleBar">#FFCDD4DF</Color> + <Color x:Key="VsColor.PanelTitleBarText">#FF1B293E</Color> + <Color x:Key="VsColor.PanelTitleBarUnselected">#FFBCC7D8</Color> + <Color x:Key="VsColor.ProjectDesignerBackgroundGradientBegin">#FFBCC7D8</Color> + <Color x:Key="VsColor.ProjectDesignerBackgroundGradientEnd">#FFBCC7D8</Color> + <Color x:Key="VsColor.ProjectDesignerBorderInside">#FFA8B3C2</Color> + <Color x:Key="VsColor.ProjectDesignerBorderOutside">#FFA8B3C2</Color> + <Color x:Key="VsColor.ProjectDesignerContentsBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.ProjectDesignerTabBackgroundGradientBegin">#FFF0F0F0</Color> + <Color x:Key="VsColor.ProjectDesignerTabBackgroundGradientEnd">#FFDEE1E7</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedHighlight1">#FFC0A776</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedHighlight2">#FFFFE8A6</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedInsideBorder">#FFF0F0F0</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepBottomGradientBegin">#FFDEE1E7</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepBottomGradientEnd">#FFDEE1E7</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepTopGradientBegin">#FFF0F0F0</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepTopGradientEnd">#FFDEE1E7</Color> + <Color x:Key="VsColor.ScreenTipBackground">#FFFFFFE1</Color> + <Color x:Key="VsColor.ScreenTipBorder">#FF000000</Color> + <Color x:Key="VsColor.ScreenTipText">#FF000000</Color> + <Color x:Key="VsColor.ScrollBar">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarArrowBackground">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarArrowDisabledBackground">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarArrowMouseOverBackground">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarArrowPressedBackground">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarBackground">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarDisabledBackground">#FFE8E8EC</Color> + <Color x:Key="VsColor.ScrollBarThumbBackground">#FFC2C3C9</Color> + <Color x:Key="VsColor.ScrollBarThumbBorder">#FFC2C3C9</Color> + <Color x:Key="VsColor.ScrollBarThumbGlyph">#FFC2C3C9</Color> + <Color x:Key="VsColor.ScrollBarThumbMouseOverBackground">#FF686868</Color> + <Color x:Key="VsColor.ScrollBarThumbPressedBackground">#FF5B5B5B</Color> + <Color x:Key="VsColor.SearchBoxBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxBorder">#FF8591A2</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundBegin">#FFFDF4BF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundEnd">#FFFDF4BF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundMiddle1">#FFFDF4BF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundMiddle2">#FFFDF4BF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SearchBoxPressedBackground">#FFFFF29D</Color> + <Color x:Key="VsColor.SearchBoxPressedBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SideBarBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.SideBarGradientDark">#FFDEE1E7</Color> + <Color x:Key="VsColor.SideBarGradientLight">#FFDEE1E7</Color> + <Color x:Key="VsColor.SideBarText">#FF1B293E</Color> + <Color x:Key="VsColor.SmartTagBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SmartTagFill">#FFFFF0D0</Color> + <Color x:Key="VsColor.SmartTagHoverBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SmartTagHoverFill">#FFFFECB5</Color> + <Color x:Key="VsColor.SmartTagHoverText">#FF000000</Color> + <Color x:Key="VsColor.SmartTagText">#FF000000</Color> + <Color x:Key="VsColor.Snaplines">#FF4169E1</Color> + <Color x:Key="VsColor.SnaplinesPadding">#FF96A9DD</Color> + <Color x:Key="VsColor.SnaplinesTextBaseline">#FFE122DF</Color> + <Color x:Key="VsColor.SortBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.SortText">#FF000000</Color> + <Color x:Key="VsColor.SplashScreenBorder">#FF8591A2</Color> + <Color x:Key="VsColor.StartPageBackgroundGradientBegin">#FF162030</Color> + <Color x:Key="VsColor.StartPageBackgroundGradientEnd">#FF162030</Color> + <Color x:Key="VsColor.StartPageButtonBorder">#FF999999</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundBegin">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundEnd">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundMiddle1">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundMiddle2">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonPinDown">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinHover">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinned">#FFF30506</Color> + <Color x:Key="VsColor.StartPageButtonText">#FF0097FB</Color> + <Color x:Key="VsColor.StartPageButtonTextHover">#FF55AAFF</Color> + <Color x:Key="VsColor.StartPageButtonUnpinned">#FFF30506</Color> + <Color x:Key="VsColor.StartPageSelectedItemBackground">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageSelectedItemStroke">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageSeparator">#FF363639</Color> + <Color x:Key="VsColor.StartPageTabBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTabBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTabMouseOverBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTabMouseOverBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTextBody">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageTextBodySelected">#FFF30506</Color> + <Color x:Key="VsColor.StartPageTextBodyUnselected">#FF555555</Color> + <Color x:Key="VsColor.StartPageTextControlLinkSelected">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageTextControlLinkSelectedHover">#FF77AAFF</Color> + <Color x:Key="VsColor.StartPageTextDate">#FF1E1E1E</Color> + <Color x:Key="VsColor.StartPageTextHeading">#FF999999</Color> + <Color x:Key="VsColor.StartPageTextHeadingMouseOver">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageTextHeadingSelected">#FF555555</Color> + <Color x:Key="VsColor.StartPageTextSubHeading">#FF999999</Color> + <Color x:Key="VsColor.StartPageTextSubHeadingMouseOver">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageTextSubHeadingSelected">#FF000000</Color> + <Color x:Key="VsColor.StartPageUnselectedItemBackgroundBegin">#FF3F3F3F</Color> + <Color x:Key="VsColor.StartPageUnselectedItemBackgroundEnd">#FF464646</Color> + <Color x:Key="VsColor.StartPageUnselectedItemStroke">#FF999999</Color> + <Color x:Key="VsColor.StatusBarText">#FFFFFFFF</Color> + <Color x:Key="VsColor.TaskListGridLines">#FFF0F0F0</Color> + <Color x:Key="VsColor.ThreeDDarkShadow">#FF696969</Color> + <Color x:Key="VsColor.ThreeDFace">#FFF0F0F0</Color> + <Color x:Key="VsColor.ThreeDHighlight">#FFFFFFFF</Color> + <Color x:Key="VsColor.ThreeDLightShadow">#FFE3E3E3</Color> + <Color x:Key="VsColor.ThreeDShadow">#FFA0A0A0</Color> + <Color x:Key="VsColor.TitleBarActive">#FFFFF29D</Color> + <Color x:Key="VsColor.TitleBarActiveGradientBegin">#FFFFF29D</Color> + <Color x:Key="VsColor.TitleBarActiveGradientEnd">#FFFFF29D</Color> + <Color x:Key="VsColor.TitleBarActiveGradientMiddle1">#FFFFF29D</Color> + <Color x:Key="VsColor.TitleBarActiveGradientMiddle2">#FFFFF29D</Color> + <Color x:Key="VsColor.TitleBarActiveText">#FF000000</Color> + <Color x:Key="VsColor.TitleBarInactive">#FF4D6082</Color> + <Color x:Key="VsColor.TitleBarInactiveGradientBegin">#FF4D6082</Color> + <Color x:Key="VsColor.TitleBarInactiveGradientEnd">#FF4D6082</Color> + <Color x:Key="VsColor.TitleBarInactiveText">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolboxBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolboxDivider">#FF8591A2</Color> + <Color x:Key="VsColor.ToolboxGradientDark">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolboxGradientLight">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolboxHeadingAccent">#FFCED4DF</Color> + <Color x:Key="VsColor.ToolboxHeadingBegin">#FFF0F0F0</Color> + <Color x:Key="VsColor.ToolboxHeadingEnd">#FFF0F0F0</Color> + <Color x:Key="VsColor.ToolboxIconHighlight">#FFF7F7FF</Color> + <Color x:Key="VsColor.ToolboxIconShadow">#FFA0A0A0</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingBegin">#FFFFFBF0</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingEnd">#FFFFF2CB</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingMiddle1">#FFFFF7DA</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingMiddle2">#FFFFF2CB</Color> + <Color x:Key="VsColor.ToolWindowBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowBorder">#FF8E9BBC</Color> + <Color x:Key="VsColor.ToolWindowButtonActiveGlyph">#FF75633D</Color> + <Color x:Key="VsColor.ToolWindowButtonDown">#FFFFE8A6</Color> + <Color x:Key="VsColor.ToolWindowButtonDownActiveGlyph">#FF000000</Color> + <Color x:Key="VsColor.ToolWindowButtonDownBorder">#FFE5C365</Color> + <Color x:Key="VsColor.ToolWindowButtonDownInactiveGlyph">#FF000000</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActive">#FFFFFCF4</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActiveBorder">#FFE5C365</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActiveGlyph">#FF000000</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactive">#FFFFFCF4</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactiveBorder">#FFE5C365</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactiveGlyph">#FF000000</Color> + <Color x:Key="VsColor.ToolWindowButtonInactive">#FF2F405E</Color> + <Color x:Key="VsColor.ToolWindowButtonInactiveBorder">#FF707E96</Color> + <Color x:Key="VsColor.ToolWindowButtonInactiveGlyph">#FFCED4DD</Color> + <Color x:Key="VsColor.ToolWindowContentTabGradientBegin">#FFFBFCFD</Color> + <Color x:Key="VsColor.ToolWindowContentTabGradientEnd">#FFFBFCFD</Color> + <Color x:Key="VsColor.ToolWindowFloatingFrame">#FF293955</Color> + <Color x:Key="VsColor.ToolWindowTabBorder">#FF4B5C74</Color> + <Color x:Key="VsColor.ToolWindowTabGradientBegin">#FF4D6082</Color> + <Color x:Key="VsColor.ToolWindowTabGradientEnd">#FF4D6082</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBackgroundBegin">#FF5B7199</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBackgroundEnd">#FF5B7199</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBorder">#FF5B7199</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverText">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowTabSelectedTab">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowTabSelectedText">#FF000000</Color> + <Color x:Key="VsColor.ToolWindowTabText">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowText">#FF000000</Color> + <Color x:Key="VsColor.VizSurfaceBrownDark">#FF705829</Color> + <Color x:Key="VsColor.VizSurfaceBrownLight">#FFB0A781</Color> + <Color x:Key="VsColor.VizSurfaceBrownMedium">#FFA19667</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldDark">#FFA79432</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldLight">#FFD0D4B7</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldMedium">#FFBFC749</Color> + <Color x:Key="VsColor.VizSurfaceGoldDark">#FFCAB22D</Color> + <Color x:Key="VsColor.VizSurfaceGoldLight">#FFFBF7C8</Color> + <Color x:Key="VsColor.VizSurfaceGoldMedium">#FFE2E442</Color> + <Color x:Key="VsColor.VizSurfaceGreenDark">#FF5D8039</Color> + <Color x:Key="VsColor.VizSurfaceGreenLight">#FFB1C97B</Color> + <Color x:Key="VsColor.VizSurfaceGreenMedium">#FF9FB861</Color> + <Color x:Key="VsColor.VizSurfacePlumDark">#FF8E5478</Color> + <Color x:Key="VsColor.VizSurfacePlumLight">#FFE2B1CD</Color> + <Color x:Key="VsColor.VizSurfacePlumMedium">#FFCB98B6</Color> + <Color x:Key="VsColor.VizSurfaceRedDark">#FFAD1C2B</Color> + <Color x:Key="VsColor.VizSurfaceRedLight">#FFFF9F99</Color> + <Color x:Key="VsColor.VizSurfaceRedMedium">#FFFF7971</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueDark">#FF779AB6</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueLight">#FFC6D4DF</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueMedium">#FFB8CCD7</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueDark">#FF427094</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueLight">#FFA0B7C9</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueMedium">#FF89ABBD</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueDark">#FF5386BF</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueLight">#FFB9D4EE</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueMedium">#FFA1C7E7</Color> + <Color x:Key="VsColor.Window">#FFFFFFFF</Color> + <Color x:Key="VsColor.WindowFrame">#FF646464</Color> + <Color x:Key="VsColor.WindowText">#FF000000</Color> + <Color x:Key="VsColor.WizardOrientationPanelBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.WizardOrientationPanelText">#FF000000</Color> +</ResourceDictionary> diff --git a/src/GitHub.VisualStudio.UI/Styles/VsColorsDark.xaml b/src/GitHub.VisualStudio.UI/Styles/VsColorsDark.xaml new file mode 100644 index 0000000000..6f9bb15af6 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsColorsDark.xaml @@ -0,0 +1,509 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <Color x:Key="VsColor.AccentBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.AccentDark">#FF3F3F46</Color> + <Color x:Key="VsColor.AccentLight">#FF252526</Color> + <Color x:Key="VsColor.AccentMedium">#FF2D2D30</Color> + <Color x:Key="VsColor.AccentPale">#FF2D2D30</Color> + <Color x:Key="VsColor.ActiveBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.ActiveCaption">#FF252526</Color> + <Color x:Key="VsColor.AppWorkspace">#FF2D2D30</Color> + <Color x:Key="VsColor.AutoHideResizeGrip">#FF2D2D30</Color> + <Color x:Key="VsColor.AutoHideTabBackgroundBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.AutoHideTabBackgroundEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.AutoHideTabBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBackgroundBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBackgroundEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBorder">#FF007ACC</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverText">#FF0097FB</Color> + <Color x:Key="VsColor.AutoHideTabText">#FFD0D0D0</Color> + <Color x:Key="VsColor.Background">#FF000000</Color> + <Color x:Key="VsColor.BrandedUIBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.BrandedUIBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.BrandedUIFill">#FF252526</Color> + <Color x:Key="VsColor.BrandedUIText">#FFF1F1F1</Color> + <Color x:Key="VsColor.BrandedUITitle">#FFF1F1F1</Color> + <Color x:Key="VsColor.ButtonFace">#FF3F3F46</Color> + <Color x:Key="VsColor.ButtonHighlight">#FF464646</Color> + <Color x:Key="VsColor.ButtonShadow">#FF3F3F46</Color> + <Color x:Key="VsColor.ButtonText">#FFF1F1F1</Color> + <Color x:Key="VsColor.CaptionText">#FFF1F1F1</Color> + <Color x:Key="VsColor.ClassDesignerClassCompartment">#FFF0F2F9</Color> + <Color x:Key="VsColor.ClassDesignerClassHeaderBackground">#FFD3DCEF</Color> + <Color x:Key="VsColor.ClassDesignerCommentBorder">#FFCCCC66</Color> + <Color x:Key="VsColor.ClassDesignerCommentShapeBackground">#FFFFFFCC</Color> + <Color x:Key="VsColor.ClassDesignerCommentText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerCompartmentSeparator">#FFD2D2D2</Color> + <Color x:Key="VsColor.ClassDesignerConnectionRouteBorder">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerDefaultConnection">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeBorder">#FF00008B</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeSubtitle">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeTitle">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeTitleBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerDelegateCompartment">#FFF7F0F0</Color> + <Color x:Key="VsColor.ClassDesignerDelegateHeader">#FFEDDADC</Color> + <Color x:Key="VsColor.ClassDesignerDiagramBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerEmphasisBorder">#FF0054E3</Color> + <Color x:Key="VsColor.ClassDesignerEnumHeader">#FFDDD6EF</Color> + <Color x:Key="VsColor.ClassDesignerFieldAssociation">#FF266035</Color> + <Color x:Key="VsColor.ClassDesignerGradientEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerInheritance">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerInterfaceCompartment">#FFF3F7F0</Color> + <Color x:Key="VsColor.ClassDesignerInterfaceHeader">#FFE6F0DB</Color> + <Color x:Key="VsColor.ClassDesignerLasso">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerLollipop">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerPropertyAssociation">#FFB0764F</Color> + <Color x:Key="VsColor.ClassDesignerReferencedAssemblyBorder">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerResizingShapeBorder">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerShapeBorder">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerShapeShadow">#FFD8D8D8</Color> + <Color x:Key="VsColor.ClassDesignerTempConnection">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerTypedef">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerTypedefHeader">#FFD6ECEF</Color> + <Color x:Key="VsColor.ClassDesignerUnresolvedText">#FFFF0000</Color> + <Color x:Key="VsColor.ClassDesignerVBModuleCompartment">#FFF8F4E9</Color> + <Color x:Key="VsColor.ClassDesignerVBModuleHeader">#FFF0E9D2</Color> + <Color x:Key="VsColor.ComboBoxBackground">#FF333337</Color> + <Color x:Key="VsColor.ComboBoxBorder">#FF434346</Color> + <Color x:Key="VsColor.ComboBoxDisabledBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.ComboBoxDisabledBorder">#FF434346</Color> + <Color x:Key="VsColor.ComboBoxDisabledGlyph">#FF434346</Color> + <Color x:Key="VsColor.ComboBoxGlyph">#FFF1F1F1</Color> + <Color x:Key="VsColor.ComboBoxMouseDownBackground">#FF3F3F46</Color> + <Color x:Key="VsColor.ComboBoxMouseDownBorder">#FF007ACC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundBegin">#FF3F3F46</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundEnd">#FF3F3F46</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundMiddle1">#FF3F3F46</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundMiddle2">#FF3F3F46</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBorder">#FF007ACC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverGlyph">#FF007ACC</Color> + <Color x:Key="VsColor.ComboBoxPopupBackgroundBegin">#FF1B1B1C</Color> + <Color x:Key="VsColor.ComboBoxPopupBackgroundEnd">#FF1B1B1C</Color> + <Color x:Key="VsColor.ComboBoxPopupBorder">#FF333337</Color> + <Color x:Key="VsColor.CommandBarBorder">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarCheckBox">#FF999999</Color> + <Color x:Key="VsColor.CommandBarDragHandle">#FF46464A</Color> + <Color x:Key="VsColor.CommandBarDragHandleShadow">#FF46464A</Color> + <Color x:Key="VsColor.CommandBarGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarGradientMiddle">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarHover">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelected">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelectedIcon">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelectedIconBorder">#FF3399FF</Color> + <Color x:Key="VsColor.CommandBarMenuBackgroundGradientBegin">#FF1B1B1C</Color> + <Color x:Key="VsColor.CommandBarMenuBackgroundGradientEnd">#FF1B1B1C</Color> + <Color x:Key="VsColor.CommandBarMenuBorder">#FF333337</Color> + <Color x:Key="VsColor.CommandBarMenuIconBackground">#FF1B1B1C</Color> + <Color x:Key="VsColor.CommandBarMenuMouseOverSubmenuGlyph">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMenuSeparator">#FF333337</Color> + <Color x:Key="VsColor.CommandBarMenuSubmenuGlyph">#FF999999</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundBegin">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundEnd">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundMiddle">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseDownBorder">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundBegin">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundEnd">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundMiddle1">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundMiddle2">#FF3E3E40</Color> + <Color x:Key="VsColor.CommandBarOptionsBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarOptionsGlyph">#FF999999</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundBegin">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundEnd">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundMiddle">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundBegin">#72555555</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundEnd">#72555555</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundMiddle1">#72555555</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundMiddle2">#72555555</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverGlyph">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarSelected">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarSelectedBorder">#FF3399FF</Color> + <Color x:Key="VsColor.CommandBarShadow">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarTextActive">#FFF1F1F1</Color> + <Color x:Key="VsColor.CommandBarTextHover">#FFF1F1F1</Color> + <Color x:Key="VsColor.CommandBarTextInactive">#FF656565</Color> + <Color x:Key="VsColor.CommandBarTextSelected">#FFF1F1F1</Color> + <Color x:Key="VsColor.CommandBarToolBarBorder">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandBarToolBarSeparator">#FF222222</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientMiddle">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientMiddle">#FF2D2D30</Color> + <Color x:Key="VsColor.ControlEditHintText">#FF999999</Color> + <Color x:Key="VsColor.ControlEditRequiredBackground">#FFFEFCC8</Color> + <Color x:Key="VsColor.ControlEditRequiredHintText">#FF555555</Color> + <Color x:Key="VsColor.ControlLinkText">#FF0097FB</Color> + <Color x:Key="VsColor.ControlLinkTextHover">#FF0097FB</Color> + <Color x:Key="VsColor.ControlLinkTextPressed">#FF0097FB</Color> + <Color x:Key="VsColor.ControlOutline">#FF333337</Color> + <Color x:Key="VsColor.Dark">#003F3F46</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveBackground">#FF424245</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveBorder">#FF4D4D50</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveHighlight">#FF505051</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveHighlightText">#FFF1F1F1</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveSeparator">#FF333337</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveText">#FFF1F1F1</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveBackground">#FF2C2C2F</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveBorder">#FF37373A</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveHighlight">#FF3D3D3F</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveHighlightText">#FF656565</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveSeparator">#FF333337</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveText">#FF656565</Color> + <Color x:Key="VsColor.DesignerBackground">#FF252526</Color> + <Color x:Key="VsColor.DesignerSelectionDots">#FF46464A</Color> + <Color x:Key="VsColor.DesignerTray">#FF3F3F46</Color> + <Color x:Key="VsColor.DesignerWatermark">#FF656565</Color> + <Color x:Key="VsColor.DiagReportBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageHeader">#FFF2F4F8</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageSubtitle">#FF000000</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageTitle">#FF4A6184</Color> + <Color x:Key="VsColor.DiagReportSummaryPageHeader">#FF4A6184</Color> + <Color x:Key="VsColor.DiagReportSummaryPageSubtitle">#FFBCC7D8</Color> + <Color x:Key="VsColor.DiagReportSummaryPageTitle">#FFFFFFFF</Color> + <Color x:Key="VsColor.DiagReportText">#FF000000</Color> + <Color x:Key="VsColor.DockTargetBackground">#FF1B1B1C</Color> + <Color x:Key="VsColor.DockTargetBorder">#FF333337</Color> + <Color x:Key="VsColor.DockTargetButtonBackgroundBegin">#FF252526</Color> + <Color x:Key="VsColor.DockTargetButtonBackgroundEnd">#FF252526</Color> + <Color x:Key="VsColor.DockTargetButtonBorder">#FF252526</Color> + <Color x:Key="VsColor.DockTargetGlyphArrow">#FFF1F1F1</Color> + <Color x:Key="VsColor.DockTargetGlyphBackgroundBegin">#FF252526</Color> + <Color x:Key="VsColor.DockTargetGlyphBackgroundEnd">#FF252526</Color> + <Color x:Key="VsColor.DockTargetGlyphBorder">#FF007ACC</Color> + <Color x:Key="VsColor.DropDownBackground">#FF333337</Color> + <Color x:Key="VsColor.DropDownBorder">#FF434346</Color> + <Color x:Key="VsColor.DropDownDisabledBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.DropDownDisabledBorder">#FF434346</Color> + <Color x:Key="VsColor.DropDownDisabledGlyph">#FF434346</Color> + <Color x:Key="VsColor.DropDownGlyph">#FF999999</Color> + <Color x:Key="VsColor.DropDownMouseDownBackground">#FF3F3F46</Color> + <Color x:Key="VsColor.DropDownMouseDownBorder">#FF434346</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundBegin">#FF3F3F46</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundEnd">#FF3F3F46</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundMiddle1">#FF3F3F46</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundMiddle2">#FF3F3F46</Color> + <Color x:Key="VsColor.DropDownMouseOverBorder">#FF434346</Color> + <Color x:Key="VsColor.DropDownMouseOverGlyph">#FF007ACC</Color> + <Color x:Key="VsColor.DropDownPopupBackgroundBegin">#FF1B1B1C</Color> + <Color x:Key="VsColor.DropDownPopupBackgroundEnd">#FF1B1B1C</Color> + <Color x:Key="VsColor.DropDownPopupBorder">#FF333337</Color> + <Color x:Key="VsColor.DropShadowBackground">#72000000</Color> + <Color x:Key="VsColor.EditorExpansionBorder">#FF333337</Color> + <Color x:Key="VsColor.EditorExpansionFill">#FF252526</Color> + <Color x:Key="VsColor.EditorExpansionLink">#FF0097FB</Color> + <Color x:Key="VsColor.EditorExpansionText">#FFF1F1F1</Color> + <Color x:Key="VsColor.EnvironmentBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientMiddle1">#FF2D2D30</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientMiddle2">#FF2D2D30</Color> + <Color x:Key="VsColor.EnvironmentBackgroundTexture1">#FF2D2D30</Color> + <Color x:Key="VsColor.EnvironmentBackgroundTexture2">#FF2D2D30</Color> + <Color x:Key="VsColor.ExtensionManagerStarHighlight1">#FFFF8C00</Color> + <Color x:Key="VsColor.ExtensionManagerStarHighlight2">#FFFF8C00</Color> + <Color x:Key="VsColor.ExtensionManagerStarInactive1">#FF656565</Color> + <Color x:Key="VsColor.ExtensionManagerStarInactive2">#FF656565</Color> + <Color x:Key="VsColor.FileTabBorder">#FF2D2D30</Color> + <Color x:Key="VsColor.FileTabChannelBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.FileTabDocumentBorderBackground">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabDocumentBorderHighlight">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabDocumentBorderShadow">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabGradientDark">#FF2D2D30</Color> + <Color x:Key="VsColor.FileTabGradientLight">#FF2D2D30</Color> + <Color x:Key="VsColor.FileTabHotBorder">#FF1C97EA</Color> + <Color x:Key="VsColor.FileTabHotGlyph">#FFD0E6F5</Color> + <Color x:Key="VsColor.FileTabHotGradientBottom">#FF1C97EA</Color> + <Color x:Key="VsColor.FileTabHotGradientTop">#FF1C97EA</Color> + <Color x:Key="VsColor.FileTabHotText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabInactiveDocumentBorderBackground">#FF3F3F46</Color> + <Color x:Key="VsColor.FileTabInactiveDocumentBorderEdge">#FF3F3F46</Color> + <Color x:Key="VsColor.FileTabInactiveGradientBottom">#FF3F3F46</Color> + <Color x:Key="VsColor.FileTabInactiveGradientTop">#FF3F3F46</Color> + <Color x:Key="VsColor.FileTabInactiveText">#FFF1F1F1</Color> + <Color x:Key="VsColor.FileTabLastActiveDocumentBorderBackground">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabLastActiveDocumentBorderEdge">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabLastActiveGlyph">#FFD0E6F5</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientBottom">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientMiddle1">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientMiddle2">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientTop">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabSelectedBackground">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedBorder">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientBottom">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientMiddle1">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientMiddle2">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientTop">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabText">#FFF1F1F1</Color> + <Color x:Key="VsColor.FormSmartTagActionTagBorder">#FF000000</Color> + <Color x:Key="VsColor.FormSmartTagActionTagFill">#FFFFFFFF</Color> + <Color x:Key="VsColor.FormSmartTagObjectTagBorder">#FF000000</Color> + <Color x:Key="VsColor.FormSmartTagObjectTagFill">#FFFFFFFF</Color> + <Color x:Key="VsColor.GrayText">#FF999999</Color> + <Color x:Key="VsColor.GridHeadingBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.GridHeadingText">#FFF1F1F1</Color> + <Color x:Key="VsColor.GridLine">#FF000000</Color> + <Color x:Key="VsColor.HelpHowDoIPaneBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpHowDoIPaneLink">#FF0066CC</Color> + <Color x:Key="VsColor.HelpHowDoIPaneText">#FF000000</Color> + <Color x:Key="VsColor.HelpHowDoITaskBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpHowDoITaskLink">#FF0066CC</Color> + <Color x:Key="VsColor.HelpHowDoITaskText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchFilterBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchFilterBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchFilterText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchFrameBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpSearchFrameText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchPanelRules">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchProviderIcon">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchProviderSelectedBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchProviderSelectedText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchProviderUnselectedBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpSearchProviderUnselectedText">#FF1B293E</Color> + <Color x:Key="VsColor.HelpSearchResultLinkSelected">#FF0066CC</Color> + <Color x:Key="VsColor.HelpSearchResultLinkUnselected">#FF0066CC</Color> + <Color x:Key="VsColor.HelpSearchResultSelectedBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.HelpSearchResultSelectedText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchText">#FF000000</Color> + <Color x:Key="VsColor.Highlight">#FF3399FF</Color> + <Color x:Key="VsColor.HighlightText">#FFFFFFFF</Color> + <Color x:Key="VsColor.InactiveBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.InactiveCaption">#FF2D2D30</Color> + <Color x:Key="VsColor.InactiveCaptionText">#FF656565</Color> + <Color x:Key="VsColor.InfoBackground">#FFFEFCC8</Color> + <Color x:Key="VsColor.InfoText">#FF1E1E1E</Color> + <Color x:Key="VsColor.Light">#001E1E1E</Color> + <Color x:Key="VsColor.LightCaption">#00F1F1F1</Color> + <Color x:Key="VsColor.MdiClientBorder">#FF333337</Color> + <Color x:Key="VsColor.Medium">#002D2D30</Color> + <Color x:Key="VsColor.Menu">#FF1B1B1C</Color> + <Color x:Key="VsColor.MenuText">#FFF1F1F1</Color> + <Color x:Key="VsColor.NewProjectBackground">#FF252526</Color> + <Color x:Key="VsColor.NewProjectItemInactiveBegin">#FF3F3F46</Color> + <Color x:Key="VsColor.NewProjectItemInactiveBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.NewProjectItemInactiveEnd">#FF3F3F46</Color> + <Color x:Key="VsColor.NewProjectItemSelected">#FF3399FF</Color> + <Color x:Key="VsColor.NewProjectItemSelectedBorder">#FF3399FF</Color> + <Color x:Key="VsColor.NewProjectProviderHoverBegin">#FF3E3E40</Color> + <Color x:Key="VsColor.NewProjectProviderHoverEnd">#FF3E3E40</Color> + <Color x:Key="VsColor.NewProjectProviderHoverForeground">#FFF1F1F1</Color> + <Color x:Key="VsColor.NewProjectProviderHoverMiddle1">#FF3E3E40</Color> + <Color x:Key="VsColor.NewProjectProviderHoverMiddle2">#FF3E3E40</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveBegin">#FF3F3F46</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveEnd">#FF3F3F46</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveForeground">#FFF1F1F1</Color> + <Color x:Key="VsColor.PageContentExpanderChevron">#FFF1F1F1</Color> + <Color x:Key="VsColor.PageContentExpanderSeparator">#FF3F3F46</Color> + <Color x:Key="VsColor.PageSideBarExpanderBody">#FF252526</Color> + <Color x:Key="VsColor.PageSideBarExpanderChevron">#FFF1F1F1</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeader">#FF3E3E40</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeaderHover">#FF3F3F46</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeaderPressed">#FF3F3F46</Color> + <Color x:Key="VsColor.PageSideBarExpanderSeparator">#FF3F3F46</Color> + <Color x:Key="VsColor.PageSideBarExpanderText">#FFF1F1F1</Color> + <Color x:Key="VsColor.PanelBorder">#FF333337</Color> + <Color x:Key="VsColor.PanelGradientDark">#FF252526</Color> + <Color x:Key="VsColor.PanelGradientLight">#FF252526</Color> + <Color x:Key="VsColor.PanelHoverOverCloseBorder">#FF3E3E40</Color> + <Color x:Key="VsColor.PanelHoverOverCloseFill">#FF3E3E40</Color> + <Color x:Key="VsColor.PanelHyperlink">#FF0097FB</Color> + <Color x:Key="VsColor.PanelHyperlinkHover">#FF55AAFF</Color> + <Color x:Key="VsColor.PanelHyperlinkPressed">#FF0097FB</Color> + <Color x:Key="VsColor.PanelSeparator">#FF2D2D30</Color> + <Color x:Key="VsColor.PanelSubGroupSeparator">#FF2D2D30</Color> + <Color x:Key="VsColor.PanelText">#FFF1F1F1</Color> + <Color x:Key="VsColor.PanelTitleBar">#FF252526</Color> + <Color x:Key="VsColor.PanelTitleBarText">#FFF1F1F1</Color> + <Color x:Key="VsColor.PanelTitleBarUnselected">#FF252526</Color> + <Color x:Key="VsColor.ProjectDesignerBackgroundGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerBackgroundGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerBorderInside">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerBorderOutside">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerContentsBackground">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerTabBackgroundGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerTabBackgroundGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedBackground">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedBorder">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedHighlight1">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedHighlight2">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedInsideBorder">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepBottomGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepBottomGradientEnd">#FF252526</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepTopGradientBegin">#FF252526</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepTopGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.ScreenTipBackground">#FFFEFCC8</Color> + <Color x:Key="VsColor.ScreenTipBorder">#FFFEFCC8</Color> + <Color x:Key="VsColor.ScreenTipText">#FF252526</Color> + <Color x:Key="VsColor.ScrollBar">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarArrowBackground">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarArrowDisabledBackground">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarArrowMouseOverBackground">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarArrowPressedBackground">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarBackground">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarDisabledBackground">#FF3E3E42</Color> + <Color x:Key="VsColor.ScrollBarThumbBackground">#FF686868</Color> + <Color x:Key="VsColor.ScrollBarThumbBorder">#FF686868</Color> + <Color x:Key="VsColor.ScrollBarThumbGlyph">#FF686868</Color> + <Color x:Key="VsColor.ScrollBarThumbMouseOverBackground">#FF9E9E9E</Color> + <Color x:Key="VsColor.ScrollBarThumbPressedBackground">#FFEFEBEF</Color> + <Color x:Key="VsColor.SearchBoxBackground">#FF333337</Color> + <Color x:Key="VsColor.SearchBoxBorder">#FF333337</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundBegin">#FF3F3F46</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundEnd">#FF3F3F46</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundMiddle1">#FF3F3F46</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundMiddle2">#FF3F3F46</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.SearchBoxPressedBackground">#FF3F3F46</Color> + <Color x:Key="VsColor.SearchBoxPressedBorder">#FF007ACC</Color> + <Color x:Key="VsColor.SideBarBackground">#FF252526</Color> + <Color x:Key="VsColor.SideBarGradientDark">#FF252526</Color> + <Color x:Key="VsColor.SideBarGradientLight">#FF252526</Color> + <Color x:Key="VsColor.SideBarText">#FFF1F1F1</Color> + <Color x:Key="VsColor.SmartTagBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SmartTagFill">#FFFFEFBB</Color> + <Color x:Key="VsColor.SmartTagHoverBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SmartTagHoverFill">#FFFEFCC8</Color> + <Color x:Key="VsColor.SmartTagHoverText">#FF000000</Color> + <Color x:Key="VsColor.SmartTagText">#FF000000</Color> + <Color x:Key="VsColor.Snaplines">#FF4169E1</Color> + <Color x:Key="VsColor.SnaplinesPadding">#FF96A9DD</Color> + <Color x:Key="VsColor.SnaplinesTextBaseline">#FFE122DF</Color> + <Color x:Key="VsColor.SortBackground">#FF252526</Color> + <Color x:Key="VsColor.SortText">#FFF1F1F1</Color> + <Color x:Key="VsColor.SplashScreenBorder">#FF434346</Color> + <Color x:Key="VsColor.StartPageBackgroundGradientBegin">#FF1F1F22</Color> + <Color x:Key="VsColor.StartPageBackgroundGradientEnd">#FF1F1F22</Color> + <Color x:Key="VsColor.StartPageButtonBorder">#FF999999</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundBegin">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundEnd">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundMiddle1">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundMiddle2">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinDown">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinHover">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinned">#FFF30506</Color> + <Color x:Key="VsColor.StartPageButtonText">#FF0097FB</Color> + <Color x:Key="VsColor.StartPageButtonTextHover">#FF55AAFF</Color> + <Color x:Key="VsColor.StartPageButtonUnpinned">#FFF30506</Color> + <Color x:Key="VsColor.StartPageSelectedItemBackground">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageSelectedItemStroke">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageSeparator">#FF363639</Color> + <Color x:Key="VsColor.StartPageTabBackgroundBegin">#FF252526</Color> + <Color x:Key="VsColor.StartPageTabBackgroundEnd">#FF252526</Color> + <Color x:Key="VsColor.StartPageTabMouseOverBackgroundBegin">#FF28282B</Color> + <Color x:Key="VsColor.StartPageTabMouseOverBackgroundEnd">#FF28282B</Color> + <Color x:Key="VsColor.StartPageTextBody">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageTextBodySelected">#FFF30506</Color> + <Color x:Key="VsColor.StartPageTextBodyUnselected">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageTextControlLinkSelected">#FF0097FB</Color> + <Color x:Key="VsColor.StartPageTextControlLinkSelectedHover">#FF88CCFE</Color> + <Color x:Key="VsColor.StartPageTextDate">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageTextHeading">#FF999999</Color> + <Color x:Key="VsColor.StartPageTextHeadingMouseOver">#FF55AAFF</Color> + <Color x:Key="VsColor.StartPageTextHeadingSelected">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageTextSubHeading">#FF999999</Color> + <Color x:Key="VsColor.StartPageTextSubHeadingMouseOver">#FF55AAFF</Color> + <Color x:Key="VsColor.StartPageTextSubHeadingSelected">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageUnselectedItemBackgroundBegin">#FF3F3F3F</Color> + <Color x:Key="VsColor.StartPageUnselectedItemBackgroundEnd">#FF464646</Color> + <Color x:Key="VsColor.StartPageUnselectedItemStroke">#FF999999</Color> + <Color x:Key="VsColor.StatusBarText">#FFFFFFFF</Color> + <Color x:Key="VsColor.TaskListGridLines">#FF000000</Color> + <Color x:Key="VsColor.ThreeDDarkShadow">#FF2D2D30</Color> + <Color x:Key="VsColor.ThreeDFace">#FF3F3F46</Color> + <Color x:Key="VsColor.ThreeDHighlight">#FF464646</Color> + <Color x:Key="VsColor.ThreeDLightShadow">#FF2D2D30</Color> + <Color x:Key="VsColor.ThreeDShadow">#FF3F3F46</Color> + <Color x:Key="VsColor.TitleBarActive">#FF2D2D30</Color> + <Color x:Key="VsColor.TitleBarActiveGradientBegin">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveGradientEnd">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveGradientMiddle1">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveGradientMiddle2">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveText">#FFFFFFFF</Color> + <Color x:Key="VsColor.TitleBarInactive">#FF2D2D30</Color> + <Color x:Key="VsColor.TitleBarInactiveGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.TitleBarInactiveGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.TitleBarInactiveText">#FFD0D0D0</Color> + <Color x:Key="VsColor.ToolboxBackground">#FF252526</Color> + <Color x:Key="VsColor.ToolboxDivider">#FF333337</Color> + <Color x:Key="VsColor.ToolboxGradientDark">#FF252526</Color> + <Color x:Key="VsColor.ToolboxGradientLight">#FF252526</Color> + <Color x:Key="VsColor.ToolboxHeadingAccent">#FF252526</Color> + <Color x:Key="VsColor.ToolboxHeadingBegin">#FF252526</Color> + <Color x:Key="VsColor.ToolboxHeadingEnd">#FF252526</Color> + <Color x:Key="VsColor.ToolboxIconHighlight">#FF252526</Color> + <Color x:Key="VsColor.ToolboxIconShadow">#FF252526</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingMiddle1">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingMiddle2">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowBackground">#FF252526</Color> + <Color x:Key="VsColor.ToolWindowBorder">#FF3F3F46</Color> + <Color x:Key="VsColor.ToolWindowButtonActiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonDown">#FF0E6198</Color> + <Color x:Key="VsColor.ToolWindowButtonDownActiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonDownBorder">#FF0E6198</Color> + <Color x:Key="VsColor.ToolWindowButtonDownInactiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActive">#FF52B0EF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActiveBorder">#FF52B0EF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactive">#FF393939</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactiveBorder">#FF393939</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactiveGlyph">#FFF1F1F1</Color> + <Color x:Key="VsColor.ToolWindowButtonInactive">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowButtonInactiveBorder">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowButtonInactiveGlyph">#FFF1F1F1</Color> + <Color x:Key="VsColor.ToolWindowContentTabGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowContentTabGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowFloatingFrame">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowTabBorder">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowTabGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowTabGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBackgroundBegin">#FF3E3E40</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBackgroundEnd">#FF3E3E40</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBorder">#FF3E3E40</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverText">#FF55AAFF</Color> + <Color x:Key="VsColor.ToolWindowTabSelectedTab">#FF252526</Color> + <Color x:Key="VsColor.ToolWindowTabSelectedText">#FF0097FB</Color> + <Color x:Key="VsColor.ToolWindowTabText">#FFD0D0D0</Color> + <Color x:Key="VsColor.ToolWindowText">#FFF1F1F1</Color> + <Color x:Key="VsColor.VizSurfaceBrownDark">#FF705829</Color> + <Color x:Key="VsColor.VizSurfaceBrownLight">#FFB0A781</Color> + <Color x:Key="VsColor.VizSurfaceBrownMedium">#FFA19667</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldDark">#FFA79432</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldLight">#FFD0D4B7</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldMedium">#FFBFC749</Color> + <Color x:Key="VsColor.VizSurfaceGoldDark">#FFCAB22D</Color> + <Color x:Key="VsColor.VizSurfaceGoldLight">#FFFBF7C8</Color> + <Color x:Key="VsColor.VizSurfaceGoldMedium">#FFE2E442</Color> + <Color x:Key="VsColor.VizSurfaceGreenDark">#FF5D8039</Color> + <Color x:Key="VsColor.VizSurfaceGreenLight">#FFB1C97B</Color> + <Color x:Key="VsColor.VizSurfaceGreenMedium">#FF9FB861</Color> + <Color x:Key="VsColor.VizSurfacePlumDark">#FF8E5478</Color> + <Color x:Key="VsColor.VizSurfacePlumLight">#FFE2B1CD</Color> + <Color x:Key="VsColor.VizSurfacePlumMedium">#FFCB98B6</Color> + <Color x:Key="VsColor.VizSurfaceRedDark">#FFAD1C2B</Color> + <Color x:Key="VsColor.VizSurfaceRedLight">#FFFF9F99</Color> + <Color x:Key="VsColor.VizSurfaceRedMedium">#FFFF7971</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueDark">#FF779AB6</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueLight">#FFC6D4DF</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueMedium">#FFB8CCD7</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueDark">#FF427094</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueLight">#FFA0B7C9</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueMedium">#FF89ABBD</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueDark">#FF5386BF</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueLight">#FFB9D4EE</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueMedium">#FFA1C7E7</Color> + <Color x:Key="VsColor.Window">#FF252526</Color> + <Color x:Key="VsColor.WindowFrame">#FF2D2D30</Color> + <Color x:Key="VsColor.WindowText">#FFF1F1F1</Color> + <Color x:Key="VsColor.WizardOrientationPanelBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.WizardOrientationPanelText">#FF000000</Color> +</ResourceDictionary> diff --git a/src/GitHub.VisualStudio.UI/Styles/VsColorsLight.xaml b/src/GitHub.VisualStudio.UI/Styles/VsColorsLight.xaml new file mode 100644 index 0000000000..cd4f4de68d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Styles/VsColorsLight.xaml @@ -0,0 +1,509 @@ +<ResourceDictionary + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + <Color x:Key="VsColor.AccentBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.AccentDark">#FFCCCEDB</Color> + <Color x:Key="VsColor.AccentLight">#FFF5F5F5</Color> + <Color x:Key="VsColor.AccentMedium">#FFEEEEF2</Color> + <Color x:Key="VsColor.AccentPale">#FFEEEEF2</Color> + <Color x:Key="VsColor.ActiveBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.ActiveCaption">#FFF5F5F5</Color> + <Color x:Key="VsColor.AppWorkspace">#FFEEEEF2</Color> + <Color x:Key="VsColor.AutoHideResizeGrip">#FFEEEEF2</Color> + <Color x:Key="VsColor.AutoHideTabBackgroundBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.AutoHideTabBackgroundEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.AutoHideTabBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBackgroundBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBackgroundEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverBorder">#FF007ACC</Color> + <Color x:Key="VsColor.AutoHideTabMouseOverText">#FF0E70C0</Color> + <Color x:Key="VsColor.AutoHideTabText">#FF444444</Color> + <Color x:Key="VsColor.Background">#FFFFFFFF</Color> + <Color x:Key="VsColor.BrandedUIBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.BrandedUIBorder">#FFCCCEBD</Color> + <Color x:Key="VsColor.BrandedUIFill">#FFF5F5F5</Color> + <Color x:Key="VsColor.BrandedUIText">#FF1E1E1E</Color> + <Color x:Key="VsColor.BrandedUITitle">#FF1E1E1E</Color> + <Color x:Key="VsColor.ButtonFace">#FFCCCEDB</Color> + <Color x:Key="VsColor.ButtonHighlight">#FFD8D8E0</Color> + <Color x:Key="VsColor.ButtonShadow">#FFCCCEBD</Color> + <Color x:Key="VsColor.ButtonText">#FF1E1E1E</Color> + <Color x:Key="VsColor.CaptionText">#FF1E1E1E</Color> + <Color x:Key="VsColor.ClassDesignerClassCompartment">#FFF0F2F9</Color> + <Color x:Key="VsColor.ClassDesignerClassHeaderBackground">#FFD3DCEF</Color> + <Color x:Key="VsColor.ClassDesignerCommentBorder">#FFCCCC66</Color> + <Color x:Key="VsColor.ClassDesignerCommentShapeBackground">#FFFFFFCC</Color> + <Color x:Key="VsColor.ClassDesignerCommentText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerCompartmentSeparator">#FFD2D2D2</Color> + <Color x:Key="VsColor.ClassDesignerConnectionRouteBorder">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerDefaultConnection">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeBorder">#FF00008B</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeSubtitle">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeText">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeTitle">#FF000000</Color> + <Color x:Key="VsColor.ClassDesignerDefaultShapeTitleBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerDelegateCompartment">#FFF7F0F0</Color> + <Color x:Key="VsColor.ClassDesignerDelegateHeader">#FFEDDADC</Color> + <Color x:Key="VsColor.ClassDesignerDiagramBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerEmphasisBorder">#FF0054E3</Color> + <Color x:Key="VsColor.ClassDesignerEnumHeader">#FFDDD6EF</Color> + <Color x:Key="VsColor.ClassDesignerFieldAssociation">#FF266035</Color> + <Color x:Key="VsColor.ClassDesignerGradientEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.ClassDesignerInheritance">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerInterfaceCompartment">#FFF3F7F0</Color> + <Color x:Key="VsColor.ClassDesignerInterfaceHeader">#FFE6F0DB</Color> + <Color x:Key="VsColor.ClassDesignerLasso">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerLollipop">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerPropertyAssociation">#FFB0764F</Color> + <Color x:Key="VsColor.ClassDesignerReferencedAssemblyBorder">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerResizingShapeBorder">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerShapeBorder">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerShapeShadow">#FFD8D8D8</Color> + <Color x:Key="VsColor.ClassDesignerTempConnection">#FF808080</Color> + <Color x:Key="VsColor.ClassDesignerTypedef">#FF716F64</Color> + <Color x:Key="VsColor.ClassDesignerTypedefHeader">#FFD6ECEF</Color> + <Color x:Key="VsColor.ClassDesignerUnresolvedText">#FFFF0000</Color> + <Color x:Key="VsColor.ClassDesignerVBModuleCompartment">#FFF8F4E9</Color> + <Color x:Key="VsColor.ClassDesignerVBModuleHeader">#FFF0E9D2</Color> + <Color x:Key="VsColor.ComboBoxBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ComboBoxBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.ComboBoxDisabledBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.ComboBoxDisabledBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.ComboBoxDisabledGlyph">#FFCCCEDB</Color> + <Color x:Key="VsColor.ComboBoxGlyph">#FF717171</Color> + <Color x:Key="VsColor.ComboBoxMouseDownBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.ComboBoxMouseDownBorder">#FF007ACC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundMiddle1">#FFFFFFFF</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBackgroundMiddle2">#FFFFFFFF</Color> + <Color x:Key="VsColor.ComboBoxMouseOverBorder">#FF007ACC</Color> + <Color x:Key="VsColor.ComboBoxMouseOverGlyph">#FF1E1E1E</Color> + <Color x:Key="VsColor.ComboBoxPopupBackgroundBegin">#FFF6F6F6</Color> + <Color x:Key="VsColor.ComboBoxPopupBackgroundEnd">#FFF6F6F6</Color> + <Color x:Key="VsColor.ComboBoxPopupBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.CommandBarBorder">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarCheckBox">#FF717171</Color> + <Color x:Key="VsColor.CommandBarDragHandle">#FF999999</Color> + <Color x:Key="VsColor.CommandBarDragHandleShadow">#FF999999</Color> + <Color x:Key="VsColor.CommandBarGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarGradientMiddle">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarHover">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelected">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelectedIcon">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarHoverOverSelectedIconBorder">#FF3399FF</Color> + <Color x:Key="VsColor.CommandBarMenuBackgroundGradientBegin">#FFF6F6F6</Color> + <Color x:Key="VsColor.CommandBarMenuBackgroundGradientEnd">#FFF6F6F6</Color> + <Color x:Key="VsColor.CommandBarMenuBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.CommandBarMenuIconBackground">#FFF6F6F6</Color> + <Color x:Key="VsColor.CommandBarMenuMouseOverSubmenuGlyph">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMenuSeparator">#FFE0E3E6</Color> + <Color x:Key="VsColor.CommandBarMenuSubmenuGlyph">#FF717171</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundBegin">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundEnd">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseDownBackgroundMiddle">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseDownBorder">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundBegin">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundEnd">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundMiddle1">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarMouseOverBackgroundMiddle2">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarOptionsBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarOptionsGlyph">#FF717171</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundBegin">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundEnd">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseDownBackgroundMiddle">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundBegin">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundEnd">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundMiddle1">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverBackgroundMiddle2">#FFC9DEF5</Color> + <Color x:Key="VsColor.CommandBarOptionsMouseOverGlyph">#FF007ACC</Color> + <Color x:Key="VsColor.CommandBarSelected">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarSelectedBorder">#FF3399FF</Color> + <Color x:Key="VsColor.CommandBarShadow">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarTextActive">#FF1E1E1E</Color> + <Color x:Key="VsColor.CommandBarTextHover">#FF1E1E1E</Color> + <Color x:Key="VsColor.CommandBarTextInactive">#FFA2A4A5</Color> + <Color x:Key="VsColor.CommandBarTextSelected">#FF1E1E1E</Color> + <Color x:Key="VsColor.CommandBarToolBarBorder">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandBarToolBarSeparator">#FFCCCEDB</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandShelfBackgroundGradientMiddle">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.CommandShelfHighlightGradientMiddle">#FFEEEEF2</Color> + <Color x:Key="VsColor.ControlEditHintText">#FF717171</Color> + <Color x:Key="VsColor.ControlEditRequiredBackground">#FFFDFBAC</Color> + <Color x:Key="VsColor.ControlEditRequiredHintText">#FF717171</Color> + <Color x:Key="VsColor.ControlLinkText">#FF0E70C0</Color> + <Color x:Key="VsColor.ControlLinkTextHover">#FF0E70C0</Color> + <Color x:Key="VsColor.ControlLinkTextPressed">#FF0E70C0</Color> + <Color x:Key="VsColor.ControlOutline">#FFCCCEDB</Color> + <Color x:Key="VsColor.Dark">#00CCCEDB</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveBackground">#FFE7E8EC</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveHighlight">#FFEDEEF0</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveHighlightText">#FF1E1E1E</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveSeparator">#FFCCCEDB</Color> + <Color x:Key="VsColor.DebuggerDataTipActiveText">#FF1E1E1E</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveBackground">#FFD6D8DC</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveHighlight">#FFEDEEF0</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveHighlightText">#FFA2A4A5</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveSeparator">#FFCCCEDB</Color> + <Color x:Key="VsColor.DebuggerDataTipInactiveText">#FFA2A4A5</Color> + <Color x:Key="VsColor.DesignerBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.DesignerSelectionDots">#FF999999</Color> + <Color x:Key="VsColor.DesignerTray">#FFCCCEDB</Color> + <Color x:Key="VsColor.DesignerWatermark">#FFA2A4A5</Color> + <Color x:Key="VsColor.DiagReportBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageHeader">#FFF2F4F8</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageSubtitle">#FF000000</Color> + <Color x:Key="VsColor.DiagReportSecondaryPageTitle">#FF4A6184</Color> + <Color x:Key="VsColor.DiagReportSummaryPageHeader">#FF4A6184</Color> + <Color x:Key="VsColor.DiagReportSummaryPageSubtitle">#FFBCC7D8</Color> + <Color x:Key="VsColor.DiagReportSummaryPageTitle">#FFFFFFFF</Color> + <Color x:Key="VsColor.DiagReportText">#FF000000</Color> + <Color x:Key="VsColor.DockTargetBackground">#FFE7E8EC</Color> + <Color x:Key="VsColor.DockTargetBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.DockTargetButtonBackgroundBegin">#FFF5F5F5</Color> + <Color x:Key="VsColor.DockTargetButtonBackgroundEnd">#FFF5F5F5</Color> + <Color x:Key="VsColor.DockTargetButtonBorder">#FFF5F5F5</Color> + <Color x:Key="VsColor.DockTargetGlyphArrow">#FF1E1E1E</Color> + <Color x:Key="VsColor.DockTargetGlyphBackgroundBegin">#FFF5F5F5</Color> + <Color x:Key="VsColor.DockTargetGlyphBackgroundEnd">#FFF5F5F5</Color> + <Color x:Key="VsColor.DockTargetGlyphBorder">#FF007ACC</Color> + <Color x:Key="VsColor.DropDownBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DropDownBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.DropDownDisabledBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.DropDownDisabledBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.DropDownDisabledGlyph">#FFCCCEDB</Color> + <Color x:Key="VsColor.DropDownGlyph">#FF717171</Color> + <Color x:Key="VsColor.DropDownMouseDownBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.DropDownMouseDownBorder">#FF007ACC</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundMiddle1">#FFFFFFFF</Color> + <Color x:Key="VsColor.DropDownMouseOverBackgroundMiddle2">#FFFFFFFF</Color> + <Color x:Key="VsColor.DropDownMouseOverBorder">#FF007ACC</Color> + <Color x:Key="VsColor.DropDownMouseOverGlyph">#FF1E1E1E</Color> + <Color x:Key="VsColor.DropDownPopupBackgroundBegin">#FFF6F6F6</Color> + <Color x:Key="VsColor.DropDownPopupBackgroundEnd">#FFF6F6F6</Color> + <Color x:Key="VsColor.DropDownPopupBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.DropShadowBackground">#72000000</Color> + <Color x:Key="VsColor.EditorExpansionBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.EditorExpansionFill">#FFF5F5F5</Color> + <Color x:Key="VsColor.EditorExpansionLink">#FF0E70C0</Color> + <Color x:Key="VsColor.EditorExpansionText">#FF1E1E1E</Color> + <Color x:Key="VsColor.EnvironmentBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientMiddle1">#FFEEEEF2</Color> + <Color x:Key="VsColor.EnvironmentBackgroundGradientMiddle2">#FFEEEEF2</Color> + <Color x:Key="VsColor.EnvironmentBackgroundTexture1">#FFEEEEF2</Color> + <Color x:Key="VsColor.EnvironmentBackgroundTexture2">#FFEEEEF2</Color> + <Color x:Key="VsColor.ExtensionManagerStarHighlight1">#FFFFA300</Color> + <Color x:Key="VsColor.ExtensionManagerStarHighlight2">#FFFFA300</Color> + <Color x:Key="VsColor.ExtensionManagerStarInactive1">#FFA2A4A5</Color> + <Color x:Key="VsColor.ExtensionManagerStarInactive2">#FFA2A4A5</Color> + <Color x:Key="VsColor.FileTabBorder">#FFEEEEF2</Color> + <Color x:Key="VsColor.FileTabChannelBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.FileTabDocumentBorderBackground">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabDocumentBorderHighlight">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabDocumentBorderShadow">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabGradientDark">#FFEEEEF2</Color> + <Color x:Key="VsColor.FileTabGradientLight">#FFEEEEF2</Color> + <Color x:Key="VsColor.FileTabHotBorder">#FF1C97EA</Color> + <Color x:Key="VsColor.FileTabHotGlyph">#FFD0E6F5</Color> + <Color x:Key="VsColor.FileTabHotGradientBottom">#FF1C97EA</Color> + <Color x:Key="VsColor.FileTabHotGradientTop">#FF1C97EA</Color> + <Color x:Key="VsColor.FileTabHotText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabInactiveDocumentBorderBackground">#FFCCCEDB</Color> + <Color x:Key="VsColor.FileTabInactiveDocumentBorderEdge">#FFCCCEDB</Color> + <Color x:Key="VsColor.FileTabInactiveGradientBottom">#FFCCCEDB</Color> + <Color x:Key="VsColor.FileTabInactiveGradientTop">#FFCCCEDB</Color> + <Color x:Key="VsColor.FileTabInactiveText">#FF717171</Color> + <Color x:Key="VsColor.FileTabLastActiveDocumentBorderBackground">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabLastActiveDocumentBorderEdge">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabLastActiveGlyph">#FFD0E6F5</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientBottom">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientMiddle1">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientMiddle2">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveGradientTop">#FF0E639C</Color> + <Color x:Key="VsColor.FileTabLastActiveText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabSelectedBackground">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedBorder">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientBottom">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientMiddle1">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientMiddle2">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedGradientTop">#FF007ACC</Color> + <Color x:Key="VsColor.FileTabSelectedText">#FFFFFFFF</Color> + <Color x:Key="VsColor.FileTabText">#FF1E1E1E</Color> + <Color x:Key="VsColor.FormSmartTagActionTagBorder">#FF000000</Color> + <Color x:Key="VsColor.FormSmartTagActionTagFill">#FFFFFFFF</Color> + <Color x:Key="VsColor.FormSmartTagObjectTagBorder">#FF000000</Color> + <Color x:Key="VsColor.FormSmartTagObjectTagFill">#FFFFFFFF</Color> + <Color x:Key="VsColor.GrayText">#FF717171</Color> + <Color x:Key="VsColor.GridHeadingBackground">#FFEFEFE2</Color> + <Color x:Key="VsColor.GridHeadingText">#FF1E1E1E</Color> + <Color x:Key="VsColor.GridLine">#FFF0F0F0</Color> + <Color x:Key="VsColor.HelpHowDoIPaneBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpHowDoIPaneLink">#FF0066CC</Color> + <Color x:Key="VsColor.HelpHowDoIPaneText">#FF000000</Color> + <Color x:Key="VsColor.HelpHowDoITaskBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpHowDoITaskLink">#FF0066CC</Color> + <Color x:Key="VsColor.HelpHowDoITaskText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchFilterBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchFilterBorder">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchFilterText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchFrameBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpSearchFrameText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchPanelRules">#FFA8B3C2</Color> + <Color x:Key="VsColor.HelpSearchProviderIcon">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchProviderSelectedBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.HelpSearchProviderSelectedText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchProviderUnselectedBackground">#FFDEE1E7</Color> + <Color x:Key="VsColor.HelpSearchProviderUnselectedText">#FF1B293E</Color> + <Color x:Key="VsColor.HelpSearchResultLinkSelected">#FF0066CC</Color> + <Color x:Key="VsColor.HelpSearchResultLinkUnselected">#FF0066CC</Color> + <Color x:Key="VsColor.HelpSearchResultSelectedBackground">#FFF0F0F0</Color> + <Color x:Key="VsColor.HelpSearchResultSelectedText">#FF000000</Color> + <Color x:Key="VsColor.HelpSearchText">#FF000000</Color> + <Color x:Key="VsColor.Highlight">#FF3399FF</Color> + <Color x:Key="VsColor.HighlightText">#FFFFFFFF</Color> + <Color x:Key="VsColor.InactiveBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.InactiveCaption">#FFEEEEF2</Color> + <Color x:Key="VsColor.InactiveCaptionText">#FFA2A4A5</Color> + <Color x:Key="VsColor.InfoBackground">#FFFDFBAC</Color> + <Color x:Key="VsColor.InfoText">#FF1E1E1E</Color> + <Color x:Key="VsColor.Light">#00F5F5F5</Color> + <Color x:Key="VsColor.LightCaption">#001E1E1E</Color> + <Color x:Key="VsColor.MdiClientBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.Medium">#00EEEEF2</Color> + <Color x:Key="VsColor.Menu">#FFF6F6F6</Color> + <Color x:Key="VsColor.MenuText">#FF1E1E1E</Color> + <Color x:Key="VsColor.NewProjectBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.NewProjectItemInactiveBegin">#FFCCCEDB</Color> + <Color x:Key="VsColor.NewProjectItemInactiveBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.NewProjectItemInactiveEnd">#FFCCCEDB</Color> + <Color x:Key="VsColor.NewProjectItemSelected">#FF3399FF</Color> + <Color x:Key="VsColor.NewProjectItemSelectedBorder">#FF3399FF</Color> + <Color x:Key="VsColor.NewProjectProviderHoverBegin">#FFFEFEFE</Color> + <Color x:Key="VsColor.NewProjectProviderHoverEnd">#FFFEFEFE</Color> + <Color x:Key="VsColor.NewProjectProviderHoverForeground">#FF1E1E1E</Color> + <Color x:Key="VsColor.NewProjectProviderHoverMiddle1">#FFFEFEFE</Color> + <Color x:Key="VsColor.NewProjectProviderHoverMiddle2">#FFFEFEFE</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveBegin">#FFCCCEDB</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveEnd">#FFCCCEDB</Color> + <Color x:Key="VsColor.NewProjectProviderInactiveForeground">#FF1E1E1E</Color> + <Color x:Key="VsColor.PageContentExpanderChevron">#FF1E1E1E</Color> + <Color x:Key="VsColor.PageContentExpanderSeparator">#FFCCCEDB</Color> + <Color x:Key="VsColor.PageSideBarExpanderBody">#FFF5F5F5</Color> + <Color x:Key="VsColor.PageSideBarExpanderChevron">#FF1E1E1E</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeader">#FFFEFEFE</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeaderHover">#FFCCCEDB</Color> + <Color x:Key="VsColor.PageSideBarExpanderHeaderPressed">#FFCCCEDB</Color> + <Color x:Key="VsColor.PageSideBarExpanderSeparator">#FFCCCEDB</Color> + <Color x:Key="VsColor.PageSideBarExpanderText">#FF1E1E1E</Color> + <Color x:Key="VsColor.PanelBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.PanelGradientDark">#FFF5F5F5</Color> + <Color x:Key="VsColor.PanelGradientLight">#FFF5F5F5</Color> + <Color x:Key="VsColor.PanelHoverOverCloseBorder">#FFFEFEFE</Color> + <Color x:Key="VsColor.PanelHoverOverCloseFill">#FFFEFEFE</Color> + <Color x:Key="VsColor.PanelHyperlink">#FF0E70C0</Color> + <Color x:Key="VsColor.PanelHyperlinkHover">#FF007ACC</Color> + <Color x:Key="VsColor.PanelHyperlinkPressed">#FF0E70C0</Color> + <Color x:Key="VsColor.PanelSeparator">#FFEEEEF2</Color> + <Color x:Key="VsColor.PanelSubGroupSeparator">#FFEEEEF2</Color> + <Color x:Key="VsColor.PanelText">#FF1E1E1E</Color> + <Color x:Key="VsColor.PanelTitleBar">#FFF5F5F5</Color> + <Color x:Key="VsColor.PanelTitleBarText">#FF1E1E1E</Color> + <Color x:Key="VsColor.PanelTitleBarUnselected">#FFF5F5F5</Color> + <Color x:Key="VsColor.ProjectDesignerBackgroundGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerBackgroundGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerBorderInside">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerBorderOutside">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerContentsBackground">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerTabBackgroundGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerTabBackgroundGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedBackground">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedBorder">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedHighlight1">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedHighlight2">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSelectedInsideBorder">#FF3399FF</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepBottomGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepBottomGradientEnd">#FFF5F5F5</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepTopGradientBegin">#FFF5F5F5</Color> + <Color x:Key="VsColor.ProjectDesignerTabSepTopGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.ScreenTipBackground">#FFFDFBAC</Color> + <Color x:Key="VsColor.ScreenTipBorder">#FFFDFBAC</Color> + <Color x:Key="VsColor.ScreenTipText">#FF1E1E1E</Color> + <Color x:Key="VsColor.ScrollBar">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarArrowBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarArrowDisabledBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarArrowMouseOverBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarArrowPressedBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarDisabledBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ScrollBarThumbBackground">#FFC2C3C9</Color> + <Color x:Key="VsColor.ScrollBarThumbBorder">#FFC2C3C9</Color> + <Color x:Key="VsColor.ScrollBarThumbGlyph">#FFC2C3C9</Color> + <Color x:Key="VsColor.ScrollBarThumbMouseOverBackground">#FF686868</Color> + <Color x:Key="VsColor.ScrollBarThumbPressedBackground">#FF5B5B5B</Color> + <Color x:Key="VsColor.SearchBoxBackground">#FFFCFCFC</Color> + <Color x:Key="VsColor.SearchBoxBorder">#FFFCFCFC</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundMiddle1">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBackgroundMiddle2">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxMouseOverBorder">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxPressedBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.SearchBoxPressedBorder">#FF007ACC</Color> + <Color x:Key="VsColor.SideBarBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.SideBarGradientDark">#FFF5F5F5</Color> + <Color x:Key="VsColor.SideBarGradientLight">#FFF5F5F5</Color> + <Color x:Key="VsColor.SideBarText">#FF1E1E1E</Color> + <Color x:Key="VsColor.SmartTagBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SmartTagFill">#FFFFEFBB</Color> + <Color x:Key="VsColor.SmartTagHoverBorder">#FFE5C365</Color> + <Color x:Key="VsColor.SmartTagHoverFill">#FFFDFBAC</Color> + <Color x:Key="VsColor.SmartTagHoverText">#FF000000</Color> + <Color x:Key="VsColor.SmartTagText">#FF000000</Color> + <Color x:Key="VsColor.Snaplines">#FF4169E1</Color> + <Color x:Key="VsColor.SnaplinesPadding">#FF96A9DD</Color> + <Color x:Key="VsColor.SnaplinesTextBaseline">#FFE122DF</Color> + <Color x:Key="VsColor.SortBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.SortText">#FF1E1E1E</Color> + <Color x:Key="VsColor.SplashScreenBorder">#FF999999</Color> + <Color x:Key="VsColor.StartPageBackgroundGradientBegin">#FF2D2D30</Color> + <Color x:Key="VsColor.StartPageBackgroundGradientEnd">#FF2D2D30</Color> + <Color x:Key="VsColor.StartPageButtonBorder">#FF999999</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundBegin">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundEnd">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundMiddle1">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonMouseOverBackgroundMiddle2">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageButtonPinDown">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinHover">#FF464646</Color> + <Color x:Key="VsColor.StartPageButtonPinned">#FFF30506</Color> + <Color x:Key="VsColor.StartPageButtonText">#FF0097FB</Color> + <Color x:Key="VsColor.StartPageButtonTextHover">#FF55AAFF</Color> + <Color x:Key="VsColor.StartPageButtonUnpinned">#FFF30506</Color> + <Color x:Key="VsColor.StartPageSelectedItemBackground">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageSelectedItemStroke">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageSeparator">#FF363639</Color> + <Color x:Key="VsColor.StartPageTabBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTabBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTabMouseOverBackgroundBegin">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTabMouseOverBackgroundEnd">#FFFFFFFF</Color> + <Color x:Key="VsColor.StartPageTextBody">#FFF1F1F1</Color> + <Color x:Key="VsColor.StartPageTextBodySelected">#FFF30506</Color> + <Color x:Key="VsColor.StartPageTextBodyUnselected">#FF555555</Color> + <Color x:Key="VsColor.StartPageTextControlLinkSelected">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageTextControlLinkSelectedHover">#FF77AAFF</Color> + <Color x:Key="VsColor.StartPageTextDate">#FF1E1E1E</Color> + <Color x:Key="VsColor.StartPageTextHeading">#FF999999</Color> + <Color x:Key="VsColor.StartPageTextHeadingMouseOver">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageTextHeadingSelected">#FF555555</Color> + <Color x:Key="VsColor.StartPageTextSubHeading">#FF999999</Color> + <Color x:Key="VsColor.StartPageTextSubHeadingMouseOver">#FF007ACC</Color> + <Color x:Key="VsColor.StartPageTextSubHeadingSelected">#FF000000</Color> + <Color x:Key="VsColor.StartPageUnselectedItemBackgroundBegin">#FF3F3F3F</Color> + <Color x:Key="VsColor.StartPageUnselectedItemBackgroundEnd">#FF464646</Color> + <Color x:Key="VsColor.StartPageUnselectedItemStroke">#FF999999</Color> + <Color x:Key="VsColor.StatusBarText">#FFFFFFFF</Color> + <Color x:Key="VsColor.TaskListGridLines">#FFF0F0F0</Color> + <Color x:Key="VsColor.ThreeDDarkShadow">#FFEEEEF2</Color> + <Color x:Key="VsColor.ThreeDFace">#FFCCCEDB</Color> + <Color x:Key="VsColor.ThreeDHighlight">#FFD8D8E0</Color> + <Color x:Key="VsColor.ThreeDLightShadow">#FFEEEEF2</Color> + <Color x:Key="VsColor.ThreeDShadow">#FFCCCEBD</Color> + <Color x:Key="VsColor.TitleBarActive">#FFEEEEF2</Color> + <Color x:Key="VsColor.TitleBarActiveGradientBegin">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveGradientEnd">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveGradientMiddle1">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveGradientMiddle2">#FF007ACC</Color> + <Color x:Key="VsColor.TitleBarActiveText">#FFFFFFFF</Color> + <Color x:Key="VsColor.TitleBarInactive">#FFEEEEF2</Color> + <Color x:Key="VsColor.TitleBarInactiveGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.TitleBarInactiveGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.TitleBarInactiveText">#FF444444</Color> + <Color x:Key="VsColor.ToolboxBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxDivider">#FFCCCEDB</Color> + <Color x:Key="VsColor.ToolboxGradientDark">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxGradientLight">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxHeadingAccent">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxHeadingBegin">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxHeadingEnd">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxIconHighlight">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxIconShadow">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingMiddle1">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolboxSelectedHeadingMiddle2">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowBackground">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolWindowBorder">#FFCCCEDB</Color> + <Color x:Key="VsColor.ToolWindowButtonActiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonDown">#FF0E6198</Color> + <Color x:Key="VsColor.ToolWindowButtonDownActiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonDownBorder">#FF0E6198</Color> + <Color x:Key="VsColor.ToolWindowButtonDownInactiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActive">#FF52B0EF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActiveBorder">#FF52B0EF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverActiveGlyph">#FFFFFFFF</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactive">#FFF7F7F9</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactiveBorder">#FFF7F7F9</Color> + <Color x:Key="VsColor.ToolWindowButtonHoverInactiveGlyph">#FF717171</Color> + <Color x:Key="VsColor.ToolWindowButtonInactive">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolWindowButtonInactiveBorder">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolWindowButtonInactiveGlyph">#FF1E1E1E</Color> + <Color x:Key="VsColor.ToolWindowContentTabGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowContentTabGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowFloatingFrame">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowTabBorder">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowTabGradientBegin">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowTabGradientEnd">#FFEEEEF2</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBackgroundBegin">#FFC9DEF5</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBackgroundEnd">#FFC9DEF5</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverBorder">#FFC9DEF5</Color> + <Color x:Key="VsColor.ToolWindowTabMouseOverText">#FF1E1E1E</Color> + <Color x:Key="VsColor.ToolWindowTabSelectedTab">#FFF5F5F5</Color> + <Color x:Key="VsColor.ToolWindowTabSelectedText">#FF0E70C0</Color> + <Color x:Key="VsColor.ToolWindowTabText">#FF444444</Color> + <Color x:Key="VsColor.ToolWindowText">#FF1E1E1E</Color> + <Color x:Key="VsColor.VizSurfaceBrownDark">#FF705829</Color> + <Color x:Key="VsColor.VizSurfaceBrownLight">#FFB0A781</Color> + <Color x:Key="VsColor.VizSurfaceBrownMedium">#FFA19667</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldDark">#FFA79432</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldLight">#FFD0D4B7</Color> + <Color x:Key="VsColor.VizSurfaceDarkGoldMedium">#FFBFC749</Color> + <Color x:Key="VsColor.VizSurfaceGoldDark">#FFCAB22D</Color> + <Color x:Key="VsColor.VizSurfaceGoldLight">#FFFBF7C8</Color> + <Color x:Key="VsColor.VizSurfaceGoldMedium">#FFE2E442</Color> + <Color x:Key="VsColor.VizSurfaceGreenDark">#FF5D8039</Color> + <Color x:Key="VsColor.VizSurfaceGreenLight">#FFB1C97B</Color> + <Color x:Key="VsColor.VizSurfaceGreenMedium">#FF9FB861</Color> + <Color x:Key="VsColor.VizSurfacePlumDark">#FF8E5478</Color> + <Color x:Key="VsColor.VizSurfacePlumLight">#FFE2B1CD</Color> + <Color x:Key="VsColor.VizSurfacePlumMedium">#FFCB98B6</Color> + <Color x:Key="VsColor.VizSurfaceRedDark">#FFAD1C2B</Color> + <Color x:Key="VsColor.VizSurfaceRedLight">#FFFF9F99</Color> + <Color x:Key="VsColor.VizSurfaceRedMedium">#FFFF7971</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueDark">#FF779AB6</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueLight">#FFC6D4DF</Color> + <Color x:Key="VsColor.VizSurfaceSoftBlueMedium">#FFB8CCD7</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueDark">#FF427094</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueLight">#FFA0B7C9</Color> + <Color x:Key="VsColor.VizSurfaceSteelBlueMedium">#FF89ABBD</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueDark">#FF5386BF</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueLight">#FFB9D4EE</Color> + <Color x:Key="VsColor.VizSurfaceStrongBlueMedium">#FFA1C7E7</Color> + <Color x:Key="VsColor.Window">#FFF5F5F5</Color> + <Color x:Key="VsColor.WindowFrame">#FFEEEEF2</Color> + <Color x:Key="VsColor.WindowText">#FF1E1E1E</Color> + <Color x:Key="VsColor.WizardOrientationPanelBackground">#FFFFFFFF</Color> + <Color x:Key="VsColor.WizardOrientationPanelText">#FF000000</Color> +</ResourceDictionary> diff --git a/src/GitHub.VisualStudio.UI/TeamFoundationColors.cs b/src/GitHub.VisualStudio.UI/TeamFoundationColors.cs new file mode 100644 index 0000000000..908896157d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/TeamFoundationColors.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel; +using System.Windows.Media; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.VisualStudio.UI +{ + /// <summary> + /// Retrieves themed Team Foundation colors. + /// </summary> + public class TeamFoundationColors : INotifyPropertyChanged + { + static readonly Guid VsTeamFoundationColorsCategory = new Guid("4aff231b-f28a-44f0-a66b-1beeb17cb920"); + static Lazy<TeamFoundationColors> instance = new Lazy<TeamFoundationColors>(() => new TeamFoundationColors()); + + /// <summary> + /// Initializes a new instance of the <see cref="TeamFoundationColors"/> class. + /// </summary> + private TeamFoundationColors() + { + VSColorTheme.ThemeChanged += _ => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); + } + + /// <summary> + /// Gets a singleton instance of the <see cref="TeamFoundationColors"/> class. + /// </summary> + public static TeamFoundationColors Instance => instance.Value; + + /// <summary> + /// Gets the Team Foundation "RequiredTextBoxBorder" color. + /// </summary> + public Color RequiredTextBoxBorderColor => GetColor("RequiredTextBoxBorder"); + + /// <summary> + /// Gets the Team Foundation "TextBoxBorder" color. + /// </summary> + public Color TextBoxBorderColor => GetColor("TextBoxBorder"); + + /// <summary> + /// Gets the Team Foundation "TextBox" color. + /// </summary> + public Color TextBoxColor => GetColor("TextBox"); + + /// <summary> + /// Gets the Team Foundation "TextBoxHintText" color. + /// </summary> + public Color TextBoxHintTextColor => GetColor("TextBoxHintText"); + + /// <summary> + /// Occurs when a property on the object changes. + /// </summary> + public event PropertyChangedEventHandler PropertyChanged; + + private static Color GetColor(string name) + { + var c = VSColorTheme.GetThemedColor(new ThemeResourceKey(VsTeamFoundationColorsCategory, name, ThemeResourceKeyType.BackgroundColor)); + return Color.FromArgb(c.A, c.R, c.G, c.B); + } + } +} diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml new file mode 100644 index 0000000000..ef3defbc5d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml @@ -0,0 +1,30 @@ +<UserControl x:Class="GitHub.VisualStudio.UI.Controls.AccountAvatar" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Controls" + Name="root" + MinWidth="16" MinHeight="16"> + <Button Command="{Binding Command, ElementName=root}" + CommandParameter="{Binding CommandParameter, ElementName=root}" + CommandTarget="{Binding CommandTarget, ElementName=root}" + MinWidth="0"> + <Button.Template> + <ControlTemplate TargetType="Button"> + <ContentPresenter/> + </ControlTemplate> + </Button.Template> + <Grid> + <Grid.OpacityMask> + <VisualBrush Visual="{Binding ElementName=avatarMask}"/> + </Grid.OpacityMask> + <Border Name="avatarMask" Background="White" CornerRadius="3" + Width="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:AccountAvatar}}}" + Height="{Binding ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:AccountAvatar}}}"/> + <Image x:Name="avatar" + Width="{Binding Width, ElementName=avatarMask}" + Height="{Binding Height, ElementName=avatarMask}" + RenderOptions.BitmapScalingMode="HighQuality" + Source="{Binding Account.Avatar, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:AccountAvatar}}}" /> + </Grid> + </Button> +</UserControl> diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs new file mode 100644 index 0000000000..7570da7e40 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Controls/AccountAvatar.xaml.cs @@ -0,0 +1,52 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using GitHub.Models; + +namespace GitHub.VisualStudio.UI.Controls +{ + public partial class AccountAvatar : UserControl, ICommandSource + { + public static readonly DependencyProperty AccountProperty = + DependencyProperty.Register( + nameof(Account), + typeof(object), + typeof(AccountAvatar)); + public static readonly DependencyProperty CommandProperty = + ButtonBase.CommandProperty.AddOwner(typeof(AccountAvatar)); + public static readonly DependencyProperty CommandParameterProperty = + ButtonBase.CommandParameterProperty.AddOwner(typeof(AccountAvatar)); + public static readonly DependencyProperty CommandTargetProperty = + ButtonBase.CommandTargetProperty.AddOwner(typeof(AccountAvatar)); + + public AccountAvatar() + { + InitializeComponent(); + } + + public object Account + { + get { return GetValue(AccountProperty); } + set { SetValue(AccountProperty, value); } + } + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public IInputElement CommandTarget + { + get { return (IInputElement)GetValue(CommandTargetProperty); } + set { SetValue(CommandTargetProperty, value); } + } + } +} diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/InfoPanel.xaml b/src/GitHub.VisualStudio.UI/UI/Controls/InfoPanel.xaml new file mode 100644 index 0000000000..3c19c8c5a9 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Controls/InfoPanel.xaml @@ -0,0 +1,67 @@ +<UserControl x:Class="GitHub.VisualStudio.UI.Controls.InfoPanel" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Controls" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + Background="{DynamicResource VsBrush.InfoBackground}" + d:DataContext="{d:DesignInstance Type=ghfvs:InfoPanelDesigner, IsDesignTimeCreatable=True}" + d:DesignHeight="300" + d:DesignWidth="300" + mc:Ignorable="d" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GitHubInfoPanel}"> + + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml"/> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + <Style x:Key="DocumentStyle" TargetType="FlowDocument"> + <Setter Property="FontFamily" Value="Segoe UI"/> + <Setter Property="FontSize" Value="12"/> + <Setter Property="TextAlignment" Value="Left"/> + <Setter Property="PagePadding" Value="0"/> + </Style> + <Style x:Key="LinkStyle" TargetType="Hyperlink"> + <Setter Property="Foreground" Value="{DynamicResource GitHubActionLinkItemBrush}"/> + <Setter Property="TextDecorations" Value="Underline"/> + </Style> + </ResourceDictionary> + </UserControl.Resources> + + <FrameworkElement.CommandBindings> + <CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" /> + </FrameworkElement.CommandBindings> + + <DockPanel Margin="8" LastChildFill="True"> + <ghfvs:OcticonImage Width="16" + Height="16" + Margin="0,0,8,0" + VerticalAlignment="Top" + DockPanel.Dock="Left" + Foreground="{Binding IconColor}" + Icon="{Binding Icon}"/> + <ghfvs:OcticonButton Width="16" + Height="16" + Margin="0" + VerticalAlignment="Top" + Background="Transparent" + Click="Dismiss_Click" + DockPanel.Dock="Right" + Foreground="Black" + Icon="x"/> + + <markdig:MarkdownViewer x:Name="document" + Margin="8,0" + VerticalAlignment="Top" + DockPanel.Dock="Top" + Foreground="Black" + Markdown="{Binding Message}" + ScrollViewer.CanContentScroll="False" + ScrollViewer.HorizontalScrollBarVisibility="Hidden" + ScrollViewer.VerticalScrollBarVisibility="Hidden"/> + </DockPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio.UI/UI/Controls/InfoPanel.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Controls/InfoPanel.xaml.cs new file mode 100644 index 0000000000..450e09c6b6 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Controls/InfoPanel.xaml.cs @@ -0,0 +1,114 @@ +using GitHub.UI; +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using GitHub.ViewModels; +using System.ComponentModel; +using GitHub.Services; +using GitHub.Extensions; +using System.Windows.Input; +using GitHub.Primitives; +using GitHub.VisualStudio.Helpers; +using Colors = System.Windows.Media.Colors; + +namespace GitHub.VisualStudio.UI.Controls +{ + public partial class InfoPanel : UserControl, IInfoPanel, INotifyPropertyChanged, INotifyPropertySource + { + static SolidColorBrush WarningColorBrush = new SolidColorBrush(Colors.DarkRed); + static SolidColorBrush InfoColorBrush = new SolidColorBrush(Colors.Black); + + static readonly DependencyProperty MessageProperty = + DependencyProperty.Register(nameof(Message), typeof(string), typeof(InfoPanel), new PropertyMetadata(String.Empty, UpdateMessage)); + + static readonly DependencyProperty MessageTypeProperty = + DependencyProperty.Register(nameof(MessageType), typeof(MessageType), typeof(InfoPanel), new PropertyMetadata(MessageType.Information, UpdateIcon)); + + public string Message + { + get { return (string)GetValue(MessageProperty); } + set { SetValue(MessageProperty, value); } + } + + public MessageType MessageType + { + get { return (MessageType)GetValue(MessageTypeProperty); } + set { SetValue(MessageTypeProperty, value); } + } + + Octicon icon; + public Octicon Icon + { + get { return icon; } + private set { icon = value; RaisePropertyChanged(nameof(Icon)); } + } + + Brush iconColor; + public Brush IconColor + { + get { return iconColor; } + private set { iconColor = value; RaisePropertyChanged(nameof(IconColor)); } + } + + static InfoPanel() + { + WarningColorBrush.Freeze(); + InfoColorBrush.Freeze(); + } + + static IVisualStudioBrowser browser; + static IVisualStudioBrowser Browser + { + get + { + if (browser == null) + browser = Services.GitHubServiceProvider.TryGetService<IVisualStudioBrowser>(); + return browser; + } + } + + public InfoPanel() + { + InitializeComponent(); + + DataContext = this; + Icon = Octicon.info; + IconColor = InfoColorBrush; + } + + void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) + { + var url = e.Parameter.ToString(); + + if (!string.IsNullOrEmpty(url)) + Browser.OpenUrl(new Uri(url)); + } + + static void UpdateMessage(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = (InfoPanel)d; + var msg = e.NewValue as string; + control.Visibility = String.IsNullOrEmpty(msg) ? Visibility.Collapsed : Visibility.Visible; + } + + static void UpdateIcon(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = (InfoPanel)d; + control.Icon = (MessageType)e.NewValue == MessageType.Warning ? Octicon.alert : Octicon.info; + control.IconColor = control.Icon == Octicon.alert ? WarningColorBrush : InfoColorBrush; + } + + void Dismiss_Click(object sender, RoutedEventArgs e) + { + SetCurrentValue(MessageProperty, String.Empty); + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/GitHub.VisualStudio/UI/DrawingExtensions.cs b/src/GitHub.VisualStudio.UI/UI/DrawingExtensions.cs similarity index 100% rename from src/GitHub.VisualStudio/UI/DrawingExtensions.cs rename to src/GitHub.VisualStudio.UI/UI/DrawingExtensions.cs diff --git a/src/GitHub.VisualStudio.UI/UI/Views/GitHubConnectContent.xaml b/src/GitHub.VisualStudio.UI/UI/Views/GitHubConnectContent.xaml new file mode 100644 index 0000000000..66ec45565a --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Views/GitHubConnectContent.xaml @@ -0,0 +1,278 @@ +<UserControl x:Class="GitHub.VisualStudio.UI.Views.GitHubConnectContent" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.UI.Views" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI" + mc:Ignorable="d" + KeyboardNavigation.TabNavigation="Local" + DataContext="{Binding ViewModel}" + Background="{DynamicResource GitHubVsToolWindowBackground}" > + + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:GitHubConnectSectionDesigner ErrorMessage="Could not connect." + ShowLogin="True" + ShowLogout="True" + ShowRetry="True"/> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + <ghfvs:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> + <ghfvs:HasItemsVisibilityConverter x:Key="HasItemsVisibilityConverter" /> + <local:FormatRepositoryNameConverter x:Key="FormatRepositoryNameConverter" /> + <local:IsCurrentRepositoryConverter x:Key="IsCurrentRepositoryConverter" /> + + + <Style x:Key="RepoNameStyle" TargetType="{x:Type TextBlock}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + <Setter Property="Margin" Value="0,0,6,3" /> + <Setter Property="Text"> + <Setter.Value> + <MultiBinding Converter="{StaticResource FormatRepositoryNameConverter}" ConverterParameter="FormatRepositoryName"> + <Binding/> + <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> + </MultiBinding> + </Setter.Value> + </Setter> + <Style.Triggers> + <DataTrigger Value="True"> + <DataTrigger.Binding> + <MultiBinding Converter="{StaticResource IsCurrentRepositoryConverter}" ConverterParameter="IsCurrentRepository"> + <Binding/> + <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> + </MultiBinding> + </DataTrigger.Binding> + <Setter Property="TextBlock.FontWeight" Value="Bold" /> + </DataTrigger> + </Style.Triggers> + </Style> + + <Style x:Key="RepoPathStyle" TargetType="{x:Type TextBlock}"> + <Setter Property="Margin" Value="0,0,3,3" /> + <Setter Property="TextTrimming" Value="CharacterEllipsis" /> + <Setter Property="TextWrapping" Value="NoWrap" /> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsGrayText}" /> + <Style.Triggers> + <MultiDataTrigger> + <MultiDataTrigger.Conditions> + <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsSelected}" Value="True"/> + </MultiDataTrigger.Conditions> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + </MultiDataTrigger> + <MultiDataTrigger> + <MultiDataTrigger.Conditions> + <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsMouseOver}" Value="true" /> + </MultiDataTrigger.Conditions> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + </MultiDataTrigger> + <DataTrigger Value="True"> + <DataTrigger.Binding> + <MultiBinding Converter="{StaticResource IsCurrentRepositoryConverter}" ConverterParameter="IsCurrentRepository"> + <Binding/> + <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> + </MultiBinding> + </DataTrigger.Binding> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}" /> + </DataTrigger> + </Style.Triggers> + + </Style> + + <Style x:Key="RepositoriesListItemContainerStyle" TargetType="{x:Type ListBoxItem}"> + <Setter Property="SnapsToDevicePixels" Value="True"/> + <Setter Property="Margin" Value="0"/> + <Setter Property="Padding" Value="1"/> + + <Setter Property="HorizontalContentAlignment" Value="Stretch"/> + <Setter Property="VerticalContentAlignment" Value="Center"/> + + <Setter Property="Background" Value="Transparent"/> + <Setter Property="BorderBrush" Value="Transparent"/> + <Setter Property="BorderThickness" Value="0"/> + + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ListBoxItem}"> + <Border x:Name="Bd" + Padding="{TemplateBinding Padding}" + BorderThickness="{TemplateBinding BorderThickness}" + Background="{TemplateBinding Background}"> + <ContentPresenter + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> + </Border> + <ControlTemplate.Triggers> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="IsMouseOver" Value="True"/> + </MultiTrigger.Conditions> + <Setter Property="Background" Value="{DynamicResource GitHubVsCommandBarHover}" /> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="Selector.IsSelectionActive" Value="True"/> + <Condition Property="IsSelected" Value="True"/> + </MultiTrigger.Conditions> + <Setter Property="Background" Value="{DynamicResource GitHubVsCommandBarSelectedBorder}" /> + </MultiTrigger> + <MultiTrigger> + <MultiTrigger.Conditions> + <Condition Property="Selector.IsSelectionActive" Value="True"/> + </MultiTrigger.Conditions> + </MultiTrigger> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.3"/> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </ResourceDictionary> + + </UserControl.Resources> + + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*"/> + </Grid.RowDefinitions> + + <WrapPanel Orientation="Horizontal" + Margin="6,0,0,6" + Visibility="{Binding SectionConnection.IsLoggedIn, Converter={ghfvs:BooleanToVisibilityConverter}}"> + <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="0" x:Name="cloneLink" Command="{Binding Clone}" Content="{x:Static prop:Resources.CloneLink}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CloneHyperlink}"/> + <Separator Margin="3,0,3,0" Style="{StaticResource VerticalSeparator}" /> + <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="1" x:Name="createLink" Click="createLink_Click" Content="{x:Static prop:Resources.CreateLink}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CreateHyperlink}"/> + <Separator Margin="3,0,3,0" Style="{StaticResource VerticalSeparator}" /> + <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="2" x:Name="signOut" Click="signOut_Click" Content="{x:Static prop:Resources.SignOutLink}" + Visibility="{Binding ShowLogout, Converter={StaticResource BooleanToVisibilityConverter}}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SignOutHyperlink}"/> + <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="2" x:Name="login" Click="login_Click" Content="{x:Static prop:Resources.LoginLink}" + Visibility="{Binding ShowLogin, Converter={StaticResource BooleanToVisibilityConverter}}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SignInHyperlink}"/> + </WrapPanel> + + <ListView x:Name="repositories" + Grid.Row="1" + ItemsSource="{Binding Repositories}" + SelectedItem="{Binding Path=SelectedRepository}" + Margin="5,0,6,0" + MouseDoubleClick="repositories_MouseDoubleClick" + ItemContainerStyle="{StaticResource RepositoriesListItemContainerStyle}" + Background="Transparent" + BorderBrush="Transparent" + TextSearch.TextPath="Name" + ScrollViewer.HorizontalScrollBarVisibility="Disabled" + VirtualizingPanel.IsVirtualizing="True" + VirtualizingPanel.ScrollUnit="Pixel" + VirtualizingPanel.IsVirtualizingWhenGrouping="True" + Visibility="{Binding Repositories, Converter={StaticResource HasItemsVisibilityConverter}}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerRepositoryListView}" > + <ListView.ItemTemplate> + <DataTemplate> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <ghfvs:OcticonImage Grid.Column="0" Margin="11,1,6,1" Width="16" Height="16" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Icon="{Binding Path=Icon}"/> + <TextBlock Grid.Column="1" Style="{StaticResource RepoNameStyle}" /> + <Separator Grid.Column="2" Margin="0,0,6,3" Style="{StaticResource VerticalSeparator}" /> + <TextBlock Grid.Column="3" Text="{Binding Path=LocalPath}" Style="{StaticResource RepoPathStyle}" /> + <Grid.ToolTip> + <ToolTip> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,6,0"> + <Run Text="{x:Static prop:Resources.nameText}"/>: + </TextBlock> + <TextBlock Grid.Row="0" Grid.Column="1"> + <TextBlock.Text> + <MultiBinding Converter="{StaticResource FormatRepositoryNameConverter}" ConverterParameter="FormatRepositoryName"> + <Binding/> + <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> + </MultiBinding> + </TextBlock.Text> + </TextBlock> + <TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,6,0"> + <Run Text="{x:Static prop:Resources.pathText}"/>: + </TextBlock> + <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=LocalPath}" /> + </Grid> + </ToolTip> + </Grid.ToolTip> + </Grid> + </DataTemplate> + </ListView.ItemTemplate> + </ListView> + + <TextBlock Grid.Row="1" + Visibility="{Binding IsLoggingIn, Converter={ghfvs:BooleanToVisibilityConverter}}"> + Logging in... + </TextBlock> + + <Grid Grid.Row="1" + Visibility="{Binding ErrorMessage, Converter={ghfvs:NotEqualsToVisibilityConverter {x:Null}}}"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <ghfvs:OcticonImage Grid.Column="0" + Grid.RowSpan="3" + Icon="alert" + Margin="8"/> + + <TextBlock Grid.Column="1" + Grid.Row="0" + Text="{Binding ErrorMessage}" + TextWrapping="Wrap"/> + + <StackPanel Grid.Column="1" + Grid.Row="1" + Margin="0,8,0,0" + Orientation="Horizontal"> + <Button Style="{StaticResource ActionLinkButton}" + Content="{x:Static prop:Resources.Retry}" + Click="retry_Click" + Margin="0,0,8,0" + Visibility="{Binding ShowRetry, Converter={StaticResource BooleanToVisibilityConverter}}"/> + <Button Style="{StaticResource ActionLinkButton}" + Click="signOut_Click" + Content="{x:Static prop:Resources.SignOutLink}" + Visibility="{Binding ShowLogout, Converter={StaticResource BooleanToVisibilityConverter}}"/> + </StackPanel> + </Grid> + + </Grid> +</UserControl> diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Views/GitHubConnectContent.xaml.cs similarity index 85% rename from src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs rename to src/GitHub.VisualStudio.UI/UI/Views/GitHubConnectContent.xaml.cs index 850e2a9a7c..7fb8770e05 100644 --- a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml.cs +++ b/src/GitHub.VisualStudio.UI/UI/Views/GitHubConnectContent.xaml.cs @@ -1,13 +1,13 @@ using System.Windows; using System.Windows.Controls; using GitHub.VisualStudio.TeamExplorer.Connect; -using GitHub.UI.Helpers; using System.Windows.Data; using System.Globalization; using GitHub.Services; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; using GitHub.Models; using System; +using System.Windows.Input; +using GitHub.VisualStudio.UI.Helpers; namespace GitHub.VisualStudio.UI.Views { @@ -18,13 +18,7 @@ public GitHubConnectContent() InitializeComponent(); DataContextChanged += (s, e) => ViewModel = e.NewValue as IGitHubConnectSection; - } - - void cloneLink_Click(object sender, RoutedEventArgs e) - { - cloneLink.IsEnabled = false; - ViewModel.DoClone(); - cloneLink.IsEnabled = true; + repositories.PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; } void createLink_Click(object sender, RoutedEventArgs e) @@ -48,6 +42,11 @@ void login_Click(object sender, RoutedEventArgs e) login.IsEnabled = true; } + private void retry_Click(object sender, RoutedEventArgs e) + { + ViewModel.Retry(); + } + void repositories_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) { ViewModel.OpenRepository(); @@ -74,7 +73,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur if (values.Length != 2 || parameter as string != "FormatRepositoryName") return String.Empty; - var item = values[0] as ISimpleRepositoryModel; + var item = values[0] as ILocalRepositoryModel; var context = values[1] as IGitHubConnectSection; if (item == null) return String.Empty; @@ -99,9 +98,9 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur if (values.Length != 2 || parameter as string != "IsCurrentRepository") return false; - var item = values[0] as ISimpleRepositoryModel; + var item = values[0] as ILocalRepositoryModel; var context = values[1] as IGitAwareItem; - return item != null && context != null && context.ActiveRepoUri == item.CloneUrl; + return context?.ActiveRepoUri == item?.CloneUrl && String.Equals(context?.ActiveRepo?.LocalPath, item?.LocalPath, StringComparison.OrdinalIgnoreCase); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) diff --git a/src/GitHub.VisualStudio.UI/UI/Views/GitHubHomeContent.xaml b/src/GitHub.VisualStudio.UI/UI/Views/GitHubHomeContent.xaml new file mode 100644 index 0000000000..86e7f9928d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Views/GitHubHomeContent.xaml @@ -0,0 +1,64 @@ +<UserControl x:Class="GitHub.VisualStudio.UI.Views.GitHubHomeContent" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI" + Width="Auto" + Height="auto" + Background="{DynamicResource GitHubVsToolWindowBackground}" + DataContext="{Binding ViewModel}" + d:DesignHeight="68" + d:DesignWidth="300" + mc:Ignorable="d"> + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:GitHubHomeSectionDesigner /> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + <ghfvs:BooleanToInverseVisibilityConverter x:Key="BooleanToInverseVisibilityConverter" /> + </ResourceDictionary> + </UserControl.Resources> + + <StackPanel Margin="4,6,4,6"> + <DockPanel> + <ghfvs:OcticonImage x:Name="repositoryIcon" + Width="32" + Height="32" + Margin="0,0,8,0" + VerticalAlignment="Center" + DockPanel.Dock="Left" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Icon="{Binding Path=Icon}" /> + + <TextBlock DockPanel.Dock="Top" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Padding="0" + Text="{Binding Path=RepoName}" + TextTrimming="CharacterEllipsis" + Margin="0,-2,0,0" /> + <TextBlock Foreground="{DynamicResource GitHubVsGrayText}"> + <Hyperlink Command="{Binding OpenOnGitHub}"> + <TextBlock Text="{Binding RepoUrl}" TextTrimming="CharacterEllipsis"/> + </Hyperlink> + </TextBlock> + </DockPanel> + + <Button Margin="0,6,0,0" + x:Name="signIn" + Click="signIn_Click" + Visibility="{Binding IsLoggedIn, Converter={StaticResource BooleanToInverseVisibilityConverter}}" + Style="{StaticResource ActionLinkButton}" + Content="{x:Static prop:Resources.SignInCallToAction}" /> + </StackPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio.UI/UI/Views/GitHubHomeContent.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Views/GitHubHomeContent.xaml.cs new file mode 100644 index 0000000000..a6d24ef567 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/UI/Views/GitHubHomeContent.xaml.cs @@ -0,0 +1,42 @@ +using System.Windows; +using System.Windows.Controls; +using GitHub.VisualStudio.TeamExplorer.Home; +using GitHub.UI.Helpers; + +namespace GitHub.VisualStudio.UI.Views +{ + /// <summary> + /// Interaction logic for GitHubHomeSection.xaml + /// </summary> + public partial class GitHubHomeContent : UserControl + { + public GitHubHomeContent() + { + InitializeComponent(); + + DataContextChanged += (s, e) => ViewModel = e.NewValue as IGitHubHomeSection; + } + + public IGitHubHomeSection ViewModel + { + get { return (IGitHubHomeSection)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + "ViewModel", + typeof(IGitHubHomeSection), + typeof(GitHubHomeContent)); + + void signIn_Click(object sender, RoutedEventArgs e) + { + signIn.IsEnabled = false; + ViewModel.Login(); + signIn.IsEnabled = true; + // move focus away from sign-in control as its probabaly going to be + // hidden now, prevents ghost focus border remaining after sign-in + Focus(); + } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubInvitationContent.xaml b/src/GitHub.VisualStudio.UI/UI/Views/GitHubInvitationContent.xaml similarity index 75% rename from src/GitHub.VisualStudio/UI/Views/GitHubInvitationContent.xaml rename to src/GitHub.VisualStudio.UI/UI/Views/GitHubInvitationContent.xaml index 2d22d83c82..41369d5ab5 100644 --- a/src/GitHub.VisualStudio/UI/Views/GitHubInvitationContent.xaml +++ b/src/GitHub.VisualStudio.UI/UI/Views/GitHubInvitationContent.xaml @@ -3,20 +3,19 @@ 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:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:cache="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:prop="clr-namespace:GitHub.VisualStudio" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI" mc:Ignorable="d" DataContext="{Binding ViewModel}" d:DesignWidth="230" d:DesignHeight="120" KeyboardNavigation.TabNavigation="Local" - > + Background="{DynamicResource GitHubVsToolWindowBackground}"> <UserControl.Style> <Style TargetType="{x:Type UserControl}"> <Setter Property="Margin" Value="-2,0,6,0" /> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> - <Setter Property="Background" Value="{DynamicResource VsBrush.CommandBarHover}"/> + <Setter Property="Background" Value="{DynamicResource GitHubVsCommandBarHover}"/> </Trigger> </Style.Triggers> </Style> @@ -25,10 +24,11 @@ <UserControl.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio;component/SharedDictionary.xaml" /> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> </ResourceDictionary.MergedDictionaries> - <ui:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> + <ghfvs:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> + <Style x:Key="VSStyledButton" BasedOn="{StaticResource VsButtonStyleKey}" TargetType="{x:Type Button}"/> </ResourceDictionary> </UserControl.Resources> @@ -42,18 +42,18 @@ <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> - <ui:OcticonImage + <ghfvs:OcticonImage x:Name="octokit" Height="32" Width="32" VerticalAlignment="Center" Margin="0,2,8,2" Icon="mark_github" - Foreground="{DynamicResource VsBrush.ToolWindowText}" /> + Foreground="{DynamicResource GitHubVsToolWindowText}" /> <StackPanel Orientation="Vertical" VerticalAlignment="Center" Grid.Column="1"> <TextBlock TextTrimming="CharacterEllipsis" FontWeight="SemiBold" Text="{Binding Name}"/> - <TextBlock TextTrimming="CharacterEllipsis" Foreground="{DynamicResource VsBrush.GrayText}" Text="{Binding Provider}"/> + <TextBlock TextTrimming="CharacterEllipsis" Foreground="{DynamicResource GitHubVsGrayText}" Text="{Binding Provider}"/> </StackPanel> </Grid> @@ -66,15 +66,14 @@ <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> - <Button Grid.Column="0" KeyboardNavigation.TabIndex="0" Style="{StaticResource ActionLinkButton}" HorizontalAlignment="Left" x:Name="login" Click="login_Click" Content="{x:Static prop:Resources.LoginLink}" + <Button Grid.Column="0" KeyboardNavigation.TabIndex="0" Style="{StaticResource ActionLinkButton}" HorizontalAlignment="Left" x:Name="login" Click="connect_Click" Content="{x:Static prop:Resources.LoginLink}" Visibility="{Binding ShowLogin, Converter={StaticResource BooleanToVisibilityConverter}}"/> <TextBlock Grid.Column="1" Margin="3,0,3,0" - Visibility="{Binding ShowLogin, Converter={StaticResource BooleanToVisibilityConverter}}" Text="{x:Static prop:Resources.orText}"/> + Visibility="{Binding ShowLogin, Converter={StaticResource BooleanToVisibilityConverter}}" Text="{x:Static prop:Resources.orText}" + Foreground="{DynamicResource GitHubVsToolWindowText}" /> <Button Grid.Column="2" KeyboardNavigation.TabIndex="1" Style="{StaticResource ActionLinkButton}" HorizontalAlignment="Left" x:Name="signup" Click="signup_Click" Content="{x:Static prop:Resources.SignUpLink}" Visibility="{Binding ShowSignup, Converter={StaticResource BooleanToVisibilityConverter}}"/> - <Button Grid.Column="3" KeyboardNavigation.TabIndex="2" Style="{StaticResource ActionLinkButton}" HorizontalAlignment="Right" x:Name="connect" Click="connect_Click" Content="{x:Static prop:Resources.GetStartedLink}" - Visibility="{Binding ShowGetStarted, Converter={StaticResource BooleanToVisibilityConverter}}"/> - <Rectangle Grid.Column="4" Width="16" Height="16" Margin="8,0,0,0" Fill="{StaticResource ConnectArrowBrush}" + <Button Grid.Column="3" KeyboardNavigation.TabIndex="2" HorizontalAlignment="Right" x:Name="connect" Click="connect_Click" Content="{x:Static prop:Resources.PublishToGitHubButton}" Visibility="{Binding ShowGetStarted, Converter={StaticResource BooleanToVisibilityConverter}}"/> </Grid> diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubInvitationContent.xaml.cs b/src/GitHub.VisualStudio.UI/UI/Views/GitHubInvitationContent.xaml.cs similarity index 86% rename from src/GitHub.VisualStudio/UI/Views/GitHubInvitationContent.xaml.cs rename to src/GitHub.VisualStudio.UI/UI/Views/GitHubInvitationContent.xaml.cs index 3aa38831c7..a2ca6d4595 100644 --- a/src/GitHub.VisualStudio/UI/Views/GitHubInvitationContent.xaml.cs +++ b/src/GitHub.VisualStudio.UI/UI/Views/GitHubInvitationContent.xaml.cs @@ -1,7 +1,6 @@ using System.Windows; using System.Windows.Controls; using GitHub.VisualStudio.TeamExplorer; -using NullGuard; namespace GitHub.VisualStudio.UI.Views { @@ -17,10 +16,8 @@ public GitHubInvitationContent() DataContextChanged += (s, e) => ViewModel = e.NewValue as IGitHubInvitationSection; } - [AllowNull] public IGitHubInvitationSection ViewModel { - [return: AllowNull] get { return GetValue(ViewModelProperty) as IGitHubInvitationSection; } set { SetValue(ViewModelProperty, value); } } @@ -40,10 +37,5 @@ void signup_Click(object sender, RoutedEventArgs e) { ViewModel.SignUp(); } - - void login_Click(object sender, RoutedEventArgs e) - { - ViewModel.Login(); - } } } diff --git a/src/GitHub.VisualStudio.UI/packages.config b/src/GitHub.VisualStudio.UI/packages.config new file mode 100644 index 0000000000..eff341d4fa --- /dev/null +++ b/src/GitHub.VisualStudio.UI/packages.config @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Markdig.Signed" version="0.13.0" targetFramework="net461" /> + <package id="Markdig.Wpf.Signed" version="0.2.1" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/AssemblyResolverPackage.cs b/src/GitHub.VisualStudio/AssemblyResolverPackage.cs new file mode 100644 index 0000000000..75f4eb5669 --- /dev/null +++ b/src/GitHub.VisualStudio/AssemblyResolverPackage.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.VisualStudio +{ + // Allow assemblies in the extension directory to be resolved by their full or partial name. + // This is required for GitHub.VisualStudio.imagemanifest, XAML and when using unsigned assemblies. + // See: https://github.com/github/VisualStudio/pull/1236/ + [ProvideBindingPath] + [Guid(Guids.guidAssemblyResolverPkgString)] + public class AssemblyResolverPackage : AsyncPackage + { + } +} diff --git a/src/GitHub.VisualStudio/Base/EnsureLoggedInSection.cs b/src/GitHub.VisualStudio/Base/EnsureLoggedInSection.cs deleted file mode 100644 index 3a1aa4956f..0000000000 --- a/src/GitHub.VisualStudio/Base/EnsureLoggedInSection.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Reactive.Linq; -using GitHub.Api; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using GitHub.VisualStudio.Base; -using Microsoft.TeamFoundation.MVVM; -using System.Globalization; -using GitHub.Primitives; -using System.Threading.Tasks; - -namespace GitHub.VisualStudio.TeamExplorer.Sync -{ - public class EnsureLoggedInSection : TeamExplorerSectionBase - { - readonly IRepositoryHosts hosts; - readonly IVSServices vsServices; - - public EnsureLoggedInSection(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, - IConnectionManager cm, IRepositoryHosts hosts, IVSServices vsServices) - : base(apiFactory, holder, cm) - { - IsVisible = false; - this.hosts = hosts; - this.vsServices = vsServices; - } - - public override void Initialize(IServiceProvider serviceProvider) - { - base.Initialize(serviceProvider); - CheckLogin().Forget(); - } - - protected override void RepoChanged() - { - base.RepoChanged(); - CheckLogin().Forget(); - } - - async Task CheckLogin() - { - // this is not a github repo, or it hasn't been published yet - if (ActiveRepo == null || ActiveRepoUri == null) - return; - - var isgithub = await IsAGitHubRepo(); - if (!isgithub) - return; - - vsServices.ClearNotifications(); - var add = HostAddress.Create(ActiveRepoUri); - bool loggedIn = await connectionManager.IsLoggedIn(hosts, add); - if (!loggedIn) - { - var msg = string.Format(CultureInfo.CurrentUICulture, Resources.NotLoggedInMessage, add.Title, add.Title); - vsServices.ShowMessage( - msg, - new RelayCommand(() => StartFlow(UIControllerFlow.Authentication)) - ); - } - } - - void StartFlow(UIControllerFlow controllerFlow) - { - var uiProvider = ServiceProvider.GetExportedValue<IUIProvider>(); - var ret = uiProvider.SetupUI(controllerFlow, null); - ret.Subscribe(c => { }, () => CheckLogin().Forget()); - uiProvider.RunUI(); - } - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Base/PackageBase.cs b/src/GitHub.VisualStudio/Base/PackageBase.cs deleted file mode 100644 index aaa62a6575..0000000000 --- a/src/GitHub.VisualStudio/Base/PackageBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.ComponentModel.Design; -using System.Diagnostics; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; - -namespace GitHub.VisualStudio.Base -{ - public abstract class PackageBase : Package - { - IServiceProvider serviceProvider; - protected IServiceProvider ServiceProvider - { - get { return serviceProvider; } - set { serviceProvider = value; } - } - - protected PackageBase() - { - ServiceProvider = this; - } - - protected PackageBase(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } - } -} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs deleted file mode 100644 index 4a1eae3d12..0000000000 --- a/src/GitHub.VisualStudio/Base/TeamExplorerBase.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using GitHub.Primitives; -using NullGuard; -using GitHub.Services; - -namespace GitHub.VisualStudio.Base -{ - public abstract class TeamExplorerBase : NotificationAwareObject, IDisposable - { - internal static readonly Guid TeamExplorerConnectionsSectionId = new Guid("ef6a7a99-f01f-4c91-ad31-183c1354dd97"); - - [AllowNull] - protected IServiceProvider ServiceProvider - { - [return: AllowNull] - get; set; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - - [return: AllowNull] - public T GetService<T>() - { - Debug.Assert(ServiceProvider != null, "GetService<T> called before service provider is set"); - if (ServiceProvider == null) - return default(T); - return (T)ServiceProvider.GetService(typeof(T)); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] - [return: AllowNull] - public Ret GetService<T, Ret>() where Ret : class - { - return GetService<T>() as Ret; - } - - protected static void OpenInBrowser(Lazy<IVisualStudioBrowser> browser, Uri uri) - { - OpenInBrowser(browser.Value, uri); - } - - protected static void OpenInBrowser(IVisualStudioBrowser browser, Uri uri) - { - Debug.Assert(browser != null, "Could not create a browser helper instance."); - browser?.OpenUrl(uri); - } - } -} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs deleted file mode 100644 index eb1d4f3e62..0000000000 --- a/src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.ComponentModel.Composition; -using System.Threading.Tasks; -using GitHub.Api; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.VisualStudio.Helpers; -using NullGuard; -using Octokit; -using GitHub.Extensions; - -namespace GitHub.VisualStudio.Base -{ - public class TeamExplorerItemBase : TeamExplorerGitRepoInfo - { - readonly ISimpleApiClientFactory apiFactory; - protected ITeamExplorerServiceHolder holder; - - ISimpleApiClient simpleApiClient; - [AllowNull] - public ISimpleApiClient SimpleApiClient - { - [return: AllowNull] get { return simpleApiClient; } - set - { - if (simpleApiClient != value && value == null) - apiFactory.ClearFromCache(simpleApiClient); - simpleApiClient = value; - } - } - - protected ISimpleApiClientFactory ApiFactory => apiFactory; - - public TeamExplorerItemBase(ITeamExplorerServiceHolder holder) - { - this.holder = holder; - } - - public TeamExplorerItemBase(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder) - { - this.apiFactory = apiFactory; - this.holder = holder; - } - - public virtual void Execute() - { - } - - public virtual void Invalidate() - { - } - - protected virtual void RepoChanged() - { - var repo = ActiveRepo; - if (repo != null) - { - var uri = repo.CloneUrl; - if (uri?.RepositoryName != null) - { - ActiveRepoUri = uri; - ActiveRepoName = uri.NameWithOwner; - } - } - } - - protected async Task<bool> IsAGitHubRepo() - { - var uri = ActiveRepoUri; - if (uri == null) - return false; - - SimpleApiClient = apiFactory.Create(uri); - - var isdotcom = HostAddress.IsGitHubDotComUri(uri.ToRepositoryUrl()); - if (!isdotcom) - { - var repo = await SimpleApiClient.GetRepository(); - return (repo.FullName == ActiveRepoName || repo.Id == 0) && SimpleApiClient.IsEnterprise(); - } - return isdotcom; - } - - bool isEnabled; - public bool IsEnabled - { - get { return isEnabled; } - set { isEnabled = value; this.RaisePropertyChange(); } - } - - bool isVisible; - public bool IsVisible - { - get { return isVisible; } - set { isVisible = value; this.RaisePropertyChange(); } - } - - string text; - [AllowNull] - public string Text - { - get { return text; } - set { text = value; this.RaisePropertyChange(); } - } - - } - - [Export(typeof(IGitHubClient))] - public class GHClient : GitHubClient - { - [ImportingConstructor] - public GHClient(IProgram program) - : base(program.ProductHeader) - { - - } - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs deleted file mode 100644 index 1a1493dd24..0000000000 --- a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Diagnostics; -using System.Drawing; -using GitHub.Api; -using GitHub.Extensions; -using GitHub.Services; -using GitHub.UI; -using GitHub.VisualStudio.Helpers; -using Microsoft.TeamFoundation.Controls; -using Microsoft.VisualStudio.PlatformUI; -using NullGuard; -using GitHub.Models; - -namespace GitHub.VisualStudio.Base -{ - public class TeamExplorerNavigationItemBase : TeamExplorerItemBase, ITeamExplorerNavigationItem2 - { - readonly Octicon octicon; - - public TeamExplorerNavigationItemBase(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, Octicon octicon) - : base(apiFactory, holder) - { - this.octicon = octicon; - - IsVisible = false; - IsEnabled = true; - - OnThemeChanged(); - VSColorTheme.ThemeChanged += _ => - { - OnThemeChanged(); - Invalidate(); - }; - - holder.Subscribe(this, UpdateRepo); - } - - public override async void Invalidate() - { - IsVisible = false; - IsVisible = await IsAGitHubRepo(); - } - - void OnThemeChanged() - { - try - { - var color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey); - var brightness = color.GetBrightness(); - var dark = brightness > 0.5f; - - Icon = SharedResources.GetDrawingForIcon(octicon, dark ? Colors.DarkThemeNavigationItem : Colors.LightThemeNavigationItem, dark ? "dark" : "light"); - } - catch (ArgumentNullException) - { - // This throws in the unit test runner. - } - } - - void UpdateRepo(ISimpleRepositoryModel repo) - { - ActiveRepo = repo; - RepoChanged(); - Invalidate(); - } - - protected void OpenInBrowser(Lazy<IVisualStudioBrowser> browser, string endpoint) - { - var uri = ActiveRepoUri; - Debug.Assert(uri != null, "OpenInBrowser: uri should never be null"); -#if !DEBUG - if (uri == null) - return; -#endif - var browseUrl = uri.ToRepositoryUrl().Append(endpoint); - - OpenInBrowser(browser, browseUrl); - } - - void Unsubscribe() - { - holder.Unsubscribe(this); - } - - bool disposed; - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (!disposed) - { - Unsubscribe(); - disposed = true; - } - } - base.Dispose(disposing); - } - - int argbColor; - public int ArgbColor - { - get { return argbColor; } - set { argbColor = value; this.RaisePropertyChange(); } - } - - object icon; - [AllowNull] - public object Icon - { - [return: AllowNull] - get { return icon; } - set { icon = value; this.RaisePropertyChange(); } - } - - Image image; - [AllowNull] - public Image Image - { - [return: AllowNull] - get{ return image; } - set { image = value; this.RaisePropertyChange(); } - } - } -} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs deleted file mode 100644 index 6cc20a6936..0000000000 --- a/src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using GitHub.VisualStudio.Helpers; -using Microsoft.TeamFoundation.Controls; -using NullGuard; -using GitHub.Services; -using System.Diagnostics; -using GitHub.Api; -using GitHub.Models; -using GitHub.ViewModels; - -namespace GitHub.VisualStudio.Base -{ - public class TeamExplorerSectionBase : TeamExplorerItemBase, ITeamExplorerSection, IServiceProviderAware - { - protected IConnectionManager connectionManager; - - bool isBusy; - public bool IsBusy - { - get { return isBusy; } - set { isBusy = value; this.RaisePropertyChange(); } - } - - bool isExpanded; - public bool IsExpanded - { - get { return isExpanded; } - set { isExpanded = value; this.RaisePropertyChange(); } - } - - object sectionContent; - [AllowNull] - public object SectionContent - { - get { return sectionContent; } - set { sectionContent = value; this.RaisePropertyChange(); } - } - - string title; - [AllowNull] - public string Title - { - get { return title; } - set { title = value; this.RaisePropertyChange(); } - } - - [return: AllowNull] - public virtual object GetExtensibilityService(Type serviceType) - { - return null; - } - - public TeamExplorerSectionBase(ITeamExplorerServiceHolder holder) - : base(holder) - { - IsVisible = false; - IsEnabled = true; - IsExpanded = true; - } - - public TeamExplorerSectionBase(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder) - : base(apiFactory, holder) - { - IsVisible = false; - IsEnabled = true; - IsExpanded = true; - } - - public TeamExplorerSectionBase(ITeamExplorerServiceHolder holder, IConnectionManager cm) : this(holder) - { - connectionManager = cm; - } - - public TeamExplorerSectionBase(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, - IConnectionManager cm) : this(apiFactory, holder) - { - connectionManager = cm; - } - - void ITeamExplorerSection.Cancel() - { - } - - void ITeamExplorerSection.Initialize(object sender, SectionInitializeEventArgs e) - { - Initialize(e.ServiceProvider); - } - - public virtual void Initialize(IServiceProvider serviceProvider) - { -#if DEBUG - //VsOutputLogger.WriteLine("{0:HHmmssff}\t{1} Initialize", DateTime.Now, GetType()); -#endif - ServiceProvider = serviceProvider; - Debug.Assert(holder != null, "Could not get an instance of TeamExplorerServiceHolder"); - if (holder == null) - return; - holder.ServiceProvider = ServiceProvider; - SubscribeToRepoChanges(); -#if DEBUG - //VsOutputLogger.WriteLine("{0:HHmmssff}\t{1} Initialize DONE", DateTime.Now, GetType()); -#endif - } - - public virtual void Loaded(object sender, SectionLoadedEventArgs e) - { - } - - public virtual void Refresh() - { - } - - public virtual void SaveContext(object sender, SectionSaveContextEventArgs e) - { - } - - void SubscribeToRepoChanges() - { - holder.Subscribe(this, (ISimpleRepositoryModel repo) => - { - ActiveRepo = repo; - RepoChanged(); - }); - } - - void Unsubscribe() - { - holder.Unsubscribe(this); - holder.ClearServiceProvider(ServiceProvider); - } - - bool disposed; - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (!disposed) - { - Unsubscribe(); - disposed = true; - } - } - base.Dispose(disposing); - } - } -} diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerServiceHolder.cs b/src/GitHub.VisualStudio/Base/TeamExplorerServiceHolder.cs deleted file mode 100644 index 046b0171a7..0000000000 --- a/src/GitHub.VisualStudio/Base/TeamExplorerServiceHolder.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Diagnostics; -using GitHub.Services; -using Microsoft.TeamFoundation.Controls; -using NullGuard; -using GitHub.Extensions; -using System.ComponentModel.Composition; -using System.Collections.Generic; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; -using System.Linq; -using System.Threading; -using System.Globalization; -using GitHub.Models; - -namespace GitHub.VisualStudio.Base -{ - [Export(typeof(ITeamExplorerServiceHolder))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class TeamExplorerServiceHolder : ITeamExplorerServiceHolder - { - readonly Dictionary<object, Action<ISimpleRepositoryModel>> activeRepoHandlers = new Dictionary<object, Action<ISimpleRepositoryModel>>(); - ISimpleRepositoryModel activeRepo; - bool activeRepoNotified = false; - - IServiceProvider serviceProvider; - IGitExt gitService; - UIContext gitUIContext; - - // ActiveRepositories PropertyChanged event comes in on a non-main thread - readonly SynchronizationContext syncContext; - - public TeamExplorerServiceHolder() - { - syncContext = SynchronizationContext.Current; - } - - // set by the sections when they get initialized - [AllowNull] - public IServiceProvider ServiceProvider - { - [return: AllowNull] get { return serviceProvider; } - set - { - if (serviceProvider == value) - return; - - serviceProvider = value; - if (serviceProvider == null) - return; - GitUIContext = GitUIContext ?? UIContext.FromUIContextGuid(new Guid("11B8E6D7-C08B-4385-B321-321078CDD1F8")); - UIContextChanged(GitUIContext?.IsActive ?? false, false); - } - } - - [AllowNull] - public ISimpleRepositoryModel ActiveRepo - { - [return: AllowNull] get { return activeRepo; } - private set - { - if (activeRepo == value) - return; - if (activeRepo != null) - activeRepo.PropertyChanged -= ActiveRepoPropertyChanged; - activeRepo = value; - if (activeRepo != null) - activeRepo.PropertyChanged += ActiveRepoPropertyChanged; - NotifyActiveRepo(); - } - } - - public void Subscribe(object who, Action<ISimpleRepositoryModel> handler) - { - bool notificationsExist; - ISimpleRepositoryModel repo; - lock(activeRepoHandlers) - { - repo = ActiveRepo; - notificationsExist = activeRepoNotified; - if (!activeRepoHandlers.ContainsKey(who)) - activeRepoHandlers.Add(who, handler); - else - activeRepoHandlers[who] = handler; - } - - // the repo url might have changed and we don't get notifications - // for that, so this is a good place to refresh it in case that happened - repo?.Refresh(); - - // if the active repo hasn't changed and there's notifications queued up, - // notify the subscriber. If the repo has changed, the set above will trigger - // notifications so we don't have to do it here. - if (repo == ActiveRepo && notificationsExist) - handler(repo); - } - - public void Unsubscribe(object who) - { - if (activeRepoHandlers.ContainsKey(who)) - activeRepoHandlers.Remove(who); - } - - /// <summary> - /// Clears the current ServiceProvider if it matches the one that is passed in. - /// This is usually called on Dispose, which might happen after another section - /// has changed the ServiceProvider to something else, which is why we require - /// the parameter to match. - /// </summary> - /// <param name="provider">If the current ServiceProvider matches this, clear it</param> - public void ClearServiceProvider(IServiceProvider provider) - { - if (serviceProvider != provider) - return; - - ServiceProvider = null; - } - - public void Refresh() - { - GitUIContext = GitUIContext ?? UIContext.FromUIContextGuid(new Guid("11B8E6D7-C08B-4385-B321-321078CDD1F8")); - UIContextChanged(GitUIContext?.IsActive ?? false, true); - } - - void NotifyActiveRepo() - { - lock (activeRepoHandlers) - { - activeRepoNotified = true; - foreach (var handler in activeRepoHandlers.Values) - handler(activeRepo); - } - } - - void UIContextChanged(object sender, UIContextChangedEventArgs e) - { - ActiveRepo = null; - UIContextChanged(e.Activated, false); - } - - async void UIContextChanged(bool active, bool refresh) - { - Debug.Assert(ServiceProvider != null, "UIContextChanged called before service provider is set"); - if (ServiceProvider == null) - return; - - if (active) - { - GitService = GitService ?? ServiceProvider.GetService<IGitExt>(); - if (ActiveRepo == null || refresh) - ActiveRepo = await System.Threading.Tasks.Task.Run(() => - { - var repos = GitService?.ActiveRepositories; - // Looks like this might return null after a while, for some unknown reason - // if it does, let's refresh the GitService instance in case something got wonky - // and try again. See issue #23 - if (repos == null) - { - VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2001: ActiveRepositories is null. GitService: '{0}'", GitService)); - GitService = ServiceProvider?.GetService<IGitExt>(); - repos = GitService?.ActiveRepositories; - if (repos == null) - VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2002: ActiveRepositories is null. GitService: '{0}'", GitService)); - } - return repos?.FirstOrDefault()?.ToModel(); - }); - } - else - ActiveRepo = null; - } - - void CheckAndUpdate(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName != "ActiveRepositories") - return; - - var service = GitService; - if (service == null) - return; - - var repo = service.ActiveRepositories.FirstOrDefault()?.ToModel(); - if (repo != ActiveRepo) - // so annoying that this is on the wrong thread - syncContext.Post(r => ActiveRepo = r as ISimpleRepositoryModel, repo); - } - - void ActiveRepoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == "CloneUrl") - ActiveRepo = sender as ISimpleRepositoryModel; - } - - public IGitAwareItem HomeSection - { - [return:AllowNull] - get - { - if (ServiceProvider == null) - return null; - var page = PageService; - if (page == null) - return null; - return page.GetSection(new Guid(TeamExplorer.Home.GitHubHomeSection.GitHubHomeSectionId)) as IGitAwareItem; - } - } - - ITeamExplorerPage PageService - { - [return:AllowNull] - get { return ServiceProvider.GetService<ITeamExplorerPage>(); } - } - - [AllowNull] - UIContext GitUIContext - { - [return: AllowNull] - get { return gitUIContext; } - set - { - if (gitUIContext == value) - return; - if (gitUIContext != null) - gitUIContext.UIContextChanged -= UIContextChanged; - gitUIContext = value; - if (gitUIContext != null) - gitUIContext.UIContextChanged += UIContextChanged; - } - } - - [AllowNull] - IGitExt GitService - { - [return: AllowNull] - get { return gitService; } - set - { - if (gitService == value) - return; - if (gitService != null) - gitService.PropertyChanged -= CheckAndUpdate; - gitService = value; - if (gitService != null) - gitService.PropertyChanged += CheckAndUpdate; - } - } - } -} diff --git a/src/GitHub.VisualStudio/Commands/AddConnectionCommand.cs b/src/GitHub.VisualStudio/Commands/AddConnectionCommand.cs new file mode 100644 index 0000000000..5ac44b7c23 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/AddConnectionCommand.cs @@ -0,0 +1,43 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Services; +using GitHub.Services.Vssdk.Commands; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Opens the login dialog to add a new connection to Team Explorer. + /// </summary> + [Export(typeof(IAddConnectionCommand))] + public class AddConnectionCommand : VsCommand, IAddConnectionCommand + { + readonly Lazy<IDialogService> dialogService; + + [ImportingConstructor] + protected AddConnectionCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId) + { + dialogService = new Lazy<IDialogService>(() => serviceProvider.TryGetService<IDialogService>()); + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.addConnectionCommand; + + /// <summary> + /// Shows the login dialog. + /// </summary> + public override Task Execute() + { + return dialogService.Value.ShowLoginDialog(); + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/BlameLinkCommand.cs b/src/GitHub.VisualStudio/Commands/BlameLinkCommand.cs new file mode 100644 index 0000000000..0d55d951be --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/BlameLinkCommand.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Exports; +using GitHub.Services; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Opens the blame view for the currently selected text on GitHub.com or an Enterprise + /// instance. + /// </summary> + [Export(typeof(IBlameLinkCommand))] + public class BlameLinkCommand : LinkCommandBase, IBlameLinkCommand + { + [ImportingConstructor] + protected BlameLinkCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId, serviceProvider) + { + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidContextMenuSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.blameCommand; + + /// <summary> + /// Opens the blame link in the browser. + /// </summary> + public async override Task Execute() + { + var isgithub = await IsGitHubRepo(); + if (!isgithub) + return; + + var link = await GenerateLink(LinkType.Blame); + if (link == null) + return; + var browser = ServiceProvider.TryGetService<IVisualStudioBrowser>(); + browser?.OpenUrl(link.ToUri()); + + await UsageTracker.IncrementCounter(x => x.NumberOfOpenInGitHub); + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/CopyLinkCommand.cs b/src/GitHub.VisualStudio/Commands/CopyLinkCommand.cs new file mode 100644 index 0000000000..c01aedfdfc --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/CopyLinkCommand.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using System.Windows; +using GitHub.Commands; +using GitHub.Exports; +using GitHub.Services; +using GitHub.VisualStudio.UI; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Copies a link to the clipboard of the currently selected text on GitHub.com or an + /// Enterprise instance. + /// </summary> + [Export(typeof(ICopyLinkCommand))] + public class CopyLinkCommand : LinkCommandBase, ICopyLinkCommand + { + [ImportingConstructor] + protected CopyLinkCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId, serviceProvider) + { + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidContextMenuSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.copyLinkCommand; + + /// <summary> + /// Copies the link to the clipboard. + /// </summary> + public async override Task Execute() + { + var isgithub = await IsGitHubRepo(); + if (!isgithub) + return; + + var link = await GenerateLink(LinkType.Blob); + if (link == null) + return; + try + { + Clipboard.SetText(link); + var ns = ServiceProvider.TryGetService<IStatusBarNotificationService>(); + ns?.ShowMessage(Resources.LinkCopiedToClipboardMessage); + await UsageTracker.IncrementCounter(x => x.NumberOfLinkToGitHub); + } + catch + { + var ns = ServiceProvider.TryGetService<IStatusBarNotificationService>(); + ns?.ShowMessage(Resources.Error_FailedToCopyToClipboard); + } + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/CreateGistCommand.cs b/src/GitHub.VisualStudio/Commands/CreateGistCommand.cs new file mode 100644 index 0000000000..1fb55e2b7d --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/CreateGistCommand.cs @@ -0,0 +1,135 @@ +using System; +using System.Linq; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Commands; +using GitHub.Logging; +using GitHub.Services; +using GitHub.Extensions; +using GitHub.Services.Vssdk.Commands; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Creates a GitHub Gist from the currently selected text. + /// </summary> + [Export(typeof(ICreateGistCommand))] + public class CreateGistCommand : CreateGistCommandBase, ICreateGistCommand + { + [ImportingConstructor] + protected CreateGistCommand( + Lazy<IDialogService> dialogService, + Lazy<ISelectedTextProvider> selectedTextProvider, + Lazy<IConnectionManager> connectionManager) + : base(CommandSet, CommandId, dialogService, selectedTextProvider, connectionManager, true, + isNotLoggedInDefault: true) + { + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidContextMenuSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.createGistCommand; + } + + /// <summary> + /// Creates a GitHub Enterprise Gist from the currently selected text. + /// </summary> + [Export(typeof(ICreateGistEnterpriseCommand))] + public class CreateGistEnterpriseCommand : CreateGistCommandBase, ICreateGistEnterpriseCommand + { + [ImportingConstructor] + protected CreateGistEnterpriseCommand( + Lazy<IDialogService> dialogService, + Lazy<ISelectedTextProvider> selectedTextProvider, + Lazy<IConnectionManager> connectionManager) + : base(CommandSet, CommandId, dialogService, selectedTextProvider, connectionManager, false) + { + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidContextMenuSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.createGistEnterpriseCommand; + } + + /// <summary> + /// Creates a GitHub or GitHub Enterprise Gist from the currently selected text. + /// </summary> + public abstract class CreateGistCommandBase : VsCommand + { + readonly bool isGitHubDotCom; + readonly bool isNotLoggedInDefault; + readonly Lazy<IDialogService> dialogService; + readonly Lazy<ISelectedTextProvider> selectedTextProvider; + readonly Lazy<IConnectionManager> connectionManager; + + protected CreateGistCommandBase( + Guid commandSet, int commandId, + Lazy<IDialogService> dialogService, + Lazy<ISelectedTextProvider> selectedTextProvider, + Lazy<IConnectionManager> connectionManager, + bool isGitHubDotCom, + bool isNotLoggedInDefault = false) + : base(commandSet, commandId) + { + this.dialogService = dialogService; + this.selectedTextProvider = selectedTextProvider; + this.connectionManager = connectionManager; + this.isGitHubDotCom = isGitHubDotCom; + this.isNotLoggedInDefault = isNotLoggedInDefault; + } + + ISelectedTextProvider SelectedTextProvider => selectedTextProvider.Value; + + /// <summary> + /// Shows the Create Gist dialog. + /// </summary> + public override async Task Execute() + { + var connection = await FindConnectionAsync(); + await dialogService.Value.ShowCreateGist(connection); + } + + protected override void QueryStatus() + { + Log.Assert(SelectedTextProvider != null, "Could not get an instance of ISelectedTextProvider"); + Visible = !string.IsNullOrWhiteSpace(SelectedTextProvider?.GetSelectedText()) && + (HasConnection() || isNotLoggedInDefault && IsLoggedIn() == false); + } + + bool HasConnection() + { + var task = FindConnectionAsync(); + return task.IsCompleted && task.Result != null; + } + + async Task<IConnection> FindConnectionAsync() + { + var connections = await connectionManager.Value.GetLoadedConnections(); + return connections.FirstOrDefault(x => x.IsLoggedIn && x.HostAddress.IsGitHubDotCom() == isGitHubDotCom); + } + + bool? IsLoggedIn() + { + var task = connectionManager.Value.IsLoggedIn(); + if (task.IsCompleted) + { + return task.Result; + } + + return null; + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/GoToSolutionOrPullRequestFileCommand.cs b/src/GitHub.VisualStudio/Commands/GoToSolutionOrPullRequestFileCommand.cs new file mode 100644 index 0000000000..0ec4ed1bfd --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/GoToSolutionOrPullRequestFileCommand.cs @@ -0,0 +1,310 @@ +using System; +using System.IO; +using System.ComponentModel.Composition; +using GitHub.Services; +using GitHub.Extensions; +using GitHub.VisualStudio; +using GitHub.Services.Vssdk.Commands; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.TextManager.Interop; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Commands +{ + /// <summary> + /// Navigate from a PR file to the equivalent file and location in the editor (or the reverse). + /// </summary> + /// <remarks> + /// This command will do one of the following depending on context. + /// Navigate from PR file diff to the working file in the solution. + /// Navigate from the working file in the solution to the PR file diff. + /// Navigate from an editable diff (e.g. 'View Changes in Solution') to the editor view. + /// </remarks> + [Export(typeof(IGoToSolutionOrPullRequestFileCommand))] + public class GoToSolutionOrPullRequestFileCommand : VsCommand, IGoToSolutionOrPullRequestFileCommand + { + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidContextMenuSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.goToSolutionOrPullRequestFileCommand; + + readonly IGitHubServiceProvider serviceProvider; + readonly Lazy<IVsEditorAdaptersFactoryService> editorAdapter; + readonly Lazy<IPullRequestSessionManager> sessionManager; + readonly Lazy<IPullRequestEditorService> pullRequestEditorService; + readonly Lazy<ITeamExplorerContext> teamExplorerContext; + readonly Lazy<IGitHubContextService> gitHubContextService; + readonly Lazy<IStatusBarNotificationService> statusBar; + readonly Lazy<IUsageTracker> usageTracker; + + [ImportingConstructor] + public GoToSolutionOrPullRequestFileCommand( + IGitHubServiceProvider serviceProvider, + Lazy<IVsEditorAdaptersFactoryService> editorAdapter, + Lazy<IPullRequestSessionManager> sessionManager, + Lazy<IPullRequestEditorService> pullRequestEditorService, + Lazy<ITeamExplorerContext> teamExplorerContext, + Lazy<IGitHubContextService> gitHubContextService, + Lazy<IStatusBarNotificationService> statusBar, + Lazy<IUsageTracker> usageTracker) : base(CommandSet, CommandId) + { + this.serviceProvider = serviceProvider; + this.editorAdapter = editorAdapter; + this.sessionManager = sessionManager; + this.pullRequestEditorService = pullRequestEditorService; + this.gitHubContextService = gitHubContextService; + this.teamExplorerContext = teamExplorerContext; + this.statusBar = statusBar; + this.usageTracker = usageTracker; + + BeforeQueryStatus += OnBeforeQueryStatus; + } + + public override async Task Execute() + { + usageTracker.Value.IncrementCounter(x => x.ExecuteGoToSolutionOrPullRequestFileCommand).Forget(); + + try + { + var sourceView = pullRequestEditorService.Value.FindActiveView(); + if (sourceView == null) + { + ShowErrorInStatusBar("Couldn't find active source view"); + return; + } + + var textView = editorAdapter.Value.GetWpfTextView(sourceView); + + bool isEditableDiff = pullRequestEditorService.Value.IsEditableDiff(textView); + if (isEditableDiff) + { + // Navigating from editable diff to code view + await usageTracker.Value.IncrementCounter(x => x.NumberOfNavigateToCodeView); + + // Open active document in Code View + pullRequestEditorService.Value.OpenActiveDocumentInCodeView(sourceView); + return; + } + + var info = sessionManager.Value.GetTextBufferInfo(textView.TextBuffer); + if (info != null) + { + if (!info.Session.IsCheckedOut) + { + ShowInfoMessage(App.Resources.NavigateToEditorNotCheckedOutInfoMessage); + return; + } + + // Navigate from PR file to solution + await usageTracker.Value.IncrementCounter(x => x.NumberOfPRDetailsNavigateToEditor); + + var fileTextView = await pullRequestEditorService.Value.OpenFile(info.Session, info.RelativePath, true); + if (fileTextView == null) + { + ShowErrorInStatusBar("Couldn't find active target view"); + return; + } + + var fileView = editorAdapter.Value.GetViewAdapter(fileTextView); + pullRequestEditorService.Value.NavigateToEquivalentPosition(sourceView, fileView); + return; + } + + if (TryNavigateFromHistoryFile(sourceView)) + { + return; + } + + var relativePath = sessionManager.Value.GetRelativePath(textView.TextBuffer); + if (relativePath == null) + { + ShowErrorInStatusBar("File isn't part of repository"); + return; + } + + var session = sessionManager.Value.CurrentSession; + if (session == null) + { + ShowErrorInStatusBar("Couldn't find Pull request session"); + return; + } + + // Navigate from solution file to PR diff file + await usageTracker.Value.IncrementCounter(x => x.NumberOfNavigateToPullRequestFileDiff); + + var diffViewer = await pullRequestEditorService.Value.OpenDiff(session, relativePath, "HEAD", scrollToFirstDiff: false); + if (diffViewer == null) + { + ShowErrorInStatusBar("Couldn't find active diff viewer"); + return; + } + + var diffTextView = FindActiveTextView(diffViewer); + var diffView = editorAdapter.Value.GetViewAdapter(diffTextView); + pullRequestEditorService.Value.NavigateToEquivalentPosition(sourceView, diffView); + } + catch (Exception e) + { + ShowErrorInStatusBar("Error Navigating", e); + } + } + + void OnBeforeQueryStatus(object sender, EventArgs e) + { + try + { + var sourceView = pullRequestEditorService.Value.FindActiveView(); + if (sourceView == null) + { + // No active text view + Visible = false; + return; + } + + var textView = editorAdapter.Value.GetWpfTextView(sourceView); + + if (pullRequestEditorService.Value.IsEditableDiff(textView)) + { + // Active text view is editable diff + Text = "Open File in Code View"; + Visible = true; + return; + } + + var info = sessionManager.Value.GetTextBufferInfo(textView.TextBuffer); + if (info != null) + { + // Active text view is a PR file + Text = "Open File in Solution"; + Visible = true; + return; + } + + var session = sessionManager.Value.CurrentSession; + if (session != null) + { + var relativePath = sessionManager.Value.GetRelativePath(textView.TextBuffer); + if (relativePath != null) + { + // Active text view is part of a repository + Text = "View Changes in #" + session.PullRequest.Number; + Visible = true; + return; + } + } + + if (TryNavigateFromHistoryFileQueryStatus(sourceView)) + { + return; + } + } + catch (Exception ex) + { + ShowErrorInStatusBar("Error QueryStatus", ex); + } + + Visible = false; + } + + // Set command Text/Visible properties and return true when active + bool TryNavigateFromHistoryFileQueryStatus(IVsTextView sourceView) + { + if (teamExplorerContext.Value.ActiveRepository?.LocalPath is string && // Check there is an active repo + FindObjectishForTFSTempFile(sourceView) is string) // Looks like a history file + { + // Navigate from history file is active + Text = "Open File in Solution"; + Visible = true; + return true; + } + + return false; + } + + // Attempt navigation to historical file + bool TryNavigateFromHistoryFile(IVsTextView sourceView) + { + if (teamExplorerContext.Value.ActiveRepository?.LocalPath is string repositoryDir && + FindObjectishForTFSTempFile(sourceView) is string objectish) + { + var (commitSha, blobPath) = gitHubContextService.Value.ResolveBlobFromHistory(repositoryDir, objectish); + if (blobPath is string) + { + var workingFile = Path.Combine(repositoryDir, blobPath); + VsShellUtilities.OpenDocument(serviceProvider, workingFile, VSConstants.LOGVIEWID.TextView_guid, + out IVsUIHierarchy hierarchy, out uint itemID, out IVsWindowFrame windowFrame, out IVsTextView targetView); + + pullRequestEditorService.Value.NavigateToEquivalentPosition(sourceView, targetView); + return true; + } + } + + return false; + } + + // Find the blob SHA in a file name if any + string FindObjectishForTFSTempFile(IVsTextView sourceView) + { + return + FindPath(sourceView) is string path && + gitHubContextService.Value.FindObjectishForTFSTempFile(path) is string objectish ? + objectish : null; + } + + // See http://microsoft.public.vstudio.extensibility.narkive.com/agfoD1GO/full-pathname-of-file-shown-in-current-view-of-core-editor#post2 + static string FindPath(IVsTextView textView) + { + ErrorHandler.ThrowOnFailure(textView.GetBuffer(out IVsTextLines buffer)); + var userData = buffer as IVsUserData; + if (userData == null) + { + return null; + } + + ErrorHandler.ThrowOnFailure(userData.GetData(typeof(IVsUserData).GUID, out object data)); + return data as string; + } + + ITextView FindActiveTextView(IDifferenceViewer diffViewer) + { + switch (diffViewer.ActiveViewType) + { + case DifferenceViewType.InlineView: + return diffViewer.InlineView; + case DifferenceViewType.LeftView: + return diffViewer.LeftView; + case DifferenceViewType.RightView: + return diffViewer.RightView; + } + + return null; + } + + void ShowInfoMessage(string message) + { + ErrorHandler.ThrowOnFailure(VsShellUtilities.ShowMessageBox( + serviceProvider, message, null, + OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST)); + } + + void ShowErrorInStatusBar(string message) + { + statusBar.Value.ShowMessage(message); + } + + void ShowErrorInStatusBar(string message, Exception e) + { + statusBar.Value.ShowMessage(message + ": " + e.Message); + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/LinkCommandBase.cs b/src/GitHub.VisualStudio/Commands/LinkCommandBase.cs new file mode 100644 index 0000000000..7326e306b8 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/LinkCommandBase.cs @@ -0,0 +1,164 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Exports; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Services.Vssdk.Commands; +using Serilog; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Base class for commands that produce a link to GitHub.com or an Enterprise instance. + /// </summary> + public abstract class LinkCommandBase : VsCommand + { + static readonly ILogger log = LogManager.ForContext<LinkCommandBase>(); + readonly Lazy<ISimpleApiClientFactory> apiFactory; + readonly Lazy<IUsageTracker> usageTracker; + + protected LinkCommandBase( + Guid commandSet, + int commandId, + IGitHubServiceProvider serviceProvider) + : base(commandSet, commandId) + { + ServiceProvider = serviceProvider; + apiFactory = new Lazy<ISimpleApiClientFactory>(() => ServiceProvider.TryGetService<ISimpleApiClientFactory>()); + usageTracker = new Lazy<IUsageTracker>(() => serviceProvider.TryGetService<IUsageTracker>()); + } + + protected ILocalRepositoryModel ActiveRepo { get; private set; } + protected ISimpleApiClientFactory ApiFactory => apiFactory.Value; + protected IGitHubServiceProvider ServiceProvider { get; } + protected IUsageTracker UsageTracker => usageTracker.Value; + + public abstract override Task Execute(); + + protected ILocalRepositoryModel GetRepositoryByPath(string path) + { + try + { + if (!string.IsNullOrEmpty(path)) + { + var repo = ServiceProvider.TryGetService<IGitService>().GetRepository(path); + return new LocalRepositoryModel(repo.Info.WorkingDirectory.TrimEnd('\\'), GitService.GitServiceHelper); + } + } + catch (Exception ex) + { + log.Error(ex, "Error loading the repository from '{Path}'", path); + } + + return null; + } + + protected ILocalRepositoryModel GetActiveRepo() + { + var activeRepo = ServiceProvider.TryGetService<ITeamExplorerServiceHolder>()?.ActiveRepo; + // activeRepo can be null at this point because it is set elsewhere as the result of async operation that may not have completed yet. + if (activeRepo == null) + { + var path = ServiceProvider.TryGetService<IVSGitServices>()?.GetActiveRepoPath() ?? String.Empty; + try + { + activeRepo = !string.IsNullOrEmpty(path) ? new LocalRepositoryModel(path, GitService.GitServiceHelper) : null; + } + catch (Exception ex) + { + log.Error(ex, "Error loading the repository from '{Path}'", path); + } + } + return activeRepo; + } + + void RefreshRepo() + { + ActiveRepo = ServiceProvider.TryGetService<ITeamExplorerServiceHolder>().ActiveRepo; + + if (ActiveRepo == null) + { + var vsGitServices = ServiceProvider.TryGetService<IVSGitServices>(); + string path = vsGitServices?.GetActiveRepoPath() ?? String.Empty; + try + { + ActiveRepo = !String.IsNullOrEmpty(path) ? new LocalRepositoryModel(path, GitService.GitServiceHelper) : null; + } + catch (Exception ex) + { + log.Error(ex, "Error loading the repository from '{Path}'", path); + } + } + } + + protected async Task<bool> IsGitHubRepo() + { + RefreshRepo(); + + var uri = ActiveRepo?.CloneUrl; + if (uri == null) + return false; + + var simpleApiClient = await ApiFactory.Create(uri); + + var isdotcom = HostAddress.IsGitHubDotComUri(uri.ToRepositoryUrl()); + if (!isdotcom) + { + var repo = await simpleApiClient.GetRepository(); + var activeRepoFullName = ActiveRepo.Owner + '/' + ActiveRepo.Name; + return (repo.FullName == activeRepoFullName || repo.Id == 0) && await simpleApiClient.IsEnterprise(); + } + return isdotcom; + } + + protected async Task<bool> IsCurrentFileInGitHubRepository() + { + if (!await IsGitHubRepo()) + return false; + + var activeDocument = ServiceProvider.TryGetService<IActiveDocumentSnapshot>(); + + return activeDocument != null && + IsFileDescendantOfDirectory(activeDocument.Name, ActiveRepo.LocalPath); + } + + protected Task<UriString> GenerateLink(LinkType linkType) + { + var activeDocument = ServiceProvider.TryGetService<IActiveDocumentSnapshot>(); + if (activeDocument == null) + return null; + + var repo = GetRepositoryByPath(activeDocument.Name); + + return repo.GenerateUrl(linkType, activeDocument.Name, activeDocument.StartLine, activeDocument.EndLine); + } + + protected override void QueryStatus() + { + var githubRepoCheckTask = IsCurrentFileInGitHubRepository(); + Visible = githubRepoCheckTask.Wait(250) ? githubRepoCheckTask.Result : false; + } + + // Taken from http://stackoverflow.com/a/26012991/6448 + public static bool IsFileDescendantOfDirectory(string file, string directory) + { + var fileInfo = new FileInfo(file); + var directoryInfo = new DirectoryInfo(directory); + + // https://connect.microsoft.com/VisualStudio/feedback/details/777308/inconsistent-behavior-of-fullname-when-provided-path-ends-with-a-backslash + string path = directoryInfo.FullName.TrimEnd(Path.DirectorySeparatorChar); + DirectoryInfo dir = fileInfo.Directory; + while (dir != null) + { + if (dir.FullName.TrimEnd(Path.DirectorySeparatorChar).Equals(path, StringComparison.OrdinalIgnoreCase)) + return true; + dir = dir.Parent; + } + return false; + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/OpenFromClipboardCommand.cs b/src/GitHub.VisualStudio/Commands/OpenFromClipboardCommand.cs new file mode 100644 index 0000000000..7e824c62a1 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/OpenFromClipboardCommand.cs @@ -0,0 +1,123 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Commands; +using GitHub.Exports; +using GitHub.Services; +using GitHub.Services.Vssdk.Commands; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.VisualStudio.Commands +{ + [Export(typeof(IOpenFromClipboardCommand))] + public class OpenFromClipboardCommand : VsCommand<string>, IOpenFromClipboardCommand + { + public const string NoGitHubUrlMessage = "Couldn't a find a GitHub URL in clipboard"; + public const string NoResolveSameOwnerMessage = "Couldn't find target URL in current repository. Try again after doing a fetch."; + public const string NoResolveDifferentOwnerMessage = "The target URL has a different owner to the current repository."; + public const string NoActiveRepositoryMessage = "There is no active repository to navigate"; + public const string ChangesInWorkingDirectoryMessage = "This file has changed since the permalink was created"; + public const string DifferentRepositoryMessage = "Please open the repository '{0}' and try again"; + public const string UnknownLinkTypeMessage = "Couldn't open from '{0}'. Only URLs that link to repository files are currently supported."; + + readonly Lazy<IGitHubContextService> gitHubContextService; + readonly Lazy<ITeamExplorerContext> teamExplorerContext; + readonly Lazy<IVSServices> vsServices; + readonly Lazy<UIContext> uiContext; + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.openFromClipboardCommand; + + [ImportingConstructor] + public OpenFromClipboardCommand( + Lazy<IGitHubContextService> gitHubContextService, + Lazy<ITeamExplorerContext> teamExplorerContext, + Lazy<IVSServices> vsServices) + : base(CommandSet, CommandId) + { + this.gitHubContextService = gitHubContextService; + this.teamExplorerContext = teamExplorerContext; + this.vsServices = vsServices; + + // See https://code.msdn.microsoft.com/windowsdesktop/AllowParams-2005-9442298f + ParametersDescription = "u"; // accept a single url + + // This command is only visible when in the context of a Git repository + uiContext = new Lazy<UIContext>(() => UIContext.FromUIContextGuid(new Guid(Guids.UIContext_Git))); + } + + public override async Task Execute(string url) + { + var context = gitHubContextService.Value.FindContextFromClipboard(); + if (context == null) + { + vsServices.Value.ShowMessageBoxInfo(NoGitHubUrlMessage); + return; + } + + if (context.LinkType != LinkType.Blob) + { + var message = string.Format(UnknownLinkTypeMessage, context.Url); + vsServices.Value.ShowMessageBoxInfo(message); + return; + } + + var activeRepository = teamExplorerContext.Value.ActiveRepository; + var repositoryDir = activeRepository?.LocalPath; + if (repositoryDir == null) + { + vsServices.Value.ShowMessageBoxInfo(NoActiveRepositoryMessage); + return; + } + + if (!string.Equals(activeRepository.Name, context.RepositoryName, StringComparison.OrdinalIgnoreCase)) + { + vsServices.Value.ShowMessageBoxInfo(string.Format(DifferentRepositoryMessage, context.RepositoryName)); + return; + } + + var (commitish, path, isSha) = gitHubContextService.Value.ResolveBlob(repositoryDir, context); + if (path == null) + { + if (!string.Equals(activeRepository.Owner, context.Owner, StringComparison.OrdinalIgnoreCase)) + { + vsServices.Value.ShowMessageBoxInfo(NoResolveDifferentOwnerMessage); + } + else + { + vsServices.Value.ShowMessageBoxInfo(NoResolveSameOwnerMessage); + } + + return; + } + + var hasChanges = gitHubContextService.Value.HasChangesInWorkingDirectory(repositoryDir, commitish, path); + if (hasChanges) + { + // AnnotateFile expects a branch name so we use the current branch + var branchName = activeRepository.CurrentBranch.Name; + + if (await gitHubContextService.Value.TryAnnotateFile(repositoryDir, branchName, context)) + { + return; + } + + vsServices.Value.ShowMessageBoxInfo(ChangesInWorkingDirectoryMessage); + } + + gitHubContextService.Value.TryOpenFile(repositoryDir, context); + } + + protected override void QueryStatus() + { + Visible = uiContext.Value.IsActive; + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/OpenFromUrlCommand.cs b/src/GitHub.VisualStudio/Commands/OpenFromUrlCommand.cs new file mode 100644 index 0000000000..23e64442e5 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/OpenFromUrlCommand.cs @@ -0,0 +1,191 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.ComponentModel.Composition; +using GitHub.Commands; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using GitHub.Services.Vssdk.Commands; +using EnvDTE; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; +using SVsServiceProvider = Microsoft.VisualStudio.Shell.SVsServiceProvider; + +namespace GitHub.VisualStudio.Commands +{ + [Export(typeof(IOpenFromUrlCommand))] + public class OpenFromUrlCommand : VsCommand<string>, IOpenFromUrlCommand + { + readonly Lazy<IGitHubContextService> gitHubContextService; + readonly Lazy<IRepositoryCloneService> repositoryCloneService; + readonly Lazy<IPullRequestEditorService> pullRequestEditorService; + readonly Lazy<ITeamExplorerContext> teamExplorerContext; + readonly Lazy<IGitHubToolWindowManager> gitHubToolWindowManager; + readonly Lazy<DTE> dte; + readonly IServiceProvider serviceProvider; + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.openFromUrlCommand; + + [ImportingConstructor] + public OpenFromUrlCommand( + Lazy<IGitHubContextService> gitHubContextService, + Lazy<IRepositoryCloneService> repositoryCloneService, + Lazy<IPullRequestEditorService> pullRequestEditorService, + Lazy<ITeamExplorerContext> teamExplorerContext, + [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) : + base(CommandSet, CommandId) + { + this.gitHubContextService = gitHubContextService; + this.repositoryCloneService = repositoryCloneService; + this.pullRequestEditorService = pullRequestEditorService; + this.teamExplorerContext = teamExplorerContext; + this.serviceProvider = serviceProvider; + dte = new Lazy<DTE>(() => (DTE)serviceProvider.GetService(typeof(DTE))); + gitHubToolWindowManager = new Lazy<IGitHubToolWindowManager>( + () => (IGitHubToolWindowManager)serviceProvider.GetService(typeof(IGitHubToolWindowManager))); + + // See https://code.msdn.microsoft.com/windowsdesktop/AllowParams-2005-9442298f + ParametersDescription = "u"; // accept a single url + } + + public override async Task Execute(string url) + { + var context = string.IsNullOrEmpty(url) ? null : gitHubContextService.Value.FindContextFromUrl(url); + if (context == null) + { + context = gitHubContextService.Value.FindContextFromClipboard(); + } + + if (context == null) + { + // Couldn't find a URL to open + return; + } + + var activeDir = teamExplorerContext.Value.ActiveRepository?.LocalPath; + if (activeDir != null) + { + // Try opening file in current context + if (gitHubContextService.Value.TryOpenFile(activeDir, context)) + { + return; + } + } + + // Keep repos in unique dir while testing + var defaultSubPath = "GitHubCache"; + + var cloneUrl = gitHubContextService.Value.ToRepositoryUrl(context).ToString(); + var targetDir = Path.Combine(repositoryCloneService.Value.DefaultClonePath, defaultSubPath, context.Owner); + var repositoryDirName = context.RepositoryName; + var repositoryDir = Path.Combine(targetDir, repositoryDirName); + + if (!Directory.Exists(repositoryDir)) + { + var result = ShowInfoMessage($"Clone {cloneUrl} to '{repositoryDir}'?"); + switch (result) + { + case VSConstants.MessageBoxResult.IDYES: + await repositoryCloneService.Value.CloneRepository(cloneUrl, repositoryDirName, targetDir); + // Open the cloned repository + dte.Value.ExecuteCommand("File.OpenFolder", repositoryDir); + dte.Value.ExecuteCommand("View.TfsTeamExplorer"); + break; + case VSConstants.MessageBoxResult.IDNO: + // Target the current solution + repositoryDir = FindSolutionDirectory(dte.Value.Solution); + if (repositoryDir == null) + { + // No current solution to use + return; + } + + break; + case VSConstants.MessageBoxResult.IDCANCEL: + return; + } + } + + var solutionDir = FindSolutionDirectory(dte.Value.Solution); + if (solutionDir == null || !ContainsDirectory(repositoryDir, solutionDir)) + { + var result = ShowInfoMessage($"Open repository at '{repositoryDir}'?"); + switch (result) + { + case VSConstants.MessageBoxResult.IDYES: + // Open if current solution isn't in repository directory + dte.Value.ExecuteCommand("File.OpenFolder", repositoryDir); + dte.Value.ExecuteCommand("View.TfsTeamExplorer"); + break; + case VSConstants.MessageBoxResult.IDNO: + break; + case VSConstants.MessageBoxResult.IDCANCEL: + return; + } + } + + await TryOpenPullRequest(context); + gitHubContextService.Value.TryOpenFile(repositoryDir, context); + } + + VSConstants.MessageBoxResult ShowInfoMessage(string message) + { + return (VSConstants.MessageBoxResult)VsShellUtilities.ShowMessageBox(serviceProvider, message, null, + OLEMSGICON.OLEMSGICON_QUERY, OLEMSGBUTTON.OLEMSGBUTTON_YESNOCANCEL, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + + static bool ContainsDirectory(string repositoryDir, string solutionDir) + { + if (solutionDir.Equals(repositoryDir, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (solutionDir.StartsWith(repositoryDir + '\\', StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + + static string FindSolutionDirectory(Solution solution) + { + var solutionPath = solution.FileName; + if (File.Exists(solutionPath)) + { + return Path.GetDirectoryName(solutionPath); + } + + if (Directory.Exists(solutionPath)) + { + return solutionPath; + } + + return null; + } + + async Task<bool> TryOpenPullRequest(GitHubContext context) + { + var pullRequest = context.PullRequest; + if (pullRequest == null) + { + return false; + } + + var host = await gitHubToolWindowManager.Value.ShowGitHubPane(); + await host.ShowPullRequest(context.Owner, context.RepositoryName, pullRequest.Value); + return true; + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/OpenLinkCommand.cs b/src/GitHub.VisualStudio/Commands/OpenLinkCommand.cs new file mode 100644 index 0000000000..5d5ddc7142 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/OpenLinkCommand.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Exports; +using GitHub.Services; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Opens the currently selected text on GitHub.com or an Enterprise instance. + /// </summary> + [Export(typeof(IOpenLinkCommand))] + public class OpenLinkCommand : LinkCommandBase, IOpenLinkCommand + { + [ImportingConstructor] + protected OpenLinkCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId, serviceProvider) + { + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidContextMenuSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.openLinkCommand; + + /// <summary> + /// Opens the link in the browser. + /// </summary> + public async override Task Execute() + { + var isgithub = await IsGitHubRepo(); + if (!isgithub) + return; + + var link = await GenerateLink(LinkType.Blob); + if (link == null) + return; + var browser = ServiceProvider.TryGetService<IVisualStudioBrowser>(); + browser?.OpenUrl(link.ToUri()); + + await UsageTracker.IncrementCounter(x => x.NumberOfOpenInGitHub); + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/OpenPullRequestsCommand.cs b/src/GitHub.VisualStudio/Commands/OpenPullRequestsCommand.cs new file mode 100644 index 0000000000..755654ce40 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/OpenPullRequestsCommand.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Logging; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using GitHub.Services.Vssdk.Commands; +using Serilog; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Opens the GitHub pane and shows the pull request list. + /// </summary> + [Export(typeof(IOpenPullRequestsCommand))] + public class OpenPullRequestsCommand : VsCommand, IOpenPullRequestsCommand + { + static readonly ILogger log = LogManager.ForContext<OpenPullRequestsCommand>(); + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + protected OpenPullRequestsCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId) + { + this.serviceProvider = serviceProvider; + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.openPullRequestsCommand; + + /// <summary> + /// Opens the GitHub pane and shows the pull request list. + /// </summary> + public override async Task Execute() + { + try + { + var host = await serviceProvider.TryGetService<IGitHubToolWindowManager>().ShowGitHubPane(); + await host.ShowPullRequests(); + } + catch (Exception ex) + { + log.Error(ex, "Error showing opening pull requests"); + } + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/ShowCurrentPullRequestCommand.cs b/src/GitHub.VisualStudio/Commands/ShowCurrentPullRequestCommand.cs new file mode 100644 index 0000000000..902778eac2 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/ShowCurrentPullRequestCommand.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Logging; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using GitHub.Services.Vssdk.Commands; +using Serilog; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Opens the GitHub pane and shows the currently checked out pull request. + /// </summary> + /// <remarks> + /// Does nothing if there is no checked out pull request. + /// </remarks> + [Export(typeof(IShowCurrentPullRequestCommand))] + public class ShowCurrentPullRequestCommand : VsCommand, IShowCurrentPullRequestCommand + { + static readonly ILogger log = LogManager.ForContext<ShowCurrentPullRequestCommand>(); + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + protected ShowCurrentPullRequestCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId) + { + this.serviceProvider = serviceProvider; + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.showCurrentPullRequestCommand; + + /// <summary> + /// Shows the current pull request. + /// </summary> + public override async Task Execute() + { + try + { + var pullRequestSessionManager = serviceProvider.ExportProvider.GetExportedValueOrDefault<IPullRequestSessionManager>(); + await pullRequestSessionManager.EnsureInitialized(); + + var session = pullRequestSessionManager?.CurrentSession; + if (session == null) + { + return; // No active PR session. + } + + var pullRequest = session.PullRequest; + var manager = serviceProvider.TryGetService<IGitHubToolWindowManager>(); + var host = await manager.ShowGitHubPane(); + await host.ShowPullRequest(session.RepositoryOwner, host.LocalRepository.Name, pullRequest.Number); + } + catch (Exception ex) + { + log.Error(ex, "Error showing current pull request"); + } + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/ShowGitHubPaneCommand.cs b/src/GitHub.VisualStudio/Commands/ShowGitHubPaneCommand.cs new file mode 100644 index 0000000000..efb0f3c566 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/ShowGitHubPaneCommand.cs @@ -0,0 +1,54 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Commands; +using GitHub.Logging; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using GitHub.Services.Vssdk.Commands; +using Serilog; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Opens the GitHub pane. + /// </summary> + [Export(typeof(IShowGitHubPaneCommand))] + public class ShowGitHubPaneCommand : VsCommand, IShowGitHubPaneCommand + { + static readonly ILogger log = LogManager.ForContext<ShowGitHubPaneCommand>(); + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + protected ShowGitHubPaneCommand(IGitHubServiceProvider serviceProvider) + : base(CommandSet, CommandId) + { + this.serviceProvider = serviceProvider; + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.showGitHubPaneCommand; + + /// <summary> + /// Shows the GitHub pane. + /// </summary> + public override async Task Execute() + { + try + { + var host = await serviceProvider.TryGetService<IGitHubToolWindowManager>().ShowGitHubPane(); + } + catch (Exception ex) + { + log.Error(ex, "Error showing opening GitHub pane"); + } + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/ShowMessageBoxCommand.cs b/src/GitHub.VisualStudio/Commands/ShowMessageBoxCommand.cs new file mode 100644 index 0000000000..837244339e --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/ShowMessageBoxCommand.cs @@ -0,0 +1,37 @@ +using System; +using GitHub.Services.Vssdk.Commands; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Show an info message dialog when a command is executed. + /// </summary> + public class ShowMessageBoxCommand : VsCommand + { + readonly IServiceProvider serviceProvider; + readonly string message; + + public ShowMessageBoxCommand(Guid commandSet, int commandId, + IServiceProvider serviceProvider, string message) : base(commandSet, commandId) + { + this.serviceProvider = serviceProvider; + this.message = message; + } + + public override Task Execute() + { + ShowInfoMessage(message); + return Task.CompletedTask; + } + + void ShowInfoMessage(string infoMessage) + { + ErrorHandler.ThrowOnFailure(VsShellUtilities.ShowMessageBox(serviceProvider, infoMessage, null, + OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST)); + } + } +} diff --git a/src/GitHub.VisualStudio/Commands/SyncSubmodulesCommand.cs b/src/GitHub.VisualStudio/Commands/SyncSubmodulesCommand.cs new file mode 100644 index 0000000000..88372d6433 --- /dev/null +++ b/src/GitHub.VisualStudio/Commands/SyncSubmodulesCommand.cs @@ -0,0 +1,100 @@ +using System; +using System.Threading.Tasks; +using System.ComponentModel.Composition; +using GitHub.Logging; +using GitHub.Commands; +using GitHub.Services; +using GitHub.Services.Vssdk.Commands; +using Serilog; +using System.IO; +using System.Globalization; +using System.Linq; + +namespace GitHub.VisualStudio.Commands +{ + /// <summary> + /// Command to sync submodules in local repository. + /// </summary> + [Export(typeof(ISyncSubmodulesCommand))] + public class SyncSubmodulesCommand : VsCommand, ISyncSubmodulesCommand + { + static readonly ILogger log = LogManager.ForContext<SyncSubmodulesCommand>(); + + readonly Lazy<IPullRequestService> lazyPullRequestService; + readonly Lazy<IStatusBarNotificationService> lazyStatusBarNotificationService; + readonly Lazy<IVSGitExt> lazyVSGitExt; + + [ImportingConstructor] + protected SyncSubmodulesCommand( + Lazy<IPullRequestService> pullRequestService, + Lazy<IStatusBarNotificationService> statusBarNotificationService, + Lazy<IVSGitExt> gitExt) + : base(CommandSet, CommandId) + { + lazyPullRequestService = pullRequestService; + lazyStatusBarNotificationService = statusBarNotificationService; + lazyVSGitExt = gitExt; + } + + /// <summary> + /// Gets the GUID of the group the command belongs to. + /// </summary> + public static readonly Guid CommandSet = Guids.guidGitHubCmdSet; + + /// <summary> + /// Gets the numeric identifier of the command. + /// </summary> + public const int CommandId = PkgCmdIDList.syncSubmodulesCommand; + + /// <summary> + /// Syncs submodules. + /// </summary> + public override async Task Execute() + { + try + { + var complete = await SyncSubmodules(); + } + catch (Exception ex) + { + log.Error(ex, "Error syncing submodules"); + lazyStatusBarNotificationService.Value.ShowMessage("Error syncing submodules"); + } + } + + /// <summary> + /// Sync submodules in local repository. + /// </summary> + /// <returns>Tuple with bool that is true if command completed successfully and string with + /// output from sync submodules Git command.</returns> + public async Task<Tuple<bool, string>> SyncSubmodules() + { + var pullRequestService = lazyPullRequestService.Value; + var statusBarNotificationService = lazyStatusBarNotificationService.Value; + var gitExt = lazyVSGitExt.Value; + + var repository = gitExt.ActiveRepositories.FirstOrDefault(); + if (repository == null) + { + statusBarNotificationService.ShowMessage("No local Git repository"); + return new Tuple<bool, string>(true, "No local Git repository"); + } + + var writer = new StringWriter(CultureInfo.CurrentCulture); + var complete = await pullRequestService.SyncSubmodules(repository, line => + { + writer.WriteLine(line); + statusBarNotificationService.ShowMessage(line); + }); + + if (!complete) + { + statusBarNotificationService.ShowMessage("Failed to sync submodules." + Environment.NewLine + writer); + return new Tuple<bool, string>(false, writer.ToString()); + } + + statusBarNotificationService.ShowMessage(string.Empty); + return new Tuple<bool, string>(true, writer.ToString()); + } + } +} diff --git a/src/GitHub.VisualStudio/Converters/CountToVisibilityConverter.cs b/src/GitHub.VisualStudio/Converters/CountToVisibilityConverter.cs deleted file mode 100644 index 6dc5b1daeb..0000000000 --- a/src/GitHub.VisualStudio/Converters/CountToVisibilityConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; -using NullGuard; - -namespace GitHub.VisualStudio.Converters -{ - /// <summary> - /// Convert a count to visibility based on the following rule: - /// * If count == 0, return Visibility.Visible - /// * If count > 0, return Visibility.Collapsed - /// </summary> - public class CountToVisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, [AllowNull] object parameter, [AllowNull] CultureInfo culture) - { - return ((int)value == 0) ? Visibility.Visible : Visibility.Collapsed; - } - - [return: AllowNull] - public object ConvertBack(object value, Type targetType, [AllowNull] object parameter, [AllowNull] CultureInfo culture) - { - return null; - } - } -} diff --git a/src/GitHub.VisualStudio/FodyWeavers.xml b/src/GitHub.VisualStudio/FodyWeavers.xml deleted file mode 100644 index 9321cb912f..0000000000 --- a/src/GitHub.VisualStudio/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <NullGuard/> -</Weavers> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitContextPackage.cs b/src/GitHub.VisualStudio/GitContextPackage.cs new file mode 100644 index 0000000000..c2b2676091 --- /dev/null +++ b/src/GitHub.VisualStudio/GitContextPackage.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading; +using System.Runtime.InteropServices; +using GitHub.Exports; +using GitHub.Services; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; +using static Microsoft.VisualStudio.VSConstants; + +namespace GitHub.VisualStudio +{ + /// <summary> + /// This package creates a custom UIContext <see cref="Guids.UIContext_Git"/> that is activated when a + /// repository is active in <see cref="IVSGitExt"/> and the current process is Visual Studio (not Blend). + /// </summary> + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [Guid(Guids.UIContext_Git)] + // Initialize when we enter the context of a Git repository + [ProvideAutoLoad(UICONTEXT.RepositoryOpen_string, PackageAutoLoadFlags.BackgroundLoad)] + public class GitContextPackage : AsyncPackage + { + protected async override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) + { + if (!ExportForVisualStudioProcessAttribute.IsVisualStudioProcess()) + { + // Don't activate 'UIContext_Git' for non-Visual Studio process + return; + } + + var gitExt = (IVSGitExt)await GetServiceAsync(typeof(IVSGitExt)); + var context = UIContext.FromUIContextGuid(new Guid(Guids.UIContext_Git)); + RefreshContext(context, gitExt); + gitExt.ActiveRepositoriesChanged += () => + { + RefreshContext(context, gitExt); + }; + } + + static void RefreshContext(UIContext context, IVSGitExt gitExt) + { + context.IsActive = gitExt.ActiveRepositories.Count > 0; + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 03cd32e0a4..31e465d154 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -1,14 +1,24 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> + <!-- When StartProgram is set to Visual Studio 2015 in .user file, install extension in the same version --> + <Import Project="GitHub.VisualStudio.csproj.user" Condition=" Exists('GitHub.VisualStudio.csproj.user') " /> + <PropertyGroup Condition=" $(StartProgram.Contains('Microsoft Visual Studio 14.0')) "> + <VisualStudioVersion>14.0</VisualStudioVersion> + </PropertyGroup> + <Import Project="..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props" Condition="'$(VisualStudioVersion)' == '14.0' And Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props')" /> + <Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props" Condition="'$(VisualStudioVersion)' == '15.0' And Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props')" /> <PropertyGroup> - <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion> - <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion> + <!-- This is added to prevent forced migrations in Visual Studio 2012 and newer --> + <MinimumVisualStudioVersion>$(MSBuildToolsVersion)</MinimumVisualStudioVersion> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">$(MSBuildToolsVersion)</VisualStudioVersion> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <TargetFrameworkProfile /> + <VsixType>v3</VsixType> + <IsProductComponent>false</IsProductComponent> + <ExtensionInstallationFolder>GitHub\GitHub</ExtensionInstallationFolder> <NuGetPackageImportStamp> </NuGetPackageImportStamp> - <TargetFrameworkProfile /> - <BuildType Condition="Exists('..\..\script\ApiClientConfiguration.cs')">Internal</BuildType> - <ApplicationVersion>1.0.16.1</ApplicationVersion> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> @@ -21,28 +31,53 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GitHub.VisualStudio</RootNamespace> <AssemblyName>GitHub.VisualStudio</AssemblyName> - <SignAssembly>true</SignAssembly> <StartAction>Program</StartAction> - <StartProgram>$(DevEnvDir)\devenv.exe</StartProgram> + <StartProgram Condition=" '$(StartProgram)' == '' ">$(DevEnvDir)\devenv.exe</StartProgram> <StartArguments>/rootsuffix Exp</StartArguments> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7.3</LangVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <ZipPackageCompressionLevel>Normal</ZipPackageCompressionLevel> + <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> + <IncludeDebugSymbolsInVSIXContainer>true</IncludeDebugSymbolsInVSIXContainer> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> - <DefineConstants>DEBUG;TRACE</DefineConstants> + <DefineConstants>TRACE;DEBUG</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>true</RunCodeAnalysis> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <OutputPath>..\..\build\$(Configuration)\</OutputPath> + <RunCodeAnalysis>false</RunCodeAnalysis> <CreateVsixContainer>True</CreateVsixContainer> <DeployExtension>True</DeployExtension> <IncludeDebugSymbolsInVSIXContainer>true</IncludeDebugSymbolsInVSIXContainer> + <OutputPath>..\..\build\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugCodeAnalysis|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>TRACE;DEBUG;CODE_ANALYSIS</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <CreateVsixContainer>True</CreateVsixContainer> + <DeployExtension>True</DeployExtension> + <OutputPath>..\..\build\Debug\</OutputPath> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'DebugWithoutVsix|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <DefineConstants>TRACE;DEBUG;CODE_ANALYSIS</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>false</RunCodeAnalysis> + <CreateVsixContainer>False</CreateVsixContainer> + <DeployExtension>False</DeployExtension> + <OutputPath>..\..\build\Debug\</OutputPath> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -50,31 +85,23 @@ <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>false</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <OutputPath>..\..\build\$(Configuration)\</OutputPath> - <IncludeDebugSymbolsInVSIXContainer>true</IncludeDebugSymbolsInVSIXContainer> + <RunCodeAnalysis>true</RunCodeAnalysis> + <CreateVsixContainer>True</CreateVsixContainer> + <DeployExtension>True</DeployExtension> + <OutputPath>..\..\build\Release\</OutputPath> </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Publish|AnyCPU' "> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseWithoutVsix|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <RunCodeAnalysis>false</RunCodeAnalysis> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - <CodeAnalysisIgnoreGeneratedCode>true</CodeAnalysisIgnoreGeneratedCode> - <CodeAnalysisRuleSet>..\common\GitHubVS.ruleset</CodeAnalysisRuleSet> - <OutputPath>..\..\build\Release\</OutputPath> + <RunCodeAnalysis>true</RunCodeAnalysis> + <CreateVsixContainer>False</CreateVsixContainer> <DeployExtension>False</DeployExtension> + <OutputPath>..\..\build\Release\</OutputPath> </PropertyGroup> - <PropertyGroup Condition="$(Buildtype) == 'Internal'"> - <AssemblyOriginatorKeyFile>..\..\script\Key.snk</AssemblyOriginatorKeyFile> - <SignAssembly>true</SignAssembly> - <DelaySign>false</DelaySign> - </PropertyGroup> + <Import Project="..\common\signing.props" /> <ItemGroup> <Reference Include="EditorUtils2013"> <HintPath>..\..\packages\EditorUtils2013.1.4.1.1\lib\net40\EditorUtils2013.dll</HintPath> @@ -82,73 +109,173 @@ </Reference> <Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <EmbedInteropTypes>False</EmbedInteropTypes> + <HintPath>..\..\packages\VSSDK.DTE.7.0.4\lib\net20\envdte.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <EmbedInteropTypes>false</EmbedInteropTypes> </Reference> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Markdig, Version=0.13.0.0, Culture=neutral, PublicKeyToken=870da25a133885f8, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Markdig.Signed.0.13.0\lib\net40\Markdig.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Markdig.Wpf, Version=0.2.1.0, Culture=neutral, PublicKeyToken=a0d0cdbebd8d164b, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Markdig.Wpf.Signed.0.2.1\lib\net452\Markdig.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="Microsoft.CSharp" /> - <Reference Include="Microsoft.TeamFoundation.Common, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Common.dll</HintPath> - <Private>False</Private> + <Reference Include="Microsoft.TeamFoundation.Controls"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Controls.dll</HintPath> </Reference> - <Reference Include="Microsoft.TeamFoundation.Client, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Client.dll</HintPath> - <Private>False</Private> + <Reference Include="Microsoft.Expression.Interactions, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\Microsoft.Expression.Interactions.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.TeamFoundation.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\\Microsoft.TeamFoundation.Controls.dll</HintPath> - <Private>False</Private> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> - <Private>False</Private> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Provider, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> - <Private>False</Private> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.ImageCatalog, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ImageCatalog.14.3.25407\lib\net45\Microsoft.VisualStudio.ImageCatalog.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.OLE.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0" /> <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0"> - <EmbedInteropTypes>False</EmbedInteropTypes> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0"> - <EmbedInteropTypes>true</EmbedInteropTypes> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> </Reference> - <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.TextManager.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0" /> - <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll</HintPath> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="NLog, Version=3.1.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NLog.3.1.0.0\lib\net45\NLog.dll</HintPath> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="NullGuard, Version=1.4.1.0, Culture=neutral, PublicKeyToken=1958ac8092168428, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\NullGuard.Fody.1.4.1\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll</HintPath> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath> + </Reference> + <Reference Include="rothko, Version=0.0.3.0, Culture=neutral, PublicKeyToken=9f664c41f503810a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rothko.0.0.3-ghfvs\lib\net45\rothko.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="SQLitePCL.raw, Version=0.7.3.0, Culture=neutral, PublicKeyToken=d89a3d1cc066b805, processorArchitecture=MSIL"> <HintPath>..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\lib\net45\SQLitePCL.raw.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="Stateless, Version=2.5.11.0, Culture=neutral, PublicKeyToken=93038f0927583c9a, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Stateless.2.5.11.0\lib\portable-net40+sl50+win+wp80\Stateless.dll</HintPath> + <Reference Include="Stateless, Version=2.5.56.0, Culture=neutral, PublicKeyToken=93038f0927583c9a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Stateless.2.5.56.0\lib\portable-net40+sl50+win+wp80+MonoAndroid10+xamarinios10+MonoTouch10\Stateless.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\VSSDK.DTE.7.0.4\lib\net20\stdole.dll</HintPath> + <EmbedInteropTypes>False</EmbedInteropTypes> + </Reference> <Reference Include="System" /> <Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.Core" /> @@ -175,7 +302,14 @@ <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> + </Reference> <Reference Include="System.Windows.Forms" /> + <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Expression.Blend.Sdk.WPF.1.0.1\lib\net45\System.Windows.Interactivity.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System.Xml" /> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> @@ -183,112 +317,146 @@ <Reference Include="System.Xaml" /> </ItemGroup> <ItemGroup> - <COMReference Include="Microsoft.VisualStudio.CommandBars"> - <Guid>{1CBA492E-7263-47BB-87FE-639000619B15}</Guid> - <VersionMajor>8</VersionMajor> - <VersionMinor>0</VersionMinor> - <Lcid>0</Lcid> - <WrapperTool>primary</WrapperTool> - <Isolated>False</Isolated> - <EmbedInteropTypes>False</EmbedInteropTypes> - </COMReference> - <COMReference Include="stdole"> - <Guid>{00020430-0000-0000-C000-000000000046}</Guid> - <VersionMajor>2</VersionMajor> - <VersionMinor>0</VersionMinor> - <Lcid>0</Lcid> - <WrapperTool>primary</WrapperTool> - <Isolated>False</Isolated> + <Reference Include="Microsoft.VisualStudio.CommandBars, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <EmbedInteropTypes>False</EmbedInteropTypes> - </COMReference> + </Reference> </ItemGroup> <ItemGroup> - <None Include="..\..\script\Key.snk" Condition="$(Buildtype) == 'Internal'"> - <Link>Key.snk</Link> - </None> <Compile Include="..\common\SolutionInfo.cs"> <Link>Properties\SolutionInfo.cs</Link> </Compile> - <Compile Include="Base\TeamExplorerGitRepoInfo.cs" /> - <Compile Include="Base\TeamExplorerInvitationBase.cs" /> - <Compile Include="Base\TeamExplorerServiceHolder.cs" /> - <Compile Include="Converters\CountToVisibilityConverter.cs" /> - <Compile Include="Helpers\SharedDictionaryManager.cs" /> + <Compile Include="AssemblyResolverPackage.cs" /> + <Compile Include="Commands\BlameLinkCommand.cs" /> + <Compile Include="Commands\CreateGistCommand.cs" /> + <Compile Include="Commands\OpenFromClipboardCommand.cs" /> + <Compile Include="Commands\OpenFromUrlCommand.cs" /> + <Compile Include="Commands\ShowMessageBoxCommand.cs" /> + <Compile Include="Commands\LinkCommandBase.cs" /> + <Compile Include="Commands\CopyLinkCommand.cs" /> + <Compile Include="Commands\OpenLinkCommand.cs" /> + <Compile Include="Commands\SyncSubmodulesCommand.cs" /> + <Compile Include="Commands\ShowCurrentPullRequestCommand.cs" /> + <Compile Include="Commands\ShowGitHubPaneCommand.cs" /> + <Compile Include="Commands\OpenPullRequestsCommand.cs" /> + <Compile Include="Commands\GoToSolutionOrPullRequestFileCommand.cs" /> + <Compile Include="GitContextPackage.cs" /> + <Compile Include="GitHubPanePackage.cs" /> + <Compile Include="IServiceProviderPackage.cs" /> + <Compile Include="Helpers\ActiveDocumentSnapshot.cs" /> + <Compile Include="Commands\AddConnectionCommand.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Base\TeamExplorerBase.cs" /> - <Compile Include="Base\TeamExplorerItemBase.cs" /> - <Compile Include="Base\TeamExplorerNavigationItemBase.cs" /> - <Compile Include="Base\TeamExplorerSectionBase.cs" /> <Compile Include="Helpers\Browser.cs" /> - <Compile Include="Helpers\Colors.cs" /> - <Compile Include="Helpers\Constants.cs" /> + <Compile Include="Services\JsonConnectionCache.cs" /> + <Compile Include="Services\LocalRepositories.cs" /> + <Compile Include="Services\ShowDialogService.cs" /> + <Compile Include="Services\UsageService.cs" /> + <Compile Include="Services\UsageTracker.cs" /> + <Compile Include="Services\VSGitExtFactory.cs" /> + <Compile Include="Settings\Constants.cs" /> <Compile Include="Services\ConnectionManager.cs" /> <Compile Include="Services\Program.cs" /> - <Compile Include="Services\SharedResources.cs" /> - <Compile Include="Settings.cs" /> - <Compile Include="Base\PackageBase.cs" /> - <Compile Include="Resources.Designer.cs"> + <Compile Include="Services\SelectedTextProvider.cs" /> + <Compile Include="Settings\generated\PackageSettingsGen.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> - <DependentUpon>Resources.resx</DependentUpon> + <DependentUpon>PackageSettingsGen.tt</DependentUpon> </Compile> + <Compile Include="Settings\OptionsPage.cs"> + <SubType>Component</SubType> + </Compile> + <Compile Include="Settings\PackageSettings.cs" /> <Compile Include="GlobalSuppressions.cs" /> <Compile Include="GitHubPackage.cs" /> - <Compile Include="PkgCmdID.cs" /> - <Compile Include="Services\UIProvider.cs" /> - <Compile Include="SimpleJson.cs" /> - <Compile Include="TeamExplorer\Connect\GitHubConnectSection1.cs" /> - <Compile Include="TeamExplorer\Connect\GitHubConnectSection0.cs" /> - <Compile Include="TeamExplorer\Connect\GitHubConnectSection.cs" /> - <Compile Include="TeamExplorer\Connect\GitHubInvitationSection.cs" /> - <Compile Include="TeamExplorer\Home\GitHubHomeSection.cs" /> - <Compile Include="TeamExplorer\Home\GraphsNavigationItem.cs" /> - <Compile Include="TeamExplorer\Home\PullRequestsNavigationItem.cs" /> - <Compile Include="TeamExplorer\Home\IssuesNavigationItem.cs" /> - <Compile Include="TeamExplorer\Home\WikiNavigationItem.cs" /> - <Compile Include="TeamExplorer\Home\PulseNavigationItem.cs" /> - <Compile Include="TeamExplorer\Sync\EnsureLoggedInSectionSync.cs" /> - <Compile Include="TeamExplorer\Sync\GitHubPublishSection.cs" /> - <Compile Include="Base\EnsureLoggedInSection.cs" /> - <Compile Include="UI\DrawingExtensions.cs" /> + <Compile Include="Services\GitHubServiceProvider.cs" /> <Compile Include="UI\GitHubPane.cs" /> - <Compile Include="UI\Views\Controls\RepositoryCloneControl.xaml.cs"> - <DependentUpon>RepositoryCloneControl.xaml</DependentUpon> + <Compile Include="UI\Settings\OptionsControl.xaml.cs"> + <DependentUpon>OptionsControl.xaml</DependentUpon> + </Compile> + <Compile Include="Views\ActorAvatarView.cs" /> + <Compile Include="Views\ContentView.cs" /> + <Compile Include="Views\Dialog\ForkRepositoryExecuteView.xaml.cs"> + <DependentUpon>ForkRepositoryExecuteView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\Controls\RepositoryCreationControl.xaml.cs"> - <DependentUpon>RepositoryCreationControl.xaml</DependentUpon> + <Compile Include="Views\Dialog\ForkRepositorySelectView.xaml.cs"> + <DependentUpon>ForkRepositorySelectView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\Controls\LoginControl.xaml.cs"> - <DependentUpon>LoginControl.xaml</DependentUpon> + <Compile Include="Views\Dialog\GistCreationView.xaml.cs"> + <DependentUpon>GistCreationView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\Controls\RepositoryPublishControl.xaml.cs"> - <DependentUpon>RepositoryPublishControl.xaml</DependentUpon> + <Compile Include="Views\Dialog\GitHubDialogWindow.xaml.cs"> + <DependentUpon>GitHubDialogWindow.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\GitHubConnectContent.xaml.cs"> - <DependentUpon>GitHubConnectContent.xaml</DependentUpon> + <Compile Include="Views\Dialog\LoginCredentialsView.xaml.cs"> + <DependentUpon>LoginCredentialsView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\GitHubInvitationContent.xaml.cs"> - <DependentUpon>GitHubInvitationContent.xaml</DependentUpon> + <Compile Include="Views\Dialog\Login2FaView.xaml.cs"> + <DependentUpon>Login2FaView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\GitHubHomeContent.xaml.cs"> - <DependentUpon>GitHubHomeContent.xaml</DependentUpon> + <Compile Include="Views\Dialog\RepositoryCloneView.xaml.cs"> + <DependentUpon>RepositoryCloneView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\Controls\TwoFactorControl.xaml.cs"> - <DependentUpon>TwoFactorControl.xaml</DependentUpon> + <Compile Include="Views\Dialog\RepositoryCreationView.xaml.cs"> + <DependentUpon>RepositoryCreationView.xaml</DependentUpon> </Compile> - <Compile Include="UI\Views\GitHubPaneView.xaml.cs"> + <Compile Include="Views\Dialog\RepositoryRecloneView.xaml.cs"> + <DependentUpon>RepositoryRecloneView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\DirectoryIsExpandedToImageMonikerConverter.cs" /> + <Compile Include="Views\GitHubPane\FileNameToImageMonikerConverter.cs" /> + <Compile Include="Views\GitHubPane\LoggedOutView.xaml.cs"> + <DependentUpon>LoggedOutView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\GitHubPaneView.xaml.cs"> <DependentUpon>GitHubPaneView.xaml</DependentUpon> </Compile> - <Compile Include="UI\WindowController.xaml.cs"> - <DependentUpon>WindowController.xaml</DependentUpon> + <Compile Include="Views\GitHubPane\LoginFailedView.xaml.cs"> + <DependentUpon>LoginFailedView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestFileCommentsView.xaml.cs"> + <DependentUpon>PullRequestFileCommentsView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestListItemView.xaml.cs"> + <DependentUpon>PullRequestListItemView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestReviewAuthoringView.xaml.cs"> + <DependentUpon>PullRequestReviewAuthoringView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestCheckView.xaml.cs"> + <DependentUpon>PullRequestCheckView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestReviewSummaryView.xaml.cs"> + <DependentUpon>PullRequestReviewSummaryView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestFilesView.xaml.cs"> + <DependentUpon>PullRequestFilesView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestListView.xaml.cs"> + <DependentUpon>PullRequestListView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestDetailView.xaml.cs"> + <DependentUpon>PullRequestDetailView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestCreationView.xaml.cs"> + <DependentUpon>PullRequestCreationView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\NotAGitHubRepositoryView.xaml.cs"> + <DependentUpon>NotAGitHubRepositoryView.xaml</DependentUpon> </Compile> + <Compile Include="Views\GitHubPane\NotAGitRepositoryView.xaml.cs"> + <DependentUpon>NotAGitRepositoryView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\GitHubPane\PullRequestUserReviewsView.xaml.cs"> + <DependentUpon>PullRequestUserReviewsView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\TeamExplorer\RepositoryPublishView.xaml.cs"> + <DependentUpon>RepositoryPublishView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\UserFilterView.xaml.cs"> + <DependentUpon>UserFilterView.xaml</DependentUpon> + </Compile> + <Compile Include="Views\ViewLocator.cs" /> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="Resources.resx"> - <Generator>PublicResXFileCodeGenerator</Generator> - <SubType>Designer</SubType> - <LastGenOutput>Resources.Designer.cs</LastGenOutput> - </EmbeddedResource> <EmbeddedResource Include="VSPackage.resx"> <MergeWithCTO>true</MergeWithCTO> <ManifestResourceName>VSPackage</ManifestResourceName> @@ -296,10 +464,6 @@ </EmbeddedResource> </ItemGroup> <ItemGroup> - <Content Include="NLog.config"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - <IncludeInVSIX>true</IncludeInVSIX> - </Content> <Content Include="Resources\preview_200x200.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <IncludeInVSIX>true</IncludeInVSIX> @@ -311,13 +475,23 @@ <Content Include="GitHub.VisualStudio.imagemanifest"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <IncludeInVSIX>true</IncludeInVSIX> + <SubType>Designer</SubType> + </Content> + <Content Include="Settings\generated\PackageSettingsGen.tt"> + <Generator>TextTemplatingFileGenerator</Generator> + <CustomToolNamespace>GitHub.VisualStudio.Settings</CustomToolNamespace> + <LastGenOutput>PackageSettingsGen.cs</LastGenOutput> </Content> <None Include="packages.config"> <SubType>Designer</SubType> </None> + <None Include="..\common\settings.json"> + <Link>Properties\settings.json</Link> + </None> <None Include="source.extension.vsixmanifest"> <SubType>Designer</SubType> </None> + <None Include="..\..\build\version" /> </ItemGroup> <ItemGroup> <VSCTCompile Include="GitHub.VisualStudio.vsct"> @@ -326,7 +500,6 @@ </VSCTCompile> </ItemGroup> <ItemGroup> - <Resource Include="FodyWeavers.xml" /> <CodeAnalysisDictionary Include="..\common\CodeAnalysisDictionary.xml"> <Link>Properties\CodeAnalysisDictionary.xml</Link> </CodeAnalysisDictionary> @@ -338,6 +511,22 @@ <Resource Include="Resources\default_user_avatar.png" /> </ItemGroup> <ItemGroup> + <Page Include="Resources\icons\clippy.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Resources\icons\link_external.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Resources\icons\question.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Resources\icons\mark_github_toolbar.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> <Page Include="Resources\icons\refresh.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> @@ -358,49 +547,112 @@ <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> - <Page Include="SharedDictionary.xaml"> + <Page Include="UI\Settings\OptionsControl.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + <CustomToolNamespace>GitHub.VisualStudio.UI</CustomToolNamespace> + </Page> + <Page Include="Views\Dialog\ForkRepositoryExecuteView.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\Dialog\ForkRepositorySelectView.xaml"> + <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> - <Page Include="UI\Views\Controls\ActionLinkButton.xaml"> + <Page Include="Views\Dialog\GistCreationView.xaml"> + <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> + </Page> + <Page Include="Views\Dialog\GitHubDialogWindow.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\Dialog\LoginCredentialsView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\Dialog\Login2FaView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\Dialog\RepositoryCloneView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\Dialog\RepositoryCreationView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\Dialog\RepositoryRecloneView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\GitHubPane\LoggedOutView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\GitHubPane\GitHubPaneView.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\GitHubPane\LoginFailedView.xaml"> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\GitHubPane\PullRequestFileCommentsView.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Page Include="Views\GitHubPane\PullRequestListItemView.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\GitHubPane\PullRequestReviewAuthoringView.xaml"> <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\Controls\RepositoryCloneControl.xaml"> + <Page Include="Views\GitHubPane\PullRequestCheckView.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\Controls\RepositoryCreationControl.xaml"> + <Page Include="Views\GitHubPane\PullRequestReviewSummaryView.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\Controls\LoginControl.xaml"> + <Page Include="Views\GitHubPane\PullRequestFilesView.xaml"> <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> - <Page Include="UI\Views\Controls\RepositoryPublishControl.xaml"> + <Page Include="Views\GitHubPane\PullRequestListView.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\GitHubConnectContent.xaml"> + <Page Include="Views\GitHubPane\PullRequestDetailView.xaml"> <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\GitHubInvitationContent.xaml"> + <Page Include="Views\GitHubPane\PullRequestCreationView.xaml"> <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\GitHubHomeContent.xaml"> + <Page Include="Views\GitHubPane\NotAGitHubRepositoryView.xaml"> <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\Controls\TwoFactorControl.xaml"> + <Page Include="Views\GitHubPane\NotAGitRepositoryView.xaml"> <Generator>MSBuild:Compile</Generator> - <SubType> - </SubType> + <SubType>Designer</SubType> </Page> - <Page Include="UI\Views\GitHubPaneView.xaml"> + <Page Include="Views\GitHubPane\PullRequestUserReviewsView.xaml"> + <Generator>MSBuild:Compile</Generator> <SubType>Designer</SubType> + </Page> + <Page Include="Views\TeamExplorer\RepositoryPublishView.xaml"> <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> </Page> - <Page Include="UI\WindowController.xaml"> + <Page Include="Views\UserFilterView.xaml"> + <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> </ItemGroup> @@ -408,55 +660,49 @@ <ProjectReference Include="..\..\submodules\akavache\Akavache.Sqlite3\Akavache.Sqlite3.csproj"> <Project>{241c47df-ca8e-4296-aa03-2c48bb646abd}</Project> <Name>Akavache.Sqlite3</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\..\submodules\akavache\Akavache\Akavache_Net45.csproj"> <Project>{b4e665e5-6caf-4414-a6e2-8de1c3bcf203}</Project> <Name>Akavache_Net45</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> - <ProjectReference Include="..\..\submodules\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj"> - <Project>{ee6ed99f-cb12-4683-b055-d28fc7357a34}</Project> - <Name>LibGit2Sharp</Name> - <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems%3bDebugSymbolsProjectOutputGroup%3b</IncludeOutputGroupsInVSIX> - <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup%3b</IncludeOutputGroupsInVSIXLocalOnly> - </ProjectReference> <ProjectReference Include="..\..\submodules\octokit.net\Octokit.Reactive\Octokit.Reactive.csproj"> <Project>{674b69b8-0780-4d54-ae2b-c15821fa51cb}</Project> <Name>Octokit.Reactive</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> <Name>Octokit</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI.Events\ReactiveUI.Events_Net45.csproj"> <Project>{600998c4-54dd-4755-bfa8-6f44544d8e2e}</Project> <Name>ReactiveUI.Events_Net45</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> <Name>ReactiveUI_Net45</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> <Name>Splat-Net45</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> @@ -468,87 +714,132 @@ <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> <Name>GitHub.Api</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> <Project>{1a1da411-8d1f-4578-80a6-04576bea2dc5}</Project> <Name>GitHub.App</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> <Name>GitHub.Exports.Reactive</Name> - <Private>False</Private> - <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> - <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> + <Private>True</Private> + <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems%3bDebugSymbolsProjectOutputGroup%3bBuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b</IncludeOutputGroupsInVSIX> + <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup%3b</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> <Name>GitHub.Exports</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\GitHub.Extensions.Reactive\GitHub.Extensions.Reactive.csproj"> <Project>{6559e128-8b40-49a5-85a8-05565ed0c7e3}</Project> <Name>GitHub.Extensions.Reactive</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> - <Private>False</Private> + <Private>True</Private> + <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> + <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> + </ProjectReference> + <ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj"> + <Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project> + <Name>GitHub.Logging</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.InlineReviews\GitHub.InlineReviews.csproj"> + <Project>{7f5ed78b-74a3-4406-a299-70cfb5885b8b}</Project> + <Name>GitHub.InlineReviews</Name> + <Private>True</Private> + <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;PkgdefProjectOutputGroup</IncludeOutputGroupsInVSIX> + <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;PkgdefProjectOutputGroup</IncludeOutputGroupsInVSIXLocalOnly> + </ProjectReference> + <ProjectReference Include="..\GitHub.Services.Vssdk\GitHub.Services.Vssdk.csproj"> + <Project>{2d3d2834-33be-45ca-b3cc-12f853557d7b}</Project> + <Name>GitHub.Services.Vssdk</Name> + </ProjectReference> + <ProjectReference Include="..\GitHub.StartPage\GitHub.StartPage.csproj"> + <Project>{50e277b8-8580-487a-8f8e-5c3b9fbf0f77}</Project> + <Name>GitHub.StartPage</Name> + <Private>True</Private> + <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;PkgdefProjectOutputGroup</IncludeOutputGroupsInVSIX> + <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;PkgdefProjectOutputGroup</IncludeOutputGroupsInVSIXLocalOnly> + </ProjectReference> + <ProjectReference Include="..\GitHub.TeamFoundation.14\GitHub.TeamFoundation.14.csproj"> + <Project>{161dbf01-1dbf-4b00-8551-c5c00f26720d}</Project> + <Name>GitHub.TeamFoundation.14</Name> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> + <Aliases>TF14</Aliases> + </ProjectReference> + <ProjectReference Include="..\GitHub.TeamFoundation.15\GitHub.TeamFoundation.15.csproj"> + <Project>{161dbf01-1dbf-4b00-8551-c5c00f26720e}</Project> + <Name>GitHub.TeamFoundation.15</Name> + <Private>True</Private> + <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> + <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> + <Aliases>TF15</Aliases> </ProjectReference> <ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj"> <Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project> <Name>GitHub.UI.Reactive</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> <Name>GitHub.UI</Name> - <Private>False</Private> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> - <ProjectReference Include="..\..\submodules\Rothko\src\Rothko.csproj"> - <Project>{4a84e568-ca86-4510-8cd0-90d3ef9b65f9}</Project> - <Name>Rothko</Name> - <Private>False</Private> + <ProjectReference Include="..\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj"> + <Project>{d1dfbb0c-b570-4302-8f1e-2e3a19c41961}</Project> + <Name>GitHub.VisualStudio.UI</Name> + <Private>True</Private> <IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX> <IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly> </ProjectReference> </ItemGroup> + <ItemGroup> + <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> + </ItemGroup> + <ItemGroup> + <Analyzer Include="..\..\packages\SerilogAnalyzer.0.12.0.0\analyzers\dotnet\cs\SerilogAnalyzer.dll" /> + </ItemGroup> <PropertyGroup> <UseCodebase>true</UseCodebase> </PropertyGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> - <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" /> + <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != '' And '$(NCrunch)' != '1'" /> <Import Project="packaging.targets" /> + <!-- For regenerating templates on build --> + <Import Project="..\common\t4.targets" /> + <Import Project="versioning.targets" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> <ErrorText>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}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.0\build\Fody.targets'))" /> <Error Condition="!Exists('..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets'))" /> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> </Target> - <Import Project="..\..\packages\Fody.1.28.0\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.0\build\Fody.targets')" /> <Import Project="..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets" Condition="Exists('..\..\packages\SQLitePCL.raw_basic.0.7.3.0-vs2012\build\net45\SQLitePCL.raw_basic.targets')" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> + <Import Project="..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets" Condition="'$(VisualStudioVersion)' == '15.0' And Exists('..\..\packages\Microsoft.VSSDK.BuildTools.15.0.26201\build\Microsoft.VSSDK.BuildTools.targets')" /> + <Import Project="..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets" Condition="'$(VisualStudioVersion)' == '14.0' And Exists('..\..\packages\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.14.0.215\build\Microsoft.VisualStudio.Sdk.BuildTasks.14.0.targets')" /> </Project> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest b/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest index c2b9f7a2a2..590146df45 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest @@ -1,13 +1,20 @@ <ImageManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/VisualStudio/ImageManifestSchema/2014"> <Symbols> + <!-- We need to ensure that `GitHub.VisualStudio` has loaded before any of these resources are used. --> + <!-- This should happen when `AssemblyResolverPackage` auto-loads on `UICONTEXT.NoSolution`. --> <String Name="Resources" Value="/GitHub.VisualStudio;component/Resources/icons" /> + <Guid Name="guidImages" Value="{27841f47-070a-46d6-90be-a5cbbfc724ac}" /> <ID Name="logo" Value="1" /> <ID Name="arrow_left" Value="2" /> <ID Name="arrow_right" Value="3" /> <ID Name="refresh" Value="4" /> <ID Name="pullrequest" Value="5" /> + <ID Name="link_external" Value="6"/> + <ID Name="clippy" Value="7"/> + <ID Name="logo_toolbar" Value="8"/> + <ID Name="question" Value="9"/> </Symbols> <Images> @@ -26,5 +33,17 @@ <Image Guid="$(guidImages)" ID="$(pullrequest)"> <Source Uri="$(Resources)/git_pull_request.xaml" /> </Image> + <Image Guid="$(guidImages)" ID="$(link_external)"> + <Source Uri="$(Resources)/link_external.xaml" /> + </Image> + <Image Guid="$(guidImages)" ID="$(clippy)"> + <Source Uri="$(Resources)/clippy.xaml" /> + </Image> + <Image Guid="$(guidImages)" ID="$(logo_toolbar)"> + <Source Uri="$(Resources)/mark_github_toolbar.xaml" /> + </Image> + <Image Guid="$(guidImages)" ID="$(question)"> + <Source Uri="$(Resources)/question.xaml" /> + </Image> </Images> </ImageManifest> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct b/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct index 5240ea05b2..17e6cd0cd0 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct @@ -3,6 +3,9 @@ xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <!-- Import the standard ImageCatalogGuid monikers --> + <Include href="KnownImageIds.vsct"/> + <!-- This is the file that defines the actual layout and type of the commands. It is divided in different sections (e.g. command definition, command placement, ...), with each defining a specific set of properties. @@ -37,6 +40,18 @@ <Group guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup1" priority="0x0501"> <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbar" /> </Group> + + <Group guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarHelpMenuGroup" priority="0x0502"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbar" /> + </Group> + + <Group guid="guidContextMenuSet" id="idGitHubContextMenuGroup"> + </Group> + + <Group guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup" priority="0x0000"> + <Parent guid="guidContextMenuSet" id="idGitHubContextMenu"/> + </Group> + </Groups> <Menus> @@ -46,78 +61,320 @@ <CommandName>Window Toolbar</CommandName> </Strings> </Menu> + + <Menu guid="guidContextMenuSet" id="idGitHubContextMenu" priority="0x200" type="Menu"> + <Parent guid="guidContextMenuSet" id="idGitHubContextMenuGroup" /> + <Strings> + <ButtonText>GitHub</ButtonText> + <CommandName>GitHub</CommandName> + </Strings> + </Menu> + </Menus> - - <!--Buttons section. --> - <!--This section defines the elements the user can interact with, like a menu command or a button - or combo box in a toolbar. --> + + <!-- Parenting of buttons is done in the CommandPlacements section --> <Buttons> - <!--To define a menu group you have to specify its ID, the parent menu and its display priority. - The command is visible and enabled by default. If you need to change the visibility, status, etc, you can use - the CommandFlag node. - You can add more than one CommandFlag node e.g.: - <CommandFlag>DefaultInvisible</CommandFlag> - <CommandFlag>DynamicVisibility</CommandFlag> - If you do not want an image next to your command, remove the Icon node /> --> - - <Button guid="guidGitHubCmdSet" id="addConnectionCommand" priority="0x0100" type="Button"> - <Parent guid="guidGitHubCmdSet" id="idGitHubMenuGroup" /> + + <Button guid="guidGitHubCmdSet" id="addConnectionCommand" type="Button"> <Icon guid="guidImages" id="logo" /> <CommandFlag>IconIsMoniker</CommandFlag> <Strings> <ButtonText>&Connect to GitHub</ButtonText> + <CanonicalName>.GitHub.ConnectToGitHub</CanonicalName> + <LocCanonicalName>.GitHub.ConnectToGitHub</LocCanonicalName> </Strings> </Button> - <Button guid="guidGitHubCmdSet" id="showGitHubPaneCommand" priority="0x0100" type="Button"> - <Parent guid="guidSHLMainMenu" id="IDG_VS_WNDO_OTRWNDWS1" /> + <Button guid="guidGitHubCmdSet" id="showGitHubPaneCommand" type="Button"> <Icon guid="guidImages" id="logo" /> <CommandFlag>IconIsMoniker</CommandFlag> <Strings> <ButtonText>GitHub</ButtonText> + <CanonicalName>.GitHub.ShowGitHubPane</CanonicalName> + <LocCanonicalName>.GitHub.ShowGitHubPane</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubCmdSet" id="showCurrentPullRequestCommand" type="Button"> + <Icon guid="guidImages" id="logo" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <Strings> + <ButtonText>Show Current Pull Request</ButtonText> + <CanonicalName>.GitHub.ShowCurrentPullRequest</CanonicalName> + <LocCanonicalName>.GitHub.ShowCurrentPullRequest</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubCmdSet" id="syncSubmodulesCommand" type="Button"> + <Icon guid="guidImages" id="logo" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <Strings> + <ButtonText>Sync Submodules</ButtonText> + <CanonicalName>.GitHub.SyncSubmodules</CanonicalName> + <LocCanonicalName>.GitHub.SyncSubmodules</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubCmdSet" id="openFromUrlCommand" type="Button"> + <Icon guid="guidImages" id="logo" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>AllowParams</CommandFlag> + <Strings> + <ButtonText>Open from GitHub...</ButtonText> + <CanonicalName>.GitHub.OpenFromUrl</CanonicalName> + <LocCanonicalName>.GitHub.OpenFromUrl</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubCmdSet" id="openFromClipboardCommand" type="Button"> + <Icon guid="guidImages" id="logo" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>AllowParams</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <ButtonText>Open from clipboard</ButtonText> + <CanonicalName>.GitHub.OpenFromClipboard</CanonicalName> + <LocCanonicalName>.GitHub.OpenFromClipboard</LocCanonicalName> </Strings> </Button> <!--- Toolbar buttons --> - <Button guid="guidGitHubToolbarCmdSet" id="backCommand" priority="0x0100" type="Button"> - <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup" /> - <Icon guid="guidImages" id="arrow_left" /> + <Button guid="guidGitHubToolbarCmdSet" id="backCommand" type="Button"> + <Icon guid="ImageCatalogGuid" id="Backwards" /> <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultDisabled</CommandFlag> <Strings> - <ButtonText></ButtonText> + <ButtonText>Back</ButtonText> + <CanonicalName>.GitHub.Back</CanonicalName> + <LocCanonicalName>.GitHub.Back</LocCanonicalName> </Strings> </Button> - <Button guid="guidGitHubToolbarCmdSet" id="forwardCommand" priority="0x0101" type="Button"> - <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup" /> - <Icon guid="guidImages" id="arrow_right" /> + <Button guid="guidGitHubToolbarCmdSet" id="forwardCommand" type="Button"> + <Icon guid="ImageCatalogGuid" id="Forwards" /> <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultDisabled</CommandFlag> <Strings> - <ButtonText></ButtonText> + <ButtonText>Forward</ButtonText> + <CanonicalName>.GitHub.Forward</CanonicalName> + <LocCanonicalName>.GitHub.Forward</LocCanonicalName> </Strings> </Button> - <Button guid="guidGitHubToolbarCmdSet" id="pullRequestCommand" priority="0x0102" type="Button"> - <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup" /> + <Button guid="guidGitHubToolbarCmdSet" id="pullRequestCommand" type="Button"> <Icon guid="guidImages" id="pullrequest" /> <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultDisabled</CommandFlag> + <Strings> + <ButtonText>Pull Requests</ButtonText> + <CanonicalName>.GitHub.PullRequests</CanonicalName> + <LocCanonicalName>.GitHub.PullRequests</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubToolbarCmdSet" id="refreshCommand" type="Button"> + <Icon guid="ImageCatalogGuid" id="Refresh" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultDisabled</CommandFlag> + <Strings> + <ButtonText>Refresh</ButtonText> + <CanonicalName>.GitHub.Refresh</CanonicalName> + <LocCanonicalName>.GitHub.Refresh</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubToolbarCmdSet" id="githubCommand" type="Button"> + <Icon guid="ImageCatalogGuid" id="OpenWebSite" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultDisabled</CommandFlag> + <Strings> + <ButtonText>View on GitHub</ButtonText> + <CanonicalName>.GitHub.ViewOnGitHub</CanonicalName> + <LocCanonicalName>.GitHub.ViewOnGitHub</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidGitHubToolbarCmdSet" id="helpCommand" type="Button"> + <Icon guid="ImageCatalogGuid" id="StatusHelp" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <Strings> + <ButtonText>Help</ButtonText> + <CanonicalName>.GitHub.Help</CanonicalName> + <LocCanonicalName>.GitHub.Help</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidContextMenuSet" id="idCreateGistCommand" priority="0x0100" type="Button"> + <Icon guid="guidImages" id="logo" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <ButtonText>Create a GitHub Gist</ButtonText> + <CanonicalName>.GitHub.CreateGist</CanonicalName> + <LocCanonicalName>.GitHub.CreateGist</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidContextMenuSet" id="idCreateGistEnterpriseCommand" priority="0x0101" type="Button"> + <Icon guid="guidImages" id="logo" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <ButtonText>Create an Enterprise Gist</ButtonText> + <CanonicalName>.GitHub.CreateGistEnterprise</CanonicalName> + <LocCanonicalName>.GitHub.CreateGistEnterprise</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidContextMenuSet" id="openLinkCommand" type="Button"> + <Icon guid="ImageCatalogGuid" id="OpenWebSite" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <Strings> + <ButtonText>Open on GitHub</ButtonText> + <CanonicalName>.GitHub.OpenLink</CanonicalName> + <LocCanonicalName>.GitHub.OpenLink</LocCanonicalName> + </Strings> + </Button> + + <Button guid="guidContextMenuSet" id="copyLinkCommand" type="Button"> + <Icon guid="guidImages" id="clippy" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> <Strings> - <ButtonText></ButtonText> + <ButtonText>Copy link to clipboard</ButtonText> + <CanonicalName>.GitHub.CopyLink</CanonicalName> + <LocCanonicalName>.GitHub.CopyLink</LocCanonicalName> </Strings> </Button> - <Button guid="guidGitHubToolbarCmdSet" id="refreshCommand" priority="0x0110" type="Button"> - <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup1" /> - <Icon guid="guidImages" id="refresh" /> + <Button guid="guidContextMenuSet" id="idBlameCommand" type="Button"> + <Icon guid="guidImages" id="link_external" /> <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> <Strings> - <ButtonText></ButtonText> + <ButtonText>Blame</ButtonText> + <CanonicalName>.GitHub.Blame</CanonicalName> + <LocCanonicalName>.GitHub.Blame</LocCanonicalName> </Strings> </Button> + + <Button guid="guidContextMenuSet" id="goToSolutionOrPullRequestFileCommand" type="Button"> + <Icon guid="guidImages" id="pullrequest" /> + <CommandFlag>IconIsMoniker</CommandFlag> + <CommandFlag>DefaultInvisible</CommandFlag> + <CommandFlag>DynamicVisibility</CommandFlag> + <CommandFlag>TextChanges</CommandFlag> + <Strings> + <ButtonText>Go To Solution/PR File</ButtonText> + <CanonicalName>.GitHub.GoToSolutionOrPRFile</CanonicalName> + <LocCanonicalName>.GitHub.GoToSolutionOrPRFile</LocCanonicalName> + </Strings> + </Button> + </Buttons> </Commands> - + + <CommandPlacements> + + <!-- context menu --> + <CommandPlacement guid="guidContextMenuSet" id="idGitHubContextMenuGroup" priority="0x1000"> + <Parent guid="GUID_XAML_EDITOR" id="ID_XAML_CTXT"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="idGitHubContextMenuGroup" priority="0x1000"> + <Parent guid="GUID_HTML_EDITOR" id="ID_HTML_CTXT"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="idGitHubContextMenuGroup" priority="0x1000"> + <Parent guid="GUID_JSON_EDITOR" id="ID_JSON_CTXT"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="idGitHubContextMenuGroup" priority="0x1000"> + <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/> + </CommandPlacement> + + <!-- open and copy link to GitHub commands --> + <CommandPlacement guid="guidContextMenuSet" id="openLinkCommand" priority="0x100"> + <Parent guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="copyLinkCommand" priority="0x101"> + <Parent guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidGitHubCmdSet" id="openFromClipboardCommand" priority="0x0102"> + <Parent guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="idCreateGistCommand" priority="0x103"> + <Parent guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="idCreateGistEnterpriseCommand" priority="0x104"> + <Parent guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="idBlameCommand" priority="0x105"> + <Parent guid="guidContextMenuSet" id="idGitHubContextSubMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidContextMenuSet" id="goToSolutionOrPullRequestFileCommand" priority="0x1000"> + <Parent guid="guidContextMenuSet" id="idGitHubContextMenuGroup" /> + </CommandPlacement> + + <!-- Standard toolbar commands --> + <CommandPlacement guid="guidGitHubToolbarCmdSet" id="backCommand" priority="0x100"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidGitHubToolbarCmdSet" id="forwardCommand" priority="0x101"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidGitHubToolbarCmdSet" id="refreshCommand" priority="0x110"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup1"/> + </CommandPlacement> + + <!-- Feature commands (open pull requests, etc) --> + <CommandPlacement guid="guidGitHubToolbarCmdSet" id="pullRequestCommand" priority="0x102"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidGitHubToolbarCmdSet" id="githubCommand" priority="0x103"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarMenuGroup"/> + </CommandPlacement> + + <CommandPlacement guid="guidGitHubToolbarCmdSet" id="helpCommand" priority="0x111"> + <Parent guid="guidGitHubToolbarCmdSet" id="idGitHubToolbarHelpMenuGroup"/> + </CommandPlacement> + + <!-- Show GitHub pane command --> + <CommandPlacement guid="guidGitHubCmdSet" id="showGitHubPaneCommand" priority="0x0100"> + <Parent guid="guidSHLMainMenu" id="IDG_VS_WNDO_OTRWNDWS1"/> + </CommandPlacement> + + <!-- Open from GitHub command --> + <!-- Removing from `File > Open` menu until we have a proper UI + <CommandPlacement guid="guidGitHubCmdSet" id="openFromUrlCommand" priority="0x0100"> + <Parent guid="guidSHLMainMenu" id="IDG_VS_FILE_OPENSCC_CASCADE"/> + </CommandPlacement> + --> + + <!-- Add Connection (Team Explorer) command --> + <CommandPlacement guid="guidGitHubCmdSet" id="addConnectionCommand" priority="0x0100"> + <Parent guid="guidGitHubCmdSet" id="idGitHubMenuGroup"/> + </CommandPlacement> + + </CommandPlacements> + <Symbols> <!-- This is the package guid. --> <GuidSymbol name="guidGitHubPkg" value="{c3d3dc68-c977-411f-b3e8-03b0dccf7dfc}" /> @@ -128,29 +385,64 @@ <IDSymbol name="idGitHubMenuGroup" value="0x1020"/> <IDSymbol name="addConnectionCommand" value="0x110"/> <IDSymbol name="showGitHubPaneCommand" value="0x200"/> + <IDSymbol name="showCurrentPullRequestCommand" value="0x202"/> + <IDSymbol name="syncSubmodulesCommand" value="0x0203" /> + <IDSymbol name="openFromUrlCommand" value="0x0204" /> + <IDSymbol name="openFromClipboardCommand" value="0x0205" /> </GuidSymbol> <!-- This is the Manage Connections menu --> <GuidSymbol name="guidManageConnections" value="{0A014553-A0AA-46DD-8D6B-B8E3178CA435}"> <IDSymbol name="idManageConnections" value="0x1009"/> </GuidSymbol> - + <GuidSymbol name="guidImages" value="{27841f47-070a-46d6-90be-a5cbbfc724ac}" > <IDSymbol name="logo" value="1" /> <IDSymbol name="arrow_left" value="2" /> <IDSymbol name="arrow_right" value="3" /> <IDSymbol name="refresh" value="4" /> <IDSymbol name="pullrequest" value="5" /> + <IDSymbol name="link_external" value="6"/> + <IDSymbol name="clippy" value="7"/> + <IDSymbol name="logo_toolbar" value="8"/> + <IDSymbol name="question" value="9"/> </GuidSymbol> <GuidSymbol name="guidGitHubToolbarCmdSet" value="{C5F1193E-F300-41B3-B4C4-5A703DD3C1C6}"> <IDSymbol name="idGitHubToolbarMenuGroup" value="0x1110" /> <IDSymbol name="idGitHubToolbarMenuGroup1" value="0x1111" /> + <IDSymbol name="idGitHubToolbarHelpMenuGroup" value="0x1112" /> <IDSymbol name="idGitHubToolbar" value="0x1120" /> <IDSymbol name="backCommand" value="0x300" /> <IDSymbol name="forwardCommand" value="0x301" /> <IDSymbol name="refreshCommand" value="0x302" /> <IDSymbol name="pullRequestCommand" value="0x310" /> + <IDSymbol name="githubCommand" value="0x320" /> + <IDSymbol name="helpCommand" value="0x321" /> + </GuidSymbol> + + <GuidSymbol name="guidContextMenuSet" value="{31057D08-8C3C-4C5B-9F91-8682EA08EC27}"> + <IDSymbol name="idGitHubContextMenu" value="0x1000" /> + <IDSymbol name="idGitHubContextMenuGroup" value="0x1001" /> + <IDSymbol name="idGitHubContextSubMenuGroup" value="0x1002" /> + <IDSymbol name="openLinkCommand" value="0x100" /> + <IDSymbol name="copyLinkCommand" value="0x101"/> + <IDSymbol name="goToSolutionOrPullRequestFileCommand" value="0x0102" /> + <IDSymbol name="idCreateGistCommand" value="0x0400" /> + <IDSymbol name="idCreateGistEnterpriseCommand" value="0x0401" /> + <IDSymbol name="idBlameCommand" value="0x0500" /> + </GuidSymbol> + + <GuidSymbol name="GUID_XAML_EDITOR" value="{4C87B692-1202-46AA-B64C-EF01FAEC53DA}"> + <IDSymbol name="ID_XAML_CTXT" value="259"/> + </GuidSymbol> + + <GuidSymbol name="GUID_HTML_EDITOR" value="{78F03954-2FB8-4087-8CE7-59D71710B3BB}"> + <IDSymbol name="ID_HTML_CTXT" value="1"/> + </GuidSymbol> + + <GuidSymbol name="GUID_JSON_EDITOR" value="{F718CA06-CF4F-4A0C-9106-E79E9EE5E7CD}"> + <IDSymbol name="ID_JSON_CTXT" value="3"/> </GuidSymbol> </Symbols> diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index 50c8c57370..770e023398 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -1,70 +1,299 @@ using System; +using System.Threading; +using System.Threading.Tasks; +using System.ComponentModel.Design; +using System.ComponentModel.Composition; using System.Runtime.InteropServices; -using GitHub.Extensions; +using GitHub.Api; +using GitHub.Commands; +using GitHub.Info; +using GitHub.Exports; +using GitHub.Logging; using GitHub.Services; -using GitHub.UI; -using GitHub.VisualStudio.Base; +using GitHub.Settings; +using GitHub.VisualStudio.Commands; +using GitHub.Services.Vssdk.Commands; +using GitHub.ViewModels.GitHubPane; +using GitHub.VisualStudio.Settings; using GitHub.VisualStudio.UI; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; +using Serilog; +using Task = System.Threading.Tasks.Task; namespace GitHub.VisualStudio { - /// <summary> - /// This is the class that implements the package exposed by this assembly. - /// - /// The minimum requirement for a class to be considered a valid package for Visual Studio - /// is to implement the IVsPackage interface and register itself with the shell. - /// This package uses the helper classes defined inside the Managed Package Framework (MPF) - /// to do it: it derives from the Package class that provides the implementation of the - /// IVsPackage interface and uses the registration attributes defined in the framework to - /// register itself and its components with the shell. - /// </summary> - // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is - // a package. - [PackageRegistration(UseManagedResourcesOnly = true)] - // This attribute is used to register the information needed to show this package - // in the Help/About dialog of Visual Studio. - [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] - [Guid(GuidList.guidGitHubPkgString)] - //[ProvideBindingPath] + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [InstalledProductRegistration("#110", "#112", AssemblyVersionInformation.Version, IconResourceID = 400)] + [Guid(Guids.guidGitHubPkgString)] [ProvideMenuResource("Menus.ctmenu", 1)] - //[ProvideAutoLoad(UIContextGuids.NoSolution)] - [ProvideAutoLoad("11B8E6D7-C08B-4385-B321-321078CDD1F8")] - [ProvideToolWindow(typeof(GitHubPane), Orientation = ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = EnvDTE.Constants.vsWindowKindSolutionExplorer)] - public class GitHubPackage : PackageBase + [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] + [ProvideOptionPage(typeof(OptionsPage), "GitHub for Visual Studio", "General", 0, 0, supportsAutomation: true)] + public class GitHubPackage : AsyncPackage { - public GitHubPackage() + static readonly ILogger log = LogManager.ForContext<GitHubPackage>(); + + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) + { + LogVersionInformation(); + await base.InitializeAsync(cancellationToken, progress); + + await InitializeLoggingAsync(); + await GetServiceAsync(typeof(IUsageTracker)); + + // Avoid delays when there is ongoing UI activity. + // See: https://github.com/github/VisualStudio/issues/1537 + await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeMenus); + } + + async Task InitializeLoggingAsync() + { + var packageSettings = await GetServiceAsync(typeof(IPackageSettings)) as IPackageSettings; + LogManager.EnableTraceLogging(packageSettings?.EnableTraceLogging ?? false); + if (packageSettings != null) + { + packageSettings.PropertyChanged += (sender, args) => + { + if (args.PropertyName == nameof(packageSettings.EnableTraceLogging)) + { + LogManager.EnableTraceLogging(packageSettings.EnableTraceLogging); + } + }; + } + } + + void LogVersionInformation() + { + var packageVersion = ApplicationInfo.GetPackageVersion(this); + var hostVersionInfo = ApplicationInfo.GetHostVersionInfo(); + log.Information("Initializing GitHub Extension v{PackageVersion} in {$FileDescription} ({$ProductVersion})", + packageVersion, hostVersionInfo.FileDescription, hostVersionInfo.ProductVersion); + } + + async Task InitializeMenus() { + IVsCommandBase[] commands; + if (ExportForVisualStudioProcessAttribute.IsVisualStudioProcess()) + { + var componentModel = (IComponentModel)(await GetServiceAsync(typeof(SComponentModel))); + var exports = componentModel.DefaultExportProvider; + commands = new IVsCommandBase[] + { + exports.GetExportedValue<IAddConnectionCommand>(), + exports.GetExportedValue<IBlameLinkCommand>(), + exports.GetExportedValue<ICopyLinkCommand>(), + exports.GetExportedValue<ICreateGistCommand>(), + exports.GetExportedValue<ICreateGistEnterpriseCommand>(), + exports.GetExportedValue<IOpenLinkCommand>(), + exports.GetExportedValue<IOpenPullRequestsCommand>(), + exports.GetExportedValue<IShowCurrentPullRequestCommand>(), + exports.GetExportedValue<IShowGitHubPaneCommand>(), + exports.GetExportedValue<IGoToSolutionOrPullRequestFileCommand>(), + exports.GetExportedValue<ISyncSubmodulesCommand>(), + exports.GetExportedValue<IOpenFromUrlCommand>(), + exports.GetExportedValue<IOpenFromClipboardCommand>() + }; + } + else + { + // Show info message box when executed in non-Visual Studio process + var message = Resources.BlendDialogText; + commands = new IVsCommandBase[] + { + new ShowMessageBoxCommand(AddConnectionCommand.CommandSet, AddConnectionCommand.CommandId, this, message), + new ShowMessageBoxCommand(ShowGitHubPaneCommand.CommandSet, ShowGitHubPaneCommand.CommandId, this, message) + }; + } + + await JoinableTaskFactory.SwitchToMainThreadAsync(); + var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService))); + menuService.AddCommands(commands); } - public GitHubPackage(IServiceProvider serviceProvider) - : base(serviceProvider) + async Task EnsurePackageLoaded(Guid packageGuid) { + var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; + if (shell != null) + { + IVsPackage vsPackage; + ErrorHandler.ThrowOnFailure(shell.LoadPackage(ref packageGuid, out vsPackage)); + } + } + } + + [PartCreationPolicy(CreationPolicy.Shared)] + public class ServiceProviderExports + { + readonly IServiceProvider serviceProvider; + + [ImportingConstructor] + public ServiceProviderExports([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + [ExportForVisualStudioProcess] + public ILoginManager LoginManager => GetService<ILoginManager>(); + + [ExportForVisualStudioProcess] + public IGitHubServiceProvider GitHubServiceProvider => GetService<IGitHubServiceProvider>(); + [ExportForVisualStudioProcess] + public IUsageTracker UsageTracker => GetService<IUsageTracker>(); + + [ExportForVisualStudioProcess] + public IVSGitExt VSGitExt => GetService<IVSGitExt>(); + + [ExportForVisualStudioProcess] + public IPackageSettings PackageSettings => GetService<IPackageSettings>(); + + T GetService<T>() => (T)serviceProvider.GetService(typeof(T)); + } + + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [ProvideService(typeof(ILoginManager), IsAsyncQueryable = true)] + [ProvideService(typeof(IGitHubServiceProvider), IsAsyncQueryable = true)] + [ProvideService(typeof(IUsageTracker), IsAsyncQueryable = true)] + [ProvideService(typeof(IPackageSettings), IsAsyncQueryable = true)] + [ProvideService(typeof(IUsageService), IsAsyncQueryable = true)] + [ProvideService(typeof(IVSGitExt), IsAsyncQueryable = true)] + [ProvideService(typeof(IGitHubToolWindowManager))] + [Guid(ServiceProviderPackageId)] + public sealed class ServiceProviderPackage : AsyncPackage, IServiceProviderPackage, IGitHubToolWindowManager + { + public const string ServiceProviderPackageId = "D5CE1488-DEDE-426D-9E5B-BFCCFBE33E53"; + static readonly ILogger log = LogManager.ForContext<ServiceProviderPackage>(); + + protected override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) + { + AddService(typeof(IGitHubServiceProvider), CreateService, true); + AddService(typeof(IVSGitExt), CreateService, true); + AddService(typeof(IUsageTracker), CreateService, true); + AddService(typeof(IUsageService), CreateService, true); + AddService(typeof(ILoginManager), CreateService, true); + AddService(typeof(IGitHubToolWindowManager), CreateService, true); + AddService(typeof(IPackageSettings), CreateService, true); + return Task.CompletedTask; } - protected override void Initialize() + public async Task<IGitHubPaneViewModel> ShowGitHubPane() { + var pane = ShowToolWindow(new Guid(GitHubPane.GitHubPaneGuid)); + if (pane == null) + return null; + var frame = pane.Frame as IVsWindowFrame; + if (frame != null) + { + ErrorHandler.Failed(frame.Show()); + } + + var gitHubPane = (GitHubPane)pane; + return await gitHubPane.GetViewModelAsync(); + } - ServiceProvider.AddTopLevelMenuItem(GuidList.guidGitHubCmdSet, PkgCmdIDList.addConnectionCommand, (s, e) => StartFlow(UIControllerFlow.Authentication)); - ServiceProvider.AddTopLevelMenuItem(GuidList.guidGitHubCmdSet, PkgCmdIDList.showGitHubPaneCommand, (s, e) => + static ToolWindowPane ShowToolWindow(Guid windowGuid) + { + IVsWindowFrame frame; + if (ErrorHandler.Failed(Services.UIShell.FindToolWindow((uint)__VSCREATETOOLWIN.CTW_fForceCreate, + ref windowGuid, out frame))) + { + log.Error("Unable to find or create GitHubPane '{Guid}'", UI.GitHubPane.GitHubPaneGuid); + return null; + } + if (ErrorHandler.Failed(frame.Show())) { - var window = FindToolWindow(typeof(GitHubPane), 0, true); - if (window?.Frame == null) - throw new NotSupportedException("Cannot create tool window"); + log.Error("Unable to show GitHubPane '{Guid}'", UI.GitHubPane.GitHubPaneGuid); + return null; + } - var windowFrame = (IVsWindowFrame)window.Frame; - ErrorHandler.ThrowOnFailure(windowFrame.Show()); - }); - base.Initialize(); + object docView = null; + if (ErrorHandler.Failed(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView))) + { + log.Error("Unable to grab instance of GitHubPane '{Guid}'", UI.GitHubPane.GitHubPaneGuid); + return null; + } + return docView as GitHubPane; } - void StartFlow(UIControllerFlow controllerFlow) + async Task<object> CreateService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) { - var uiProvider = ServiceProvider.GetExportedValue<IUIProvider>(); - uiProvider.RunUI(controllerFlow, null); + if (serviceType == null) + return null; + + if (container != this) + return null; + + if (serviceType == typeof(IGitHubServiceProvider)) + { + //var sp = await GetServiceAsync(typeof(SVsServiceProvider)) as IServiceProvider; + var result = new GitHubServiceProvider(this, this); + await result.Initialize(); + return result; + } + else if (serviceType == typeof(ILoginManager)) + { + // These services are got through MEF and we will take a performance hit if ILoginManager is requested during + // InitializeAsync. TODO: We can probably make LoginManager a normal MEF component rather than a service. + var serviceProvider = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider; + var keychain = serviceProvider.GetService<IKeychain>(); + var oauthListener = serviceProvider.GetService<IOAuthCallbackListener>(); + + // HACK: We need to make sure this is run on the main thread. We really + // shouldn't be injecting a view model concern into LoginManager - this + // needs to be refactored. See #1398. + var lazy2Fa = new Lazy<ITwoFactorChallengeHandler>(() => + ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return serviceProvider.GetService<ITwoFactorChallengeHandler>(); + })); + + return new LoginManager( + keychain, + lazy2Fa, + oauthListener, + ApiClientConfiguration.ClientId, + ApiClientConfiguration.ClientSecret, + ApiClientConfiguration.RequiredScopes, + ApiClientConfiguration.AuthorizationNote, + ApiClientConfiguration.MachineFingerprint); + } + else if (serviceType == typeof(IUsageService)) + { + var sp = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider; + var environment = new Rothko.Environment(); + return new UsageService(sp, environment); + } + else if (serviceType == typeof(IUsageTracker)) + { + var usageService = await GetServiceAsync(typeof(IUsageService)) as IUsageService; + var serviceProvider = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider; + var settings = await GetServiceAsync(typeof(IPackageSettings)) as IPackageSettings; + return new UsageTracker(serviceProvider, usageService, settings); + } + else if (serviceType == typeof(IVSGitExt)) + { + var vsVersion = ApplicationInfo.GetHostVersionInfo().FileMajorPart; + return new VSGitExtFactory(vsVersion, this).Create(); + } + else if (serviceType == typeof(IGitHubToolWindowManager)) + { + return this; + } + else if (serviceType == typeof(IPackageSettings)) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var sp = new ServiceProvider(Services.Dte as Microsoft.VisualStudio.OLE.Interop.IServiceProvider); + return new PackageSettings(sp); + } + // go the mef route + else + { + var sp = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider; + return sp.TryGetService(serviceType); + } } } } diff --git a/src/GitHub.VisualStudio/GitHubPanePackage.cs b/src/GitHub.VisualStudio/GitHubPanePackage.cs new file mode 100644 index 0000000000..a92afcef08 --- /dev/null +++ b/src/GitHub.VisualStudio/GitHubPanePackage.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; +using GitHub.VisualStudio.UI; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.VisualStudio +{ + /// <summary> + /// This is the host package for the <see cref="GitHubPane"/> tool window. + /// </summary> + /// <remarks> + /// This package mustn't use MEF. + /// See: https://github.com/github/VisualStudio/issues/1550 + /// </remarks> + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [Guid(Guids.GitHubPanePackageId)] + [ProvideToolWindow(typeof(GitHubPane), Orientation = ToolWindowOrientation.Right, + Style = VsDockStyle.Tabbed, Window = EnvDTE.Constants.vsWindowKindSolutionExplorer)] + public sealed class GitHubPanePackage : AsyncPackage + { + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Helpers/ActiveDocumentSnapshot.cs b/src/GitHub.VisualStudio/Helpers/ActiveDocumentSnapshot.cs new file mode 100644 index 0000000000..437544c345 --- /dev/null +++ b/src/GitHub.VisualStudio/Helpers/ActiveDocumentSnapshot.cs @@ -0,0 +1,50 @@ +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TextManager.Interop; +using System; +using System.ComponentModel.Composition; +using GitHub.Logging; + +namespace GitHub.VisualStudio +{ + [Export(typeof(IActiveDocumentSnapshot))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ActiveDocumentSnapshot : IActiveDocumentSnapshot + { + public string Name { get; private set; } + public int StartLine { get; private set; } + public int EndLine { get; private set; } + + [ImportingConstructor] + public ActiveDocumentSnapshot([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + StartLine = EndLine = -1; + var document = Services.Dte2?.ActiveDocument; + Name = document.FullName.Equals(document.ProjectItem.FileNames[1], StringComparison.OrdinalIgnoreCase) ? document.ProjectItem.FileNames[1] : document.FullName; + + var textManager = serviceProvider.GetService(typeof(SVsTextManager)) as IVsTextManager; + Log.Assert(textManager != null, "No SVsTextManager service available"); + if (textManager == null) + return; + IVsTextView view; + int anchorLine, anchorCol, endLine, endCol; + if (ErrorHandler.Succeeded(textManager.GetActiveView(0, null, out view)) && + ErrorHandler.Succeeded(view.GetSelection(out anchorLine, out anchorCol, out endLine, out endCol))) + { + // Ignore the bottom anchor or end line if it has zero width (starts on column 0) + // This prevents non-visible parts of the selection from being inclused in the range + if (anchorLine < endLine && endCol == 0) + { + endLine--; + } + else if (anchorLine > endLine && anchorCol == 0) + { + anchorLine--; + } + + StartLine = anchorLine + 1; + EndLine = endLine + 1; + } + } + } +} diff --git a/src/GitHub.VisualStudio/Helpers/Colors.cs b/src/GitHub.VisualStudio/Helpers/Colors.cs deleted file mode 100644 index f3f33f3c54..0000000000 --- a/src/GitHub.VisualStudio/Helpers/Colors.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -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 Color LightThemeNavigationItem = Color.FromRgb(66, 66, 66); - public static Color DarkThemeNavigationItem = Color.FromRgb(200, 200, 200); - - public static int ToInt32(this Color color) - { - return BitConverter.ToInt32(new byte[]{ color.B, color.G, color.R, color.A }, 0); - } - - public static Color ToColor(this System.Drawing.Color color) - { - return Color.FromArgb(color.A, color.R, color.G, color.B); - } - } -} diff --git a/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs b/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs deleted file mode 100644 index 80424db8f7..0000000000 --- a/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Windows; -using System.Reflection; -using System.IO; -using System.Linq; - -namespace GitHub.VisualStudio.Helpers -{ - public class SharedDictionaryManager : ResourceDictionary - { - static readonly string[] ourAssemblies = - { - "GitHub.Api", - "GitHub.App", - "GitHub.CredentialManagement", - "GitHub.Exports", - "GitHub.Exports.Reactive", - "GitHub.Extensions", - "GitHub.Extensions.Reactive", - "GitHub.UI", - "GitHub.UI.Reactive", - "GitHub.VisualStudio" - }; - - static SharedDictionaryManager() - { - AppDomain.CurrentDomain.AssemblyResolve += LoadAssemblyFromRunDir; - } - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - static Assembly LoadAssemblyFromRunDir(object sender, ResolveEventArgs e) - { - try - { - var name = new AssemblyName(e.Name); - if (!ourAssemblies.Contains(name.Name)) - return null; - var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var filename = Path.Combine(path, name.Name + ".dll"); - if (!File.Exists(filename)) - return null; - return Assembly.LoadFrom(filename); - } - catch (Exception ex) - { - var log = string.Format(CultureInfo.CurrentCulture, "Error occurred loading {0} from {1}.{2}{3}{4}", e.Name, Assembly.GetExecutingAssembly().Location, Environment.NewLine, ex, Environment.NewLine); - VsOutputLogger.Write(log); - } - return null; - } - -#region ResourceDictionaryImplementation - static readonly Dictionary<Uri, ResourceDictionary> resourceDicts = new Dictionary<Uri, ResourceDictionary>(); - - Uri sourceUri; - public new Uri Source - { - get { return sourceUri; } - set - { - sourceUri = value; - ResourceDictionary ret; - if (resourceDicts.TryGetValue(value, out ret)) - { - MergedDictionaries.Add(ret); - return; - } - base.Source = value; - resourceDicts.Add(value, this); - } - } -#endregion - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/IServiceProviderPackage.cs b/src/GitHub.VisualStudio/IServiceProviderPackage.cs new file mode 100644 index 0000000000..92206942f8 --- /dev/null +++ b/src/GitHub.VisualStudio/IServiceProviderPackage.cs @@ -0,0 +1,8 @@ +using System; + +namespace GitHub.VisualStudio +{ + public interface IServiceProviderPackage : IServiceProvider, Microsoft.VisualStudio.Shell.IAsyncServiceProvider + { + } +} diff --git a/src/GitHub.VisualStudio/LICENSE.txt b/src/GitHub.VisualStudio/LICENSE.txt index cdefe99d5e..c86728a003 100644 --- a/src/GitHub.VisualStudio/LICENSE.txt +++ b/src/GitHub.VisualStudio/LICENSE.txt @@ -9,8 +9,8 @@ IF YOU DO NOT AGREE TO ALL OF THE TERMS OF THIS EULA, DO NOT INSTALL, USE OR COP • You must agree to all of the terms of this EULA to use this Software. • If so, you may use the Software for free and for any lawful purpose. • This Software automatically communicates with GitHub servers - for three reasons: (1) to receive and install updates; (2) to send error - reports; and (3) to send anonymized usage information. You can view + for two reasons: (1) to send error reports; and + (2) to send anonymized usage information. You can view sample data to see what information is sent, and you may opt out of sending the anonymized usage data. • This Software is provided "as-is" with no warranties, and you agree @@ -42,6 +42,25 @@ This EULA entitles you to install as many copies of the Software as you want, an 4. You may not remove or alter any proprietary notices or marks on the Software. + PRIVACY NOTICES + +The Software automatically communicates with GitHub servers for two purposes: (1) sending error reports; and (2) sending anonymized usage data so we may improve the Software. If you would like to learn more about the specific information we send, please visit https://visualstudio.github.com/samples.html. You may opt out of sending the anonymized usage data, but if you do not want the Software to send error reports, you must uninstall the Software. + + 1. ERROR REPORTS. In order to help us improve the Software, when the + Software encounters certain errors, it will automatically send some + information to GitHub about the error (as described at the URL + above). + This feature may not be disabled. If you do not want to send error + reports to GitHub, you must uninstall the Software. + + 2. ANONYMIZED USAGE DATA. GitHub collects anonymized data about + your usage of the Software to help us make it more awesome. + Approximately once a day the Software sends such data + (as described in more detail at the URL above) to GitHub's servers. + If you do not want to send anonymized usage data to GitHub, + you may opt out by changing your settings in the + Visual Studio Options dialog under the GitHub section. + OPEN-SOURCE NOTICES Certain components of the Software may be subject to open-source software licenses ("Open-Source Components"), which means any software license approved as open-source licenses by the Open Source Initiative or any substantially similar licenses, including without limitation any license that, as a condition of distribution of the software licensed under such license, requires that the distributor make the software available in source code format. If you would like to see copies of the licenses applicable to the Open-Source Components, please visit https://visualstudio.github.com/credits.html. diff --git a/src/GitHub.VisualStudio/NLog.config b/src/GitHub.VisualStudio/NLog.config deleted file mode 100644 index 465d160f37..0000000000 --- a/src/GitHub.VisualStudio/NLog.config +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - - <!-- The path for the log file is set at runtime. --> - - <targets> - <target name="file" xsi:type="File" FileName="${specialfolder:dir=Temp:file=extension.log:folder=LocalApplicationData}"/> - <target name="info" xsi:type="File" FileName="${specialfolder:dir=Temp:file=extension.log:folder=LocalApplicationData}" layout="${longdate}|${level:uppercase=true}|thread:${threadid:padding=2}|${logger:shortName=true}|${message}${onexception:inner=${newline}${exception:innerformat=ToString,StackTrace:format=ToString,StackTrace}}"/> - </targets> - - <rules> - <logger name="*" minlevel="Info" writeTo="file" /> - <logger name="*" minlevel="Info" writeTo="info" /> - </rules> -</nlog> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/PkgCmdID.cs b/src/GitHub.VisualStudio/PkgCmdID.cs deleted file mode 100644 index 1b20104b15..0000000000 --- a/src/GitHub.VisualStudio/PkgCmdID.cs +++ /dev/null @@ -1,15 +0,0 @@ -// PkgCmdID.cs -// MUST match PkgCmdID.h -namespace GitHub.VisualStudio -{ - static class PkgCmdIDList - { - public const int addConnectionCommand = 0x110; - public const int idGitHubToolbar = 0x1120; - public const int showGitHubPaneCommand = 0x200; - public const int backCommand = 0x300; - public const int forwardCommand = 0x301; - public const int refreshCommand = 0x302; - public const int pullRequestCommand = 0x310; - }; -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs b/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs index 2d3f835c69..9b8e052f21 100644 --- a/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs +++ b/src/GitHub.VisualStudio/Properties/AssemblyInfo.cs @@ -1,6 +1,12 @@ -using System.Reflection; +using System; +using System.Reflection; using System.Runtime.InteropServices; +using System.Windows.Markup; [assembly: AssemblyTitle("GitHub.VisualStudio")] [assembly: AssemblyDescription("GitHub for Visual Studio VSPackage")] -[assembly: Guid("fad77eaa-3fe1-4c4b-88dc-3753b6263cd7")] \ No newline at end of file +[assembly: Guid("fad77eaa-3fe1-4c4b-88dc-3753b6263cd7")] + +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.Views")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.Views.Dialog")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.VisualStudio.Views.GitHubPane")] diff --git a/src/GitHub.VisualStudio/Resources.Designer.cs b/src/GitHub.VisualStudio/Resources.Designer.cs index 2a41594329..a224bebbd7 100644 --- a/src/GitHub.VisualStudio/Resources.Designer.cs +++ b/src/GitHub.VisualStudio/Resources.Designer.cs @@ -114,6 +114,15 @@ public static string browsePathButtonContent { } } + /// <summary> + /// Looks up a localized string similar to Cancel. + /// </summary> + public static string CancelLink { + get { + return ResourceManager.GetString("CancelLink", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Clone. /// </summary> @@ -213,6 +222,15 @@ public static string enterpriseUrlPromptText { } } + /// <summary> + /// Looks up a localized string similar to File Name. + /// </summary> + public static string fileNameText { + get { + return ResourceManager.GetString("fileNameText", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Search repositories. /// </summary> @@ -240,6 +258,15 @@ public static string GetStartedLink { } } + /// <summary> + /// Looks up a localized string similar to Failed to create gist. + /// </summary> + public static string gistCreationFailedMessage { + get { + return ResourceManager.GetString("gistCreationFailedMessage", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Connect…. /// </summary> @@ -366,6 +393,15 @@ public static string makePrivateContent { } } + /// <summary> + /// Looks up a localized string similar to Private Gist. + /// </summary> + public static string makePrivateGist { + get { + return ResourceManager.GetString("makePrivateGist", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Name. /// </summary> @@ -375,6 +411,15 @@ public static string nameText { } } + /// <summary> + /// Looks up a localized string similar to To use this feature you need to sign in again.. + /// </summary> + public static string needLogout { + get { + return ResourceManager.GetString("needLogout", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to No repositories. /// </summary> @@ -474,6 +519,15 @@ public static string RepoNameText { } } + /// <summary> + /// Looks up a localized string similar to Repository created successfully.. + /// </summary> + public static string RepositoryPublishedMessage { + get { + return ResourceManager.GetString("RepositoryPublishedMessage", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Resend. /// </summary> diff --git a/src/GitHub.VisualStudio/Resources.resx b/src/GitHub.VisualStudio/Resources.resx deleted file mode 100644 index c7e6c4739d..0000000000 --- a/src/GitHub.VisualStudio/Resources.resx +++ /dev/null @@ -1,282 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<root> - <!-- - Microsoft ResX Schema - - Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes - associated with the data types. - - Example: - - ... ado.net/XML headers & schema ... - <resheader name="resmimetype">text/microsoft-resx</resheader> - <resheader name="version">2.0</resheader> - <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> - <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> - <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> - <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> - <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> - <value>[base64 mime encoded serialized .NET Framework object]</value> - </data> - <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> - <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> - <comment>This is a comment</comment> - </data> - - There are any number of "resheader" rows that contain simple - name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the - mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can - read any of the formats listed below. - - mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with - : System.Runtime.Serialization.Formatters.Soap.SoapFormatter - : and then encoded with base64 encoding. - - mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array - : using a System.ComponentModel.TypeConverter - : and then encoded with base64 encoding. - --> - <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> - <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> - <xsd:element name="root" msdata:IsDataSet="true"> - <xsd:complexType> - <xsd:choice maxOccurs="unbounded"> - <xsd:element name="metadata"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" /> - </xsd:sequence> - <xsd:attribute name="name" use="required" type="xsd:string" /> - <xsd:attribute name="type" type="xsd:string" /> - <xsd:attribute name="mimetype" type="xsd:string" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="assembly"> - <xsd:complexType> - <xsd:attribute name="alias" type="xsd:string" /> - <xsd:attribute name="name" type="xsd:string" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="data"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> - <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> - <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> - <xsd:attribute ref="xml:space" /> - </xsd:complexType> - </xsd:element> - <xsd:element name="resheader"> - <xsd:complexType> - <xsd:sequence> - <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> - </xsd:sequence> - <xsd:attribute name="name" type="xsd:string" use="required" /> - </xsd:complexType> - </xsd:element> - </xsd:choice> - </xsd:complexType> - </xsd:element> - </xsd:schema> - <resheader name="resmimetype"> - <value>text/microsoft-resx</value> - </resheader> - <resheader name="version"> - <value>2.0</value> - </resheader> - <resheader name="reader"> - <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <resheader name="writer"> - <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> - </resheader> - <data name="authenticationFailedLabelContent" xml:space="preserve"> - <value>Invalid authentication code</value> - </data> - <data name="authenticationFailedLabelMessage" xml:space="preserve"> - <value>Try entering the code again or clicking the resend button to get a new authentication code.</value> - </data> - <data name="authenticationSentLabelContent" xml:space="preserve"> - <value>Authentication code sent!</value> - </data> - <data name="authenticationSentLabelMessage" xml:space="preserve"> - <value>If you do not receive the authentication code, contact support@github.com.</value> - </data> - <data name="browsePathButtonContent" xml:space="preserve"> - <value>Browse</value> - </data> - <data name="GetStartedLink" xml:space="preserve"> - <value>Get Started</value> - </data> - <data name="couldNotConnectToGitHubText" xml:space="preserve"> - <value>Could not connect to github.com</value> - </data> - <data name="couldNotConnectToTheServerText" xml:space="preserve"> - <value>Could not connect to the server.</value> - </data> - <data name="CreateLink" xml:space="preserve"> - <value>Create</value> - </data> - <data name="descriptionOptionalText" xml:space="preserve"> - <value>Description (Optional)</value> - </data> - <data name="descriptionText" xml:space="preserve"> - <value>Description</value> - </data> - <data name="dontHaveAnAccountText" xml:space="preserve"> - <value>Don’t have an account? </value> - </data> - <data name="dontHaveGitHubEnterpriseText" xml:space="preserve"> - <value>Don’t have GitHub Enterprise? </value> - </data> - <data name="dotComConnectionFailedMessageMessage" xml:space="preserve"> - <value>Please check your internet connection and try again.</value> - </data> - <data name="ForgotPasswordLink" xml:space="preserve"> - <value>(forgot your password?)</value> - </data> - <data name="LoginLink" xml:space="preserve"> - <value>Login</value> - </data> - <data name="LoginFailedMessage" xml:space="preserve"> - <value>Check your username and password, then try again</value> - </data> - <data name="PasswordPrompt" xml:space="preserve"> - <value>Password</value> - </data> - <data name="UserNameOrEmailPromptText" xml:space="preserve"> - <value>Username or email</value> - </data> - <data name="enterpriseConnectingFailedMessage" xml:space="preserve"> - <value>The host isn't available or is not a GitHub Enterprise server. Check the address and try again.</value> - </data> - <data name="enterpriseUrlPromptText" xml:space="preserve"> - <value>GitHub Enterprise server address</value> - </data> - <data name="loadingFailedMessageMessage" xml:space="preserve"> - <value>An error occurred while loading repositories</value> - </data> - <data name="loadingFailedMessageContent" xml:space="preserve"> - <value>Some or all repositories may not have loaded. Close the dialog and try again.</value> - </data> - <data name="filterTextPromptText" xml:space="preserve"> - <value>Search repositories</value> - </data> - <data name="ignoreTemplateListText" xml:space="preserve"> - <value>Git ignore</value> - </data> - <data name="learnMoreLink" xml:space="preserve"> - <value>Learn more</value> - </data> - <data name="licenseListText" xml:space="preserve"> - <value>License</value> - </data> - <data name="localPathText" xml:space="preserve"> - <value>Local path</value> - </data> - <data name="LoginFailedText" xml:space="preserve"> - <value>Login failed.</value> - </data> - <data name="makePrivateContent" xml:space="preserve"> - <value>Private Repository</value> - </data> - <data name="nameText" xml:space="preserve"> - <value>Name</value> - </data> - <data name="noRepositoriesMessageText" xml:space="preserve"> - <value>No repositories</value> - </data> - <data name="openTwoFactorAuthAppText" xml:space="preserve"> - <value>Open the two-factor authentication app on your device to view your authentication code.</value> - </data> - <data name="orText" xml:space="preserve"> - <value>or</value> - </data> - <data name="publishText" xml:space="preserve"> - <value>Publish</value> - </data> - <data name="RepoDoesNotHaveRemoteText" xml:space="preserve"> - <value>This repository does not have a remote. Fill out the form to publish it to GitHub.</value> - </data> - <data name="RepoNameText" xml:space="preserve"> - <value>Repository Name</value> - </data> - <data name="resendCodeButtonContent" xml:space="preserve"> - <value>Resend</value> - </data> - <data name="resendCodeButtonToolTip" xml:space="preserve"> - <value>Send the code to your registered SMS Device again</value> - </data> - <data name="SignOutLink" xml:space="preserve"> - <value>Sign out</value> - </data> - <data name="SignUpLink" xml:space="preserve"> - <value>Sign up</value> - </data> - <data name="twoFactorAuthText" xml:space="preserve"> - <value>Two-factor authentication</value> - </data> - <data name="verifyText" xml:space="preserve"> - <value>Verify</value> - </data> - <data name="CloneLink" xml:space="preserve"> - <value>Clone</value> - </data> - <data name="GitHubInvitationSectionConnectLabel" xml:space="preserve"> - <value>Connect…</value> - </data> - <data name="BlurbText" xml:space="preserve"> - <value>Powerful collaboration, code review, and code management for open source and private projects.</value> - </data> - <data name="GitHubPublishSectionTitle" xml:space="preserve"> - <value>Publish to GitHub</value> - </data> - <data name="GraphsNavigationItemText" xml:space="preserve"> - <value>Graphs</value> - </data> - <data name="IssuesNavigationItemText" xml:space="preserve"> - <value>Issues</value> - </data> - <data name="pathText" xml:space="preserve"> - <value>Path</value> - </data> - <data name="PullRequestsNavigationItemText" xml:space="preserve"> - <value>Pull Requests</value> - </data> - <data name="PulseNavigationItemText" xml:space="preserve"> - <value>Pulse</value> - </data> - <data name="WikiNavigationItemText" xml:space="preserve"> - <value>Wiki</value> - </data> - <data name="NotLoggedInMessage" xml:space="preserve"> - <value>You are not logged in to {0}, so certain git operations may fail. [Login now]({1})</value> - </data> -</root> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/clippy.xaml b/src/GitHub.VisualStudio/Resources/icons/clippy.xaml new file mode 100644 index 0000000000..0ef4213aa6 --- /dev/null +++ b/src/GitHub.VisualStudio/Resources/icons/clippy.xaml @@ -0,0 +1,17 @@ +<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + Width="1024" Height="1024"> + <Border BorderBrush="Transparent" BorderThickness="1"> + <ghfvs:OcticonImage xmlns:ghfvs="https://github.com/github/VisualStudio" + Foreground="{DynamicResource GitHubContextMenuIconBrush}" + Icon="clippy"> + <ghfvs:OcticonImage.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </ghfvs:OcticonImage.Resources> + </ghfvs:OcticonImage> + </Border> +</Viewbox> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/link_external.xaml b/src/GitHub.VisualStudio/Resources/icons/link_external.xaml new file mode 100644 index 0000000000..d013a78fca --- /dev/null +++ b/src/GitHub.VisualStudio/Resources/icons/link_external.xaml @@ -0,0 +1,17 @@ +<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + Width="1024" Height="1024"> + <Border BorderBrush="Transparent" BorderThickness="1"> + <ghfvs:OcticonImage xmlns:ghfvs="https://github.com/github/VisualStudio" + Foreground="{DynamicResource GitHubContextMenuIconBrush}" + Icon="link_external"> + <ghfvs:OcticonImage.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </ghfvs:OcticonImage.Resources> + </ghfvs:OcticonImage> + </Border> +</Viewbox> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml b/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml index 0e48f1ceb3..c364f48ea7 100644 --- a/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml +++ b/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml @@ -1,18 +1,19 @@ <Viewbox - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:cache="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - > - <Viewbox.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </Viewbox.Resources> - <ui:OcticonImage - Icon="mark_github" - VerticalAlignment="Center" - Background="{DynamicResource VsBrush.ToolWindowText}" - /> + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + Width="1024" Height="1024"> + + <Viewbox.Resources> + <ResourceDictionary> + <Color x:Key="GitHubContextMenuIconColor">#424242</Color> + <SolidColorBrush x:Key="GitHubContextMenuIconBrush" Color="{StaticResource GitHubContextMenuIconColor}" PresentationOptions:Freeze="true" /> + </ResourceDictionary> + </Viewbox.Resources> + + <Border BorderBrush="Transparent" BorderThickness="1"> + <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="16" Height="16"> + <Path xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Fill="{StaticResource GitHubContextMenuIconBrush}" Data="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/> + </Canvas> + </Border> </Viewbox> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/mark_github_toolbar.xaml b/src/GitHub.VisualStudio/Resources/icons/mark_github_toolbar.xaml new file mode 100644 index 0000000000..a5e026a248 --- /dev/null +++ b/src/GitHub.VisualStudio/Resources/icons/mark_github_toolbar.xaml @@ -0,0 +1,17 @@ +<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + Width="1024" Height="1024"> + <Border BorderBrush="Transparent" BorderThickness="1"> + <ghfvs:OcticonImage xmlns:ghfvs="https://github.com/github/VisualStudio" + Foreground="#00458D" + Icon="mark_github"> + <ghfvs:OcticonImage.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </ghfvs:OcticonImage.Resources> + </ghfvs:OcticonImage> + </Border> +</Viewbox> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/question.xaml b/src/GitHub.VisualStudio/Resources/icons/question.xaml new file mode 100644 index 0000000000..65ec1298d0 --- /dev/null +++ b/src/GitHub.VisualStudio/Resources/icons/question.xaml @@ -0,0 +1,17 @@ +<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + Width="1024" Height="1024"> + <Border BorderBrush="Transparent" BorderThickness="1"> + <ghfvs:OcticonImage xmlns:ghfvs="https://github.com/github/VisualStudio" + Foreground="#00458D" + Icon="question"> + <ghfvs:OcticonImage.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </ghfvs:OcticonImage.Resources> + </ghfvs:OcticonImage> + </Border> +</Viewbox> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Services/ConnectionManager.cs b/src/GitHub.VisualStudio/Services/ConnectionManager.cs index 41cc18c7d2..66e497bceb 100644 --- a/src/GitHub.VisualStudio/Services/ConnectionManager.cs +++ b/src/GitHub.VisualStudio/Services/ConnectionManager.cs @@ -2,228 +2,232 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Linq; -using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; using GitHub.Models; -using GitHub.Services; using GitHub.Primitives; +using GitHub.Services; +using GitHubClient = Octokit.GitHubClient; +using IGitHubClient = Octokit.IGitHubClient; +using OauthClient = Octokit.OauthClient; +using User = Octokit.User; namespace GitHub.VisualStudio { - class CacheData - { - public IEnumerable<ConnectionCacheItem> connections; - } - - class ConnectionCacheItem - { - public Uri HostUrl { get; set; } - public string UserName { get; set; } - } - + /// <summary> + /// Manages the configured <see cref="IConnection"/>s to GitHub instances. + /// </summary> [Export(typeof(IConnectionManager))] - [PartCreationPolicy(CreationPolicy.Shared)] public class ConnectionManager : IConnectionManager { - readonly string cachePath; - readonly IVSServices vsServices; - const string cacheFile = "ghfvs.connections"; - - public event Func<IConnection, IObservable<IConnection>> DoLogin; - - Func<string, bool> fileExists; - Func<string, Encoding, string> readAllText; - Action<string, string> writeAllText; - Action<string> fileDelete; - Func<string, bool> dirExists; - Action<string> dirCreate; + readonly IProgram program; + readonly IConnectionCache cache; + readonly IKeychain keychain; + readonly ILoginManager loginManager; + readonly TaskCompletionSource<object> loaded; + readonly Lazy<ObservableCollectionEx<IConnection>> connections; + readonly IUsageTracker usageTracker; + readonly IVisualStudioBrowser browser; [ImportingConstructor] - public ConnectionManager(IProgram program, IVSServices services) + public ConnectionManager( + IProgram program, + IConnectionCache cache, + IKeychain keychain, + ILoginManager loginManager, + IUsageTracker usageTracker, + IVisualStudioBrowser browser) { - vsServices = services; - fileExists = (path) => System.IO.File.Exists(path); - readAllText = (path, encoding) => System.IO.File.ReadAllText(path, encoding); - writeAllText = (path, content) => System.IO.File.WriteAllText(path, content); - fileDelete = (path) => System.IO.File.Delete(path); - dirExists = (path) => System.IO.Directory.Exists(path); - dirCreate = (path) => System.IO.Directory.CreateDirectory(path); - - Connections = new ObservableCollection<IConnection>(); - cachePath = System.IO.Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - program.ApplicationName, - cacheFile); - - LoadConnectionsFromCache(); - - Connections.CollectionChanged += RefreshConnections; + this.program = program; + this.cache = cache; + this.keychain = keychain; + this.loginManager = loginManager; + this.usageTracker = usageTracker; + this.browser = browser; + loaded = new TaskCompletionSource<object>(); + connections = new Lazy<ObservableCollectionEx<IConnection>>( + this.CreateConnections, + LazyThreadSafetyMode.ExecutionAndPublication); } - public ConnectionManager(IProgram program, Rothko.IOperatingSystem os, IVSServices services) - { - vsServices = services; - fileExists = (path) => os.File.Exists(path); - readAllText = (path, encoding) => os.File.ReadAllText(path, encoding); - writeAllText = (path, content) => os.File.WriteAllText(path, content); - fileDelete = (path) => os.File.Delete(path); - dirExists = (path) => os.Directory.Exists(path); - dirCreate = (path) => os.Directory.CreateDirectory(path); - - Connections = new ObservableCollection<IConnection>(); - cachePath = System.IO.Path.Combine( - os.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - program.ApplicationName, - cacheFile); - - LoadConnectionsFromCache(); - - Connections.CollectionChanged += RefreshConnections; - } + /// <inheritdoc/> + public IReadOnlyObservableCollection<IConnection> Connections => connections.Value; - public IConnection CreateConnection(HostAddress address, string username) + /// <inheritdoc/> + public async Task<IConnection> GetConnection(HostAddress address) { - return SetupConnection(address, username); + return (await GetLoadedConnections()).FirstOrDefault(x => x.HostAddress == address); } - public bool AddConnection(HostAddress address, string username) + /// <inheritdoc/> + public async Task<IReadOnlyObservableCollection<IConnection>> GetLoadedConnections() { - if (Connections.FirstOrDefault(x => x.HostAddress.Equals(address)) != null) - return false; - Connections.Add(SetupConnection(address, username)); - return true; + return await GetLoadedConnectionsInternal(); } - void AddConnection(Uri hostUrl, string username) + /// <inheritdoc/> + public async Task<IConnection> LogIn(HostAddress address, string userName, string password) { - var address = HostAddress.Create(hostUrl); - if (Connections.FirstOrDefault(x => x.HostAddress.Equals(address)) != null) - return; - var conn = SetupConnection(address, username); - Connections.Add(conn); - } + var conns = await GetLoadedConnectionsInternal(); - public bool RemoveConnection(HostAddress address) - { - var c = Connections.FirstOrDefault(x => x.HostAddress.Equals(address)); - if (c == null) - return false; - RequestLogout(c); - return true; - } + if (conns.Any(x => x.HostAddress == address)) + { + throw new InvalidOperationException($"A connection to {address.Title} already exists."); + } - public IObservable<IConnection> RequestLogin(IConnection connection) - { - var handler = DoLogin; - if (handler == null) - return null; - return handler(connection); - } + var client = CreateClient(address); + var user = await loginManager.Login(address, client, userName, password); + var connection = new Connection(address, userName, user); - public void RequestLogout(IConnection connection) - { - Connections.Remove(connection); + conns.Add(connection); + await SaveConnections(); + await usageTracker.IncrementCounter(x => x.NumberOfLogins); + return connection; } - public void RefreshRepositories() + /// <inheritdoc/> + public async Task<IConnection> LogInViaOAuth(HostAddress address, CancellationToken cancel) { - var list = vsServices.GetKnownRepositories(); - list.GroupBy(r => Connections.FirstOrDefault(c => r.CloneUrl != null && c.HostAddress.Equals(HostAddress.Create(r.CloneUrl)))) - .Where(g => g.Key != null) - .ForEach(g => + var conns = await GetLoadedConnectionsInternal(); + + if (conns.Any(x => x.HostAddress == address)) { - var repos = g.Key.Repositories; - repos.Except(g).ToList().ForEach(c => repos.Remove(c)); - g.Except(repos).ToList().ForEach(c => repos.Add(c)); - }); - } + throw new InvalidOperationException($"A connection to {address} already exists."); + } - IConnection SetupConnection(HostAddress address, string username) - { - var conn = new Connection(this, address, username); - return conn; + var client = CreateClient(address); + var oauthClient = new OauthClient(client.Connection); + var user = await loginManager.LoginViaOAuth(address, client, oauthClient, OpenBrowser, cancel); + var connection = new Connection(address, user.Login, user); + + conns.Add(connection); + await SaveConnections(); + await usageTracker.IncrementCounter(x => x.NumberOfLogins); + await usageTracker.IncrementCounter(x => x.NumberOfOAuthLogins); + return connection; } - void RefreshConnections(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + /// <inheritdoc/> + public async Task<IConnection> LogInWithToken(HostAddress address, string token) { - if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) + var conns = await GetLoadedConnectionsInternal(); + + if (conns.Any(x => x.HostAddress == address)) { - foreach (IConnection c in e.OldItems) - { - // RepositoryHosts hasn't been loaded so it can't handle logging out, we have to do it ourselves - if (DoLogin == null) - Api.SimpleCredentialStore.RemoveCredentials(c.HostAddress.CredentialCacheKeyHost); - c.Dispose(); - } + throw new InvalidOperationException($"A connection to {address.Title} already exists."); } - SaveConnectionsToCache(); + + var client = CreateClient(address); + var user = await loginManager.LoginWithToken(address, client, token); + var connection = new Connection(address, user.Login, user); + + conns.Add(connection); + await SaveConnections(); + await usageTracker.IncrementCounter(x => x.NumberOfLogins); + await usageTracker.IncrementCounter(x => x.NumberOfTokenLogins); + return connection; } - void LoadConnectionsFromCache() + /// <inheritdoc/> + public async Task LogOut(HostAddress address) { - EnsureCachePath(); + var connection = await GetConnection(address); - if (!fileExists(cachePath)) - return; + if (connection == null) + { + throw new KeyNotFoundException($"Could not find a connection to {address.Title}."); + } - string data = readAllText(cachePath, Encoding.UTF8); + var client = CreateClient(address); + await loginManager.Logout(address, client); + connections.Value.Remove(connection); + await SaveConnections(); + } + + /// <inheritdoc/> + public async Task Retry(IConnection connection) + { + var c = (Connection)connection; + c.SetLoggingIn(); - CacheData cacheData; try { - cacheData = SimpleJson.DeserializeObject<CacheData>(data); + var client = CreateClient(c.HostAddress); + var user = await loginManager.LoginFromCache(connection.HostAddress, client); + c.SetSuccess(user); + await usageTracker.IncrementCounter(x => x.NumberOfLogins); } - catch + catch (Exception e) { - cacheData = null; + c.SetError(e); } + } - if (cacheData == null || cacheData.connections == null) - { - // cache is corrupt, remove - fileDelete(cachePath); - return; - } + ObservableCollectionEx<IConnection> CreateConnections() + { + var result = new ObservableCollectionEx<IConnection>(); + LoadConnections(result).Forget(); + return result; + } - cacheData.connections.ForEach(c => - { - if (c.HostUrl != null) - AddConnection(c.HostUrl, c.UserName); - }); + IGitHubClient CreateClient(HostAddress address) + { + return new GitHubClient( + program.ProductHeader, + new KeychainCredentialStore(keychain, address), + address.ApiUri); } - void SaveConnectionsToCache() + async Task<ObservableCollectionEx<IConnection>> GetLoadedConnectionsInternal() { - EnsureCachePath(); + var result = Connections; + await loaded.Task; + return connections.Value; + } - var cache = new CacheData(); - cache.connections = Connections.Select(conn => - new ConnectionCacheItem - { - HostUrl = conn.HostAddress.WebUri, - UserName = conn.Username, - }); + async Task LoadConnections(ObservableCollection<IConnection> result) + { try { - string data = SimpleJson.SerializeObject(cache); - writeAllText(cachePath, data); + foreach (var c in await cache.Load()) + { + var connection = new Connection(c.HostAddress); + result.Add(connection); + } + + foreach (Connection connection in result) + { + var client = CreateClient(connection.HostAddress); + User user = null; + + try + { + user = await loginManager.LoginFromCache(connection.HostAddress, client); + connection.SetSuccess(user); + await usageTracker.IncrementCounter(x => x.NumberOfLogins); + } + catch (Exception e) + { + connection.SetError(e); + } + } } - catch (Exception ex) + finally { - Debug.Fail(ex.ToString()); + loaded.SetResult(null); } } - void EnsureCachePath() + async Task SaveConnections() { - if (fileExists(cachePath)) - return; - var di = System.IO.Path.GetDirectoryName(cachePath); - if (!dirExists(di)) - dirCreate(di); + var conns = await GetLoadedConnectionsInternal(); + var details = conns.Select(x => new ConnectionDetails(x.HostAddress, x.Username)); + await cache.Save(details); } - public ObservableCollection<IConnection> Connections { get; private set; } + void OpenBrowser(Uri uri) => browser.OpenUrl(uri); } } diff --git a/src/GitHub.VisualStudio/Services/GitHubServiceProvider.cs b/src/GitHub.VisualStudio/Services/GitHubServiceProvider.cs new file mode 100644 index 0000000000..99d19c2c3b --- /dev/null +++ b/src/GitHub.VisualStudio/Services/GitHubServiceProvider.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.ComponentModel.Composition.Primitives; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Exports; +using GitHub.Services; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; +using System.Threading.Tasks; +using GitHub.Extensions; +using Serilog; +using Log = GitHub.Logging.Log; + +namespace GitHub.VisualStudio +{ + /// <summary> + /// This is a globally registered service (see `GitHubPackage`). + /// If you need to access this service via MEF, use the `IGitHubServiceProvider` type + /// </summary> + public class GitHubServiceProvider : IGitHubServiceProvider, IDisposable + { + public static IGitHubServiceProvider Instance => Package.GetGlobalService(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider; + + class OwnedComposablePart + { + public object Owner { get; set; } + public ComposablePart Part { get; set; } + } + + static readonly ILogger log = LogManager.ForContext<GitHubServiceProvider>(); + readonly IServiceProviderPackage asyncServiceProvider; + readonly IServiceProvider syncServiceProvider; + readonly Dictionary<string, OwnedComposablePart> tempParts; + readonly Version currentVersion; + List<IDisposable> disposables = new List<IDisposable>(); + bool initialized = false; + + public ExportProvider ExportProvider { get; private set; } + + CompositionContainer tempContainer; + CompositionContainer TempContainer + { + get + { + if (tempContainer == null) + { + tempContainer = AddToDisposables(new CompositionContainer(new ComposablePartExportProvider() + { + SourceProvider = ExportProvider + })); + } + return tempContainer; + } + } + + public IServiceProvider GitServiceProvider { get; set; } + + public GitHubServiceProvider(IServiceProviderPackage asyncServiceProvider, IServiceProvider syncServiceProvider) + { + Guard.ArgumentNotNull(asyncServiceProvider, nameof(asyncServiceProvider)); + Guard.ArgumentNotNull(syncServiceProvider, nameof(syncServiceProvider)); + + this.currentVersion = this.GetType().Assembly.GetName().Version; + this.asyncServiceProvider = asyncServiceProvider; + this.syncServiceProvider = syncServiceProvider; + + tempParts = new Dictionary<string, OwnedComposablePart>(); + } + + public async Task Initialize() + { + IComponentModel componentModel = await asyncServiceProvider.GetServiceAsync(typeof(SComponentModel)) as IComponentModel; + + Log.Assert(componentModel != null, "Service of type SComponentModel not found"); + if (componentModel == null) + { + log.Error("Service of type SComponentModel not found"); + return; + } + + ExportProvider = componentModel.DefaultExportProvider; + if (ExportProvider == null) + { + log.Error("DefaultExportProvider could not be obtained"); + } + initialized = true; + } + + + public object TryGetService(Type serviceType) + { + var contract = AttributedModelServices.GetContractName(serviceType); + var instance = AddToDisposables(TempContainer.GetExportedValueOrDefault<object>(contract)); + if (instance != null) + return instance; + + var sp = initialized ? syncServiceProvider : asyncServiceProvider; + + try + { + instance = sp.GetService(serviceType); + if (instance != null) + return instance; + } + catch (Exception ex) + { + log.Error(ex, "Error loading {ServiceType}", serviceType); + } + + instance = AddToDisposables(ExportProvider.GetExportedValues<object>(contract).FirstOrDefault(x => contract.StartsWith("github.", StringComparison.OrdinalIgnoreCase) ? x.GetType().Assembly.GetName().Version == currentVersion : true)); + + if (instance != null) + return instance; + + instance = GitServiceProvider?.GetService(serviceType); + if (instance != null) + return instance; + + return null; + } + + public object TryGetService(string typename) + { + Guard.ArgumentNotEmptyString(typename, nameof(typename)); + + var type = Type.GetType(typename, false, true); + return TryGetService(type); + } + + public object GetService(Type serviceType) + { + Guard.ArgumentNotNull(serviceType, nameof(serviceType)); + + var instance = TryGetService(serviceType); + if (instance != null) + return instance; + + string contract = AttributedModelServices.GetContractName(serviceType); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Could not locate any instances of contract {0}.", contract)); + } + + public T GetService<T>() where T : class + { + return (T)GetService(typeof(T)); + } + + public T TryGetService<T>() where T : class + { + return TryGetService(typeof(T)) as T; + } + + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] + public Ret GetService<T, Ret>() where T : class + where Ret : class + { + return TryGetService(typeof(T)) as Ret; + } + + public void AddService<T>(object owner, T instance) where T : class + { + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(instance, nameof(instance)); + + AddService(typeof(T), owner, instance); + } + + public void AddService(Type t, object owner, object instance) + { + Guard.ArgumentNotNull(t, nameof(t)); + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(instance, nameof(instance)); + + string contract = AttributedModelServices.GetContractName(t); + + if (string.IsNullOrEmpty(contract)) + { + throw new GitHubLogicException("Every type must have a contract name"); + } + + // we want to remove stale instances of a service, if they exist, regardless of who put them there + RemoveService(t, null); + + var batch = new CompositionBatch(); + var part = batch.AddExportedValue(contract, instance); + + if (part == null) + { + throw new GitHubLogicException("Adding an exported value must return a non-null part"); + } + + tempParts.Add(contract, new OwnedComposablePart { Owner = owner, Part = part }); + TempContainer.Compose(batch); + } + + /// <summary> + /// Removes a service from the catalog + /// </summary> + /// <param name="t">The type we want to remove</param> + /// <param name="owner">The owner, which either has to match what was passed to AddService, + /// or if it's null, the service will be removed without checking for ownership</param> + public void RemoveService(Type t, object owner) + { + Guard.ArgumentNotNull(t, nameof(t)); + + string contract = AttributedModelServices.GetContractName(t); + Log.Assert(!string.IsNullOrEmpty(contract), "Every type must have a contract name"); + + OwnedComposablePart part; + if (tempParts.TryGetValue(contract, out part)) + { + if (owner != null && part.Owner != owner) + return; + tempParts.Remove(contract); + var batch = new CompositionBatch(); + batch.RemovePart(part.Part); + TempContainer.Compose(batch); + } + } + + T AddToDisposables<T>(T instance) where T : class + { + var disposable = instance as IDisposable; + if (disposable != null) + { + disposables.Add(disposable); + } + return instance; + } + + bool disposed; + protected void Dispose(bool disposing) + { + if (disposing) + { + if (disposed) return; + + if (disposables != null) + { + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + } + + disposables = null; + if (tempContainer != null) + tempContainer.Dispose(); + tempContainer = null; + disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/GitHub.VisualStudio/Services/JsonConnectionCache.cs b/src/GitHub.VisualStudio/Services/JsonConnectionCache.cs new file mode 100644 index 0000000000..2e0f900223 --- /dev/null +++ b/src/GitHub.VisualStudio/Services/JsonConnectionCache.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Rothko; +using Serilog; + +namespace GitHub.VisualStudio +{ + /// <summary> + /// Loads and saves the configured connections from/to a .json file. + /// </summary> + [Export(typeof(IConnectionCache))] + public class JsonConnectionCache : IConnectionCache + { + static readonly ILogger log = LogManager.ForContext<JsonConnectionCache>(); + const string DefaultCacheFile = "ghfvs.connections"; + readonly string cachePath; + readonly IOperatingSystem os; + + [ImportingConstructor] + public JsonConnectionCache(IProgram program, IOperatingSystem os) + : this(program, os, DefaultCacheFile) + { + } + + public JsonConnectionCache(IProgram program, IOperatingSystem os, string cacheFile) + { + this.os = os; + cachePath = Path.Combine( + os.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), + program.ApplicationName, + cacheFile); + } + + public Task<IEnumerable<ConnectionDetails>> Load() + { + if (os.File.Exists(cachePath)) + { + try + { + // TODO: Need a ReadAllTextAsync method here. + var data = os.File.ReadAllText(cachePath, Encoding.UTF8); + var result = SimpleJson.DeserializeObject<CacheData>(data); + return Task.FromResult(result.connections.Select(FromCache)); + } + catch (Exception e) + { + try + { + os.File.Delete(cachePath); + } + catch { } + + log.Error(e, "Failed to read connection cache from {Path}", cachePath); + } + } + + return Task.FromResult(Enumerable.Empty<ConnectionDetails>()); + } + + public Task Save(IEnumerable<ConnectionDetails> connections) + { + var data = SimpleJson.SerializeObject(new CacheData + { + connections = connections.Select(ToCache).ToList(), + }); + + try + { + os.File.WriteAllText(cachePath, data); + } + catch (Exception e) + { + log.Error(e, "Failed to write connection cache to {Path}", cachePath); + } + + return Task.CompletedTask; + } + + static ConnectionDetails FromCache(ConnectionCacheItem c) + { + return new ConnectionDetails(HostAddress.Create(c.HostUrl), c.UserName); + } + + static ConnectionCacheItem ToCache(ConnectionDetails c) + { + return new ConnectionCacheItem + { + HostUrl = c.HostAddress.WebUri, + UserName = c.UserName, + }; + } + + class CacheData + { + public IEnumerable<ConnectionCacheItem> connections { get; set; } + } + + class ConnectionCacheItem + { + public Uri HostUrl { get; set; } + public string UserName { get; set; } + } + } +} diff --git a/src/GitHub.VisualStudio/Services/LocalRepositories.cs b/src/GitHub.VisualStudio/Services/LocalRepositories.cs new file mode 100644 index 0000000000..d413b39422 --- /dev/null +++ b/src/GitHub.VisualStudio/Services/LocalRepositories.cs @@ -0,0 +1,46 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Helpers; +using GitHub.Models; + +namespace GitHub.Services +{ + /// <summary> + /// Stores a collection of known local repositories. + /// </summary> + /// <remarks> + /// The list of repositories exposed here is the list of local repositories known to Team + /// Explorer. + /// </remarks> + [Export(typeof(ILocalRepositories))] + public class LocalRepositories : ILocalRepositories + { + readonly IVSGitServices vsGitServices; + + [ImportingConstructor] + public LocalRepositories(IVSGitServices vsGitServices) + { + this.vsGitServices = vsGitServices; + } + + /// <inheritdoc/> + public async Task Refresh() + { + await ThreadingHelper.SwitchToPoolThreadAsync(); + var list = vsGitServices.GetKnownRepositories(); + await ThreadingHelper.SwitchToMainThreadAsync(); + + repositories.Except(list).ToList().ForEach(x => repositories.Remove(x)); + list.Except(repositories).ToList().ForEach(x => repositories.Add(x)); + } + + readonly ObservableCollectionEx<ILocalRepositoryModel> repositories + = new ObservableCollectionEx<ILocalRepositoryModel>(); + + /// <inheritdoc/> + public IReadOnlyObservableCollection<ILocalRepositoryModel> Repositories => repositories; + } +} diff --git a/src/GitHub.VisualStudio/Services/SelectedTextProvider.cs b/src/GitHub.VisualStudio/Services/SelectedTextProvider.cs new file mode 100644 index 0000000000..e831ec4f97 --- /dev/null +++ b/src/GitHub.VisualStudio/Services/SelectedTextProvider.cs @@ -0,0 +1,38 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Services; +using GitHub.Extensions; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace GitHub.VisualStudio +{ + [Export(typeof(ISelectedTextProvider))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class SelectedTextProvider : ISelectedTextProvider + { + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + public SelectedTextProvider(IGitHubServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public string GetSelectedText() + { + IVsTextManager textManager = serviceProvider.GetService<SVsTextManager, IVsTextManager>(); + string selectedText; + IVsTextView activeView; + + if (textManager != null && + ErrorHandler.Succeeded(textManager.GetActiveView(1, null, out activeView)) && + ErrorHandler.Succeeded(activeView.GetSelectedText(out selectedText))) + return selectedText; + + return string.Empty; + } + } +} + diff --git a/src/GitHub.VisualStudio/Services/ShowDialogService.cs b/src/GitHub.VisualStudio/Services/ShowDialogService.cs new file mode 100644 index 0000000000..ec7da398f4 --- /dev/null +++ b/src/GitHub.VisualStudio/Services/ShowDialogService.cs @@ -0,0 +1,62 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Services; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; +using GitHub.VisualStudio.Views.Dialog; + +namespace GitHub.VisualStudio.UI.Services +{ + [Export(typeof(IShowDialogService))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class ShowDialogService : IShowDialogService + { + readonly IGitHubServiceProvider serviceProvider; + + [ImportingConstructor] + public ShowDialogService(IGitHubServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + public Task<object> Show(IDialogContentViewModel viewModel) + { + var result = default(object); + + using (var dialogViewModel = CreateViewModel()) + using (dialogViewModel.Done.Take(1).Subscribe(x => result = x)) + { + dialogViewModel.Start(viewModel); + + var window = new GitHubDialogWindow(dialogViewModel); + window.ShowModal(); + } + + return Task.FromResult(result); + } + + public async Task<object> ShowWithFirstConnection<TViewModel>(TViewModel viewModel) + where TViewModel : IDialogContentViewModel, IConnectionInitializedViewModel + { + var result = default(object); + + using (var dialogViewModel = CreateViewModel()) + using (dialogViewModel.Done.Take(1).Subscribe(x => result = x)) + { + await dialogViewModel.StartWithConnection(viewModel); + + var window = new GitHubDialogWindow(dialogViewModel); + window.ShowModal(); + } + + return result; + } + + IGitHubDialogWindowViewModel CreateViewModel() + { + return serviceProvider.GetService<IGitHubDialogWindowViewModel>(); + } + } +} diff --git a/src/GitHub.VisualStudio/Services/UIProvider.cs b/src/GitHub.VisualStudio/Services/UIProvider.cs deleted file mode 100644 index c0d6222954..0000000000 --- a/src/GitHub.VisualStudio/Services/UIProvider.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.ComponentModel.Composition.Hosting; -using System.ComponentModel.Composition.Primitives; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using System.Reactive.Disposables; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; -using NullGuard; -using NLog; -using System.Reactive.Linq; -using GitHub.Infrastructure; -using System.Windows.Controls; - -namespace GitHub.VisualStudio -{ - [Export(typeof(IUIProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - public class UIProvider : IUIProvider, IDisposable - { - static readonly Logger log = LogManager.GetCurrentClassLogger(); - CompositeDisposable disposables = new CompositeDisposable(); - readonly IServiceProvider serviceProvider; - CompositionContainer tempContainer; - readonly Dictionary<string, ComposablePart> tempParts; - ExportLifetimeContext<IUIController> currentUIFlow; - readonly Version currentVersion; - bool initializingLogging = false; - - [AllowNull] - public ExportProvider ExportProvider { get; private set; } - - IServiceProvider gitServiceProvider; - [AllowNull] - public IServiceProvider GitServiceProvider { - get { return gitServiceProvider; } - set { gitServiceProvider = value; } - } - - bool Initialized { get { return ExportProvider != null; } } - - [ImportingConstructor] - public UIProvider([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) - { - this.currentVersion = typeof(UIProvider).Assembly.GetName().Version; - this.serviceProvider = serviceProvider; - - var componentModel = serviceProvider.GetService(typeof(SComponentModel)) as IComponentModel; - Debug.Assert(componentModel != null, "Service of type SComponentModel not found"); - if (componentModel == null) - { - log.Error("Service of type SComponentModel not found"); - return; - } - ExportProvider = componentModel.DefaultExportProvider; - - if (ExportProvider == null) - { - log.Error("DefaultExportProvider could not be obtained."); - return; - } - - tempContainer = AddToDisposables(new CompositionContainer(new ComposablePartExportProvider() - { - SourceProvider = ExportProvider - })); - tempParts = new Dictionary<string, ComposablePart>(); - } - - [return: AllowNull] - public object TryGetService(Type serviceType) - { - if (!Initialized) - return null; - - if (!initializingLogging && log.Factory.Configuration == null) - { - initializingLogging = true; - try - { - var logging = TryGetService<ILoggingConfiguration>(); - logging.Configure(); - } - catch - { - } - } - - string contract = AttributedModelServices.GetContractName(serviceType); - var instance = AddToDisposables(tempContainer.GetExportedValueOrDefault<object>(contract)); - if (instance != null) - return instance; - - instance = AddToDisposables(ExportProvider.GetExportedValues<object>(contract).FirstOrDefault(x => contract.StartsWith("github.", StringComparison.OrdinalIgnoreCase) ? x.GetType().Assembly.GetName().Version == currentVersion : true)); - - if (instance != null) - return instance; - - instance = serviceProvider.GetService(serviceType); - if (instance != null) - return instance; - - if (gitServiceProvider != null) - { - instance = gitServiceProvider.GetService(serviceType); - if (instance != null) - return instance; - } - - return null; - } - - public object GetService(Type serviceType) - { - var instance = TryGetService(serviceType); - if (instance != null) - return instance; - - string contract = AttributedModelServices.GetContractName(serviceType); - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "Could not locate any instances of contract {0}.", contract)); - } - - public T GetService<T>() - { - return (T)GetService(typeof(T)); - } - - public T TryGetService<T>() where T : class - { - return TryGetService(typeof(T)) as T; - } - - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] - public Ret GetService<T, Ret>() where Ret : class - { - return GetService<T>() as Ret; - } - - public void AddService(Type t, object instance) - { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot add service."); - return; - } - - var batch = new CompositionBatch(); - string contract = AttributedModelServices.GetContractName(t); - Debug.Assert(!string.IsNullOrEmpty(contract), "Every type must have a contract name"); - var part = batch.AddExportedValue(contract, instance); - Debug.Assert(part != null, "Adding an exported value must return a non-null part"); - tempParts.Add(contract, part); - tempContainer.Compose(batch); - } - - public void RemoveService(Type t) - { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot remove service."); - return; - } - - string contract = AttributedModelServices.GetContractName(t); - Debug.Assert(!string.IsNullOrEmpty(contract), "Every type must have a contract name"); - - ComposablePart part; - - if (tempParts.TryGetValue(contract, out part)) - { - tempParts.Remove(contract); - var batch = new CompositionBatch(); - batch.RemovePart(part); - tempContainer.Compose(batch); - } - } - - UI.WindowController windowController; - public IObservable<UserControl> SetupUI(UIControllerFlow controllerFlow, [AllowNull] IConnection connection) - { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot setup UI."); - return Observable.Return<UserControl>(null); - } - - StopUI(); - - var factory = GetService<IExportFactoryProvider>(); - currentUIFlow = factory.UIControllerFactory.CreateExport(); - var disposable = currentUIFlow; - var ui = currentUIFlow.Value; - var creation = ui.SelectFlow(controllerFlow); - windowController = new UI.WindowController(creation); - windowController.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner; - windowController.Closed += StopUIFlowWhenWindowIsClosedByUser; - creation.Subscribe((c) => {}, () => - { - windowController.Closed -= StopUIFlowWhenWindowIsClosedByUser; - windowController.Close(); - if (currentUIFlow != disposable) - StopUI(disposable); - else - StopUI(); - }); - ui.Start(connection); - return creation; - } - - public void RunUI() - { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot run UI."); - return; - } - - Debug.Assert(windowController != null, "WindowController is null, did you forget to call SetupUI?"); - if (windowController == null) - { - log.Error("WindowController is null, cannot run UI."); - return; - } - try - { - windowController.ShowModal(); - } - catch (Exception ex) - { - log.Error("WindowController ShowModal failed. {0}", ex); - } - } - - public void RunUI(UIControllerFlow controllerFlow, [AllowNull] IConnection connection) - { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot run UI for {0}.", controllerFlow); - return; - } - - SetupUI(controllerFlow, connection); - try - { - windowController.ShowModal(); - } - catch (Exception ex) - { - log.Error("WindowController ShowModal failed for {0}. {1}", controllerFlow, ex); - } - } - - public void StopUI() - { - if (!Initialized) - { - log.Error("ExportProvider is not initialized, cannot stop UI."); - return; - } - - StopUI(currentUIFlow); - currentUIFlow = null; - } - - static void StopUI(ExportLifetimeContext<IUIController> disposable) - { - try { - if (disposable != null && disposable.Value != null) - { - if (!disposable.Value.IsStopped) - disposable.Value.Stop(); - disposable.Dispose(); - } - } - catch (Exception ex) - { - log.Error("Failed to dispose UI. {0}", ex); - } - } - - T AddToDisposables<T>(T instance) - { - var disposable = instance as IDisposable; - if (disposable != null) - { - disposables.Add(disposable); - } - return instance; - } - - void StopUIFlowWhenWindowIsClosedByUser(object sender, EventArgs e) - { - StopUI(); - } - - bool disposed; - protected void Dispose(bool disposing) - { - if (disposing) - { - if (disposed) return; - - StopUI(); - if (disposables != null) - disposables.Dispose(); - disposables = null; - if (tempContainer != null) - tempContainer.Dispose(); - tempContainer = null; - disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/GitHub.VisualStudio/Services/UsageService.cs b/src/GitHub.VisualStudio/Services/UsageService.cs new file mode 100644 index 0000000000..79c33e6b86 --- /dev/null +++ b/src/GitHub.VisualStudio/Services/UsageService.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Helpers; +using GitHub.Logging; +using GitHub.Models; +using Serilog; +using Rothko; +using Environment = System.Environment; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services +{ + [Export(typeof(IUsageService))] + public sealed class UsageService : IUsageService, IDisposable + { + const string StoreFileName = "metrics.json"; + const string UserStoreFileName = "user.json"; + static readonly ILogger log = LogManager.ForContext<UsageService>(); + + readonly IGitHubServiceProvider serviceProvider; + readonly IEnvironment environment; + readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + + string storePath; + string userStorePath; + Guid? userGuid; + + [ImportingConstructor] + public UsageService(IGitHubServiceProvider serviceProvider, IEnvironment environment) + { + this.serviceProvider = serviceProvider; + this.environment = environment; + } + + public void Dispose() + { + semaphoreSlim.Dispose(); + } + + public async Task<Guid> GetUserGuid() + { + await Initialize(); + + if (!userGuid.HasValue) + { + try + { + if (File.Exists(userStorePath)) + { + var json = await ReadAllTextAsync(userStorePath); + var data = SimpleJson.DeserializeObject<UserData>(json); + userGuid = data.UserGuid; + } + } + catch (Exception ex) + { + log.Error(ex, "Failed reading user metrics GUID"); + } + } + + if (!userGuid.HasValue || userGuid.Value == Guid.Empty) + { + userGuid = Guid.NewGuid(); + + try + { + var data = new UserData { UserGuid = userGuid.Value }; + var json = SimpleJson.SerializeObject(data); + await WriteAllTextAsync(userStorePath, json); + } + catch (Exception ex) + { + log.Error(ex, "Failed writing user metrics GUID"); + } + } + + return userGuid.Value; + } + + public IDisposable StartTimer(Func<Task> callback, TimeSpan dueTime, TimeSpan period) + { + return new Timer( + async _ => + { + try { await callback(); } + catch (Exception ex) { log.Warning(ex, "Failed submitting usage data"); } + }, + null, + dueTime, + period); + } + + public async Task<UsageData> ReadLocalData() + { + await Initialize(); + + var json = File.Exists(storePath) ? await ReadAllTextAsync(storePath) : null; + + try + { + return json != null ? + SimpleJson.DeserializeObject<UsageData>(json) : + new UsageData { Reports = new List<UsageModel>() }; + } + catch (Exception ex) + { + log.Error(ex, "Error deserializing usage"); + return new UsageData { Reports = new List<UsageModel>() }; + } + } + + public async Task WriteLocalData(UsageData data) + { + try + { + Directory.CreateDirectory(Path.GetDirectoryName(storePath)); + var json = SimpleJson.SerializeObject(data); + + await WriteAllTextAsync(storePath, json); + } + catch (Exception ex) + { + log.Error(ex, "Failed to write usage data"); + } + } + + async Task Initialize() + { + if (storePath == null) + { + await ThreadingHelper.SwitchToMainThreadAsync(); + + var program = serviceProvider.GetService<IProgram>(); + + var localApplicationDataPath = environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + storePath = Path.Combine(localApplicationDataPath, program.ApplicationName, StoreFileName); + userStorePath = Path.Combine(localApplicationDataPath, program.ApplicationName, UserStoreFileName); + } + } + + async Task<string> ReadAllTextAsync(string path) + { + // Avoid IOException when metrics updated multiple times in quick succession + await semaphoreSlim.WaitAsync(); + try + { + using (var s = File.OpenRead(path)) + using (var r = new StreamReader(s, Encoding.UTF8)) + { + return await r.ReadToEndAsync(); + } + } + finally + { + semaphoreSlim.Release(); + } + } + + async Task WriteAllTextAsync(string path, string text) + { + // Avoid IOException when metrics updated multiple times in quick succession + await semaphoreSlim.WaitAsync(); + try + { + using (var s = new FileStream(path, FileMode.Create)) + using (var w = new StreamWriter(s, Encoding.UTF8)) + { + await w.WriteAsync(text); + } + } + finally + { + semaphoreSlim.Release(); + } + } + + class UserData + { + public Guid UserGuid { get; set; } + } + } +} diff --git a/src/GitHub.VisualStudio/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs new file mode 100644 index 0000000000..f35a3af30f --- /dev/null +++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs @@ -0,0 +1,144 @@ +using System; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using GitHub.Helpers; +using GitHub.Logging; +using GitHub.Models; +using GitHub.Settings; +using Serilog; +using Task = System.Threading.Tasks.Task; + +namespace GitHub.Services +{ + public sealed class UsageTracker : IUsageTracker, IDisposable + { + static readonly ILogger log = LogManager.ForContext<UsageTracker>(); + readonly IGitHubServiceProvider gitHubServiceProvider; + + bool initialized; + IMetricsService client; + IUsageService service; + IConnectionManager connectionManager; + IPackageSettings userSettings; + IVSServices vsservices; + IDisposable timer; + bool firstTick = true; + + public UsageTracker( + IGitHubServiceProvider gitHubServiceProvider, + IUsageService service, + IPackageSettings settings) + { + this.gitHubServiceProvider = gitHubServiceProvider; + this.service = service; + this.userSettings = settings; + timer = StartTimer(); + } + + public void Dispose() + { + timer?.Dispose(); + } + + public async Task IncrementCounter(Expression<Func<UsageModel.MeasuresModel, int>> counter) + { + await Initialize(); + var data = await service.ReadLocalData(); + var usage = await GetCurrentReport(data); + var property = (MemberExpression)counter.Body; + var propertyInfo = (PropertyInfo)property.Member; + log.Verbose("Increment counter {Name}", propertyInfo.Name); + var value = (int)propertyInfo.GetValue(usage.Measures); + propertyInfo.SetValue(usage.Measures, value + 1); + await service.WriteLocalData(data); + } + + IDisposable StartTimer() + { + return service.StartTimer(TimerTick, TimeSpan.FromMinutes(3), TimeSpan.FromDays(1)); + } + + async Task Initialize() + { + if (initialized) + return; + + // The services needed by the usage tracker are loaded when they are first needed to + // improve the startup time of the extension. + await ThreadingHelper.SwitchToMainThreadAsync(); + + client = gitHubServiceProvider.TryGetService<IMetricsService>(); + connectionManager = gitHubServiceProvider.GetService<IConnectionManager>(); + vsservices = gitHubServiceProvider.GetService<IVSServices>(); + initialized = true; + } + + async Task TimerTick() + { + await Initialize(); + + if (firstTick) + { + await IncrementCounter(x => x.NumberOfStartups); + firstTick = false; + } + + if (client == null || !userSettings.CollectMetrics) + { + timer.Dispose(); + timer = null; + return; + } + + var data = await service.ReadLocalData(); + + var changed = false; + for (var i = data.Reports.Count - 1; i >= 0; --i) + { + if (data.Reports[i].Dimensions.Date.Date != DateTimeOffset.Now.Date) + { + try + { + await client.PostUsage(data.Reports[i]); + data.Reports.RemoveAt(i); + changed = true; + } + catch (Exception ex) + { + log.Error(ex, "Failed to send metrics"); + } + } + } + + if (changed) + { + await service.WriteLocalData(data); + } + } + + async Task<UsageModel> GetCurrentReport(UsageData data) + { + var current = data.Reports.FirstOrDefault(x => x.Dimensions.Date.Date == DateTimeOffset.Now.Date); + + if (current == null) + { + var guid = await service.GetUserGuid(); + current = UsageModel.Create(guid); + data.Reports.Add(current); + } + + current.Dimensions.Lang = CultureInfo.InstalledUICulture.IetfLanguageTag; + current.Dimensions.CurrentLang = CultureInfo.CurrentCulture.IetfLanguageTag; + current.Dimensions.AppVersion = AssemblyVersionInformation.Version; + current.Dimensions.VSVersion = vsservices.VSVersion; + + current.Dimensions.IsGitHubUser = connectionManager.Connections.Any(x => x.HostAddress.IsGitHubDotCom()); + current.Dimensions.IsEnterpriseUser = connectionManager.Connections.Any(x => !x.HostAddress.IsGitHubDotCom()); + return current; + } + } +} diff --git a/src/GitHub.VisualStudio/Services/VSGitExtFactory.cs b/src/GitHub.VisualStudio/Services/VSGitExtFactory.cs new file mode 100644 index 0000000000..4d7e8d57d2 --- /dev/null +++ b/src/GitHub.VisualStudio/Services/VSGitExtFactory.cs @@ -0,0 +1,44 @@ +extern alias TF14; +extern alias TF15; + +using System; +using GitHub.Logging; +using Serilog; +using Microsoft.VisualStudio.Shell; +using VSGitExt14 = TF14.GitHub.VisualStudio.Base.VSGitExt; +using VSGitExt15 = TF15.GitHub.VisualStudio.Base.VSGitExt; + +namespace GitHub.Services +{ + public class VSGitExtFactory + { + static readonly ILogger log = LogManager.ForContext<VSGitExtFactory>(); + + readonly int vsVersion; + readonly IAsyncServiceProvider asyncServiceProvider; + + public VSGitExtFactory(int vsVersion, IAsyncServiceProvider asyncServiceProvider) + { + this.vsVersion = vsVersion; + this.asyncServiceProvider = asyncServiceProvider; + } + + public IVSGitExt Create() + { + switch (vsVersion) + { + case 14: + return Create(() => new VSGitExt14(asyncServiceProvider)); + case 15: + return Create(() => new VSGitExt15(asyncServiceProvider)); + default: + log.Error("There is no IVSGitExt implementation for DTE version {Version}", vsVersion); + return null; + } + } + + // NOTE: We're being careful to only reference VSGitExt14 and VSGitExt15 from inside a lambda expression. + // This ensures that only the type that's compatible with the running DTE version is loaded. + static IVSGitExt Create(Func<IVSGitExt> factory) => factory.Invoke(); + } +} diff --git a/src/GitHub.VisualStudio/Settings.cs b/src/GitHub.VisualStudio/Settings.cs deleted file mode 100644 index 22ff3eaf94..0000000000 --- a/src/GitHub.VisualStudio/Settings.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Guids.cs -// MUST match guids.h -using Microsoft.TeamFoundation.Controls; -using System; - -namespace GitHub.VisualStudio -{ - static class GuidList - { - public const string guidGitHubPkgString = "c3d3dc68-c977-411f-b3e8-03b0dccf7dfc"; - public const string guidGitHubCmdSetString = "c4c91892-8881-4588-a5d9-b41e8f540f5a"; - public const string guidGitHubToolbarCmdSetString = "C5F1193E-F300-41B3-B4C4-5A703DD3C1C6"; - - public static readonly Guid guidGitHubCmdSet = new Guid(guidGitHubCmdSetString); - public static readonly Guid guidGitHubToolbarCmdSet = new Guid(guidGitHubToolbarCmdSetString); - } - - static class NavigationItemPriority - { - public const int PullRequests = TeamExplorerNavigationItemPriority.GitCommits - 1; - public const int Wiki = TeamExplorerNavigationItemPriority.Settings - 1; - public const int Pulse = Wiki - 3; - public const int Graphs = Wiki - 2; - public const int Issues = Wiki - 1; - } -} diff --git a/src/GitHub.VisualStudio/Helpers/Constants.cs b/src/GitHub.VisualStudio/Settings/Constants.cs similarity index 100% rename from src/GitHub.VisualStudio/Helpers/Constants.cs rename to src/GitHub.VisualStudio/Settings/Constants.cs diff --git a/src/GitHub.VisualStudio/Settings/OptionsPage.cs b/src/GitHub.VisualStudio/Settings/OptionsPage.cs new file mode 100644 index 0000000000..3bd032ebde --- /dev/null +++ b/src/GitHub.VisualStudio/Settings/OptionsPage.cs @@ -0,0 +1,84 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.ComponentModel; +using System.Runtime.InteropServices; +using GitHub.Exports; +using GitHub.Logging; +using GitHub.Settings; +using GitHub.VisualStudio.UI; +using Microsoft.VisualStudio.Shell; +using Serilog; + +namespace GitHub.VisualStudio +{ + [ClassInterface(ClassInterfaceType.AutoDual)] + [ComVisible(true)] + [Guid("68C87C7B-0212-4256-BB6D-6A6BB847A3A7")] + public class OptionsPage : UIElementDialogPage + { + static readonly ILogger log = LogManager.ForContext<OptionsPage>(); + + OptionsControl child; + IPackageSettings packageSettings; + + protected override UIElement Child + { + get + { + if (!ExportForVisualStudioProcessAttribute.IsVisualStudioProcess()) + { + return new Grid(); // Show blank page + } + + return child ?? (child = new OptionsControl()); + } + } + + protected override void OnActivate(CancelEventArgs e) + { + if (!ExportForVisualStudioProcessAttribute.IsVisualStudioProcess()) + { + log.Warning("Don't activate options for non-Visual Studio process"); + return; + } + + base.OnActivate(e); + packageSettings = Services.DefaultExportProvider.GetExportedValue<IPackageSettings>(); + LoadSettings(); + } + + void LoadSettings() + { + child.CollectMetrics = packageSettings.CollectMetrics; + child.EditorComments = packageSettings.EditorComments; + child.ForkButton = packageSettings.ForkButton; + child.EnableTraceLogging = packageSettings.EnableTraceLogging; + } + + void SaveSettings() + { + packageSettings.CollectMetrics = child.CollectMetrics; + packageSettings.EditorComments = child.EditorComments; + packageSettings.ForkButton = child.ForkButton; + packageSettings.EnableTraceLogging = child.EnableTraceLogging; + packageSettings.Save(); + } + + protected override void OnApply(PageApplyEventArgs args) + { + if (!ExportForVisualStudioProcessAttribute.IsVisualStudioProcess()) + { + log.Warning("Don't apply options for non-Visual Studio process"); + return; + } + + if (args.ApplyBehavior == ApplyKind.Apply) + { + SaveSettings(); + } + + base.OnApply(args); + } + } +} diff --git a/src/GitHub.VisualStudio/Settings/PackageSettings.cs b/src/GitHub.VisualStudio/Settings/PackageSettings.cs new file mode 100644 index 0000000000..9c7b456a08 --- /dev/null +++ b/src/GitHub.VisualStudio/Settings/PackageSettings.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Settings; +using SettingsStore = GitHub.Helpers.SettingsStore; +using GitHub.Settings; +using GitHub.Primitives; + +namespace GitHub.VisualStudio.Settings +{ + public partial class PackageSettings : NotificationAwareObject, IPackageSettings + { + readonly SettingsStore settingsStore; + + public PackageSettings(IServiceProvider serviceProvider) + { + var sm = new ShellSettingsManager(serviceProvider); + settingsStore = new SettingsStore(sm.GetWritableSettingsStore(SettingsScope.UserSettings), Info.ApplicationInfo.ApplicationSafeName); + LoadSettings(); + } + + public void Save() + { + SaveSettings(); + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.cs b/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.cs new file mode 100644 index 0000000000..f8f6d30236 --- /dev/null +++ b/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.cs @@ -0,0 +1,113 @@ +// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt +/* settings.json content: +{ + "settings": [ + { + "name": "CollectMetrics", + "type": "bool", + "default": 'true' + }, + { + "name": "EditorComments", + "type": "bool", + "default": "false" + }, + { + "name": "ForkButton", + "type": "bool", + "default": "false" + }, + { + "name": "UIState", + "type": "object", + "typename": "UIState", + "default": "null" + }, + { + "name": "HideTeamExplorerWelcomeMessage", + "type": "bool", + "default": "false" + }, + { + "name": "EnableTraceLogging", + "type": "bool", + "default": "false" + } + ] +} +*/ + +using GitHub.Settings; +using GitHub.Primitives; +using GitHub.VisualStudio.Helpers; + +namespace GitHub.VisualStudio.Settings { + + public partial class PackageSettings : NotificationAwareObject, IPackageSettings + { + + bool collectMetrics; + public bool CollectMetrics + { + get { return collectMetrics; } + set { collectMetrics = value; this.RaisePropertyChange(); } + } + + bool editorComments; + public bool EditorComments + { + get { return editorComments; } + set { editorComments = value; this.RaisePropertyChange(); } + } + + bool forkButton; + public bool ForkButton + { + get { return forkButton; } + set { forkButton = value; this.RaisePropertyChange(); } + } + + UIState uIState; + public UIState UIState + { + get { return uIState; } + set { uIState = value; this.RaisePropertyChange(); } + } + + bool hideTeamExplorerWelcomeMessage; + public bool HideTeamExplorerWelcomeMessage + { + get { return hideTeamExplorerWelcomeMessage; } + set { hideTeamExplorerWelcomeMessage = value; this.RaisePropertyChange(); } + } + + bool enableTraceLogging; + public bool EnableTraceLogging + { + get { return enableTraceLogging; } + set { enableTraceLogging = value; this.RaisePropertyChange(); } + } + + + void LoadSettings() + { + CollectMetrics = (bool)settingsStore.Read("CollectMetrics", true); + EditorComments = (bool)settingsStore.Read("EditorComments", false); + ForkButton = (bool)settingsStore.Read("ForkButton", false); + UIState = SimpleJson.DeserializeObject<UIState>((string)settingsStore.Read("UIState", "{}")); + HideTeamExplorerWelcomeMessage = (bool)settingsStore.Read("HideTeamExplorerWelcomeMessage", false); + EnableTraceLogging = (bool)settingsStore.Read("EnableTraceLogging", false); + } + + void SaveSettings() + { + settingsStore.Write("CollectMetrics", CollectMetrics); + settingsStore.Write("EditorComments", EditorComments); + settingsStore.Write("ForkButton", ForkButton); + settingsStore.Write("UIState", SimpleJson.SerializeObject(UIState)); + settingsStore.Write("HideTeamExplorerWelcomeMessage", HideTeamExplorerWelcomeMessage); + settingsStore.Write("EnableTraceLogging", EnableTraceLogging); + } + + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.tt b/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.tt new file mode 100644 index 0000000000..a575552ca8 --- /dev/null +++ b/src/GitHub.VisualStudio/Settings/generated/PackageSettingsGen.tt @@ -0,0 +1,86 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.IO" #> +<#@ assembly name="$(PackageDir)\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll" #> +<#@ import namespace="Newtonsoft.Json.Linq" #> +<#@ output extension=".cs" #> +<# +var file = this.Host.ResolvePath(@"..\..\..\common\settings.json"); +var json = JObject.Parse(File.ReadAllText(file)); +#> +// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt +/* settings.json content: +<#@ include file="..\..\..\common\settings.json" #> +*/ + +using GitHub.Settings; +using GitHub.Primitives; +using GitHub.VisualStudio.Helpers; + +namespace GitHub.VisualStudio.Settings { + + public partial class PackageSettings : NotificationAwareObject, IPackageSettings + { + +<# +foreach (var j in json["settings"].Children()) { + var type = j["type"].ToString(); + if (type == "object") + type = j["typename"].ToString(); + var field = Char.ToLower(j["name"].ToString()[0]) + j["name"].ToString().Substring(1); +#> + <#= type #> <#= field #>; + public <#= type #> <#= j["name"] #> + { + get { return <#= field #>; } + set { <#= field #> = value; this.RaisePropertyChange(); } + } + +<# +} +#> + + void LoadSettings() + { +<# +foreach (var j in json.Children().Children().Children()) { + if (j["type"].ToString() == "object") + { +#> + <#= j["name"] #> = SimpleJson.DeserializeObject<<#= j["typename"] #>>((string)settingsStore.Read("<#= j["name"] #>", "{}")); +<# + } + else + { +#> + <#= j["name"] #> = (<#= j["type"] #>)settingsStore.Read("<#= j["name"] #>", <#= j["default"] #>); +<# + } +} +#> + } + + void SaveSettings() + { +<# +foreach (var j in json.Children().Children().Children()) { + if (j["type"].ToString() == "object") + { +#> + settingsStore.Write("<#= j["name"] #>", SimpleJson.SerializeObject(<#= j["name"] #>)); +<# + } + else + { +#> + settingsStore.Write("<#= j["name"] #>", <#= j["name"] #>); +<# + } +} +#> + } + + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/SharedDictionary.xaml b/src/GitHub.VisualStudio/SharedDictionary.xaml deleted file mode 100644 index 5ff78eec64..0000000000 --- a/src/GitHub.VisualStudio/SharedDictionary.xaml +++ /dev/null @@ -1,60 +0,0 @@ -<ResourceDictionary - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"> - - <ResourceDictionary.MergedDictionaries> - <ResourceDictionary Source="pack://application:,,,/GitHub.VisualStudio;component/UI/Views/Controls/ActionLinkButton.xaml" /> - </ResourceDictionary.MergedDictionaries> - - <Style x:Key="VSStyledButton" BasedOn="{StaticResource VsButtonStyleKey}" TargetType="{x:Type Button}" /> - <Style x:Key="VSStyledCheckBox" BasedOn="{StaticResource VsCheckBoxStyleKey}" TargetType="{x:Type CheckBox}"> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.ToolWindowText}" /> - <Setter Property="Background" Value="{DynamicResource VsBrush.ToolWindowBackground}" /> - <Style.Triggers> - <Trigger Property="UIElement.IsMouseOver" Value="true"> - <Setter Property="Background" Value="{DynamicResource VsBrush.ToolWindowBackground}" /> - </Trigger> - </Style.Triggers> - </Style> - <Style x:Key="VSStyledComboBox" BasedOn="{StaticResource VsComboBoxStyleKey}" TargetType="{x:Type ComboBox}" /> - - <DrawingBrush x:Key="ConnectArrowBrush"> - <DrawingBrush.Drawing> - <DrawingGroup> - <DrawingGroup.Children> - <GeometryDrawing Brush="{DynamicResource VsBrush.ControlLinkText}" Geometry="F1 M 9,11 L 7,11 9,9 4,9 4,7 9,7 7,5 9,5 12,8 Z" /> - <GeometryDrawing Brush="{DynamicResource VsBrush.ControlLinkText}" Geometry="F1 M 7.9741,1.0698 C 4.1461,1.0698 1.0441,4.1728 1.0441,7.9998 1.0441,11.8268 4.1461,14.9298 7.9741,14.9298 11.8011,14.9298 14.9041,11.8268 14.9041,7.9998 14.9041,4.1728 11.8011,1.0698 7.9741,1.0698 M 7.9741,2.0598 C 11.2501,2.0598 13.9151,4.7248 13.9151,7.9998 13.9151,11.2758 11.2501,13.9408 7.9741,13.9408 4.6991,13.9408 2.0341,11.2758 2.0341,7.9998 2.0341,4.7248 4.6991,2.0598 7.9741,2.0598 " /> - </DrawingGroup.Children> - </DrawingGroup> - </DrawingBrush.Drawing> - </DrawingBrush> - - <Style x:Key="TitleVerticalSeparator" TargetType="{x:Type Separator}"> - <Setter Property="Foreground" Value="#BBBBBB" /> - <Setter Property="Width" Value="2" /> - <Setter Property="Margin" Value="3,0,3,0" /> - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="{x:Type Separator}"> - <Border Width="{TemplateBinding Width}" - Background="{TemplateBinding Foreground}" - BorderBrush="{TemplateBinding Foreground}" - BorderThickness="{TemplateBinding BorderThickness}" - SnapsToDevicePixels="True" /> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - - <Style x:Key="VerticalSeparator" TargetType="{x:Type Separator}"> - <Setter Property="Background" Value="Gray" /> - <Setter Property="Margin" Value="3,0,3,0" /> - <Setter Property="LayoutTransform"> - <Setter.Value> - <RotateTransform Angle="90" /> - </Setter.Value> - </Setter> - </Style> - -</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/SimpleJson.cs b/src/GitHub.VisualStudio/SimpleJson.cs deleted file mode 100644 index adc7ca962c..0000000000 --- a/src/GitHub.VisualStudio/SimpleJson.cs +++ /dev/null @@ -1,2127 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="SimpleJson.cs" company="The Outercurve Foundation"> -// Copyright (c) 2011, The Outercurve Foundation. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.opensource.org/licenses/mit-license.php -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// </copyright> -// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author> -// <website>https://github.com/facebook-csharp-sdk/simple-json</website> -//----------------------------------------------------------------------- - -// VERSION: 0.38.0 - -// NOTE: uncomment the following line to make SimpleJson class internal. -//#define SIMPLE_JSON_INTERNAL - -// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. -//#define SIMPLE_JSON_OBJARRAYINTERNAL - -// NOTE: uncomment the following line to enable dynamic support. -//#define SIMPLE_JSON_DYNAMIC - -// NOTE: uncomment the following line to enable DataContract support. -//#define SIMPLE_JSON_DATACONTRACT - -// NOTE: uncomment the following line to enable IReadOnlyCollection<T> and IReadOnlyList<T> support. -//#define SIMPLE_JSON_READONLY_COLLECTIONS - -// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). -// define if you are using .net framework <= 3.0 or < WP7.5 -//#define SIMPLE_JSON_NO_LINQ_EXPRESSION - -// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. -// usually already defined in properties -//#define NETFX_CORE; - -// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; - -// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html - -#if NETFX_CORE -#define SIMPLE_JSON_TYPEINFO -#endif - -using System; -using System.CodeDom.Compiler; -using System.Collections; -using System.Collections.Generic; -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION -using System.Linq.Expressions; -#endif -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -#if SIMPLE_JSON_DYNAMIC -using System.Dynamic; -#endif -using System.Globalization; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; -using GitHub.VisualStudio.Reflection; - -// ReSharper disable LoopCanBeConvertedToQuery -// ReSharper disable RedundantExplicitArrayCreation -// ReSharper disable SuggestUseVarKeywordEvident -namespace GitHub.VisualStudio -{ - /// <summary> - /// Represents the json array. - /// </summary> - [GeneratedCode("simple-json", "1.0.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonArray : List<object> - { - /// <summary> - /// Initializes a new instance of the <see cref="JsonArray"/> class. - /// </summary> - public JsonArray() { } - - /// <summary> - /// Initializes a new instance of the <see cref="JsonArray"/> class. - /// </summary> - /// <param name="capacity">The capacity of the json array.</param> - public JsonArray(int capacity) : base(capacity) { } - - /// <summary> - /// The json representation of the array. - /// </summary> - /// <returns>The json representation of the array.</returns> - public override string ToString() - { - return SimpleJson.SerializeObject(this) ?? string.Empty; - } - } - - /// <summary> - /// Represents the json object. - /// </summary> - [GeneratedCode("simple-json", "1.0.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonObject : -#if SIMPLE_JSON_DYNAMIC - DynamicObject, -#endif - IDictionary<string, object> - { - /// <summary> - /// The internal member dictionary. - /// </summary> - private readonly Dictionary<string, object> _members; - - /// <summary> - /// Initializes a new instance of <see cref="JsonObject"/>. - /// </summary> - public JsonObject() - { - _members = new Dictionary<string, object>(); - } - - /// <summary> - /// Initializes a new instance of <see cref="JsonObject"/>. - /// </summary> - /// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param> - public JsonObject(IEqualityComparer<string> comparer) - { - _members = new Dictionary<string, object>(comparer); - } - - /// <summary> - /// Gets the <see cref="System.Object"/> at the specified index. - /// </summary> - /// <value></value> - public object this[int index] - { - get { return GetAtIndex(_members, index); } - } - - internal static object GetAtIndex(IDictionary<string, object> obj, int index) - { - if (obj == null) - throw new ArgumentNullException("obj"); - if (index >= obj.Count) - throw new ArgumentOutOfRangeException("index"); - int i = 0; - foreach (KeyValuePair<string, object> o in obj) - if (i++ == index) return o.Value; - return null; - } - - /// <summary> - /// Adds the specified key. - /// </summary> - /// <param name="key">The key.</param> - /// <param name="value">The value.</param> - public void Add(string key, object value) - { - _members.Add(key, value); - } - - /// <summary> - /// Determines whether the specified key contains key. - /// </summary> - /// <param name="key">The key.</param> - /// <returns> - /// <c>true</c> if the specified key contains key; otherwise, <c>false</c>. - /// </returns> - public bool ContainsKey(string key) - { - return _members.ContainsKey(key); - } - - /// <summary> - /// Gets the keys. - /// </summary> - /// <value>The keys.</value> - public ICollection<string> Keys - { - get { return _members.Keys; } - } - - /// <summary> - /// Removes the specified key. - /// </summary> - /// <param name="key">The key.</param> - /// <returns></returns> - public bool Remove(string key) - { - return _members.Remove(key); - } - - /// <summary> - /// Tries the get value. - /// </summary> - /// <param name="key">The key.</param> - /// <param name="value">The value.</param> - /// <returns></returns> - public bool TryGetValue(string key, out object value) - { - return _members.TryGetValue(key, out value); - } - - /// <summary> - /// Gets the values. - /// </summary> - /// <value>The values.</value> - public ICollection<object> Values - { - get { return _members.Values; } - } - - /// <summary> - /// Gets or sets the <see cref="System.Object"/> with the specified key. - /// </summary> - /// <value></value> - public object this[string key] - { - get { return _members[key]; } - set { _members[key] = value; } - } - - /// <summary> - /// Adds the specified item. - /// </summary> - /// <param name="item">The item.</param> - public void Add(KeyValuePair<string, object> item) - { - _members.Add(item.Key, item.Value); - } - - /// <summary> - /// Clears this instance. - /// </summary> - public void Clear() - { - _members.Clear(); - } - - /// <summary> - /// Determines whether [contains] [the specified item]. - /// </summary> - /// <param name="item">The item.</param> - /// <returns> - /// <c>true</c> if [contains] [the specified item]; otherwise, <c>false</c>. - /// </returns> - public bool Contains(KeyValuePair<string, object> item) - { - return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; - } - - /// <summary> - /// Copies to. - /// </summary> - /// <param name="array">The array.</param> - /// <param name="arrayIndex">Index of the array.</param> - public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) - { - if (array == null) throw new ArgumentNullException("array"); - int num = Count; - foreach (KeyValuePair<string, object> kvp in this) - { - array[arrayIndex++] = kvp; - if (--num <= 0) - return; - } - } - - /// <summary> - /// Gets the count. - /// </summary> - /// <value>The count.</value> - public int Count - { - get { return _members.Count; } - } - - /// <summary> - /// Gets a value indicating whether this instance is read only. - /// </summary> - /// <value> - /// <c>true</c> if this instance is read only; otherwise, <c>false</c>. - /// </value> - public bool IsReadOnly - { - get { return false; } - } - - /// <summary> - /// Removes the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns></returns> - public bool Remove(KeyValuePair<string, object> item) - { - return _members.Remove(item.Key); - } - - /// <summary> - /// Gets the enumerator. - /// </summary> - /// <returns></returns> - public IEnumerator<KeyValuePair<string, object>> GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// <summary> - /// Returns an enumerator that iterates through a collection. - /// </summary> - /// <returns> - /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - /// </returns> - IEnumerator IEnumerable.GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// <summary> - /// Returns a json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </summary> - /// <returns> - /// A json <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. - /// </returns> - public override string ToString() - { - return SimpleJson.SerializeObject(this); - } - -#if SIMPLE_JSON_DYNAMIC - /// <summary> - /// Provides implementation for type conversion operations. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that convert an object from one type to another. - /// </summary> - /// <param name="binder">Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Type returns the <see cref="T:System.String"/> type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion.</param> - /// <param name="result">The result of the type conversion operation.</param> - /// <returns> - /// Alwasy returns true. - /// </returns> - public override bool TryConvert(ConvertBinder binder, out object result) - { - // <pex> - if (binder == null) - throw new ArgumentNullException("binder"); - // </pex> - Type targetType = binder.Type; - - if ((targetType == typeof(IEnumerable)) || - (targetType == typeof(IEnumerable<KeyValuePair<string, object>>)) || - (targetType == typeof(IDictionary<string, object>)) || - (targetType == typeof(IDictionary))) - { - result = this; - return true; - } - - return base.TryConvert(binder, out result); - } - - /// <summary> - /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. - /// </summary> - /// <param name="binder">Provides information about the deletion.</param> - /// <returns> - /// Alwasy returns true. - /// </returns> - public override bool TryDeleteMember(DeleteMemberBinder binder) - { - // <pex> - if (binder == null) - throw new ArgumentNullException("binder"); - // </pex> - return _members.Remove(binder.Name); - } - - /// <summary> - /// Provides the implementation for operations that get a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for indexing operations. - /// </summary> - /// <param name="binder">Provides information about the operation.</param> - /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, <paramref name="indexes"/> is equal to 3.</param> - /// <param name="result">The result of the index operation.</param> - /// <returns> - /// Alwasy returns true. - /// </returns> - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - if (indexes == null) throw new ArgumentNullException("indexes"); - if (indexes.Length == 1) - { - result = ((IDictionary<string, object>)this)[(string)indexes[0]]; - return true; - } - result = null; - return true; - } - - /// <summary> - /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as getting a value for a property. - /// </summary> - /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param> - /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result"/>.</param> - /// <returns> - /// Alwasy returns true. - /// </returns> - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - object value; - if (_members.TryGetValue(binder.Name, out value)) - { - result = value; - return true; - } - result = null; - return true; - } - - /// <summary> - /// Provides the implementation for operations that set a value by index. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations that access objects by a specified index. - /// </summary> - /// <param name="binder">Provides information about the operation.</param> - /// <param name="indexes">The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="indexes"/> is equal to 3.</param> - /// <param name="value">The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, <paramref name="value"/> is equal to 10.</param> - /// <returns> - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. - /// </returns> - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) - { - if (indexes == null) throw new ArgumentNullException("indexes"); - if (indexes.Length == 1) - { - ((IDictionary<string, object>)this)[(string)indexes[0]] = value; - return true; - } - return base.TrySetIndex(binder, indexes, value); - } - - /// <summary> - /// Provides the implementation for operations that set member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as setting a value for a property. - /// </summary> - /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param> - /// <param name="value">The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, the <paramref name="value"/> is "Test".</param> - /// <returns> - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) - /// </returns> - public override bool TrySetMember(SetMemberBinder binder, object value) - { - // <pex> - if (binder == null) - throw new ArgumentNullException("binder"); - // </pex> - _members[binder.Name] = value; - return true; - } - - /// <summary> - /// Returns the enumeration of all dynamic member names. - /// </summary> - /// <returns> - /// A sequence that contains dynamic member names. - /// </returns> - public override IEnumerable<string> GetDynamicMemberNames() - { - foreach (var key in Keys) - yield return key; - } -#endif - } -} - -namespace GitHub.VisualStudio -{ - /// <summary> - /// This class encodes and decodes JSON strings. - /// Spec. details, see http://www.json.org/ - /// - /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). - /// All numbers are parsed to doubles. - /// </summary> - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - static class SimpleJson - { - private const int TOKEN_NONE = 0; - private const int TOKEN_CURLY_OPEN = 1; - private const int TOKEN_CURLY_CLOSE = 2; - private const int TOKEN_SQUARED_OPEN = 3; - private const int TOKEN_SQUARED_CLOSE = 4; - private const int TOKEN_COLON = 5; - private const int TOKEN_COMMA = 6; - private const int TOKEN_STRING = 7; - private const int TOKEN_NUMBER = 8; - private const int TOKEN_TRUE = 9; - private const int TOKEN_FALSE = 10; - private const int TOKEN_NULL = 11; - private const int BUILDER_CAPACITY = 2000; - - private static readonly char[] EscapeTable; - private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; - private static readonly string EscapeCharactersString = new string(EscapeCharacters); - - static SimpleJson() - { - EscapeTable = new char[93]; - EscapeTable['"'] = '"'; - EscapeTable['\\'] = '\\'; - EscapeTable['\b'] = 'b'; - EscapeTable['\f'] = 'f'; - EscapeTable['\n'] = 'n'; - EscapeTable['\r'] = 'r'; - EscapeTable['\t'] = 't'; - } - - /// <summary> - /// Parses the string json into a value - /// </summary> - /// <param name="json">A JSON string.</param> - /// <returns>An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false</returns> - public static object DeserializeObject(string json) - { - object obj; - if (TryDeserializeObject(json, out obj)) - return obj; - throw new SerializationException("Invalid JSON string"); - } - - /// <summary> - /// Try parsing the json string into a value. - /// </summary> - /// <param name="json"> - /// A JSON string. - /// </param> - /// <param name="obj"> - /// The object. - /// </param> - /// <returns> - /// Returns true if successfull otherwise false. - /// </returns> - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - public static bool TryDeserializeObject(string json, out object obj) - { - bool success = true; - if (json != null) - { - char[] charArray = json.ToCharArray(); - int index = 0; - obj = ParseValue(charArray, ref index, ref success); - } - else - obj = null; - - return success; - } - - public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) - { - object jsonObject = DeserializeObject(json); - return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) - ? jsonObject - : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); - } - - public static object DeserializeObject(string json, Type type) - { - return DeserializeObject(json, type, null); - } - - public static T DeserializeObject<T>(string json, IJsonSerializerStrategy jsonSerializerStrategy) - { - return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); - } - - public static T DeserializeObject<T>(string json) - { - return (T)DeserializeObject(json, typeof(T), null); - } - - /// <summary> - /// Converts a IDictionary<string,object> / IList<object> object into a JSON string - /// </summary> - /// <param name="json">A IDictionary<string,object> / IList<object></param> - /// <param name="jsonSerializerStrategy">Serializer strategy to use</param> - /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns> - public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) - { - StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); - bool success = SerializeValue(jsonSerializerStrategy, json, builder); - return (success ? builder.ToString() : null); - } - - public static string SerializeObject(object json) - { - return SerializeObject(json, CurrentJsonSerializerStrategy); - } - - public static string EscapeToJavascriptString(string jsonString) - { - if (string.IsNullOrEmpty(jsonString)) - return jsonString; - - StringBuilder sb = new StringBuilder(); - char c; - - for (int i = 0; i < jsonString.Length; ) - { - c = jsonString[i++]; - - if (c == '\\') - { - int remainingLength = jsonString.Length - i; - if (remainingLength >= 2) - { - char lookahead = jsonString[i]; - if (lookahead == '\\') - { - sb.Append('\\'); - ++i; - } - else if (lookahead == '"') - { - sb.Append("\""); - ++i; - } - else if (lookahead == 't') - { - sb.Append('\t'); - ++i; - } - else if (lookahead == 'b') - { - sb.Append('\b'); - ++i; - } - else if (lookahead == 'n') - { - sb.Append('\n'); - ++i; - } - else if (lookahead == 'r') - { - sb.Append('\r'); - ++i; - } - } - } - else - { - sb.Append(c); - } - } - return sb.ToString(); - } - - static IDictionary<string, object> ParseObject(char[] json, ref int index, ref bool success) - { - IDictionary<string, object> table = new JsonObject(); - int token; - - // { - NextToken(json, ref index); - - bool done = false; - while (!done) - { - token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_CURLY_CLOSE) - { - NextToken(json, ref index); - return table; - } - else - { - // name - string name = ParseString(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - // : - token = NextToken(json, ref index); - if (token != TOKEN_COLON) - { - success = false; - return null; - } - // value - object value = ParseValue(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - table[name] = value; - } - } - return table; - } - - static JsonArray ParseArray(char[] json, ref int index, ref bool success) - { - JsonArray array = new JsonArray(); - - // [ - NextToken(json, ref index); - - bool done = false; - while (!done) - { - int token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_SQUARED_CLOSE) - { - NextToken(json, ref index); - break; - } - else - { - object value = ParseValue(json, ref index, ref success); - if (!success) - return null; - array.Add(value); - } - } - return array; - } - - static object ParseValue(char[] json, ref int index, ref bool success) - { - switch (LookAhead(json, index)) - { - case TOKEN_STRING: - return ParseString(json, ref index, ref success); - case TOKEN_NUMBER: - return ParseNumber(json, ref index, ref success); - case TOKEN_CURLY_OPEN: - return ParseObject(json, ref index, ref success); - case TOKEN_SQUARED_OPEN: - return ParseArray(json, ref index, ref success); - case TOKEN_TRUE: - NextToken(json, ref index); - return true; - case TOKEN_FALSE: - NextToken(json, ref index); - return false; - case TOKEN_NULL: - NextToken(json, ref index); - return null; - case TOKEN_NONE: - break; - } - success = false; - return null; - } - - static string ParseString(char[] json, ref int index, ref bool success) - { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); - char c; - - EatWhitespace(json, ref index); - - // " - c = json[index++]; - bool complete = false; - while (!complete) - { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') - { - complete = true; - break; - } - else if (c == '\\') - { - if (index == json.Length) - break; - c = json[index++]; - if (c == '"') - s.Append('"'); - else if (c == '\\') - s.Append('\\'); - else if (c == '/') - s.Append('/'); - else if (c == 'b') - s.Append('\b'); - else if (c == 'f') - s.Append('\f'); - else if (c == 'n') - s.Append('\n'); - else if (c == 'r') - s.Append('\r'); - else if (c == 't') - s.Append('\t'); - else if (c == 'u') - { - int remainingLength = json.Length - index; - if (remainingLength >= 4) - { - // parse the 32 bit hex into an integer codepoint - uint codePoint; - if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) - return ""; - - // convert the integer codepoint to a unicode char and add to string - if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate - { - index += 4; // skip 4 chars - remainingLength = json.Length - index; - if (remainingLength >= 6) - { - uint lowCodePoint; - if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) - { - if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate - { - s.Append((char)codePoint); - s.Append((char)lowCodePoint); - index += 6; // skip 6 chars - continue; - } - } - } - success = false; // invalid surrogate pair - return ""; - } - s.Append(ConvertFromUtf32((int)codePoint)); - // skip 4 chars - index += 4; - } - else - break; - } - } - else - s.Append(c); - } - if (!complete) - { - success = false; - return null; - } - return s.ToString(); - } - - private static string ConvertFromUtf32(int utf32) - { - // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm - if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); - if (0xD800 <= utf32 && utf32 <= 0xDFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); - if (utf32 < 0x10000) - return new string((char)utf32, 1); - utf32 -= 0x10000; - return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); - } - - static object ParseNumber(char[] json, ref int index, ref bool success) - { - EatWhitespace(json, ref index); - int lastIndex = GetLastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - object returnNumber; - string str = new string(json, index, charLength); - if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) - { - double number; - success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - else - { - long number; - success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - index = lastIndex + 1; - return returnNumber; - } - - static int GetLastIndexOfNumber(char[] json, int index) - { - int lastIndex; - for (lastIndex = index; lastIndex < json.Length; lastIndex++) - if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; - return lastIndex - 1; - } - - static void EatWhitespace(char[] json, ref int index) - { - for (; index < json.Length; index++) - if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; - } - - static int LookAhead(char[] json, int index) - { - int saveIndex = index; - return NextToken(json, ref saveIndex); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - static int NextToken(char[] json, ref int index) - { - EatWhitespace(json, ref index); - if (index == json.Length) - return TOKEN_NONE; - char c = json[index]; - index++; - switch (c) - { - case '{': - return TOKEN_CURLY_OPEN; - case '}': - return TOKEN_CURLY_CLOSE; - case '[': - return TOKEN_SQUARED_OPEN; - case ']': - return TOKEN_SQUARED_CLOSE; - case ',': - return TOKEN_COMMA; - case '"': - return TOKEN_STRING; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return TOKEN_NUMBER; - case ':': - return TOKEN_COLON; - } - index--; - int remainingLength = json.Length - index; - // false - if (remainingLength >= 5) - { - if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') - { - index += 5; - return TOKEN_FALSE; - } - } - // true - if (remainingLength >= 4) - { - if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') - { - index += 4; - return TOKEN_TRUE; - } - } - // null - if (remainingLength >= 4) - { - if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') - { - index += 4; - return TOKEN_NULL; - } - } - return TOKEN_NONE; - } - - static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) - { - bool success = true; - string stringValue = value as string; - if (stringValue != null) - success = SerializeString(stringValue, builder); - else - { - IDictionary<string, object> dict = value as IDictionary<string, object>; - if (dict != null) - { - success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); - } - else - { - IDictionary<string, string> stringDictionary = value as IDictionary<string, string>; - if (stringDictionary != null) - { - success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); - } - else - { - IEnumerable enumerableValue = value as IEnumerable; - if (enumerableValue != null) - success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); - else if (IsNumeric(value)) - success = SerializeNumber(value, builder); - else if (value is bool) - builder.Append((bool)value ? "true" : "false"); - else if (value == null) - builder.Append("null"); - else - { - object serializedObject; - success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); - if (success) - SerializeValue(jsonSerializerStrategy, serializedObject, builder); - } - } - } - } - return success; - } - - static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) - { - builder.Append("{"); - IEnumerator ke = keys.GetEnumerator(); - IEnumerator ve = values.GetEnumerator(); - bool first = true; - while (ke.MoveNext() && ve.MoveNext()) - { - object key = ke.Current; - object value = ve.Current; - if (!first) - builder.Append(","); - string stringKey = key as string; - if (stringKey != null) - SerializeString(stringKey, builder); - else - if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; - builder.Append(":"); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - first = false; - } - builder.Append("}"); - return true; - } - - static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) - { - builder.Append("["); - bool first = true; - foreach (object value in anArray) - { - if (!first) - builder.Append(","); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - first = false; - } - builder.Append("]"); - return true; - } - - static bool SerializeString(string aString, StringBuilder builder) - { - // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) - if (aString.IndexOfAny(EscapeCharacters) == -1) - { - builder.Append('"'); - builder.Append(aString); - builder.Append('"'); - - return true; - } - - builder.Append('"'); - int safeCharacterCount = 0; - char[] charArray = aString.ToCharArray(); - - for (int i = 0; i < charArray.Length; i++) - { - char c = charArray[i]; - - // Non ascii characters are fine, buffer them up and send them to the builder - // in larger chunks if possible. The escape table is a 1:1 translation table - // with \0 [default(char)] denoting a safe character. - if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) - { - safeCharacterCount++; - } - else - { - if (safeCharacterCount > 0) - { - builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); - safeCharacterCount = 0; - } - - builder.Append('\\'); - builder.Append(EscapeTable[c]); - } - } - - if (safeCharacterCount > 0) - { - builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); - } - - builder.Append('"'); - return true; - } - - static bool SerializeNumber(object number, StringBuilder builder) - { - if (number is long) - builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); - else if (number is ulong) - builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); - else if (number is int) - builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); - else if (number is uint) - builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); - else if (number is decimal) - builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); - else if (number is float) - builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); - else - builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); - return true; - } - - /// <summary> - /// Determines if a given object is numeric in any way - /// (can be integer, double, null, etc). - /// </summary> - static bool IsNumeric(object value) - { - if (value is sbyte) return true; - if (value is byte) return true; - if (value is short) return true; - if (value is ushort) return true; - if (value is int) return true; - if (value is uint) return true; - if (value is long) return true; - if (value is ulong) return true; - if (value is float) return true; - if (value is double) return true; - if (value is decimal) return true; - return false; - } - - private static IJsonSerializerStrategy _currentJsonSerializerStrategy; - public static IJsonSerializerStrategy CurrentJsonSerializerStrategy - { - get - { - return _currentJsonSerializerStrategy ?? - (_currentJsonSerializerStrategy = -#if SIMPLE_JSON_DATACONTRACT - DataContractJsonSerializerStrategy -#else - PocoJsonSerializerStrategy -#endif -); - } - set - { - _currentJsonSerializerStrategy = value; - } - } - - private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy - { - get - { - return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); - } - } - -#if SIMPLE_JSON_DATACONTRACT - - private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; - [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] - public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy - { - get - { - return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); - } - } - -#endif - } - - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - interface IJsonSerializerStrategy - { - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - bool TrySerializeNonPrimitiveObject(object input, out object output); - object DeserializeObject(object value, Type type); - } - - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class PocoJsonSerializerStrategy : IJsonSerializerStrategy - { - internal IDictionary<Type, ReflectionUtils.ConstructorDelegate> ConstructorCache; - internal IDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>> GetCache; - internal IDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>> SetCache; - - internal static readonly Type[] EmptyTypes = new Type[0]; - internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; - - private static readonly string[] Iso8601Format = new string[] - { - @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", - @"yyyy-MM-dd\THH:mm:ss\Z", - @"yyyy-MM-dd\THH:mm:ssK" - }; - - public PocoJsonSerializerStrategy() - { - ConstructorCache = new ReflectionUtils.ThreadSafeDictionary<Type, ReflectionUtils.ConstructorDelegate>(ContructorDelegateFactory); - GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory); - SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory); - } - - protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) - { - return clrPropertyName; - } - - internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key) - { - return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes); - } - - internal virtual IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type) - { - IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanRead) - { - MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (getMethod.IsStatic || !getMethod.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); - } - return result; - } - - internal virtual IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type) - { - IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanWrite) - { - MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); - if (setMethod.IsStatic || !setMethod.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); - } - return result; - } - - public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) - { - return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - public virtual object DeserializeObject(object value, Type type) - { - if (type == null) throw new ArgumentNullException("type"); - string str = value as string; - - if (type == typeof (Guid) && string.IsNullOrEmpty(str)) - return default(Guid); - - if (value == null) - return null; - - object obj = null; - - if (str != null) - { - if (str.Length != 0) // We know it can't be null now. - { - if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) - return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); - if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) - return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); - if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) - return new Guid(str); - if (type == typeof(Uri)) - { - bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); - - Uri result; - if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) - return result; - - return null; - } - - if (type == typeof(string)) - return str; - - return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); - } - else - { - if (type == typeof(Guid)) - obj = default(Guid); - else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - obj = null; - else - obj = str; - } - // Empty string case - if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - return str; - } - else if (value is bool) - return value; - - bool valueIsLong = value is long; - bool valueIsDouble = value is double; - if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) - return value; - if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) - { - obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) - ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) - : value; - } - else - { - IDictionary<string, object> objects = value as IDictionary<string, object>; - if (objects != null) - { - IDictionary<string, object> jsonObject = objects; - - if (ReflectionUtils.IsTypeDictionary(type)) - { - // if dictionary then - Type[] types = ReflectionUtils.GetGenericTypeArguments(type); - Type keyType = types[0]; - Type valueType = types[1]; - - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); - - IDictionary dict = (IDictionary)ConstructorCache[genericType](); - - foreach (KeyValuePair<string, object> kvp in jsonObject) - dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); - - obj = dict; - } - else - { - if (type == typeof(object)) - obj = value; - else - { - obj = ConstructorCache[type](); - foreach (KeyValuePair<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> setter in SetCache[type]) - { - object jsonValue; - if (jsonObject.TryGetValue(setter.Key, out jsonValue)) - { - jsonValue = DeserializeObject(jsonValue, setter.Value.Key); - setter.Value.Value(obj, jsonValue); - } - } - } - } - } - else - { - IList<object> valueAsList = value as IList<object>; - if (valueAsList != null) - { - IList<object> jsonObject = valueAsList; - IList list = null; - - if (type.IsArray) - { - list = (IList)ConstructorCache[type](jsonObject.Count); - int i = 0; - foreach (object o in jsonObject) - list[i++] = DeserializeObject(o, type.GetElementType()); - } - else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) - { - Type innerType = ReflectionUtils.GetGenericListElementType(type); - list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); - foreach (object o in jsonObject) - list.Add(DeserializeObject(o, innerType)); - } - obj = list; - } - } - return obj; - } - if (ReflectionUtils.IsNullableType(type)) - return ReflectionUtils.ToNullableType(obj, type); - return obj; - } - - protected virtual object SerializeEnum(Enum p) - { - return Convert.ToDouble(p, CultureInfo.InvariantCulture); - } - - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - protected virtual bool TrySerializeKnownTypes(object input, out object output) - { - bool returnValue = true; - if (input is DateTime) - output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); - else if (input is DateTimeOffset) - output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); - else if (input is Guid) - output = ((Guid)input).ToString("D"); - else if (input is Uri) - output = input.ToString(); - else - { - Enum inputEnum = input as Enum; - if (inputEnum != null) - output = SerializeEnum(inputEnum); - else - { - returnValue = false; - output = null; - } - } - return returnValue; - } - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - protected virtual bool TrySerializeUnknownTypes(object input, out object output) - { - if (input == null) throw new ArgumentNullException("input"); - output = null; - Type type = input.GetType(); - if (type.FullName == null) - return false; - IDictionary<string, object> obj = new JsonObject(); - IDictionary<string, ReflectionUtils.GetDelegate> getters = GetCache[type]; - foreach (KeyValuePair<string, ReflectionUtils.GetDelegate> getter in getters) - { - if (getter.Value != null) - obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); - } - output = obj; - return true; - } - } - -#if SIMPLE_JSON_DATACONTRACT - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy - { - public DataContractJsonSerializerStrategy() - { - GetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, ReflectionUtils.GetDelegate>>(GetterValueFactory); - SetCache = new ReflectionUtils.ThreadSafeDictionary<Type, IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>>(SetterValueFactory); - } - - internal override IDictionary<string, ReflectionUtils.GetDelegate> GetterValueFactory(Type type) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - return base.GetterValueFactory(type); - string jsonKey; - IDictionary<string, ReflectionUtils.GetDelegate> result = new Dictionary<string, ReflectionUtils.GetDelegate>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanRead) - { - MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) - result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) - result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); - } - return result; - } - - internal override IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - return base.SetterValueFactory(type); - string jsonKey; - IDictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> result = new Dictionary<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanWrite) - { - MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); - if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) - result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) - result[jsonKey] = new KeyValuePair<Type, ReflectionUtils.SetDelegate>(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); - } - // todo implement sorting for DATACONTRACT. - return result; - } - - private static bool CanAdd(MemberInfo info, out string jsonKey) - { - jsonKey = null; - if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) - return false; - DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); - if (dataMemberAttribute == null) - return false; - jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; - return true; - } - } - -#endif - - namespace Reflection - { - // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules - // that might be in place in the target project. - [GeneratedCode("reflection-utils", "1.0.0")] -#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC - public -#else - internal -#endif - class ReflectionUtils - { - private static readonly object[] EmptyObjects = new object[] { }; - - public delegate object GetDelegate(object source); - public delegate void SetDelegate(object source, object value); - public delegate object ConstructorDelegate(params object[] args); - - public delegate TValue ThreadSafeDictionaryValueFactory<TKey, TValue>(TKey key); - -#if SIMPLE_JSON_TYPEINFO - public static TypeInfo GetTypeInfo(Type type) - { - return type.GetTypeInfo(); - } -#else - public static Type GetTypeInfo(Type type) - { - return type; - } -#endif - - public static Attribute GetAttribute(MemberInfo info, Type type) - { -#if SIMPLE_JSON_TYPEINFO - if (info == null || type == null || !info.IsDefined(type)) - return null; - return info.GetCustomAttribute(type); -#else - if (info == null || type == null || !Attribute.IsDefined(info, type)) - return null; - return Attribute.GetCustomAttribute(info, type); -#endif - } - - public static Type GetGenericListElementType(Type type) - { - IEnumerable<Type> interfaces; -#if SIMPLE_JSON_TYPEINFO - interfaces = type.GetTypeInfo().ImplementedInterfaces; -#else - interfaces = type.GetInterfaces(); -#endif - foreach (Type implementedInterface in interfaces) - { - if (IsTypeGeneric(implementedInterface) && - implementedInterface.GetGenericTypeDefinition() == typeof (IList<>)) - { - return GetGenericTypeArguments(implementedInterface)[0]; - } - } - return GetGenericTypeArguments(type)[0]; - } - - public static Attribute GetAttribute(Type objectType, Type attributeType) - { - -#if SIMPLE_JSON_TYPEINFO - if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) - return null; - return objectType.GetTypeInfo().GetCustomAttribute(attributeType); -#else - if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) - return null; - return Attribute.GetCustomAttribute(objectType, attributeType); -#endif - } - - public static Type[] GetGenericTypeArguments(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetTypeInfo().GenericTypeArguments; -#else - return type.GetGenericArguments(); -#endif - } - - public static bool IsTypeGeneric(Type type) - { - return GetTypeInfo(type).IsGenericType; - } - - public static bool IsTypeGenericeCollectionInterface(Type type) - { - if (!IsTypeGeneric(type)) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(IList<>) - || genericDefinition == typeof(ICollection<>) - || genericDefinition == typeof(IEnumerable<>) -#if SIMPLE_JSON_READONLY_COLLECTIONS - || genericDefinition == typeof(IReadOnlyCollection<>) - || genericDefinition == typeof(IReadOnlyList<>) -#endif - ); - } - - public static bool IsAssignableFrom(Type type1, Type type2) - { - return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); - } - - public static bool IsTypeDictionary(Type type) - { -#if SIMPLE_JSON_TYPEINFO - if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - return true; -#else - if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) - return true; -#endif - if (!GetTypeInfo(type).IsGenericType) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - return genericDefinition == typeof(IDictionary<,>); - } - - public static bool IsNullableType(Type type) - { - return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - - public static object ToNullableType(object obj, Type nullableType) - { - return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); - } - - public static bool IsValueType(Type type) - { - return GetTypeInfo(type).IsValueType; - } - - public static IEnumerable<ConstructorInfo> GetConstructors(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetTypeInfo().DeclaredConstructors; -#else - return type.GetConstructors(); -#endif - } - - public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) - { - IEnumerable<ConstructorInfo> constructorInfos = GetConstructors(type); - int i; - bool matches; - foreach (ConstructorInfo constructorInfo in constructorInfos) - { - ParameterInfo[] parameters = constructorInfo.GetParameters(); - if (argsType.Length != parameters.Length) - continue; - - i = 0; - matches = true; - foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) - { - if (parameterInfo.ParameterType != argsType[i]) - { - matches = false; - break; - } - } - - if (matches) - return constructorInfo; - } - - return null; - } - - public static IEnumerable<PropertyInfo> GetProperties(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetRuntimeProperties(); -#else - return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); -#endif - } - - public static IEnumerable<FieldInfo> GetFields(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetRuntimeFields(); -#else - return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); -#endif - } - - public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_TYPEINFO - return propertyInfo.GetMethod; -#else - return propertyInfo.GetGetMethod(true); -#endif - } - - public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_TYPEINFO - return propertyInfo.SetMethod; -#else - return propertyInfo.GetSetMethod(true); -#endif - } - - public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetConstructorByReflection(constructorInfo); -#else - return GetConstructorByExpression(constructorInfo); -#endif - } - - public static ConstructorDelegate GetContructor(Type type, params Type[] argsType) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetConstructorByReflection(type, argsType); -#else - return GetConstructorByExpression(type, argsType); -#endif - } - - public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) - { - return delegate(object[] args) { return constructorInfo.Invoke(args); }; - } - - public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) - { - ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); - return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); - } - -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION - - public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) - { - ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); - ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); - Expression[] argsExp = new Expression[paramsInfo.Length]; - for (int i = 0; i < paramsInfo.Length; i++) - { - Expression index = Expression.Constant(i); - Type paramType = paramsInfo[i].ParameterType; - Expression paramAccessorExp = Expression.ArrayIndex(param, index); - Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); - argsExp[i] = paramCastExp; - } - NewExpression newExp = Expression.New(constructorInfo, argsExp); - Expression<Func<object[], object>> lambda = Expression.Lambda<Func<object[], object>>(newExp, param); - Func<object[], object> compiledLambda = lambda.Compile(); - return delegate(object[] args) { return compiledLambda(args); }; - } - - public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) - { - ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); - return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); - } - -#endif - - public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetGetMethodByReflection(propertyInfo); -#else - return GetGetMethodByExpression(propertyInfo); -#endif - } - - public static GetDelegate GetGetMethod(FieldInfo fieldInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetGetMethodByReflection(fieldInfo); -#else - return GetGetMethodByExpression(fieldInfo); -#endif - } - - public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) - { - MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); - return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; - } - - public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) - { - return delegate(object source) { return fieldInfo.GetValue(source); }; - } - -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION - - public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) - { - MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); - Func<object, object> compiled = Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); - return delegate(object source) { return compiled(source); }; - } - - public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) - { - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); - GetDelegate compiled = Expression.Lambda<GetDelegate>(Expression.Convert(member, typeof(object)), instance).Compile(); - return delegate(object source) { return compiled(source); }; - } - -#endif - - public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetSetMethodByReflection(propertyInfo); -#else - return GetSetMethodByExpression(propertyInfo); -#endif - } - - public static SetDelegate GetSetMethod(FieldInfo fieldInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetSetMethodByReflection(fieldInfo); -#else - return GetSetMethodByExpression(fieldInfo); -#endif - } - - public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) - { - MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); - return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; - } - - public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) - { - return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; - } - -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION - - public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) - { - MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - ParameterExpression value = Expression.Parameter(typeof(object), "value"); - UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); - UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); - Action<object, object> compiled = Expression.Lambda<Action<object, object>>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); - return delegate(object source, object val) { compiled(source, val); }; - } - - public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) - { - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - ParameterExpression value = Expression.Parameter(typeof(object), "value"); - Action<object, object> compiled = Expression.Lambda<Action<object, object>>( - Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); - return delegate(object source, object val) { compiled(source, val); }; - } - - public static BinaryExpression Assign(Expression left, Expression right) - { -#if SIMPLE_JSON_TYPEINFO - return Expression.Assign(left, right); -#else - MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); - BinaryExpression assignExpr = Expression.Add(left, right, assign); - return assignExpr; -#endif - } - - private static class Assigner<T> - { - public static T Assign(ref T left, T right) - { - return (left = right); - } - } - -#endif - - public sealed class ThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue> - { - private readonly object _lock = new object(); - private readonly ThreadSafeDictionaryValueFactory<TKey, TValue> _valueFactory; - private Dictionary<TKey, TValue> _dictionary; - - public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory<TKey, TValue> valueFactory) - { - _valueFactory = valueFactory; - } - - private TValue Get(TKey key) - { - if (_dictionary == null) - return AddValue(key); - TValue value; - if (!_dictionary.TryGetValue(key, out value)) - return AddValue(key); - return value; - } - - private TValue AddValue(TKey key) - { - TValue value = _valueFactory(key); - lock (_lock) - { - if (_dictionary == null) - { - _dictionary = new Dictionary<TKey, TValue>(); - _dictionary[key] = value; - } - else - { - TValue val; - if (_dictionary.TryGetValue(key, out val)) - return val; - Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(_dictionary); - dict[key] = value; - _dictionary = dict; - } - } - return value; - } - - public void Add(TKey key, TValue value) - { - throw new NotImplementedException(); - } - - public bool ContainsKey(TKey key) - { - return _dictionary.ContainsKey(key); - } - - public ICollection<TKey> Keys - { - get { return _dictionary.Keys; } - } - - public bool Remove(TKey key) - { - throw new NotImplementedException(); - } - - public bool TryGetValue(TKey key, out TValue value) - { - value = this[key]; - return true; - } - - public ICollection<TValue> Values - { - get { return _dictionary.Values; } - } - - public TValue this[TKey key] - { - get { return Get(key); } - set { throw new NotImplementedException(); } - } - - public void Add(KeyValuePair<TKey, TValue> item) - { - throw new NotImplementedException(); - } - - public void Clear() - { - throw new NotImplementedException(); - } - - public bool Contains(KeyValuePair<TKey, TValue> item) - { - throw new NotImplementedException(); - } - - public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public int Count - { - get { return _dictionary.Count; } - } - - public bool IsReadOnly - { - get { throw new NotImplementedException(); } - } - - public bool Remove(KeyValuePair<TKey, TValue> item) - { - throw new NotImplementedException(); - } - - public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() - { - return _dictionary.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return _dictionary.GetEnumerator(); - } - } - - } - } -} -// ReSharper restore LoopCanBeConvertedToQuery -// ReSharper restore RedundantExplicitArrayCreation -// ReSharper restore SuggestUseVarKeywordEvident diff --git a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection.cs b/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection.cs deleted file mode 100644 index 477a4a5f06..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection.cs +++ /dev/null @@ -1,399 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using GitHub.Api; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using GitHub.VisualStudio.Base; -using GitHub.VisualStudio.Helpers; -using GitHub.VisualStudio.UI.Views; -using Microsoft.TeamFoundation.Controls; -using Microsoft.TeamFoundation.MVVM; -using Microsoft.VisualStudio; -using NullGuard; -using ReactiveUI; - -namespace GitHub.VisualStudio.TeamExplorer.Connect -{ - public class GitHubConnectSection : TeamExplorerSectionBase, IGitHubConnectSection - { - readonly int sectionIndex; - bool isCloning; - bool isCreating; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - SectionStateTracker sectionTracker; - - protected GitHubConnectContent View - { - get { return SectionContent as GitHubConnectContent; } - set { SectionContent = value; } - } - - [AllowNull] - public IConnection SectionConnection { [return:AllowNull] get; set; } - - bool loggedIn; - bool LoggedIn - { - get { return loggedIn; } - set { - loggedIn = ShowLogout = value; - ShowLogin = !value; - } - } - - bool showLogin; - public bool ShowLogin - { - get { return showLogin; } - set { showLogin = value; this.RaisePropertyChange(); } - } - - bool showLogout; - public bool ShowLogout - { - get { return showLogout; } - set { showLogout = value; this.RaisePropertyChange(); } - } - - IReactiveDerivedList<ISimpleRepositoryModel> repositories; - [AllowNull] - public IReactiveDerivedList<ISimpleRepositoryModel> Repositories - { - [return:AllowNull] - get { return repositories; } - set { repositories = value; this.RaisePropertyChange(); } - } - - ISimpleRepositoryModel selectedRepository; - [AllowNull] - public ISimpleRepositoryModel SelectedRepository - { - [return: AllowNull] - get { return selectedRepository; } - set { selectedRepository = value; this.RaisePropertyChange(); } - } - - internal ITeamExplorerServiceHolder Holder => holder; - - public GitHubConnectSection(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, IConnectionManager manager, int index) - : base(apiFactory, holder, manager) - { - Title = "GitHub"; - IsEnabled = true; - IsVisible = false; - IsExpanded = true; - LoggedIn = false; - - sectionIndex = index; - - connectionManager.Connections.CollectionChanged += RefreshConnections; - PropertyChanged += OnPropertyChange; - UpdateConnection(); - } - - void RefreshConnections(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - if (connectionManager.Connections.Count > sectionIndex) - Refresh(connectionManager.Connections[sectionIndex]); - break; - case NotifyCollectionChangedAction.Remove: - Refresh(connectionManager.Connections.Count <= sectionIndex - ? null - : connectionManager.Connections[sectionIndex]); - break; - } - } - - protected void Refresh(IConnection connection) - { - if (connection == null) - { - LoggedIn = false; - IsVisible = false; - SectionConnection = null; - if (Repositories != null) - Repositories.CollectionChanged -= UpdateRepositoryList; - Repositories = null; - - if (sectionIndex == 0 && ServiceProvider != null) - { - var section = ServiceProvider.GetSection(TeamExplorerInvitationBase.TeamExplorerInvitationSectionGuid); - IsVisible = !(section?.IsVisible ?? true); // only show this when the invitation section is hidden. When in doubt, don't show it. - if (section != null) - section.PropertyChanged += (s, p) => - { - if (p.PropertyName == "IsVisible") - IsVisible = LoggedIn || !((ITeamExplorerSection)s).IsVisible; - }; - } - } - else - { - if (connection != SectionConnection) - { - SectionConnection = connection; - Repositories = SectionConnection.Repositories.CreateDerivedCollection(x => x, - orderer: OrderedComparer<ISimpleRepositoryModel>.OrderBy(x => x.Name).Compare); - Repositories.CollectionChanged += UpdateRepositoryList; - Title = connection.HostAddress.Title; - IsVisible = true; - LoggedIn = true; - if (ServiceProvider != null) - RefreshRepositories(); - } - } - } - - public override void Refresh() - { - UpdateConnection(); - base.Refresh(); - } - - public override void Initialize(IServiceProvider serviceProvider) - { - base.Initialize(serviceProvider); - UpdateConnection(); - - // watch for new repos added to the local repo list - var section = ServiceProvider.GetSection(TeamExplorerConnectionsSectionId); - if (section != null) - sectionTracker = new SectionStateTracker(section, RefreshRepositories); - } - - void UpdateConnection() - { - Refresh(connectionManager.Connections.Count > sectionIndex - ? connectionManager.Connections[sectionIndex] - : SectionConnection); - } - - void OnPropertyChange(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == "IsVisible" && IsVisible && View == null) - View = new GitHubConnectContent { DataContext = this }; - } - - async void UpdateRepositoryList(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Add) - { - // if we're cloning or creating, only one repo will be added to the list - // so we can handle just one new entry separately - if (isCloning || isCreating) - { - var newrepo = e.NewItems.Cast<ISimpleRepositoryModel>().First(); - - SelectedRepository = newrepo; - if (isCreating) - HandleCreatedRepo(newrepo); - else - HandleClonedRepo(newrepo); - - isCreating = isCloning = false; - var repo = await ApiFactory.Create(newrepo.CloneUrl).GetRepository(); - newrepo.SetIcon(repo.Private, repo.Fork); - } - // looks like it's just a refresh with new stuff on the list, update the icons - else - { - e.NewItems - .Cast<ISimpleRepositoryModel>() - .ForEach(async r => - { - if (Equals(Holder.ActiveRepo, r)) - SelectedRepository = r; - var repo = await ApiFactory.Create(r.CloneUrl).GetRepository(); - r.SetIcon(repo.Private, repo.Fork); - }); - } - } - } - - void HandleCreatedRepo(ISimpleRepositoryModel newrepo) - { - var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCreated, newrepo.Name, newrepo.CloneUrl); - msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); - ShowNotification(newrepo, msg); - } - - void HandleClonedRepo(ISimpleRepositoryModel newrepo) - { - var msg = string.Format(CultureInfo.CurrentUICulture, Constants.Notification_RepoCloned, newrepo.Name, newrepo.CloneUrl); - if (newrepo.HasCommits() && newrepo.MightContainSolution()) - msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_OpenProject, newrepo.LocalPath); - else - msg += " " + string.Format(CultureInfo.CurrentUICulture, Constants.Notification_CreateNewProject, newrepo.LocalPath); - ShowNotification(newrepo, msg); - } - - void ShowNotification(ISimpleRepositoryModel newrepo, string msg) - { - var vsservices = ServiceProvider.GetExportedValue<IVSServices>(); - vsservices.ClearNotifications(); - vsservices.ShowMessage( - msg, - new RelayCommand(o => - { - var str = o.ToString(); - /* the prefix is the action to perform: - * u: launch browser with url - * c: launch create new project dialog - * o: launch open existing project dialog - */ - var prefix = str.Substring(0, 2); - if (prefix == "u:") - OpenInBrowser(ServiceProvider.TryGetService<IVisualStudioBrowser>(), new Uri(str.Substring(2))); - else if (prefix == "o:") - { - if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(str.Substring(2), 1))) - ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); - } - else if (prefix == "c:") - { - vsservices.SetDefaultProjectPath(newrepo.LocalPath); - if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().CreateNewProjectViaDlg(null, null, 0))) - ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); - } - }) - ); -#if DEBUG - VsOutputLogger.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} Notification", DateTime.Now)); -#endif - } - - void RefreshRepositories() - { - connectionManager.RefreshRepositories(); - RaisePropertyChanged("Repositories"); // trigger a re-check of the visibility of the listview based on item count - } - - public void DoCreate() - { - StartFlow(UIControllerFlow.Create); - } - - public void DoClone() - { - StartFlow(UIControllerFlow.Clone); - } - - public void SignOut() - { - SectionConnection.Logout(); - } - - public void Login() - { - StartFlow(UIControllerFlow.Authentication); - } - - public bool OpenRepository() - { - var old = Repositories.FirstOrDefault(x => x.Equals(Holder.ActiveRepo)); - // open the solution selection dialog when the user wants to switch to a different repo - // since there's no other way of changing the source control context in VS - if (!Equals(SelectedRepository, old)) - { - if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(SelectedRepository.LocalPath, 1))) - { - ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); - return true; - } - else - { - SelectedRepository = old; - return false; - } - } - return false; - } - - void StartFlow(UIControllerFlow controllerFlow) - { - var uiProvider = ServiceProvider.GetExportedValue<IUIProvider>(); - uiProvider.GitServiceProvider = ServiceProvider; - var ret = uiProvider.SetupUI(controllerFlow, SectionConnection); - ret.Subscribe(c => - { - if (c.IsViewType(UIViewType.Clone)) - isCloning = true; - else if (c.IsViewType(UIViewType.Create)) - isCreating = true; - }); - uiProvider.RunUI(); - } - - bool disposed; - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (!disposed) - { - connectionManager.Connections.CollectionChanged -= RefreshConnections; - if (Repositories != null) - Repositories.CollectionChanged -= UpdateRepositoryList; - disposed = true; - } - } - base.Dispose(disposing); - } - - - class SectionStateTracker - { - enum SectionState - { - Idle, - Busy, - Refreshing - } - - readonly Stateless.StateMachine<SectionState, string> machine; - readonly ITeamExplorerSection section; - - public SectionStateTracker(ITeamExplorerSection section, Action onRefreshed) - { - this.section = section; - machine = new Stateless.StateMachine<SectionState, string>(SectionState.Idle); - machine.Configure(SectionState.Idle) - .PermitIf("IsBusy", SectionState.Busy, () => this.section.IsBusy) - .IgnoreIf("IsBusy", () => !this.section.IsBusy); - machine.Configure(SectionState.Busy) - .Permit("Title", SectionState.Refreshing) - .PermitIf("IsBusy", SectionState.Idle, () => !this.section.IsBusy) - .IgnoreIf("IsBusy", () => this.section.IsBusy); - machine.Configure(SectionState.Refreshing) - .Ignore("Title") - .PermitIf("IsBusy", SectionState.Idle, () => !this.section.IsBusy) - .IgnoreIf("IsBusy", () => this.section.IsBusy) - .OnExit(onRefreshed); - - section.PropertyChanged += TrackState; - } -#if DEBUG - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] -#endif - void TrackState(object sender, PropertyChangedEventArgs e) - { - if (machine.PermittedTriggers.Contains(e.PropertyName)) - { -#if DEBUG - VsOutputLogger.WriteLine(String.Format(CultureInfo.InvariantCulture, "{3} {0} title:{1} busy:{2}", e.PropertyName, ((ITeamExplorerSection)sender).Title, ((ITeamExplorerSection)sender).IsBusy, DateTime.Now)); -#endif - machine.Fire(e.PropertyName); - } - } - } - } -} diff --git a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection0.cs b/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection0.cs deleted file mode 100644 index 50ca7ea2fd..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection0.cs +++ /dev/null @@ -1,21 +0,0 @@ -using GitHub.Api; -using GitHub.Models; -using GitHub.Services; -using Microsoft.TeamFoundation.Controls; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.TeamExplorer.Connect -{ - [TeamExplorerSection(GitHubConnectSection0Id, TeamExplorerPageIds.Connect, 10)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class GitHubConnectSection0 : GitHubConnectSection - { - public const string GitHubConnectSection0Id = "519B47D3-F2A9-4E19-8491-8C9FA25ABE90"; - - [ImportingConstructor] - public GitHubConnectSection0(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, IConnectionManager manager) - : base(apiFactory, holder, manager, 0) - { - } - } -} diff --git a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection1.cs b/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection1.cs deleted file mode 100644 index c764d5a63a..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubConnectSection1.cs +++ /dev/null @@ -1,21 +0,0 @@ -using GitHub.Api; -using GitHub.Models; -using GitHub.Services; -using Microsoft.TeamFoundation.Controls; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.TeamExplorer.Connect -{ - [TeamExplorerSection(GitHubConnectSection1Id, TeamExplorerPageIds.Connect, 10)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class GitHubConnectSection1 : GitHubConnectSection - { - public const string GitHubConnectSection1Id = "519B47D3-F2A9-4E19-8491-8C9FA25ABE91"; - - [ImportingConstructor] - public GitHubConnectSection1(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, IConnectionManager manager) - : base(apiFactory, holder, manager, 1) - { - } - } -} diff --git a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs b/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs deleted file mode 100644 index 53a1275333..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs +++ /dev/null @@ -1,79 +0,0 @@ -using GitHub.Info; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using GitHub.VisualStudio.Base; -using GitHub.Extensions; -using Microsoft.TeamFoundation.Controls; -using Microsoft.VisualStudio.PlatformUI; -using System; -using System.ComponentModel.Composition; -using System.Windows; -using System.Windows.Media; - -namespace GitHub.VisualStudio.TeamExplorer.Connect -{ - [TeamExplorerServiceInvitation(GitHubInvitationSectionId, GitHubInvitationSectionPriority)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class GitHubInvitationSection : TeamExplorerInvitationBase - { - public const string GitHubInvitationSectionId = "C2443FCC-6D62-4D31-B08A-C4DE70109C7F"; - public const int GitHubInvitationSectionPriority = 100; - readonly Lazy<IVisualStudioBrowser> lazyBrowser; - - [ImportingConstructor] - public GitHubInvitationSection(IConnectionManager cm, Lazy<IVisualStudioBrowser> browser) - { - lazyBrowser = browser; - CanConnect = true; - CanSignUp = true; - ConnectLabel = Resources.GitHubInvitationSectionConnectLabel; - SignUpLabel = Resources.SignUpLink; - Name = "GitHub"; - Provider = "GitHub, Inc."; - Description = Resources.BlurbText; - OnThemeChanged(); - VSColorTheme.ThemeChanged += _ => - { - OnThemeChanged(); - }; - - IsVisible = cm.Connections.Count == 0; - - cm.Connections.CollectionChanged += (s, e) => IsVisible = cm.Connections.Count == 0; - } - - public override void Connect() - { - StartFlow(UIControllerFlow.Authentication); - base.Connect(); - } - - public override void SignUp() - { - OpenInBrowser(lazyBrowser, GitHubUrls.Plans); - } - - void StartFlow(UIControllerFlow controllerFlow) - { - var uiProvider = ServiceProvider.GetExportedValue<IUIProvider>(); - uiProvider.RunUI(controllerFlow, null); - } - - void OnThemeChanged() - { - try - { - var color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey); - var brightness = color.GetBrightness(); - var dark = brightness > 0.5f; - - Icon = SharedResources.GetDrawingForIcon(Octicon.mark_github, dark ? Helpers.Colors.DarkThemeNavigationItem : Helpers.Colors.LightThemeNavigationItem, dark ? "dark" : "light"); - } - catch (ArgumentNullException) - { - // This throws in the unit test runner. - } - } - } -} diff --git a/src/GitHub.VisualStudio/TeamExplorer/Home/GitHubHomeSection.cs b/src/GitHub.VisualStudio/TeamExplorer/Home/GitHubHomeSection.cs deleted file mode 100644 index c4787056db..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Home/GitHubHomeSection.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using GitHub.UI; -using GitHub.VisualStudio.Base; -using GitHub.VisualStudio.Helpers; -using GitHub.VisualStudio.UI.Views; -using Microsoft.TeamFoundation.Controls; -using GitHub.Services; -using GitHub.Api; -using GitHub.Primitives; -using System.Threading.Tasks; -using System.Diagnostics; - -namespace GitHub.VisualStudio.TeamExplorer.Home -{ - [TeamExplorerSection(GitHubHomeSectionId, TeamExplorerPageIds.Home, 10)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class GitHubHomeSection : TeamExplorerSectionBase, IGitHubHomeSection - { - public const string GitHubHomeSectionId = "72008232-2104-4FA0-A189-61B0C6F91198"; - - [ImportingConstructor] - public GitHubHomeSection(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder) - : base(apiFactory, holder) - { - Title = "GitHub"; - View = new GitHubHomeContent(); - View.DataContext = this; - } - - protected async override void RepoChanged() - { - IsVisible = false; - - base.RepoChanged(); - - IsVisible = await IsAGitHubRepo(); - - if (IsVisible) - { - RepoName = ActiveRepoName; - RepoUrl = ActiveRepoUri.ToString(); - Icon = GetIcon(false, true, false); - Debug.Assert(SimpleApiClient != null, - "If we're in this block, simpleApiClient cannot be null. It was created by UpdateStatus"); - var repo = await SimpleApiClient.GetRepository(); - Icon = GetIcon(repo.Private, true, repo.Fork); - } - } - - public override async void Refresh() - { - IsVisible = await IsAGitHubRepo(); - base.Refresh(); - } - - static Octicon GetIcon(bool isPrivate, bool isHosted, bool isFork) - { - return !isHosted ? Octicon.device_desktop - : isPrivate ? Octicon.@lock - : isFork ? Octicon.repo_forked : Octicon.repo; - } - - protected GitHubHomeContent View - { - get { return SectionContent as GitHubHomeContent; } - set { SectionContent = value; } - } - - string repoName = String.Empty; - public string RepoName - { - get { return repoName; } - set { repoName = value; this.RaisePropertyChange(); } - } - - string repoUrl = String.Empty; - public string RepoUrl - { - get { return repoUrl; } - set { repoUrl = value; this.RaisePropertyChange(); } - } - - Octicon icon; - public Octicon Icon - { - get { return icon; } - set { icon = value; this.RaisePropertyChange(); } - } - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/TeamExplorer/Home/PullRequestsNavigationItem.cs b/src/GitHub.VisualStudio/TeamExplorer/Home/PullRequestsNavigationItem.cs deleted file mode 100644 index a58c02d868..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Home/PullRequestsNavigationItem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.Base; -using GitHub.VisualStudio.Helpers; -using Microsoft.TeamFoundation.Controls; -using GitHub.UI; - -namespace GitHub.VisualStudio.TeamExplorer.Home -{ - [TeamExplorerNavigationItem(PullRequestsNavigationItemId, NavigationItemPriority.PullRequests)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class PullRequestsNavigationItem : TeamExplorerNavigationItemBase - { - public const string PullRequestsNavigationItemId = "5245767A-B657-4F8E-BFEE-F04159F1DDA3"; - - readonly Lazy<IVisualStudioBrowser> browser; - - [ImportingConstructor] - public PullRequestsNavigationItem(ISimpleApiClientFactory apiFactory, Lazy<IVisualStudioBrowser> browser, - ITeamExplorerServiceHolder holder) - : base(apiFactory, holder, Octicon.git_pull_request) - { - this.browser = browser; - Text = Resources.PullRequestsNavigationItemText; - ArgbColor = Colors.RedNavigationItem.ToInt32(); - } - - public override void Execute() - { - OpenInBrowser(browser, "pulls"); - base.Execute(); - } - } -} diff --git a/src/GitHub.VisualStudio/TeamExplorer/Sync/EnsureLoggedInSectionSync.cs b/src/GitHub.VisualStudio/TeamExplorer/Sync/EnsureLoggedInSectionSync.cs deleted file mode 100644 index dbeb9f5fcb..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Sync/EnsureLoggedInSectionSync.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.Composition; -using GitHub.Api; -using GitHub.Models; -using GitHub.Services; -using Microsoft.TeamFoundation.Controls; - -namespace GitHub.VisualStudio.TeamExplorer.Sync -{ - [TeamExplorerSection(SyncLoginSectionId, TeamExplorerPageIds.GitCommits, 10)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class EnsureLoggedInSectionSync : EnsureLoggedInSection - { - public const string SyncLoginSectionId = "C5975729-3CF1-47B4-AE92-C2934906CDDA"; - - [ImportingConstructor] - public EnsureLoggedInSectionSync(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, - IConnectionManager cm, IRepositoryHosts hosts, IVSServices vsServices) - : base(apiFactory, holder, cm, hosts, vsServices) - {} - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/TeamExplorer/Sync/GitHubPublishSection.cs b/src/GitHub.VisualStudio/TeamExplorer/Sync/GitHubPublishSection.cs deleted file mode 100644 index b07a09ced4..0000000000 --- a/src/GitHub.VisualStudio/TeamExplorer/Sync/GitHubPublishSection.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using GitHub.UI; -using GitHub.VisualStudio.Base; -using GitHub.VisualStudio.Helpers; -using GitHub.VisualStudio.UI.Views; -using Microsoft.TeamFoundation.Controls; -using GitHub.Models; -using GitHub.Services; -using GitHub.Info; -using ReactiveUI; -using System.Reactive.Linq; -using GitHub.Extensions; -using GitHub.Api; -using GitHub.VisualStudio.TeamExplorer; - -namespace GitHub.VisualStudio.TeamExplorer.Sync -{ - [TeamExplorerSection(GitHubPublishSectionId, TeamExplorerPageIds.GitCommits, 10)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class GitHubPublishSection : TeamExplorerSectionBase, IGitHubInvitationSection - { - public const string GitHubPublishSectionId = "92655B52-360D-4BF5-95C5-D9E9E596AC76"; - - readonly Lazy<IVisualStudioBrowser> lazyBrowser; - readonly IRepositoryHosts hosts; - IDisposable disposable; - bool loggedIn; - - [ImportingConstructor] - public GitHubPublishSection(ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, - IConnectionManager cm, Lazy<IVisualStudioBrowser> browser, - IRepositoryHosts hosts) - : base(apiFactory, holder, cm) - { - - lazyBrowser = browser; - this.hosts = hosts; - Title = Resources.GitHubPublishSectionTitle; - Name = "GitHub"; - Provider = "GitHub, Inc"; - Description = Resources.BlurbText; - ShowLogin = false; - ShowSignup = false; - ShowGetStarted = false; - IsVisible = false; - IsExpanded = true; - var view = new GitHubInvitationContent(); - SectionContent = view; - view.DataContext = this; - } - - async void Setup() - { - if (ActiveRepo != null && ActiveRepoUri == null) - { - IsVisible = true; - ShowGetStarted = true; - loggedIn = await connectionManager.IsLoggedIn(hosts); - ShowLogin = !loggedIn; - ShowSignup = !loggedIn; - } - else - IsVisible = false; - } - - public override void Initialize(IServiceProvider serviceProvider) - { - base.Initialize(serviceProvider); - Setup(); - } - - protected override void RepoChanged() - { - base.RepoChanged(); - Setup(); - } - - public async void Connect() - { - loggedIn = await connectionManager.IsLoggedIn(hosts); - if (loggedIn) - ShowPublish(); - else - Login(); - } - - public void SignUp() - { - OpenInBrowser(lazyBrowser, GitHubUrls.Plans); - } - - public void Login() - { - StartFlow(UIControllerFlow.Authentication); - } - - void StartFlow(UIControllerFlow controllerFlow) - { - var uiProvider = ServiceProvider.GetExportedValue<IUIProvider>(); - var ret = uiProvider.SetupUI(controllerFlow, null); - ret.Subscribe((c) => { }, async () => - { - loggedIn = await connectionManager.IsLoggedIn(hosts); - if (loggedIn) - ShowPublish(); - }); - uiProvider.RunUI(); - } - - void ShowPublish() - { - IsBusy = true; - var uiProvider = ServiceProvider.GetExportedValue<IUIProvider>(); - var factory = uiProvider.GetService<IExportFactoryProvider>(); - var uiflow = factory.UIControllerFactory.CreateExport(); - disposable = uiflow; - var ui = uiflow.Value; - var creation = ui.SelectFlow(UIControllerFlow.Publish); - ui.ListenToCompletionState().Subscribe(done => - { - IsVisible = false; - ServiceProvider.TryGetService<ITeamExplorer>()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); - }); - - creation.Subscribe(c => - { - SectionContent = c; - c.DataContext = this; - ((IView)c).IsBusy.Subscribe(x => IsBusy = x); - }); - ui.Start(null); - } - - bool disposed; - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (!disposed) - { - if (disposable != null) - disposable.Dispose(); - disposed = true; - } - } - base.Dispose(disposing); - } - - string name = String.Empty; - public string Name - { - get { return name; } - set { name = value; this.RaisePropertyChange(); } - } - - string provider = String.Empty; - public string Provider - { - get { return provider; } - set { provider = value; this.RaisePropertyChange(); } - } - - string description = String.Empty; - public string Description - { - get { return description; } - set { description = value; this.RaisePropertyChange(); } - } - - bool showLogin; - public bool ShowLogin - { - get { return showLogin; } - set { showLogin = value; this.RaisePropertyChange(); } - } - - - bool showSignup; - public bool ShowSignup - { - get { return showSignup; } - set { showSignup = value; this.RaisePropertyChange(); } - } - - bool showGetStarted; - public bool ShowGetStarted - { - get { return showGetStarted; } - set { showGetStarted = value; this.RaisePropertyChange(); } - } - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/UI/GitHubPane.cs b/src/GitHub.VisualStudio/UI/GitHubPane.cs index 0f0cce969e..498d5cf4ca 100644 --- a/src/GitHub.VisualStudio/UI/GitHubPane.cs +++ b/src/GitHub.VisualStudio/UI/GitHubPane.cs @@ -1,20 +1,21 @@ using System; -using System.Runtime.InteropServices; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; +using System.Threading.Tasks; using System.ComponentModel.Design; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using System.Windows; using System.Windows.Controls; +using GitHub.Factories; using GitHub.Services; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.UI; -using GitHub.VisualStudio.Base; -using GitHub.ViewModels; -using System.Diagnostics; +using GitHub.ViewModels.GitHubPane; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using ReactiveUI; namespace GitHub.VisualStudio.UI { - /// <summary> /// This class implements the tool window exposed by this package and hosts a user control. /// </summary> @@ -26,36 +27,189 @@ namespace GitHub.VisualStudio.UI /// implementation of the IVsUIElementPane interface. /// </para> /// </remarks> - [Guid("6b0fdc0a-f28e-47a0-8eed-cc296beff6d2")] + [Guid(GitHubPaneGuid)] public class GitHubPane : ToolWindowPane { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public const string GitHubPaneGuid = "6b0fdc0a-f28e-47a0-8eed-cc296beff6d2"; + + JoinableTask<IGitHubPaneViewModel> viewModelTask; + + IDisposable viewSubscription; + ContentPresenter contentPresenter; + + public FrameworkElement View + { + get { return contentPresenter.Content as FrameworkElement; } + set + { + viewSubscription?.Dispose(); + viewSubscription = null; + + contentPresenter.Content = value; + + viewSubscription = value.WhenAnyValue(x => x.DataContext) + .SelectMany(x => + { + var pane = x as IGitHubPaneViewModel; + return pane?.WhenAnyValue(p => p.IsSearchEnabled, p => p.SearchQuery) + ?? Observable.Return(Tuple.Create<bool, string>(false, null)); + }) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => UpdateSearchHost(x.Item1, x.Item2)); + } + } + + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public GitHubPane() : base(null) { Caption = "GitHub"; + Content = contentPresenter = new ContentPresenter(); - // Set the image that will appear on the tab of the window frame - // when docked with an other window - // The resource ID correspond to the one defined in the resx file - // while the Index is the offset in the bitmap strip. Each image in - // the strip being 16x16. - BitmapResourceID = 301; - BitmapIndex = 1; - ToolBar = new CommandID(GuidList.guidGitHubToolbarCmdSet, PkgCmdIDList.idGitHubToolbar); + BitmapImageMoniker = new Microsoft.VisualStudio.Imaging.Interop.ImageMoniker() + { + Guid = Guids.guidImageMoniker, + Id = 1 + }; + ToolBar = new CommandID(Guids.guidGitHubToolbarCmdSet, PkgCmdIDList.idGitHubToolbar); ToolBarLocation = (int)VSTWT_LOCATION.VSTWT_TOP; - - var factory = this.GetExportedValue<IUIProvider>().GetService<IExportFactoryProvider>(); - // placeholder logic to load the view until the UIController is able to do it for us - Content = factory.GetView(Exports.UIViewType.GitHubPane).Value; - (Content as UserControl).DataContext = factory.GetViewModel(Exports.UIViewType.GitHubPane).Value; } + public override bool SearchEnabled => true; + protected override void Initialize() { - base.Initialize(); - var vm = (Content as IView).ViewModel as IServiceProviderAware; - Debug.Assert(vm != null); - vm?.Initialize(this); + // Using JoinableTaskFactory from parent AsyncPackage. That way if VS shuts down before this + // work is done, we won't risk crashing due to arbitrary work going on in background threads. + var asyncPackage = (AsyncPackage)Package; + viewModelTask = asyncPackage.JoinableTaskFactory.RunAsync(() => InitializeAsync(asyncPackage)); + } + + public Task<IGitHubPaneViewModel> GetViewModelAsync() => viewModelTask.JoinAsync(); + + async Task<IGitHubPaneViewModel> InitializeAsync(AsyncPackage asyncPackage) + { + try + { + ShowInitializing(); + + // Allow MEF to initialize its cache asynchronously + var provider = (IGitHubServiceProvider)await asyncPackage.GetServiceAsync(typeof(IGitHubServiceProvider)); + + var teServiceHolder = provider.GetService<ITeamExplorerServiceHolder>(); + teServiceHolder.ServiceProvider = this; + + var factory = provider.GetService<IViewViewModelFactory>(); + var viewModel = provider.ExportProvider.GetExportedValue<IGitHubPaneViewModel>(); + await viewModel.InitializeAsync(this); + + View = factory.CreateView<IGitHubPaneViewModel>(); + View.DataContext = viewModel; + + return viewModel; + } + catch (Exception e) + { + ShowError(e); + throw; + } + } + + [SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Justification = "WTF CA, I'm overriding!")] + public override IVsSearchTask CreateSearch(uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback) + { + var pane = View?.DataContext as IGitHubPaneViewModel; + + if (pane != null) + { + return new SearchTask(pane, dwCookie, pSearchQuery, pSearchCallback); + } + + return null; + } + + public override void ClearSearch() + { + var pane = View?.DataContext as IGitHubPaneViewModel; + + if (pane != null) + { + pane.SearchQuery = null; + } + } + + public override void OnToolWindowCreated() + { + base.OnToolWindowCreated(); + + Marshal.ThrowExceptionForHR(((IVsWindowFrame)Frame)?.SetProperty( + (int)__VSFPROPID5.VSFPROPID_SearchPlacement, + __VSSEARCHPLACEMENT.SP_STRETCH) ?? 0); + + var pane = View?.DataContext as IGitHubPaneViewModel; + UpdateSearchHost(pane?.IsSearchEnabled ?? false, pane?.SearchQuery); + } + + void ShowInitializing() + { + // This page is intentionally left blank. + } + + void ShowError(Exception e) + { + View = new TextBox + { + Text = e.ToString(), + IsReadOnly = true, + }; + } + + void UpdateSearchHost(bool enabled, string query) + { + if (SearchHost != null) + { + SearchHost.IsEnabled = enabled; + + if (SearchHost.SearchQuery?.SearchString != query) + { + SearchHost.SearchAsync(query != null ? new SearchQuery(query) : null); + } + } + } + + class SearchTask : VsSearchTask + { + readonly IGitHubPaneViewModel viewModel; + + public SearchTask( + IGitHubPaneViewModel viewModel, + uint dwCookie, + IVsSearchQuery pSearchQuery, + IVsSearchCallback pSearchCallback) + : base(dwCookie, pSearchQuery, pSearchCallback) + { + this.viewModel = viewModel; + } + + protected override void OnStartSearch() + { + viewModel.SearchQuery = SearchQuery.SearchString; + base.OnStartSearch(); + } + + protected override void OnStopSearch() => viewModel.SearchQuery = null; + } + + class SearchQuery : IVsSearchQuery + { + public SearchQuery(string query) + { + SearchString = query; + } + + public uint ParseError => 0; + public string SearchString { get; } + + public uint GetTokens(uint dwMaxTokens, IVsSearchToken[] rgpSearchTokens) => 0; } } } diff --git a/src/GitHub.VisualStudio/UI/Settings/OptionsControl.xaml b/src/GitHub.VisualStudio/UI/Settings/OptionsControl.xaml new file mode 100644 index 0000000000..385b8d205f --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Settings/OptionsControl.xaml @@ -0,0 +1,124 @@ +<UserControl x:Class="GitHub.VisualStudio.UI.OptionsControl" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:GitHub.VisualStudio.UI" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + d:DesignHeight="200" + mc:Ignorable="d"> + <UserControl.Resources> + <BorderGapMaskConverter x:Key="BorderGapMaskConverter" /> + <Style x:Key="GroupBoxFlat" TargetType="{x:Type GroupBox}"> + <Setter Property="BorderBrush" Value="#D5DFE5" /> + <Setter Property="BorderThickness" Value="1" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type GroupBox}"> + <Grid SnapsToDevicePixels="true"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="6" /> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="6" /> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="6" /> + </Grid.RowDefinitions> + <Border x:Name="Header" + Grid.Row="0" + Grid.RowSpan="2" + Grid.Column="1" + Padding="3,1,3,0"> + <ContentPresenter ContentSource="Header" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> + </Border> + <Border Grid.Row="1" + Grid.RowSpan="3" + Grid.ColumnSpan="4" + BorderBrush="Transparent" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="4"> + <Border.OpacityMask> + <MultiBinding Converter="{StaticResource BorderGapMaskConverter}" ConverterParameter="7"> + <Binding ElementName="Header" Path="ActualWidth" /> + <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" /> + <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" /> + </MultiBinding> + </Border.OpacityMask> + <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3" /> + </Border> + <ContentPresenter Grid.Row="2" + Grid.Column="1" + Grid.ColumnSpan="2" + Margin="{TemplateBinding Padding}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> + </Grid> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </UserControl.Resources> + <StackPanel> + <GroupBox + Margin="8,0,8,0" + VerticalAlignment="Top" + BorderBrush="LightGray" + BorderThickness="1" + Padding="6 8 6 8" + Style="{DynamicResource GroupBoxFlat}"> + <GroupBox.Header> + <Run Text="{x:Static prop:Resources.Options_PrivacyTitle}" /> + </GroupBox.Header> + <DockPanel> + <WrapPanel DockPanel.Dock="Top"> + <CheckBox x:Name="chkMetrics" VerticalAlignment="Center" Content="{x:Static prop:Resources.Options_MetricsLabel}" /> + </WrapPanel> + <TextBlock> + <Hyperlink ToolTip="https://visualstudio.github.com/samples.html" NavigateUri="https://visualstudio.github.com/samples.html" RequestNavigate="Hyperlink_RequestNavigate"><TextBlock Text="{x:Static prop:Resources.learnMoreLink}"/></Hyperlink> + </TextBlock> + </DockPanel> + </GroupBox> + <GroupBox + Margin="8,0,8,0" + VerticalAlignment="Top" + BorderBrush="LightGray" + BorderThickness="1" + Padding="6 8 6 8" + Style="{DynamicResource GroupBoxFlat}"> + <GroupBox.Header> + <Run Text="{x:Static prop:Resources.Options_DebuggingTitle}" /> + </GroupBox.Header> + <DockPanel> + <CheckBox x:Name="chkEnableTraceLogging" VerticalAlignment="Center" Content="{x:Static prop:Resources.Options_EnableTraceLoggingText}" /> + </DockPanel> + </GroupBox> + <GroupBox + Margin="8,0,8,0" + VerticalAlignment="Top" + BorderBrush="LightGray" + BorderThickness="1" + Padding="6 8 6 8" + Style="{DynamicResource GroupBoxFlat}"> + <GroupBox.Header> + <Run Text="{x:Static prop:Resources.Options_ExperimentalTitle}" /> + </GroupBox.Header> + <DockPanel> + <WrapPanel DockPanel.Dock="Top"> + <CheckBox x:Name="chkEditorComments" VerticalAlignment="Center" Content="{x:Static prop:Resources.Options_EditorCommentsLabel}" /> + </WrapPanel> + <WrapPanel DockPanel.Dock="Top"> + <CheckBox x:Name="chkForkButton" VerticalAlignment="Center" Content="{x:Static prop:Resources.Options_ForkButtonLabel}" /> + </WrapPanel> + <TextBlock DockPanel.Dock="Bottom" TextWrapping="Wrap"> + <LineBreak /> + <Italic> + <Run Text="{x:Static prop:Resources.Options_ExperimentalNote}" /> + </Italic> + </TextBlock> + </DockPanel> + </GroupBox> + </StackPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio/UI/Settings/OptionsControl.xaml.cs b/src/GitHub.VisualStudio/UI/Settings/OptionsControl.xaml.cs new file mode 100644 index 0000000000..98d58bb649 --- /dev/null +++ b/src/GitHub.VisualStudio/UI/Settings/OptionsControl.xaml.cs @@ -0,0 +1,46 @@ +using System.Windows.Controls; +using GitHub.Services; + +namespace GitHub.VisualStudio.UI +{ + /// <summary> + /// Interaction logic for OptionsPage.xaml + /// </summary> + public partial class OptionsControl : UserControl + { + public OptionsControl() + { + InitializeComponent(); + } + + public bool CollectMetrics + { + get { return chkMetrics.IsChecked ?? false; } + set { chkMetrics.IsChecked = value; } + } + + public bool EnableTraceLogging + { + get { return chkEnableTraceLogging.IsChecked ?? false; } + set { chkEnableTraceLogging.IsChecked = value; } + } + + public bool EditorComments + { + get { return chkEditorComments.IsChecked ?? false; } + set { chkEditorComments.IsChecked = value; } + } + + public bool ForkButton + { + get { return chkForkButton.IsChecked ?? false; } + set { chkForkButton.IsChecked = value; } + } + + private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) + { + var browser = VisualStudio.Services.DefaultExportProvider.GetExportedValue<IVisualStudioBrowser>(); + browser?.OpenUrl(e.Uri); + } + } +} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/ActionLinkButton.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/ActionLinkButton.xaml deleted file mode 100644 index 1a507c1d34..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/ActionLinkButton.xaml +++ /dev/null @@ -1,58 +0,0 @@ -<ResourceDictionary - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" - xmlns:vsui="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" -> - <Style x:Key="ActionLinkButton" TargetType="{x:Type Button}"> - <Setter Property="Background" Value="Transparent"/> - <Setter Property="Focusable" Value="True" /> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.ControlLinkText}" /> - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="Button"> - <TextBlock> - <Hyperlink Foreground="{TemplateBinding Foreground}" > - <Hyperlink.Resources> - <Style TargetType="{x:Type Hyperlink}"> - <Style.Triggers> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="UIElement.IsMouseOver" Value="false"/> - <Condition Property="IsEnabled" Value="true"/> - </MultiTrigger.Conditions> - <MultiTrigger.Setters> - <Setter Property="TextDecorations" Value="None"/> - <Setter Property="FrameworkElement.Cursor" Value="None" /> - </MultiTrigger.Setters> - </MultiTrigger> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="IsMouseOver" Value="true"/> - <Condition Property="IsEnabled" Value="true"/> - </MultiTrigger.Conditions> - <MultiTrigger.Setters> - <Setter Property="TextDecorations" Value="Underline"/> - <Setter Property="FrameworkElement.Cursor" Value="Hand" /> - </MultiTrigger.Setters> - </MultiTrigger> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="IsEnabled" Value="false"/> - </MultiTrigger.Conditions> - <MultiTrigger.Setters> - <Setter Property="TextDecorations" Value="None"/> - </MultiTrigger.Setters> - </MultiTrigger> - </Style.Triggers> - </Style> - </Hyperlink.Resources> - <Run Text="{TemplateBinding Content}" /> - </Hyperlink> - </TextBlock> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - -</ResourceDictionary> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml deleted file mode 100644 index 6ad835a9ec..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml +++ /dev/null @@ -1,183 +0,0 @@ -<local:GenericLoginControl x:Class="GitHub.VisualStudio.UI.Views.Controls.LoginControl" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - 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:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:cache="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - mc:Ignorable="d" - d:DesignWidth="414" - d:DesignHeight="440" - Style="{DynamicResource DialogUserControl}"> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </UserControl.Resources> - - <DockPanel Style="{DynamicResource DialogContainerDockPanel}"> - <DockPanel.Resources> - <Style TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}"> - <Setter Property="Margin" Value="0" /> - </Style> - <Style TargetType="{x:Type Border}" x:Key="LoginButtonBorder"> - <Setter Property="Margin" Value="0,0,0,15" /> - <Setter Property="HorizontalAlignment" Value="Center" /> - </Style> - <Style TargetType="{x:Type ui:SecurePasswordBox}" BasedOn="{StaticResource RoundedPromptTextBox}"> - <Setter Property="Margin" Value="0" /> - </Style> - </DockPanel.Resources> - <StackPanel DockPanel.Dock="Top"> - <ui:OcticonImage Icon="logo_github" Style="{DynamicResource GitHubLogo}" /> - - <TextBlock - x:Name="loginLabelPrefix" - HorizontalAlignment="Center" - Margin="0,0,0,10" - Style="{DynamicResource GitHubH1TextBlock}" - IsHitTestVisible="False" - Text="{x:Static prop:Resources.LoginLink}" /> - - <ui:HorizontalShadowDivider /> - </StackPanel> - <TabControl - x:Name="hostTabControl" - Margin="30,0" - Style="{StaticResource LightModalViewTabControl}" - FocusManager.IsFocusScope="True" - FocusVisualStyle="{x:Null}" - helpers:AccessKeysManagerScoping.IsEnabled="True"> - <TabControl.Resources> - <Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource LightModalViewTabItem}" /> - <Style TargetType="{x:Type TabPanel}"> - <Setter Property="HorizontalAlignment" Value="Center" /> - </Style> - <Style x:Key="TabDockPanel" TargetType="{x:Type DockPanel}"> - <Setter Property="Margin" Value="0,20,0,0" /> - <Setter Property="LastChildFill" Value="True" /> - </Style> - <Style x:Key="FormFieldStackPanel" TargetType="{x:Type StackPanel}"> - <Setter Property="Margin" Value="0" /> - </Style> - <Style - TargetType="{x:Type uirx:ErrorMessageDisplay}" - BasedOn="{StaticResource ErrorMessageStyle}"> - <Setter Property="Margin" Value="0,10" /> - </Style> - </TabControl.Resources> - - <TabItem x:Name="dotComTab" Header="GitHub"> - <DockPanel Style="{StaticResource TabDockPanel}"> - <StackPanel DockPanel.Dock="Bottom" Margin="0"> - <Border Style="{StaticResource LoginButtonBorder}"> - <ui:OcticonCircleButton x:Name="dotComLogInButton" Icon="check" Content="{x:Static prop:Resources.LoginLink}" IsDefault="True" /> - </Border> - - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0" Text="{x:Static prop:Resources.dontHaveAnAccountText}"> - <Hyperlink x:Name="pricingLink" ToolTip="https://github.com/pricing"><TextBlock Text="{x:Static prop:Resources.SignUpLink}"/></Hyperlink> - </TextBlock> - </StackPanel> - - <StackPanel x:Name="dotComloginControlsPanel"> - - <StackPanel Style="{StaticResource FormFieldStackPanel}"> - <ui:PromptTextBox - x:Name="dotComUserNameOrEmail" - PromptText="{x:Static prop:Resources.UserNameOrEmailPromptText}" - Margin="0,0,0,10" /> - - <ui:SecurePasswordBox x:Name="dotComPassword" PromptText="{x:Static prop:Resources.PasswordPrompt}" /> - - <uirx:ErrorMessageDisplay - x:Name="dotComLoginFailedMessage" - Message="{x:Static prop:Resources.LoginFailedMessage}"> - <TextBlock TextWrapping="Wrap" Text="{x:Static prop:Resources.LoginFailedText}"> - <Hyperlink x:Name="dotComForgotPasswordLink"><TextBlock Text="{x:Static prop:Resources.ForgotPasswordLink}"/></Hyperlink> - </TextBlock> - </uirx:ErrorMessageDisplay> - - <uirx:ErrorMessageDisplay - x:Name="dotComConnectionFailedMessage" - Message="{x:Static prop:Resources.dotComConnectionFailedMessageMessage}"> - <TextBlock TextWrapping="Wrap" Text="{x:Static prop:Resources.couldNotConnectToGitHubText}"/> - </uirx:ErrorMessageDisplay> - - <uirx:ValidationMessage - x:Name="dotComUserNameOrEmailValidationMessage" - ValidatesControl="{Binding ElementName=dotComUserNameOrEmail}" /> - - <uirx:ValidationMessage - x:Name="dotComPasswordValidationMessage" - ValidatesControl="{Binding ElementName=dotComPassword}" /> - - </StackPanel> - </StackPanel> - </DockPanel> - </TabItem> - - <TabItem x:Name="enterpriseTab" Header="GitHub Enterprise" Margin="10,0,-10,0"> - <DockPanel Style="{StaticResource TabDockPanel}"> - <StackPanel DockPanel.Dock="Bottom"> - <Border Style="{StaticResource LoginButtonBorder}"> - <ui:OcticonCircleButton x:Name="enterpriseLogInButton" Icon="check" Content="{x:Static prop:Resources.LoginLink}" IsDefault="True" /> - </Border> - - <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0" Text="{x:Static prop:Resources.dontHaveGitHubEnterpriseText}"> - <Hyperlink x:Name="learnMoreLink" ToolTip="enterprise.github.com"><TextBlock Text="{x:Static prop:Resources.learnMoreLink}"></TextBlock></Hyperlink> - </TextBlock> - </StackPanel> - - <StackPanel - x:Name="enterpriseloginControlsPanel" - Style="{StaticResource FormFieldStackPanel}"> - - <ui:PromptTextBox - x:Name="enterpriseUserNameOrEmail" - PromptText="{x:Static prop:Resources.UserNameOrEmailPromptText}" - Margin="0,0,0,10" /> - - <ui:SecurePasswordBox x:Name="enterprisePassword" PromptText="{x:Static prop:Resources.PasswordPrompt}" Margin="0,0,0,10" /> - - <ui:PromptTextBox x:Name="enterpriseUrl" PromptText="{x:Static prop:Resources.enterpriseUrlPromptText}" /> - - <uirx:ValidationMessage - x:Name="enterpriseUserNameOrEmailValidationMessage" - ValidatesControl="{Binding ElementName=enterpriseUserNameOrEmail}"/> - - <uirx:ValidationMessage - x:Name="enterprisePasswordValidationMessage" - ValidatesControl="{Binding ElementName=enterprisePassword}" /> - - <uirx:ValidationMessage - x:Name="enterpriseUrlValidationMessage" - ValidatesControl="{Binding ElementName=enterpriseUrl}" - TextChangeThrottle="1.0"/> - - <uirx:ErrorMessageDisplay - x:Name="enterpriseLoginFailedMessage" - Message="{x:Static prop:Resources.LoginFailedMessage}"> - <TextBlock TextWrapping="Wrap" Text="{x:Static prop:Resources.LoginFailedText}"> - <Hyperlink x:Name="enterpriseForgotPasswordLink"><TextBlock Text="{x:Static prop:Resources.ForgotPasswordLink}"/></Hyperlink> - </TextBlock> - </uirx:ErrorMessageDisplay> - - <uirx:ErrorMessageDisplay - x:Name="enterpriseConnectingFailedMessage" - Message="{x:Static prop:Resources.enterpriseConnectingFailedMessage}"> - <TextBlock TextWrapping="Wrap" Text="{x:Static prop:Resources.couldNotConnectToTheServerText}"/> - </uirx:ErrorMessageDisplay> - </StackPanel> - </DockPanel> - </TabItem> - </TabControl> - </DockPanel> -</local:GenericLoginControl> diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs deleted file mode 100644 index d65ea8b937..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/LoginControl.xaml.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Windows; -using System.Windows.Input; -using GitHub.Controls; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.UI; -using GitHub.ViewModels; -using ReactiveUI; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.UI.Views.Controls -{ - public class GenericLoginControl : SimpleViewUserControl<ILoginControlViewModel, LoginControl> - { } - - /// <summary> - /// Interaction logic for LoginControl.xaml - /// </summary> - [ExportView(ViewType=UIViewType.Login)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class LoginControl : GenericLoginControl - { - public LoginControl() - { - InitializeComponent(); - - DataContextChanged += (s, e) => ViewModel = (ILoginControlViewModel)e.NewValue; - - this.WhenActivated(d => - { - SetupDotComBindings(d); - SetupEnterpriseBindings(d); - SetupSelectedAndVisibleTabBindings(d); - ViewModel.AuthenticationResults - .Subscribe(ret => - { - if (ret == Authentication.AuthenticationResult.Success) - { - NotifyDone(); - } - }); - }); - IsVisibleChanged += (s, e) => - { - if (IsVisible) - dotComUserNameOrEmail.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); - }; - } - - void SetupDotComBindings(Action<IDisposable> d) - { - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.IsLoggingIn, x => x.dotComloginControlsPanel.IsEnabled, x => x == false)); - - d(this.Bind(ViewModel, vm => vm.GitHubLogin.UsernameOrEmail, x => x.dotComUserNameOrEmail.Text)); - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.UsernameOrEmailValidator, v => v.dotComUserNameOrEmailValidationMessage.ReactiveValidator)); - - d(this.BindPassword(ViewModel, vm => vm.GitHubLogin.Password, v => v.dotComPassword.Text, dotComPassword)); - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.PasswordValidator, v => v.dotComPasswordValidationMessage.ReactiveValidator)); - - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.Login, v => v.dotComLogInButton.Command)); - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.IsLoggingIn, v => v.dotComLogInButton.ShowSpinner)); - - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.NavigateForgotPassword, v => v.dotComForgotPasswordLink.Command)); - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.NavigatePricing, v => v.pricingLink.Command)); - - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.ShowLogInFailedError, v => v.dotComLoginFailedMessage.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.LoginFailedMessage, v => v.dotComLoginFailedMessage.Message)); - d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.ShowConnectingToHostFailed, v => v.dotComConnectionFailedMessage.Visibility)); - } - - void SetupEnterpriseBindings(Action<IDisposable> d) - { - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.IsLoggingIn, x => x.enterpriseloginControlsPanel.IsEnabled, x => x == false)); - - d(this.Bind(ViewModel, vm => vm.EnterpriseLogin.UsernameOrEmail, x => x.enterpriseUserNameOrEmail.Text)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.UsernameOrEmailValidator, v => v.enterpriseUserNameOrEmailValidationMessage.ReactiveValidator)); - - d(this.BindPassword(ViewModel, vm => vm.EnterpriseLogin.Password, v => v.enterprisePassword.Text, enterprisePassword)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.PasswordValidator, v => v.enterprisePasswordValidationMessage.ReactiveValidator)); - - d(this.Bind(ViewModel, vm => vm.EnterpriseLogin.EnterpriseUrl, v => v.enterpriseUrl.Text)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.EnterpriseUrlValidator, v => v.enterpriseUrlValidationMessage.ReactiveValidator)); - - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.Login, v => v.enterpriseLogInButton.Command)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.IsLoggingIn, v => v.enterpriseLogInButton.ShowSpinner)); - - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.NavigateForgotPassword, v => v.enterpriseForgotPasswordLink.Command)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.NavigateLearnMore, v => v.learnMoreLink.Command)); - - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.ShowLogInFailedError, v => v.enterpriseLoginFailedMessage.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.LoginFailedMessage, v => v.enterpriseLoginFailedMessage.Message)); - d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.ShowConnectingToHostFailed, v => v.enterpriseConnectingFailedMessage.Visibility)); - } - - void SetupSelectedAndVisibleTabBindings(Action<IDisposable> d) - { - d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) - .Select(x => x == LoginMode.DotComOrEnterprise || x == LoginMode.DotComOnly) - .BindTo(this, v => v.dotComTab.IsEnabled)); - - d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) - .Select(x => x == LoginMode.DotComOrEnterprise || x == LoginMode.EnterpriseOnly) - .BindTo(this, v => v.enterpriseTab.IsEnabled)); - - d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) - .Select(x => x == LoginMode.DotComOrEnterprise || x == LoginMode.DotComOnly) - .Where(x => x == true) - .BindTo(this, v => v.dotComTab.IsSelected)); - - d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) - .Select(x => x == LoginMode.EnterpriseOnly) - .Where(x => x == true) - .BindTo(this, v => v.enterpriseTab.IsSelected)); - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCloneControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCloneControl.xaml deleted file mode 100644 index a5783008d1..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCloneControl.xaml +++ /dev/null @@ -1,234 +0,0 @@ -<local:GenericRepositoryCloneControl x:Class="GitHub.VisualStudio.UI.Views.Controls.RepositoryCloneControl" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - 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:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - mc:Ignorable="d" - d:DesignWidth="414" - d:DesignHeight="440" - Background="Transparent"> - <d:DesignProperties.DataContext> - <Binding> - <Binding.Source> - <sampleData:RepositoryCloneViewModelDesigner /> - </Binding.Source> - </Binding> - </d:DesignProperties.DataContext> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </UserControl.Resources> - - <DockPanel LastChildFill="True"> - <DockPanel.Resources> - <Style x:Key="repositoryBorderStyle" - TargetType="Border"> - <Setter Property="BorderBrush" - Value="#EAEAEA" /> - <Setter Property="BorderThickness" - Value="0,0,0,1" /> - <Setter Property="VerticalAlignment" - Value="Center" /> - <Setter Property="Height" - Value="30" /> - <Setter Property="Margin" - Value="0" /> - </Style> - </DockPanel.Resources> - - <ui:FilterTextBox x:Name="filterText" - DockPanel.Dock="Top" - PromptText="{x:Static prop:Resources.filterTextPromptText}" - Margin="10" /> - - <ui:GitHubProgressBar x:Name="loadingProgressBar" - DockPanel.Dock="Top" - Foreground="{DynamicResource GitHubAccentBrush}" - Style="{DynamicResource GitHubProgressBar}" - HorizontalContentAlignment="Stretch" - Visibility="Visible" - IsIndeterminate="True" - Margin="0" /> - - <StackPanel x:Name="loadingFailedPanel" - DockPanel.Dock="Top" - VerticalAlignment="Center" - HorizontalAlignment="Center" - Margin="0,4,0,4"> - <uirx:ErrorMessageDisplay x:Name="loadingFailedMessage" - Icon="stop" - Visibility="Visible" - Margin="8,0" - Content="{x:Static prop:Resources.loadingFailedMessageContent}" - Message="{x:Static prop:Resources.loadingFailedMessageMessage}" /> - </StackPanel> - - <ui:OcticonCircleButton DockPanel.Dock="Bottom" - IsDefault="True" - Margin="12" - x:Name="cloneButton" - HorizontalAlignment="Center" - Icon="check"> - <TextBlock Text="{x:Static prop:Resources.CloneLink}" /> - </ui:OcticonCircleButton> - - <Border DockPanel.Dock="Bottom" - Style="{StaticResource repositoryBorderStyle}"> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <Label Grid.Column="0" - Grid.Row="3" - Margin="4,0" - Target="{Binding ElementName=clonePath}" - Content="{x:Static prop:Resources.pathText}" /> - - <ui:PromptTextBox x:Name="clonePath" - Grid.Column="1" - Margin="0,2" /> - <uirx:ValidationMessage x:Name="pathValidationMessage" - Grid.Column="1" - Grid.Row="4" - ValidatesControl="{Binding ElementName=clonePath}" /> - <Button x:Name="browsePathButton" - Grid.Column="2" - VerticalContentAlignment="Center" - Padding="0" - Margin="4,0,4,0" - Style="{StaticResource GitHubBlueLinkButton}" - Content="{x:Static prop:Resources.browsePathButtonContent}" /> - </Grid> - </Border> - - <Border x:Name="repositoriesListPane" - FocusManager.IsFocusScope="True" - FocusVisualStyle="{x:Null}" - BorderBrush="#EAEAEA" - BorderThickness="0,1" - Margin="0"> - <Border.Resources> - <Style TargetType="{x:Type ui:TrimmedTextBlock}" - BasedOn="{StaticResource GitHubTextBlock}"> - <Style.Triggers> - <Trigger Property="IsTextTrimmed" - Value="True"> - <Setter Property="ToolTip" - Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" /> - </Trigger> - </Style.Triggers> - </Style> - <Style x:Key="cloneRepoHeaderStyle" - TargetType="TextBlock"> - <Setter Property="Foreground" - Value="{DynamicResource GHTextBrush}" /> - <Setter Property="Margin" - Value="0,6,12,6" /> - </Style> - </Border.Resources> - <Grid Margin="0"> - <ListBox x:Name="repositoryList" - HorizontalContentAlignment="Stretch" - ItemsSource="{Binding FilteredRepositories}" - Style="{DynamicResource LightListBox}"> - <ListBox.GroupStyle> - <GroupStyle HidesIfEmpty="true"> - <GroupStyle.ContainerStyle> - <Style TargetType="{x:Type GroupItem}"> - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="{x:Type GroupItem}"> - <StackPanel Orientation="Vertical" - Margin="0"> - <Border Background="#F8F8F8" - Style="{StaticResource repositoryBorderStyle}"> - <StackPanel Orientation="Horizontal" - VerticalAlignment="Center" - Margin="0"> - <Image x:Name="avatar" - Width="16" - Height="16" - Margin="10,0,6,0" - RenderOptions.BitmapScalingMode="HighQuality" - Source="{Binding Items[0].Owner.Avatar}" /> - <TextBlock Text="{Binding Path=Name}" - Style="{StaticResource cloneRepoHeaderStyle}" /> - </StackPanel> - </Border> - <ItemsPresenter Margin="0" /> - </StackPanel> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - </GroupStyle.ContainerStyle> - </GroupStyle> - </ListBox.GroupStyle> - <ListBox.ItemTemplate> - <DataTemplate> - <Border Style="{StaticResource repositoryBorderStyle}"> - <StackPanel Orientation="Horizontal" - VerticalAlignment="Center" - Margin="0"> - <ui:OcticonImage x:Name="iconPath" - Width="16" - Height="16" - Margin="32,0,6,0" - VerticalAlignment="Center" - Icon="{Binding Icon}" - Foreground="#D0D0D0" /> - - <ui:TrimmedTextBlock x:Name="label" - VerticalAlignment="Center" - Text="{Binding Name}" - Foreground="#666" - TextTrimming="CharacterEllipsis" /> - </StackPanel> - </Border> - <DataTemplate.Triggers> - <MultiDataTrigger> - <MultiDataTrigger.Conditions> - <Condition Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" - Value="True" /> - <Condition Binding="{Binding Path=(Selector.IsSelectionActive), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" - Value="True" /> - </MultiDataTrigger.Conditions> - <MultiDataTrigger.Setters> - <Setter Property="Foreground" - Value="White" - TargetName="iconPath" /> - <Setter Property="Foreground" - Value="White" - TargetName="label" /> - </MultiDataTrigger.Setters> - </MultiDataTrigger> - </DataTemplate.Triggers> - </DataTemplate> - </ListBox.ItemTemplate> - </ListBox> - <StackPanel x:Name="noRepositoriesMessage" - VerticalAlignment="Center" - HorizontalAlignment="Center" - Margin="0,-70,0,0"> - <TextBlock HorizontalAlignment="Center" - Style="{DynamicResource GitHubH2TextBlock}" - Text="{x:Static prop:Resources.noRepositoriesMessageText}" /> - </StackPanel> - </Grid> - </Border> - </DockPanel> -</local:GenericRepositoryCloneControl> diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCloneControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCloneControl.xaml.cs deleted file mode 100644 index 73938e4b5a..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCloneControl.xaml.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Reactive.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Input; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.UI; -using GitHub.ViewModels; -using NullGuard; -using ReactiveUI; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.UI.Views.Controls -{ - public class GenericRepositoryCloneControl : SimpleViewUserControl<IRepositoryCloneViewModel, RepositoryCloneControl> - {} - - /// <summary> - /// Interaction logic for CloneRepoControl.xaml - /// </summary> - [ExportView(ViewType=UIViewType.Clone)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class RepositoryCloneControl : GenericRepositoryCloneControl - { - public RepositoryCloneControl() - { - InitializeComponent(); - - DataContextChanged += (s, e) => ViewModel = e.NewValue as IRepositoryCloneViewModel; - - this.WhenActivated(d => - { - d(this.OneWayBind(ViewModel, vm => vm.IsLoading, v => v.loadingProgressBar.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.LoadingFailed, v => v.loadingFailedPanel.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.NoRepositoriesFound, v => v.noRepositoriesMessage.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.FilteredRepositories, v => v.repositoryList.ItemsSource, CreateRepositoryListCollectionView)); - d(this.Bind(ViewModel, vm => vm.SelectedRepository, v => v.repositoryList.SelectedItem)); - d(this.Bind(ViewModel, vm => vm.BaseRepositoryPath, v => v.clonePath.Text)); - d(this.OneWayBind(ViewModel, vm => vm.BaseRepositoryPathValidator, v => v.pathValidationMessage.ReactiveValidator)); - d(this.BindCommand(ViewModel, vm => vm.BrowseForDirectory, v => v.browsePathButton)); - d(this.BindCommand(ViewModel, vm => vm.CloneCommand, v => v.cloneButton)); - d(this.OneWayBind(ViewModel, vm => vm.FilterTextIsEnabled, v => v.filterText.IsEnabled)); - d(this.Bind(ViewModel, vm => vm.FilterText, v => v.filterText.Text)); - d(repositoryList.Events().MouseDoubleClick.InvokeCommand(this, x => x.ViewModel.CloneCommand)); - d(ViewModel.LoadRepositoriesCommand.ExecuteAsync().Subscribe()); - ViewModel.CloneCommand.Subscribe(_ => NotifyDone()); - }); - IsVisibleChanged += (s, e) => - { - if (IsVisible) - this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); - }; - } - - static ListCollectionView CreateRepositoryListCollectionView(IEnumerable<IRepositoryModel> repositories) - { - var view = new ListCollectionView((IList)repositories); - Debug.Assert(view.GroupDescriptions != null, "view.GroupDescriptions is null"); - view.GroupDescriptions.Add(new RepositoryGroupDescription()); - return view; - } - - class RepositoryGroupDescription : GroupDescription - { - public override object GroupNameFromItem(object item, int level, System.Globalization.CultureInfo culture) - { - return ((IRepositoryModel)item).Owner.Login; - } - - public override bool NamesMatch(object groupName, object itemName) - { - return string.Equals((string)groupName, (string)itemName); - } - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml deleted file mode 100644 index 4a5894e94c..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml +++ /dev/null @@ -1,234 +0,0 @@ -<local:GenericRepositoryCreationControl x:Class="GitHub.VisualStudio.UI.Views.Controls.RepositoryCreationControl" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - mc:Ignorable="d" - d:DesignWidth="414" - d:DesignHeight="440" - Style="{DynamicResource DialogUserControl}"> - <d:DesignProperties.DataContext> - <Binding> - <Binding.Source> - <sampleData:RepositoryCreationViewModelDesigner /> - </Binding.Source> - </Binding> - </d:DesignProperties.DataContext> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </UserControl.Resources> - - <DockPanel Style="{DynamicResource DialogContainerDockPanel}"> - <ui:OcticonImage Icon="logo_github" Style="{DynamicResource GitHubLogo}" Margin="0,-20,0,-10" Panel.ZIndex="100" DockPanel.Dock="Top" /> - - <ui:OcticonCircleButton - DockPanel.Dock="Bottom" - IsDefault="True" - Margin="0" - x:Name="createRepositoryButton" - HorizontalAlignment="Center" - Icon="check"> - <TextBlock Text="{x:Static prop:Resources.CreateLink}"/> - </ui:OcticonCircleButton> - - <StackPanel> - <StackPanel.Resources> - <Style TargetType="{x:Type uirx:UserErrorMessages}" BasedOn="{StaticResource {x:Type uirx:UserErrorMessages}}"> - <Setter Property="IconMargin" Value="-1,2,7,0" /> - <Setter Property="ErrorMessageFontWeight" Value="Normal" /> - <Setter Property="Icon" Value="stop" /> - <Setter Property="Margin" Value="0,5,0,0"/> - </Style> - </StackPanel.Resources> - <ui:HorizontalShadowDivider /> - <Grid - FocusManager.IsFocusScope="True" - x:Name="loginStackPanel" - Margin="30,-10,30,10" - FocusVisualStyle="{x:Null}"> - - <Grid.Resources> - <Style TargetType="{x:Type TextBlock}"> - <Setter Property="Margin" Value="0,3,0,3" /> - </Style> - - <Style TargetType="{x:Type Label}"> - <Setter Property="Foreground" Value="{StaticResource GHTextBrush}" /> - <Setter Property="VerticalAlignment" Value="Center" /> - <Setter Property="HorizontalAlignment" Value="Right" /> - <Setter Property="Margin" Value="0,0,10,0" /> - <Setter Property="Padding" Value="0" /> - </Style> - - <Style TargetType="{x:Type ui:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}"> - <Setter Property="Margin" Value="0,5" /> - </Style> - - <Style TargetType="{x:Type Button}"> - <Setter Property="Padding" Value="0" /> - <Setter Property="VerticalContentAlignment" Value="Center" /> - </Style> - </Grid.Resources> - - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - - <Grid.RowDefinitions> - <RowDefinition Height="35" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="35" /> - <RowDefinition Height="35" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - - <Label Grid.Column="0" Grid.Row="0" Target="{Binding ElementName=nameText}" Content="{x:Static prop:Resources.nameText}"/> - <ui:PromptTextBox x:Name="nameText" Grid.Column="1" Grid.Row="0" MaxLength="{x:Static GitHub:Constants.MaxRepositoryNameLength}"/> - - <StackPanel Grid.Column="1" Grid.Row="1"> - <uirx:ValidationMessage - x:Name="nameValidationMessage" - ValidatesControl="{Binding ElementName=nameText}" /> - <uirx:ValidationMessage - x:Name="safeRepositoryNameWarning" - ValidatesControl="{Binding ElementName=nameText}" - Style="{DynamicResource InlineValidationMessage}" - ErrorAdornerTemplate="None" - Icon="alert" - Fill="#f39c12" /> - </StackPanel> - - <Label Grid.Column="0" Grid.Row="2" Target="{Binding ElementName=description}" Content="{x:Static prop:Resources.descriptionText}"/> - <ui:PromptTextBox x:Name="description" Grid.Column="1" Grid.Row="2"/> - - <Label Grid.Column="0" Grid.Row="3" Target="{Binding ElementName=localPathText}" Content="{x:Static prop:Resources.localPathText}"/> - <Grid Grid.Column="1" Grid.Row="3"> - - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - - <ui:PromptTextBox x:Name="localPathText" Grid.Column="0" Grid.Row="0" /> - <Button - x:Name="browsePathButton" - Grid.Column="1" - VerticalContentAlignment="Center" - Grid.Row="0" - Padding="0" - Margin="3,0,0,0" - Style="{StaticResource GitHubBlueLinkButton}" Content="{x:Static prop:Resources.browsePathButtonContent}"/> - </Grid> - - <uirx:ValidationMessage - x:Name="pathValidationMessage" - Grid.Column="1" - Grid.Row="4" - ValidatesControl="{Binding ElementName=localPathText}" /> - - <Label Grid.Column="0" Grid.Row="5" Target="{Binding ElementName=ignoreTemplateList}" Content="{x:Static prop:Resources.ignoreTemplateListText}"/> - <uirx:FilteredComboBox - x:Name="ignoreTemplateList" - Grid.Column="1" - Grid.Row="5" - Style="{StaticResource GitHubFilterComboBox}"> - <ComboBox.ItemTemplate> - <DataTemplate> - <TextBlock - x:Name="itemName" - Text="{Binding Name}" - FontWeight="{Binding Recommended, Converter={ui:BooleanToFontWeightConverter}}" /> - <DataTemplate.Triggers> - <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBoxItem}}" Value="{x:Null}"> - <Setter TargetName="itemName" Property="FontWeight" Value="Normal" /> - </DataTrigger> - </DataTemplate.Triggers> - </DataTemplate> - </ComboBox.ItemTemplate> - </uirx:FilteredComboBox> - - <Label Grid.Column="0" Grid.Row="6" Target="{Binding ElementName=licenseList}" Content="{x:Static prop:Resources.licenseListText}"/> - <uirx:FilteredComboBox - x:Name="licenseList" - Grid.Column="1" - Grid.Row="6" - Style="{StaticResource GitHubFilterComboBox}"> - <ComboBox.ItemTemplate> - <DataTemplate> - <TextBlock - x:Name="itemName" - Text="{Binding Name}" - FontWeight="{Binding Recommended, Converter={ui:BooleanToFontWeightConverter}}" /> - <DataTemplate.Triggers> - <DataTrigger - Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBoxItem}}" - Value="{x:Null}"> - <Setter TargetName="itemName" Property="FontWeight" Value="Normal" /> - </DataTrigger> - </DataTemplate.Triggers> - </DataTemplate> - </ComboBox.ItemTemplate> - </uirx:FilteredComboBox> - - <ui:HorizontalShadowDivider Grid.Row="7" Grid.ColumnSpan="2" Margin="0,12" /> - - <ui:GitHubComboBox - x:Name="accountsComboBox" - Grid.Column="1" - Grid.Row="8" - Margin="0,0,0,8" - ItemsSource="{Binding Accounts}" - Style="{StaticResource GitHubComboBox}"> - <ComboBox.ItemTemplate> - <DataTemplate> - <StackPanel Orientation="Horizontal"> - <Image - Source="{Binding Avatar}" - Width="16" - Height="16" - RenderOptions.BitmapScalingMode="HighQuality" - Margin="0,0,8,0" /> - <TextBlock Text="{Binding Login}"/> - </StackPanel> - </DataTemplate> - </ComboBox.ItemTemplate> - </ui:GitHubComboBox> - - <CheckBox - x:Name="makePrivate" - Grid.Column="1" - Grid.Row="9" - Content="{x:Static prop:Resources.makePrivateContent}" - Style="{DynamicResource BlueRoundedCheckBox}" - Padding="8,0,0,0" /> - </Grid> - - <uirx:UserErrorMessages - x:Name="userErrorMessages" - HorizontalAlignment="Center" - HorizontalContentAlignment="Stretch" - Margin="0" /> - </StackPanel> - - </DockPanel> -</local:GenericRepositoryCreationControl> diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml.cs deleted file mode 100644 index 7e6d50929c..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryCreationControl.xaml.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Windows; -using System.Windows.Input; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Extensions.Reactive; -using GitHub.UI; -using GitHub.UserErrors; -using GitHub.ViewModels; -using NullGuard; -using ReactiveUI; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.UI.Views.Controls -{ - public class GenericRepositoryCreationControl : SimpleViewUserControl<IRepositoryCreationViewModel, RepositoryCreationControl> - { } - - /// <summary> - /// Interaction logic for CloneRepoControl.xaml - /// </summary> - [ExportView(ViewType=UIViewType.Create)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class RepositoryCreationControl : GenericRepositoryCreationControl - { - public RepositoryCreationControl() - { - InitializeComponent(); - - var clearErrorWhenChanged = this.WhenAny( - x => x.ViewModel.RepositoryName, - x => x.ViewModel.Description, - x => x.ViewModel.BaseRepositoryPath, - (x, y, z) => new { x, y, z }) - .WhereNotNull() - .Select(x => true); - - this.WhenActivated(d => - { - d(this.OneWayBind(ViewModel, vm => vm.GitIgnoreTemplates, v => v.ignoreTemplateList.ItemsSource)); - d(this.Bind(ViewModel, vm => vm.SelectedGitIgnoreTemplate, v => v.ignoreTemplateList.SelectedItem)); - d(this.OneWayBind(ViewModel, vm => vm.Licenses, v => v.licenseList.ItemsSource)); - d(this.Bind(ViewModel, vm => vm.SelectedLicense, v => v.licenseList.SelectedItem)); - - d(this.Bind(ViewModel, vm => vm.RepositoryName, v => v.nameText.Text)); - d(this.OneWayBind(ViewModel, vm => vm.RepositoryNameValidator, v => v.nameValidationMessage.ReactiveValidator)); - d(this.OneWayBind(ViewModel, vm => vm.SafeRepositoryNameWarningValidator, v => v.safeRepositoryNameWarning.ReactiveValidator)); - - d(this.Bind(ViewModel, vm => vm.BaseRepositoryPath, v => v.localPathText.Text)); - d(this.OneWayBind(ViewModel, vm => vm.BaseRepositoryPathValidator, v => v.pathValidationMessage.ReactiveValidator)); - - d(this.Bind(ViewModel, vm => vm.Description, v => v.description.Text)); - d(this.Bind(ViewModel, vm => vm.KeepPrivate, v => v.makePrivate.IsChecked)); - d(this.OneWayBind(ViewModel, vm => vm.CanKeepPrivate, v => v.makePrivate.IsEnabled)); - - d(this.OneWayBind(ViewModel, vm => vm.Accounts, v => v.accountsComboBox.ItemsSource)); - d(this.Bind(ViewModel, vm => vm.SelectedAccount, v => v.accountsComboBox.SelectedItem)); - - 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)); - - d(this.OneWayBind(ViewModel, vm => vm.IsCreating, v => v.nameText.IsEnabled, x => x == false)); - d(this.OneWayBind(ViewModel, vm => vm.IsCreating, v => v.description.IsEnabled, x => x == false)); - d(this.OneWayBind(ViewModel, vm => vm.IsCreating, v => v.accountsComboBox.IsEnabled, x => x == false)); - - d(userErrorMessages.RegisterHandler<PublishRepositoryUserError>(clearErrorWhenChanged)); - - ViewModel.CreateRepository.Subscribe(_ => NotifyDone()); - }); - IsVisibleChanged += (s, e) => - { - if (IsVisible) - this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); - }; - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryPublishControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryPublishControl.xaml deleted file mode 100644 index bbde7ac7d0..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryPublishControl.xaml +++ /dev/null @@ -1,157 +0,0 @@ -<local:GenericRepositoryPublishControl x:Class="GitHub.VisualStudio.UI.Views.Controls.RepositoryPublishControl" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:converters="clr-namespace:GitHub.VisualStudio.Converters" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - mc:Ignorable="d" - d:DesignHeight="440" - d:DesignWidth="414" - Margin="0" - Background="Transparent"> - - <d:DesignProperties.DataContext> - <Binding> - <Binding.Source> - <sampleData:RepositoryPublishViewModelDesigner /> - </Binding.Source> - </Binding> - </d:DesignProperties.DataContext> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio;component/SharedDictionary.xaml" /> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource VSStyledComboBox}"> - <Setter Property="Margin" Value="0,0,0,8" /> - <Setter Property="HorizontalAlignment" Value="Stretch" /> - <Setter Property="Height" Value="23" /> - </Style> - <converters:CountToVisibilityConverter x:Key="CountToVisibilityConverter" /> - </ResourceDictionary> - </UserControl.Resources> - - <StackPanel Margin="8,6,18,0" Orientation="Vertical" Style="{DynamicResource DialogContainerStackPanel}"> - - <DockPanel VerticalAlignment="Top" Margin="0,0,0,13"> - <ui:OcticonImage - x:Name="octokit" - DockPanel.Dock="Left" - Height="32" - Width="32" - VerticalAlignment="Center" - Margin="0,6,8,0" - Icon="mark_github" - Foreground="{DynamicResource VsBrush.ToolWindowText}" /> - <Border Height="32" Margin="0,6,0,0" DockPanel.Dock="Right"> - <TextBlock - TextWrapping="Wrap" - VerticalAlignment="Center" - HorizontalAlignment="Stretch" - Text="{x:Static prop:Resources.RepoDoesNotHaveRemoteText}" - Foreground="{DynamicResource VsBrush.GrayText}" - TextTrimming="CharacterEllipsis" - ToolTip="{Binding Text, RelativeSource={RelativeSource Self}}"/> - </Border> - </DockPanel> - - <ComboBox x:Name="hostsComboBox"> - <ComboBox.ItemTemplate> - <DataTemplate> - <StackPanel Orientation="Horizontal"> - <TextBlock Text="{Binding HostAddress.Title}"/> - </StackPanel> - </DataTemplate> - </ComboBox.ItemTemplate> - </ComboBox> - - <ComboBox x:Name="accountsComboBox" ItemsSource="{Binding Accounts}"> - <ComboBox.ItemTemplate> - <DataTemplate> - <StackPanel Orientation="Horizontal"> - <Image - Source="{Binding Avatar}" - Width="15" - Height="15" - RenderOptions.BitmapScalingMode="HighQuality" - Margin="0,0,8,0" /> - <TextBlock Text="{Binding Login}"/> - </StackPanel> - </DataTemplate> - </ComboBox.ItemTemplate> - </ComboBox> - - <!-- TODO: Make this a user control --> - <Grid> - <TextBox x:Name="nameText" - Margin="0" - Height="23" - MaxLength="{x:Static GitHub:Constants.MaxRepositoryNameLength}" - Background="{DynamicResource VsBrush.SearchBoxBackground}" - Foreground="{DynamicResource VsBrush.WindowText}" /> - - <TextBlock Margin="7,4,0,0" - IsHitTestVisible="False" - Visibility="{Binding ElementName=nameText, Path=Text.Length, Converter={StaticResource CountToVisibilityConverter}}" - Text="{x:Static prop:Resources.RepoNameText}" - Foreground="{DynamicResource VsBrush.GrayText}" /> - </Grid> - - <!-- TODO: Make this a user control --> - <Grid> - <TextBox x:Name="description" - Height="52" - AcceptsReturn="True" - TextWrapping="WrapWithOverflow" - Margin="0,8,0,0" - Background="{DynamicResource VsBrush.SearchBoxBackground}" - Foreground="{DynamicResource VsBrush.WindowText}" /> - - <TextBlock Margin="7,12,0,0" - IsHitTestVisible="False" - Visibility="{Binding ElementName=description, Path=Text.Length, Converter={StaticResource CountToVisibilityConverter}}" - Text="{x:Static prop:Resources.descriptionOptionalText}" - Foreground="{DynamicResource VsBrush.GrayText}" /> - </Grid> - - <CheckBox x:Name="makePrivate" - Margin="0,8,0,0" - Style="{StaticResource VSStyledCheckBox}"> - <TextBlock TextWrapping="Wrap" - Foreground="{DynamicResource VsBrush.ToolWindowText}" - Background="{DynamicResource VsBrush.ToolWindowBackground}"> - <TextBlock.Resources> - <Style TargetType="{x:Type TextBlock}"> - <Style.Triggers> - <Trigger Property="IsEnabled" Value="False"> - <Setter Property="Opacity" Value="0.5" /> - </Trigger> - </Style.Triggers> - </Style> - </TextBlock.Resources> - <TextBlock.Inlines> - <Run Text="{x:Static prop:Resources.makePrivateContent}"/> - </TextBlock.Inlines> - </TextBlock> - </CheckBox> - - <Button - Style="{StaticResource VSStyledButton}" - IsDefault="True" - x:Name="publishRepositoryButton" - Margin="0,8,0,0" - HorizontalAlignment="Left"> - <TextBlock Text="{x:Static prop:Resources.publishText}"/> - </Button> - - </StackPanel> -</local:GenericRepositoryPublishControl> diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryPublishControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryPublishControl.xaml.cs deleted file mode 100644 index 3c55012e02..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/RepositoryPublishControl.xaml.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Windows; -using System.Windows.Input; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.UI; -using GitHub.ViewModels; -using NullGuard; -using ReactiveUI; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.UI.Views.Controls -{ - public class GenericRepositoryPublishControl : SimpleViewUserControl<IRepositoryPublishViewModel, RepositoryPublishControl> - { } - - [ExportView(ViewType=UIViewType.Publish)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class RepositoryPublishControl : GenericRepositoryPublishControl - { - public RepositoryPublishControl() - { - InitializeComponent(); - - this.WhenActivated(d => - { - d(this.OneWayBind(ViewModel, vm => vm.Connections, v => v.hostsComboBox.ItemsSource)); - d(this.OneWayBind(ViewModel, vm => vm.IsHostComboBoxVisible, v => v.hostsComboBox.Visibility)); - d(this.Bind(ViewModel, vm => vm.SelectedConnection, v => v.hostsComboBox.SelectedItem)); - - d(this.Bind(ViewModel, vm => vm.RepositoryName, v => v.nameText.Text)); - - d(this.Bind(ViewModel, vm => vm.Description, v => v.description.Text)); - d(this.Bind(ViewModel, vm => vm.KeepPrivate, v => v.makePrivate.IsChecked)); - d(this.OneWayBind(ViewModel, vm => vm.CanKeepPrivate, v => v.makePrivate.IsEnabled)); - - d(this.OneWayBind(ViewModel, vm => vm.Accounts, v => v.accountsComboBox.ItemsSource)); - d(this.Bind(ViewModel, vm => vm.SelectedAccount, v => v.accountsComboBox.SelectedItem)); - - d(this.BindCommand(ViewModel, vm => vm.PublishRepository, v => v.publishRepositoryButton)); - - d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.nameText.IsEnabled, x => x == false)); - d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.description.IsEnabled, x => x == false)); - d(this.OneWayBind(ViewModel, vm => vm.IsPublishing, v => v.accountsComboBox.IsEnabled, x => x == false)); - - ViewModel.PublishRepository.Subscribe(state => - { - if (state == ProgressState.Success) - NotifyDone(); - }); - - d(this.WhenAny(x => x.ViewModel.IsPublishing, x => x.Value) - .Subscribe(x => NotifyIsBusy(x))); - - nameText.Text = ViewModel.DefaultRepositoryName; - }); - IsVisibleChanged += (s, e) => - { - if (IsVisible) - this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); - }; - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml deleted file mode 100644 index e711b6546d..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml +++ /dev/null @@ -1,85 +0,0 @@ -<local:GenericTwoFactorControl x:Class="GitHub.VisualStudio.UI.Views.Controls.TwoFactorControl" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - 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:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:GitHub="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - mc:Ignorable="d" - d:DesignWidth="414" - d:DesignHeight="440" - Style="{DynamicResource DialogUserControl}"> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - <GitHub:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - </ResourceDictionary> - </UserControl.Resources> - - <StackPanel - Style="{DynamicResource DialogContainerStackPanel}" - helpers:AccessKeysManagerScoping.IsEnabled="True"> - <ui:OcticonImage Icon="logo_github" Style="{DynamicResource GitHubLogo}" /> - <WrapPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="0,0,0,12"> - <TextBlock - Text="{x:Static prop:Resources.twoFactorAuthText}" - Padding="0" - Margin="0,0,12,0" - Style="{DynamicResource GitHubH1TextBlock}" - Foreground="{DynamicResource GHTextBrush}" /> - </WrapPanel> - - <ui:HorizontalShadowDivider /> - - <TextBlock - Margin="30,0,30,16" - HorizontalAlignment="Center" - TextWrapping="Wrap" - Style="{DynamicResource GitHubDescriptionTextBlock}"> - <Run x:Name="description" Text="{x:Static prop:Resources.openTwoFactorAuthAppText}"/> - <Hyperlink x:Name="twoFactorReadMoreLink" ToolTip="https://help.github.com/articles/about-two-factor-authentication"><TextBlock Text="{x:Static prop:Resources.learnMoreLink}"/></Hyperlink> - </TextBlock> - <uirx:TwoFactorInput - x:Name="authenticationCode" - TabIndex="1" /> - <Grid Margin="0,12,0,0" Height="18"> - <uirx:ErrorMessageDisplay - x:Name="authenticationFailedLabel" - Message="{x:Static prop:Resources.authenticationFailedLabelMessage}" - Content="{x:Static prop:Resources.authenticationFailedLabelContent}" /> - <uirx:ErrorMessageDisplay - x:Name="authenticationSentLabel" - Icon="check" - IconFill="{DynamicResource GitHubAccentBrush}" - Message="{x:Static prop:Resources.authenticationSentLabelMessage}" - Content="{x:Static prop:Resources.authenticationSentLabelContent}" /> - </Grid> - <StackPanel - Orientation="Horizontal" - HorizontalAlignment="Center" - Margin="0,18,0,60"> - <ui:OcticonCircleButton - x:Name="okButton" - TabIndex="2" - Icon="check" - IsDefault="True" - Content="{x:Static prop:Resources.verifyText}" /> - <ui:OcticonLinkButton - x:Name="resendCodeButton" - TabIndex="3" - ToolTip="{x:Static prop:Resources.resendCodeButtonToolTip}" - FontSize="12" - Icon="sync" - Margin="18,0,0,0" - Content="{x:Static prop:Resources.resendCodeButtonContent}" /> - </StackPanel> - </StackPanel> -</local:GenericTwoFactorControl> diff --git a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs b/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs deleted file mode 100644 index c08e909872..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/Controls/TwoFactorControl.xaml.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Windows; -using GitHub.Exports; -using GitHub.Extensions.Reactive; -using GitHub.UI; -using GitHub.ViewModels; -using ReactiveUI; -using System.ComponentModel.Composition; - -namespace GitHub.VisualStudio.UI.Views.Controls -{ - public class GenericTwoFactorControl : SimpleViewUserControl<ITwoFactorDialogViewModel, TwoFactorControl> - { } - - /// <summary> - /// Interaction logic for PasswordView.xaml - /// </summary> - [ExportView(ViewType=UIViewType.TwoFactor)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class TwoFactorControl : GenericTwoFactorControl - { - public TwoFactorControl() - { - InitializeComponent(); - - DataContextChanged += (s, e) => ViewModel = (ITwoFactorDialogViewModel)e.NewValue; - - this.WhenActivated(d => - { - d(this.BindCommand(ViewModel, vm => vm.OkCommand, view => view.okButton)); - d(this.OneWayBind(ViewModel, vm => vm.IsBusy, view => view.okButton.ShowSpinner)); - d(this.BindCommand(ViewModel, vm => vm.ResendCodeCommand, view => view.resendCodeButton)); - - d(this.Bind(ViewModel, vm => vm.AuthenticationCode, view => view.authenticationCode.Text)); - d(this.OneWayBind(ViewModel, vm => vm.NavigateLearnMore, view => view.twoFactorReadMoreLink.Command)); - d(this.OneWayBind(ViewModel, vm => vm.IsAuthenticationCodeSent, - view => view.authenticationSentLabel.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.IsSms, view => view.resendCodeButton.Visibility)); - d(this.OneWayBind(ViewModel, vm => vm.Description, view => view.description.Text)); - d(this.OneWayBind(ViewModel, vm => vm.ShowErrorMessage, view => view.authenticationFailedLabel.Visibility)); - d(this.ViewModel.ResendCodeCommand.Subscribe(_ => SetFocus())); - d(this.ViewModel.OkCommand.Subscribe(_ => SetFocus())); - }); - IsVisibleChanged += (s, e) => - { - if (IsVisible) - { - SetFocus(); - } - }; - } - - void SetFocus() - { - authenticationCode.TryFocus().Subscribe(); - } - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml b/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml deleted file mode 100644 index 961fa56b2d..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/GitHubConnectContent.xaml +++ /dev/null @@ -1,226 +0,0 @@ -<UserControl x:Class="GitHub.VisualStudio.UI.Views.GitHubConnectContent" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - 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.UI.Views" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:cache="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:vsui="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:helpers="clr-namespace:GitHub.Helpers;assembly=GitHub.UI" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="300" d:DesignBackground="White" - KeyboardNavigation.TabNavigation="Local" - DataContext="{Binding ViewModel}"> - <d:DesignProperties.DataContext> - <Binding> - <Binding.Source> - <sampleData:GitHubConnectSectionDesigner /> - </Binding.Source> - </Binding> - </d:DesignProperties.DataContext> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio;component/SharedDictionary.xaml" /> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - <ui:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> - <ui:HasItemsVisibilityConverter x:Key="HasItemsVisibilityConverter" /> - <local:FormatRepositoryNameConverter x:Key="FormatRepositoryNameConverter" /> - <local:IsCurrentRepositoryConverter x:Key="IsCurrentRepositoryConverter" /> - - - <Style x:Key="RepoNameStyle" TargetType="{x:Type TextBlock}"> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.ToolWindowText}" /> - <Setter Property="Margin" Value="0,0,6,3" /> - <Setter Property="Text"> - <Setter.Value> - <MultiBinding Converter="{StaticResource FormatRepositoryNameConverter}" ConverterParameter="FormatRepositoryName"> - <Binding/> - <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> - </MultiBinding> - </Setter.Value> - </Setter> - <Style.Triggers> - <DataTrigger Value="True"> - <DataTrigger.Binding> - <MultiBinding Converter="{StaticResource IsCurrentRepositoryConverter}" ConverterParameter="IsCurrentRepository"> - <Binding/> - <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> - </MultiBinding> - </DataTrigger.Binding> - <Setter Property="TextBlock.FontWeight" Value="Bold" /> - </DataTrigger> - </Style.Triggers> - </Style> - - <Style x:Key="RepoPathStyle" TargetType="{x:Type TextBlock}"> - <Setter Property="Margin" Value="0,0,3,3" /> - <Setter Property="TextTrimming" Value="CharacterEllipsis" /> - <Setter Property="TextWrapping" Value="NoWrap" /> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.GrayText}" /> - <Style.Triggers> - <MultiDataTrigger> - <MultiDataTrigger.Conditions> - <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsSelected}" Value="True"/> - </MultiDataTrigger.Conditions> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.ToolWindowText}" /> - </MultiDataTrigger> - <MultiDataTrigger> - <MultiDataTrigger.Conditions> - <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsMouseOver}" Value="true" /> - </MultiDataTrigger.Conditions> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.ToolWindowText}" /> - </MultiDataTrigger> - <DataTrigger Value="True"> - <DataTrigger.Binding> - <MultiBinding Converter="{StaticResource IsCurrentRepositoryConverter}" ConverterParameter="IsCurrentRepository"> - <Binding/> - <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> - </MultiBinding> - </DataTrigger.Binding> - <Setter Property="Foreground" Value="{DynamicResource VsBrush.ToolWindowText}" /> - </DataTrigger> - </Style.Triggers> - - </Style> - - <Style x:Key="RepositoriesListItemContainerStyle" TargetType="{x:Type ListBoxItem}"> - <Setter Property="SnapsToDevicePixels" Value="True"/> - <Setter Property="Margin" Value="0"/> - <Setter Property="Padding" Value="1"/> - - <Setter Property="HorizontalContentAlignment" Value="Stretch"/> - <Setter Property="VerticalContentAlignment" Value="Center"/> - - <Setter Property="Background" Value="Transparent"/> - <Setter Property="BorderBrush" Value="Transparent"/> - <Setter Property="BorderThickness" Value="0"/> - - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="{x:Type ListBoxItem}"> - <Border x:Name="Bd" - Padding="{TemplateBinding Padding}" - BorderThickness="{TemplateBinding BorderThickness}" - Background="{TemplateBinding Background}"> - <ContentPresenter - HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" - SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" - VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> - </Border> - <ControlTemplate.Triggers> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="IsMouseOver" Value="True"/> - </MultiTrigger.Conditions> - <Setter Property="Background" Value="{DynamicResource VsBrush.CommandBarHover}" /> - </MultiTrigger> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="Selector.IsSelectionActive" Value="True"/> - <Condition Property="IsSelected" Value="True"/> - </MultiTrigger.Conditions> - <Setter Property="Background" Value="{DynamicResource VsBrush.CommandBarSelectedBorder}" /> - </MultiTrigger> - <MultiTrigger> - <MultiTrigger.Conditions> - <Condition Property="Selector.IsSelectionActive" Value="True"/> - </MultiTrigger.Conditions> - </MultiTrigger> - <Trigger Property="IsEnabled" Value="False"> - <Setter Property="Opacity" Value="0.3"/> - </Trigger> - </ControlTemplate.Triggers> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - </ResourceDictionary> - - </UserControl.Resources> - - <DockPanel> - <WrapPanel Orientation="Horizontal" Margin="6,0,0,6" DockPanel.Dock="Top"> - <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="0" x:Name="cloneLink" Click="cloneLink_Click" Content="{x:Static prop:Resources.CloneLink}"/> - <Separator Margin="3,0,3,0" Style="{StaticResource VerticalSeparator}" /> - <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="1" x:Name="createLink" Click="createLink_Click" Content="{x:Static prop:Resources.CreateLink}"/> - <Separator Margin="3,0,3,0" Style="{StaticResource VerticalSeparator}" /> - <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="2" x:Name="signOut" Click="signOut_Click" Content="{x:Static prop:Resources.SignOutLink}" - Visibility="{Binding ShowLogout, Converter={StaticResource BooleanToVisibilityConverter}}"/> - <Button Style="{StaticResource ActionLinkButton}" KeyboardNavigation.TabIndex="2" x:Name="login" Click="login_Click" Content="{x:Static prop:Resources.LoginLink}" - Visibility="{Binding ShowLogin, Converter={StaticResource BooleanToVisibilityConverter}}"/> - </WrapPanel> - - <ListView x:Name="repositories" - ItemsSource="{Binding Repositories}" - SelectedItem="{Binding Path=SelectedRepository}" - Margin="5,0,6,0" - MouseDoubleClick="repositories_MouseDoubleClick" - ItemContainerStyle="{StaticResource RepositoriesListItemContainerStyle}" - Background="Transparent" - BorderBrush="Transparent" - VerticalAlignment="Top" - HorizontalContentAlignment="Stretch" - TextSearch.TextPath="Name" - ScrollViewer.HorizontalScrollBarVisibility="Disabled" - VirtualizingPanel.IsVirtualizing="True" - VirtualizingPanel.ScrollUnit="Pixel" - VirtualizingPanel.IsVirtualizingWhenGrouping="True" - Visibility="{Binding Repositories, Converter={StaticResource HasItemsVisibilityConverter}}" - > - <ListView.ItemTemplate> - <DataTemplate> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="*" /> - </Grid.ColumnDefinitions> - <ui:OcticonImage Grid.Column="0" Margin="11,1,6,1" Width="16" Height="16" - Foreground="{DynamicResource VsBrush.ToolWindowText}" - Icon="{Binding Path=Icon}"/> - <TextBlock Grid.Column="1" Style="{StaticResource RepoNameStyle}" /> - <Separator Grid.Column="2" Margin="0,0,6,3" Style="{StaticResource VerticalSeparator}" /> - <TextBlock Grid.Column="3" Text="{Binding Path=LocalPath}" Style="{StaticResource RepoPathStyle}" /> - <Grid.ToolTip> - <ToolTip> - <Grid> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="Auto" /> - <ColumnDefinition Width="Auto" /> - </Grid.ColumnDefinitions> - <Grid.RowDefinitions> - <RowDefinition Height="Auto" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> - <TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,6,0"> - <Run Text="{x:Static prop:Resources.nameText}"/>: - </TextBlock> - <TextBlock Grid.Row="0" Grid.Column="1"> - <TextBlock.Text> - <MultiBinding Converter="{StaticResource FormatRepositoryNameConverter}" ConverterParameter="FormatRepositoryName"> - <Binding/> - <Binding Path="ViewModel" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:GitHubConnectContent}}" /> - </MultiBinding> - </TextBlock.Text> - </TextBlock> - <TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,6,0"> - <Run Text="{x:Static prop:Resources.pathText}"/>: - </TextBlock> - <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=LocalPath}" /> - </Grid> - </ToolTip> - </Grid.ToolTip> - </Grid> - </DataTemplate> - </ListView.ItemTemplate> - </ListView> - - </DockPanel> -</UserControl> diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml deleted file mode 100644 index c62ac15db5..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml +++ /dev/null @@ -1,57 +0,0 @@ -<UserControl x:Class="GitHub.VisualStudio.UI.Views.GitHubHomeContent" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - 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.UI.Views" - xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" - xmlns:cache="clr-namespace:GitHub.VisualStudio.Helpers" - mc:Ignorable="d" - d:DesignHeight="48" - Height="48" - d:DesignWidth="300" - Background="Transparent" - DataContext="{Binding ViewModel}"> - <d:DesignProperties.DataContext> - <Binding> - <Binding.Source> - <sampleData:GitHubHomeSectionDesigner /> - </Binding.Source> - </Binding> - </d:DesignProperties.DataContext> - - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - <Style TargetType="Label"> - <Setter Property="Padding" Value="0" /> - </Style> - </ResourceDictionary> - </UserControl.Resources> - - <StackPanel Margin="8,0"> - <Grid Margin="0,6,-4,6"> - <Grid.ColumnDefinitions> - <ColumnDefinition ColumnDefinition.Width="Auto" /> - <ColumnDefinition ColumnDefinition.Width="*" /> - </Grid.ColumnDefinitions> - - <ui:OcticonImage - x:Name="repositoryIcon" - Height="32" - Width="32" - VerticalAlignment="Center" - Margin="0,0,8,0" - Icon="{Binding Path=Icon}" - Foreground="{DynamicResource VsBrush.ToolWindowText}" /> - - <StackPanel Orientation="Vertical" Margin="0" Grid.Column="1"> - <Label Content="{Binding Path=RepoName}" Foreground="{DynamicResource VsBrush.ToolWindowText}"/> - <Label Content="{Binding Path=RepoUrl}" Foreground="{DynamicResource VsBrush.GrayText}" /> - </StackPanel> - </Grid> - </StackPanel> -</UserControl> diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs b/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs deleted file mode 100644 index 8420039a95..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/GitHubHomeContent.xaml.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using GitHub.VisualStudio.TeamExplorer.Home; -using GitHub.UI.Helpers; - -namespace GitHub.VisualStudio.UI.Views -{ - /// <summary> - /// Interaction logic for GitHubHomeSection.xaml - /// </summary> - public partial class GitHubHomeContent : UserControl - { - public GitHubHomeContent() - { - InitializeComponent(); - - DataContextChanged += (s, e) => ViewModel = e.NewValue as IGitHubHomeSection; - } - - public IGitHubHomeSection ViewModel - { - get { return (IGitHubHomeSection)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register( - "ViewModel", - typeof(IGitHubHomeSection), - typeof(GitHubHomeContent)); - } -} diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubPaneView.xaml b/src/GitHub.VisualStudio/UI/Views/GitHubPaneView.xaml deleted file mode 100644 index 30db441100..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/GitHubPaneView.xaml +++ /dev/null @@ -1,79 +0,0 @@ -<view:GenericGitHubPaneView x:Class="GitHub.VisualStudio.UI.Views.GitHubPaneView" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:cache="clr-namespace:GitHub.VisualStudio.Helpers" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:opts="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" - xmlns:prop="clr-namespace:GitHub.VisualStudio" - xmlns:uirx="clr-namespace:GitHub.UI;assembly=GitHub.UI.Reactive" - xmlns:view="clr-namespace:GitHub.VisualStudio.UI.Views" - Background="White" - Foreground="{DynamicResource VsBrush.WindowText}" - d:DesignHeight="300" - d:DesignWidth="300" - mc:Ignorable="d"> - <UserControl.Resources> - <ResourceDictionary> - <ResourceDictionary.MergedDictionaries> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio;component/SharedDictionary.xaml" /> - <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> - </ResourceDictionary.MergedDictionaries> - - <Color x:Key="GitHubPaneTitleColor">#FF1B293E</Color> - <SolidColorBrush x:Key="GitHubPaneTitleBrush" opts:Freeze="true" Color="{StaticResource GitHubPaneTitleColor}" /> - - <Color x:Key="GitHubPaneSubTitleColor">#BBBBBB</Color> - <SolidColorBrush x:Key="GitHubPaneSubTitleBrush" opts:Freeze="true" Color="{StaticResource GitHubPaneSubTitleColor}" /> - - <Style x:Key="PaneHorizontalSeparator" TargetType="{x:Type Separator}"> - <Setter Property="Background" Value="{DynamicResource VsBrush.CommandBarGradientMiddle}" /> - <Setter Property="Height" Value="2" /> - <Setter Property="SnapsToDevicePixels" Value="True" /> - <Setter Property="Template"> - <Setter.Value> - <ControlTemplate TargetType="{x:Type Separator}"> - <Border Width="{TemplateBinding Width}" - Background="{TemplateBinding Background}" - BorderBrush="{TemplateBinding Background}" - SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> - </ControlTemplate> - </Setter.Value> - </Setter> - </Style> - - </ResourceDictionary> - </UserControl.Resources> - - <DockPanel> - <StackPanel Margin="6,9,9,5" DockPanel.Dock="Top" Orientation="Horizontal"> - <TextBlock Margin="0,-5,0,0" - FontSize="14.7" - FontWeight="SemiBold" - Foreground="{StaticResource GitHubPaneTitleBrush}" - Text="{x:Static prop:Resources.PullRequestsNavigationItemText}" /> - <Separator Margin="5,-2,5,0" Foreground="{StaticResource GitHubPaneSubTitleBrush}" Style="{StaticResource TitleVerticalSeparator}" /> - <TextBlock Margin="0,-5,0,0" - VerticalAlignment="Center" - Foreground="{DynamicResource VsBrush.GrayText}" - Text="{Binding ActiveRepo.Name}" /> - </StackPanel> - - <Separator Margin="0,0,0,9" DockPanel.Dock="Top" Style="{StaticResource PaneHorizontalSeparator}" /> - - <ListBox x:Name="container" Margin="6,0,6,9" ItemsSource="{Binding Panels}"> - <ListBox.ItemContainerStyle> - <Style TargetType="{x:Type ListBoxItem}"> - <Setter Property="DockPanel.Dock" Value="{Binding Dock}" /> - <Setter Property="HorizontalContentAlignment" Value="Stretch" /> - <Setter Property="VerticalContentAlignment" Value="Stretch" /> - </Style> - </ListBox.ItemContainerStyle> - <ListBox.ItemsPanel> - <ItemsPanelTemplate> - <DockPanel IsItemsHost="True" /> - </ItemsPanelTemplate> - </ListBox.ItemsPanel> - </ListBox> - </DockPanel> -</view:GenericGitHubPaneView> diff --git a/src/GitHub.VisualStudio/UI/Views/GitHubPaneView.xaml.cs b/src/GitHub.VisualStudio/UI/Views/GitHubPaneView.xaml.cs deleted file mode 100644 index c46efd9011..0000000000 --- a/src/GitHub.VisualStudio/UI/Views/GitHubPaneView.xaml.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.ComponentModel.Composition; -using System.Reactive.Disposables; -using System.Windows.Input; -using GitHub.Exports; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using GitHub.ViewModels; -using GitHub.VisualStudio.Base; -using GitHub.VisualStudio.Helpers; -using NullGuard; -using ReactiveUI; - -namespace GitHub.VisualStudio.UI.Views -{ - public class GenericGitHubPaneView : SimpleViewUserControl<IGitHubPaneViewModel, GitHubPaneView> - { - } - - [ExportView(ViewType = UIViewType.GitHubPane)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class GitHubPaneView : GenericGitHubPaneView - { - public GitHubPaneView() - { - this.InitializeComponent(); - DataContextChanged += (s, e) => ViewModel = e.NewValue as GitHubPaneViewModel; - } - } - - [ExportViewModel(ViewType = UIViewType.GitHubPane)] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class GitHubPaneViewModel : TeamExplorerSectionBase, IGitHubPaneViewModel - { - CompositeDisposable disposables = new CompositeDisposable(); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - IUIController uiController; - - [ImportingConstructor] - public GitHubPaneViewModel(ITeamExplorerServiceHolder holder, IConnectionManager cm) - : base(holder, cm) - { - Controls = new ObservableCollection<IView>(); - } - - public override void Initialize(IServiceProvider serviceProvider) - { - base.Initialize(serviceProvider); - - var disp = ServiceProvider.GetExportedValue<IUIProvider>().GetService<IExportFactoryProvider>().UIControllerFactory.CreateExport(); - disposables.Add(disp); - uiController = disp.Value; - - ServiceProvider.AddTopLevelMenuItem(GuidList.guidGitHubToolbarCmdSet, PkgCmdIDList.pullRequestCommand, - (s, e) => {}); - - ServiceProvider.AddTopLevelMenuItem(GuidList.guidGitHubToolbarCmdSet, PkgCmdIDList.backCommand, - (s, e) => { }); - - ServiceProvider.AddTopLevelMenuItem(GuidList.guidGitHubToolbarCmdSet, PkgCmdIDList.forwardCommand, - (s, e) => { }); - - ServiceProvider.AddTopLevelMenuItem(GuidList.guidGitHubToolbarCmdSet, PkgCmdIDList.refreshCommand, - (s, e) => { }); - } - - ObservableCollection<IView> controls; - public ObservableCollection<IView> Controls - { - [return: AllowNull] get { return controls; } - set { controls = value; this.RaisePropertyChange(); } - } - - public ReactiveCommand<object> CancelCommand { get; private set; } - public ICommand Cancel => CancelCommand; - - public bool IsShowing => true; - - bool disposed = false; - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (!disposed) - { - controls.Clear(); - disposables.Dispose(); - disposed = true; - } - } - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml b/src/GitHub.VisualStudio/UI/WindowController.xaml deleted file mode 100644 index 5f7aa3ed09..0000000000 --- a/src/GitHub.VisualStudio/UI/WindowController.xaml +++ /dev/null @@ -1,23 +0,0 @@ -<pfui:DialogWindow x:Class="GitHub.VisualStudio.UI.WindowController" - xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" - xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:local="clr-namespace:GitHub.VisualStudio.UI.Views" - xmlns:ctl="clr-namespace:GitHub.VisualStudio.UI.Views.Controls" - xmlns:pfui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0" - mc:Ignorable="d" - Title="GitHub" - Height="440" - Width="414" - MinHeight="440" - MinWidth="414" - HasMinimizeButton="False" - HasMaximizeButton="False" - Background="White" - SnapsToDevicePixels="True" - UseLayoutRounding="True"> - <Grid x:Name="Container"> - - </Grid> -</pfui:DialogWindow> diff --git a/src/GitHub.VisualStudio/UI/WindowController.xaml.cs b/src/GitHub.VisualStudio/UI/WindowController.xaml.cs deleted file mode 100644 index 02c0d9ecc3..0000000000 --- a/src/GitHub.VisualStudio/UI/WindowController.xaml.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.ComponentModel; -using System.Windows.Controls; -using GitHub.UI; -using GitHub.ViewModels; -using Microsoft.VisualStudio.PlatformUI; - -namespace GitHub.VisualStudio.UI -{ - public partial class WindowController : DialogWindow - { - public WindowController(IObservable<UserControl> controls) - { - InitializeComponent(); - controls.Subscribe(c => Load(c)); - } - - public void Load(UserControl control) - { - var view = control as IView; - if (view != null) - { - var viewModel = view.ViewModel as IViewModel; - if (viewModel != null) - { - Title = viewModel.Title; - } - } - Container.Children.Clear(); - Container.Children.Add(control); - } - } -} diff --git a/src/GitHub.VisualStudio/VSPackage.resx b/src/GitHub.VisualStudio/VSPackage.resx index 981e444af9..944809e33b 100644 --- a/src/GitHub.VisualStudio/VSPackage.resx +++ b/src/GitHub.VisualStudio/VSPackage.resx @@ -127,11 +127,14 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> - <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <data name="110" xml:space="preserve"> <value>GitHub.VisualStudio</value> </data> <data name="112" xml:space="preserve"> <value>A Visual Studio Extension that brings the GitHub Flow into Visual Studio.</value> </data> + <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <data name="301" type="System.Resources.ResXFileRef, System.Windows.Forms"> + <value>resources\logo_32x32@2x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> + </data> </root> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/ActorAvatarView.cs b/src/GitHub.VisualStudio/Views/ActorAvatarView.cs new file mode 100644 index 0000000000..12b4b4d38f --- /dev/null +++ b/src/GitHub.VisualStudio/Views/ActorAvatarView.cs @@ -0,0 +1,181 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using GitHub.Exports; +using GitHub.ViewModels; + +namespace GitHub.VisualStudio.Views +{ + [ExportViewFor(typeof(IActorViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ActorAvatarView : FrameworkElement, ICommandSource + { + public static readonly DependencyProperty CommandProperty = + ButtonBase.CommandProperty.AddOwner(typeof(ActorAvatarView)); + public static readonly DependencyProperty CommandParameterProperty = + ButtonBase.CommandParameterProperty.AddOwner(typeof(ActorAvatarView)); + public static readonly DependencyProperty CommandTargetProperty = + ButtonBase.CommandTargetProperty.AddOwner(typeof(ActorAvatarView)); + + public static readonly DependencyProperty IdleUpdateProperty = + DependencyProperty.Register( + nameof(IdleUpdate), + typeof(bool), + typeof(ActorAvatarView), + new FrameworkPropertyMetadata(false)); + + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + nameof(ViewModel), + typeof(IActorViewModel), + typeof(ActorAvatarView), + new FrameworkPropertyMetadata(HandleViewModelChanged)); + + IActorViewModel toRender; + Geometry clip; + + static ActorAvatarView() + { + WidthProperty.OverrideMetadata(typeof(ActorAvatarView), new FrameworkPropertyMetadata(30.0)); + HeightProperty.OverrideMetadata(typeof(ActorAvatarView), new FrameworkPropertyMetadata(30.0)); + } + + public ActorAvatarView() + { + } + + public bool IdleUpdate + { + get { return (bool)GetValue(IdleUpdateProperty); } + set { SetValue(IdleUpdateProperty, value); } + } + + public IActorViewModel ViewModel + { + get { return (IActorViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public IInputElement CommandTarget + { + get { return (IInputElement)GetValue(CommandTargetProperty); } + set { SetValue(CommandTargetProperty, value); } + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + this.CaptureMouse(); + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (this.IsMouseCaptured) + { + if (this.InputHitTest(e.GetPosition(this)) != null && + Command?.CanExecute(CommandParameter) == true) + { + Command.Execute(CommandParameter); + } + + this.ReleaseMouseCapture(); + } + } + + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + clip = null; + } + + protected override void OnRender(DrawingContext drawingContext) + { + if (toRender != null) + { + if (clip == null) + { + clip = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight), 3, 3); + } + + drawingContext.PushClip(clip); + drawingContext.DrawImage(toRender.Avatar, new Rect(0, 0, ActualWidth, ActualHeight)); + drawingContext.Pop(); + } + } + + void ViewModelChanged(DependencyPropertyChangedEventArgs e) + { + var oldValue = e.OldValue as IActorViewModel; + var newValue = e.NewValue as IActorViewModel; + + if (oldValue != null) + { + oldValue.PropertyChanged -= ViewModelPropertyChanged; + } + + if (IdleUpdate) + { + QueueIdleUpdate(); + } + else + { + toRender = newValue; + + if (toRender != null) + { + toRender.PropertyChanged += ViewModelPropertyChanged; + } + } + + InvalidateVisual(); + } + + void QueueIdleUpdate() + { + toRender = null; + ComponentDispatcher.ThreadIdle += OnIdle; + } + + void OnIdle(object sender, EventArgs e) + { + toRender = ViewModel; + + if (toRender != null) + { + toRender.PropertyChanged += ViewModelPropertyChanged; + } + + InvalidateVisual(); + ComponentDispatcher.ThreadIdle -= OnIdle; + } + + void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(IActorViewModel.Avatar)) + { + QueueIdleUpdate(); + } + } + + static void HandleViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + (d as ActorAvatarView)?.ViewModelChanged(e); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/ContentView.cs b/src/GitHub.VisualStudio/Views/ContentView.cs new file mode 100644 index 0000000000..95cb9b7d5a --- /dev/null +++ b/src/GitHub.VisualStudio/Views/ContentView.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel.Composition; +using System.Windows.Controls; +using System.Windows.Data; +using GitHub.Exports; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views +{ + /// <summary> + /// A view that simply displays whatever is in the Content property of its DataContext. + /// </summary> + /// <remarks> + /// A control which displays the Content property of a view model is commonly needed when + /// displaying multi-page interfaces. To use this control as a view for such a purpose, + /// simply add an `[ExportViewFor]` attribute to this class with the type of the view model + /// interface. + /// </remarks> + [ExportViewFor(typeof(IForkRepositoryViewModel))] + [ExportViewFor(typeof(ILoginViewModel))] + [ExportViewFor(typeof(INavigationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ContentView : ContentControl + { + public ContentView() + { + BindingOperations.SetBinding(this, ContentProperty, new Binding("Content")); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/ForkRepositoryExecuteView.xaml b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositoryExecuteView.xaml new file mode 100644 index 0000000000..0477774a24 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositoryExecuteView.xaml @@ -0,0 +1,101 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.Dialog.ForkRepositoryExecuteView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:ui="https://github.com/github/VisualStudio" + VerticalAlignment="Top" + Margin="8" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + mc:Ignorable="d" d:DesignWidth="300" Height="315.179"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <!-- Disable links until we make them work--> + <Style TargetType="Hyperlink"> + <Setter Property="IsEnabled" Value="False"/> + <Setter Property="Foreground" Value="Black"/> + </Style> + + </ResourceDictionary> + </Control.Resources> + + <d:DesignProperties.DataContext> + <sampleData:ForkRepositoryExecuteViewModelDesigner/> + </d:DesignProperties.DataContext> + + <StackPanel Margin="0 8 0 0"> + <TextBlock FontSize="16" TextWrapping="Wrap"> + You're about to fork the + <Hyperlink> + <Run Text="{Binding SourceRepository.Owner, Mode=OneWay}"/>/<Run Text="{Binding SourceRepository.Name, Mode=OneWay}"/> + </Hyperlink> + repository to + <Hyperlink> + <Run Text="{Binding DestinationRepository.Owner, Mode=OneWay}"/>/<Run Text="{Binding DestinationRepository.Name, Mode=OneWay}"/> + </Hyperlink>. + </TextBlock> + + <TextBlock Margin="0 16 0 0" FontSize="14">This operation will:</TextBlock> + + <StackPanel Orientation="Vertical"> + <Border Margin="0 8 0 4" CornerRadius="2" Background="#ffeff1f5" Padding="8 16"> + <StackPanel> + <Grid Margin="0 0 0 0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <ui:OcticonImage Grid.Column="0" Icon="repo_forked" Height="16" Width="16" /> + <TextBlock Margin="8 0 0 0" Grid.Column="1" TextWrapping="Wrap">Fork the repository</TextBlock> + </Grid> + + <Grid Margin="0 16 0 0"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <ui:OcticonImage Grid.Column="0" Icon="home" Height="16" Width="16" /> + + <TextBlock Margin="8 0 0 0" Grid.Column="1" TextWrapping="Wrap"> + Update your local repository's <Run Style="{DynamicResource {x:Static markdig:Styles.CodeStyleKey}}">origin</Run> to point to + <Hyperlink><Run Text="{Binding DestinationRepository.CloneUrl, Mode=OneWay}"/></Hyperlink> + </TextBlock> + </Grid> + + <Grid Margin="0 16 0 0" > + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <ui:OcticonImage Grid.Column="0" Icon="globe" Height="16" Width="16" /> + <TextBlock Margin="8 0 0 0" Grid.Column="1" TextWrapping="Wrap"> + Add an <Run Style="{DynamicResource {x:Static markdig:Styles.CodeStyleKey}}">upstream</Run> remote pointing to + <Hyperlink><Run Text="{Binding SourceRepository.CloneUrl, Mode=OneWay}"/></Hyperlink> + </TextBlock> + </Grid> + </StackPanel> + </Border> + + <TextBlock TextWrapping="Wrap" + Margin="0 4" + Foreground="Red" + Text="{Binding Error, Mode=OneWay}" + Visibility="{Binding Error, Converter={ui:NullToVisibilityConverter}}" + HorizontalAlignment="Left" /> + <StackPanel Margin="0 8" Orientation="Horizontal" HorizontalAlignment="Right"> + <Button HorizontalAlignment="Right" Padding="16 4" BorderThickness="0" Margin="0 0 4 0" Click="backButton_OnClick">Back</Button> + <Button HorizontalAlignment="Right" Padding="16 4" BorderThickness="0" Click="repoForkButton_OnClick">Fork Repository</Button> + </StackPanel> + </StackPanel> + </StackPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/Dialog/ForkRepositoryExecuteView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositoryExecuteView.xaml.cs new file mode 100644 index 0000000000..1bc3a26d21 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositoryExecuteView.xaml.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + [ExportViewFor(typeof(IForkRepositoryExecuteViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class ForkRepositoryExecuteView : UserControl + { + public ForkRepositoryExecuteView() + { + InitializeComponent(); + } + + private void repoForkButton_OnClick(object sender, RoutedEventArgs e) + { + ((IForkRepositoryExecuteViewModel)DataContext).CreateFork.Execute(new object()); + } + + private void backButton_OnClick(object sender, RoutedEventArgs e) + { + ((IForkRepositoryExecuteViewModel)DataContext).BackCommand.Execute(new object()); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/ForkRepositorySelectView.xaml b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositorySelectView.xaml new file mode 100644 index 0000000000..68e1c73916 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositorySelectView.xaml @@ -0,0 +1,102 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.Dialog.ForkRepositorySelectView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + 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" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + mc:Ignorable="d" + d:DesignHeight="414" d:DesignWidth="440"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <d:DesignProperties.DataContext> + <sampleData:ForkRepositorySelectViewModelDesigner IsLoading="True"/> + </d:DesignProperties.DataContext> + + <Grid Margin="0 8"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <TextBlock Grid.Row="0" FontSize="16" Margin="8"> + Where should we fork this repository? + </TextBlock> + + <ui:GitHubProgressBar Grid.Row="1" + Foreground="{DynamicResource GitHubAccentBrush}" + IsIndeterminate="True" + Style="{DynamicResource GitHubProgressBar}" + Visibility="{Binding IsLoading, Converter={ui:BooleanToHiddenVisibilityConverter}}"/> + + <ListBox Name="accountsListBox" + Grid.Row="2" + BorderThickness="0" + ItemsSource="{Binding Accounts}" + VerticalContentAlignment="Top" + HorizontalContentAlignment="Stretch" + Padding="8" + DockPanel.Dock="Top" + ScrollViewer.HorizontalScrollBarVisibility="Disabled" + SelectionChanged="accountsListBox_SelectionChanged"> + <ListBox.ItemsPanel> + <ItemsPanelTemplate> + <UniformGrid Columns="3" IsItemsHost="True" /> + </ItemsPanelTemplate> + </ListBox.ItemsPanel> + <ListBox.ItemTemplate> + <DataTemplate> + <Border Margin="4" Background="#fff2f2f2" CornerRadius="3"> + <DockPanel> + <TextBlock DockPanel.Dock="Bottom" + Margin="4,0,4,4" + Text="{Binding Login, StringFormat=@{0}}" + TextAlignment="Center" + TextTrimming="CharacterEllipsis"/> + <Image Margin="4" + Width="100" + Height="100" + Stretch="Uniform" + Source="{Binding AvatarUrl}" /> + </DockPanel> + </Border> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + + <DockPanel Grid.Row="3" + Margin="0 16 0 0" + Visibility="{Binding ExistingForks, Converter={ui:HasItemsVisibilityConverter}}"> + <TextBlock DockPanel.Dock="Top" Margin="8 0"> + <Run FontWeight="SemiBold">Can't find what you're looking for?</Run> + <LineBreak/> + <Run>You have existing forks of this repository:</Run> + </TextBlock> + + <ItemsControl ItemsSource="{Binding ExistingForks}" DockPanel.Dock="Bottom" MaxHeight="120" Padding="8 0" ScrollViewer.VerticalScrollBarVisibility="Auto"> + <ItemsControl.ItemTemplate> + <DataTemplate> + <StackPanel Orientation="Horizontal" Margin="0 4 0 0"> + <ui:OcticonImage Icon="repo_forked" Margin="0,0,2,-2"/> + <TextBlock Text="{Binding Owner, Mode=OneWay}"/> + <TextBlock Text="/"/> + <TextBlock Text="{Binding Name, Mode=OneWay}"/> + </StackPanel> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </DockPanel> + </Grid> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/Dialog/ForkRepositorySelectView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositorySelectView.xaml.cs new file mode 100644 index 0000000000..718d2990ab --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/ForkRepositorySelectView.xaml.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.Models; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + [ExportViewFor(typeof(IForkRepositorySelectViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class ForkRepositorySelectView : UserControl + { + public ForkRepositorySelectView() + { + InitializeComponent(); + } + + private void accountsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var account = e.AddedItems.OfType<IAccount>().FirstOrDefault(); + + if (account != null) + { + ((IForkRepositorySelectViewModel)DataContext).SelectedAccount.Execute(account); + } + } + + private void existingForksListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var repository = e.AddedItems.OfType<IRemoteRepositoryModel>().FirstOrDefault(); + if (repository != null) + { + ((IForkRepositorySelectViewModel)DataContext).SwitchOrigin.Execute(repository); + } + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/GistCreationView.xaml b/src/GitHub.VisualStudio/Views/Dialog/GistCreationView.xaml new file mode 100644 index 0000000000..5e8c29b6d6 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/GistCreationView.xaml @@ -0,0 +1,109 @@ +<local:GenericGistCreationView x:Class="GitHub.VisualStudio.Views.Dialog.GistCreationView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + xmlns:ghfvs="https://github.com/github/VisualStudio" + mc:Ignorable="d" + d:DesignWidth="414" + d:DesignHeight="440" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GistCreationControlCustom}"> + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + <DockPanel Style="{DynamicResource DialogContainerDockPanel}"> + <ghfvs:OcticonImage Icon="logo_github" Style="{DynamicResource GitHubLogo}" Margin="0,10,0,-10" Panel.ZIndex="100" DockPanel.Dock="Top" /> + + <ghfvs:OcticonCircleButton + DockPanel.Dock="Bottom" + IsDefault="True" + Margin="0" + x:Name="createGistButton" + HorizontalAlignment="Center" + Icon="check"> + <TextBlock Text="{x:Static prop:Resources.CreateLink}"/> + </ghfvs:OcticonCircleButton> + + <StackPanel> + <ghfvs:HorizontalShadowDivider /> + <Grid + FocusManager.IsFocusScope="True" + Margin="30,-10,30,10" + FocusVisualStyle="{x:Null}"> + + <Grid.Resources> + + <Style TargetType="{x:Type Label}"> + <Setter Property="Foreground" Value="{StaticResource GHTextBrush}" /> + <Setter Property="VerticalAlignment" Value="Center" /> + <Setter Property="HorizontalAlignment" Value="Right" /> + <Setter Property="Margin" Value="0,0,10,0" /> + <Setter Property="Padding" Value="0" /> + </Style> + + <Style TargetType="{x:Type ghfvs:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}"> + <Setter Property="Margin" Value="0,5" /> + </Style> + + <Style TargetType="{x:Type Button}"> + <Setter Property="Padding" Value="0" /> + <Setter Property="VerticalContentAlignment" Value="Center" /> + </Style> + </Grid.Resources> + + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <Grid.RowDefinitions> + <RowDefinition Height="35" /> + <RowDefinition Height="35" /> + <RowDefinition Height="35" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Label Grid.Column="0" Grid.Row="0" Target="{Binding ElementName=nameTextBox}" Content="{x:Static prop:Resources.fileNameText}"/> + <ghfvs:PromptTextBox x:Name="fileNameTextBox" Grid.Column="1" Grid.Row="0" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GistFileNameTextBox}" /> + + <Label Grid.Column="0" Grid.Row="1" Target="{Binding ElementName=descriptionTextBox}" Content="{x:Static prop:Resources.Description}"/> + <ghfvs:PromptTextBox x:Name="descriptionTextBox" Grid.Column="1" Grid.Row="1" SpellCheck.IsEnabled="True" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GistDescriptionTextBlock}"/> + + <StackPanel x:Name="accountStackPanel" Grid.Column="1" Grid.Row="2" Orientation="Horizontal"> + <Image + Source="{Binding Avatar}" + Width="16" + Height="16" + RenderOptions.BitmapScalingMode="HighQuality" + Margin="0,0,8,0" /> + <TextBlock Text="{Binding Login}" VerticalAlignment="Center"/> + </StackPanel> + + <CheckBox + x:Name="makePrivate" + Grid.Column="1" + Grid.Row="3" + Content="{x:Static prop:Resources.makePrivateGist}" + Style="{DynamicResource BlueRoundedCheckBox}" + Padding="8,4,0,4" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PrivateGistCheckBox}" /> + + <ghfvs:ErrorMessageDisplay + x:Name="errorMessage" + Grid.ColumnSpan="2" + Grid.Row="4" + Margin="0,10" + Message="{x:Static prop:Resources.gistCreationFailedMessage}"> + <TextBlock x:Name="errorMessageText" TextWrapping="Wrap" Text="{x:Static prop:Resources.gistCreationFailedMessage}"/> + </ghfvs:ErrorMessageDisplay> + </Grid> + </StackPanel> + </DockPanel> +</local:GenericGistCreationView> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/Dialog/GistCreationView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/GistCreationView.xaml.cs new file mode 100644 index 0000000000..7a7dd8ef77 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/GistCreationView.xaml.cs @@ -0,0 +1,64 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Threading; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.Dialog +{ + public class GenericGistCreationView : ViewBase<IGistCreationViewModel, GistCreationView> + { } + + public class FooBar { } + + [ExportViewFor(typeof(IGistCreationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class GistCreationView : GenericGistCreationView + { + [ImportingConstructor] + public GistCreationView( + INotificationDispatcher notifications, + IGitHubServiceProvider serviceProvider) + { + InitializeComponent(); + + this.WhenActivated(d => + { + errorMessage.Visibility = Visibility.Collapsed; + + d(this.Bind(ViewModel, vm => vm.Description, v => v.descriptionTextBox.Text)); + d(this.Bind(ViewModel, vm => vm.FileName, v => v.fileNameTextBox.Text)); + d(this.Bind(ViewModel, vm => vm.IsPrivate, v => v.makePrivate.IsChecked)); + d(this.BindCommand(ViewModel, vm => vm.CreateGist, v => v.createGistButton)); + + d(this.Bind(ViewModel, vm => vm.Account, v => v.accountStackPanel.DataContext)); + + ViewModel.CreateGist + .Where(x => x != null) + .Subscribe(gist => + { + var browser = serviceProvider.TryGetService<IVisualStudioBrowser>(); + browser?.OpenUrl(new Uri(gist.HtmlUrl)); + + var ns = serviceProvider.TryGetService<IStatusBarNotificationService>(); + ns?.ShowMessage(UI.Resources.gistCreatedMessage); + }); + + d(notifications.Listen() + .Where(n => n.Type == Notification.NotificationType.Error) + .ObserveOnDispatcher(DispatcherPriority.Normal) + .Subscribe(n => + { + errorMessage.Visibility = Visibility.Visible; + errorMessageText.Text = n.Message; + })); + }); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml b/src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml new file mode 100644 index 0000000000..2d2b89d700 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml @@ -0,0 +1,33 @@ +<pfui:DialogWindow x:Class="GitHub.VisualStudio.Views.Dialog.GitHubDialogWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:pfui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views" + xmlns:ghfvs="https://github.com/github/VisualStudio" + mc:Ignorable="d" + Title="{Binding Content.Title, FallbackValue=GitHub}" + Width="414" + Height="460" + MinWidth="414" + MinHeight="460" + Background="White" + FontFamily="Segoe UI" + FontSize="12" + FontStretch="Normal" + FontStyle="Normal" + FontWeight="Normal" + HasMaximizeButton="False" + HasMinimizeButton="False" + SnapsToDevicePixels="True" + UseLayoutRounding="True" + WindowStartupLocation="CenterOwner" + Content="{Binding Content}"> + <Window.Resources> + <local:ViewLocator x:Key="viewLocator"/> + <DataTemplate DataType="{x:Type ghfvs:ViewModelBase}"> + <ContentControl Content="{Binding Converter={StaticResource viewLocator}}"/> + </DataTemplate> + </Window.Resources> +</pfui:DialogWindow> diff --git a/src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml.cs new file mode 100644 index 0000000000..4f09882042 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/GitHubDialogWindow.xaml.cs @@ -0,0 +1,19 @@ +using System; +using GitHub.ViewModels.Dialog; +using Microsoft.VisualStudio.PlatformUI; + +namespace GitHub.VisualStudio.Views.Dialog +{ + /// <summary> + /// The main window for GitHub for Visual Studio's dialog. + /// </summary> + public partial class GitHubDialogWindow : DialogWindow + { + public GitHubDialogWindow(IGitHubDialogWindowViewModel viewModel) + { + DataContext = viewModel; + viewModel.Done.Subscribe(_ => Close()); + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/Login2FaView.xaml b/src/GitHub.VisualStudio/Views/Dialog/Login2FaView.xaml new file mode 100644 index 0000000000..254eb0c555 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/Login2FaView.xaml @@ -0,0 +1,87 @@ +<local:GenericLogin2FaView x:Class="GitHub.VisualStudio.Views.Dialog.Login2FaView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views.Dialog" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + mc:Ignorable="d" + d:DesignWidth="414" + d:DesignHeight="440" + Style="{DynamicResource DialogUserControl}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticationCustom}" > + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <StackPanel + Style="{DynamicResource DialogContainerStackPanel}" + ghfvs:AccessKeysManagerScoping.IsEnabled="True"> + <ghfvs:OcticonImage Icon="logo_github" Style="{DynamicResource GitHubLogo}" /> + <WrapPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="0,0,0,12"> + <TextBlock + Text="{x:Static prop:Resources.twoFactorAuthText}" + Padding="0" + Margin="0,0,12,0" + Style="{DynamicResource GitHubH1TextBlock}" + Foreground="{DynamicResource GHTextBrush}" /> + </WrapPanel> + + <ghfvs:HorizontalShadowDivider /> + + <TextBlock + Margin="30,0,30,16" + HorizontalAlignment="Center" + TextWrapping="Wrap" + Style="{DynamicResource GitHubDescriptionTextBlock}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticationInformationTextBlock}" > + <Run x:Name="description" Text="{x:Static prop:Resources.openTwoFactorAuthAppText}"/> + <Hyperlink x:Name="twoFactorReadMoreLink" ToolTip="https://help.github.com/articles/about-two-factor-authentication" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticationLearnMoreHyperlink}"> + <TextBlock Text="{x:Static prop:Resources.learnMoreLink}"/></Hyperlink> + </TextBlock> + <ghfvs:TwoFactorInput + x:Name="authenticationCode" + TabIndex="1" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticatonInputCustom}" /> + <Grid Margin="0,12,0,0" Height="18"> + <ghfvs:ErrorMessageDisplay + x:Name="authenticationFailedLabel" + Message="{x:Static prop:Resources.authenticationFailedLabelMessage}" + Content="{x:Static prop:Resources.authenticationFailedLabelContent}" /> + <ghfvs:ErrorMessageDisplay + x:Name="authenticationSentLabel" + Icon="check" + IconFill="{DynamicResource GitHubAccentBrush}" + Message="{x:Static prop:Resources.authenticationSentLabelMessage}" + Content="{x:Static prop:Resources.authenticationSentLabelContent}" /> + </Grid> + <StackPanel + Orientation="Horizontal" + HorizontalAlignment="Center" + Margin="0,18,0,60"> + <ghfvs:OcticonCircleButton + x:Name="okButton" + TabIndex="2" + Icon="check" + IsDefault="True" + Content="{x:Static prop:Resources.verifyText}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticatonVerifyButton}" /> + <ghfvs:OcticonLinkButton + x:Name="resendCodeButton" + TabIndex="3" + ToolTip="{x:Static prop:Resources.resendCodeButtonToolTip}" + FontSize="12" + Icon="sync" + Margin="18,0,0,0" + Content="{x:Static prop:Resources.resendCodeButtonContent}" /> + </StackPanel> + </StackPanel> +</local:GenericLogin2FaView> diff --git a/src/GitHub.VisualStudio/Views/Dialog/Login2FaView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/Login2FaView.xaml.cs new file mode 100644 index 0000000000..e602a9f453 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/Login2FaView.xaml.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using GitHub.Exports; +using GitHub.UI; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.Dialog +{ + public class GenericLogin2FaView : ViewBase<ILogin2FaViewModel, Login2FaView> + { } + + /// <summary> + /// Interaction logic for PasswordView.xaml + /// </summary> + [ExportViewFor(typeof(ILogin2FaViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class Login2FaView : GenericLogin2FaView + { + public Login2FaView() + { + InitializeComponent(); + + this.WhenActivated(d => + { + d(this.BindCommand(ViewModel, vm => vm.OkCommand, view => view.okButton)); + d(this.OneWayBind(ViewModel, vm => vm.IsBusy, view => view.okButton.ShowSpinner)); + d(this.BindCommand(ViewModel, vm => vm.ResendCodeCommand, view => view.resendCodeButton)); + + d(this.Bind(ViewModel, vm => vm.AuthenticationCode, view => view.authenticationCode.Text)); + d(this.OneWayBind(ViewModel, vm => vm.NavigateLearnMore, view => view.twoFactorReadMoreLink.Command)); + d(this.OneWayBind(ViewModel, vm => vm.IsAuthenticationCodeSent, + view => view.authenticationSentLabel.Visibility)); + d(this.OneWayBind(ViewModel, vm => vm.IsSms, view => view.resendCodeButton.Visibility)); + d(this.OneWayBind(ViewModel, vm => vm.Description, view => view.description.Text)); + d(this.OneWayBind(ViewModel, vm => vm.ShowErrorMessage, view => view.authenticationFailedLabel.Visibility)); + d(this.ViewModel.ResendCodeCommand.Subscribe(_ => SetFocus())); + d(this.ViewModel.OkCommand.Subscribe(_ => SetFocus())); + }); + IsVisibleChanged += (s, e) => + { + if (IsVisible) + { + SetFocus(); + } + }; + } + + void SetFocus() + { + authenticationCode.TryFocus().Subscribe(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/LoginCredentialsView.xaml b/src/GitHub.VisualStudio/Views/Dialog/LoginCredentialsView.xaml new file mode 100644 index 0000000000..7b4ad8418e --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/LoginCredentialsView.xaml @@ -0,0 +1,218 @@ +<local:GenericLoginCredentialsView x:Class="GitHub.VisualStudio.Views.Dialog.LoginCredentialsView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.Dialog" + mc:Ignorable="d" + d:DesignWidth="414" + d:DesignHeight="440" + Style="{DynamicResource DialogUserControl}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SignInCustom}"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel Style="{DynamicResource DialogContainerDockPanel}"> + <DockPanel.Resources> + <Style TargetType="{x:Type ghfvs:PromptTextBox}" BasedOn="{StaticResource RoundedPromptTextBox}"> + <Setter Property="Margin" Value="0" /> + </Style> + <Style TargetType="{x:Type Border}" x:Key="LoginButtonBorder"> + <Setter Property="Margin" Value="0,0,0,15" /> + <Setter Property="HorizontalAlignment" Value="Center" /> + </Style> + <Style TargetType="{x:Type ghfvs:SecurePasswordBox}" BasedOn="{StaticResource RoundedPromptTextBox}"> + <Setter Property="Margin" Value="0" /> + </Style> + </DockPanel.Resources> + <StackPanel DockPanel.Dock="Top"> + <ghfvs:OcticonImage Icon="logo_github" Style="{DynamicResource GitHubLogo}" /> + + <TextBlock + x:Name="loginLabelPrefix" + HorizontalAlignment="Center" + Margin="0,0,0,10" + Style="{DynamicResource GitHubH1TextBlock}" + IsHitTestVisible="False" + Text="{x:Static prop:Resources.LoginLink}" /> + + <ghfvs:HorizontalShadowDivider /> + </StackPanel> + <TabControl + x:Name="hostTabControl" + Margin="30,0" + Style="{StaticResource LightModalViewTabControl}" + FocusManager.IsFocusScope="True" + FocusVisualStyle="{x:Null}" + ghfvs:AccessKeysManagerScoping.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SignInHostTab}" > + <TabControl.Resources> + <Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource LightModalViewTabItem}" /> + <Style TargetType="{x:Type TabPanel}"> + <Setter Property="HorizontalAlignment" Value="Center" /> + </Style> + <Style x:Key="TabDockPanel" TargetType="{x:Type DockPanel}"> + <Setter Property="Margin" Value="0,20,0,0" /> + <Setter Property="LastChildFill" Value="True" /> + </Style> + <Style x:Key="FormFieldStackPanel" TargetType="{x:Type StackPanel}"> + <Setter Property="Margin" Value="0" /> + </Style> + <Style + TargetType="{x:Type ghfvs:ErrorMessageDisplay}" + BasedOn="{StaticResource ErrorMessageStyle}"> + <Setter Property="Margin" Value="0,10" /> + </Style> + </TabControl.Resources> + + <TabItem x:Name="dotComTab" Header="GitHub" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SignInDotcomHostTabItem}" > + <DockPanel Style="{StaticResource TabDockPanel}"> + <StackPanel DockPanel.Dock="Bottom" Margin="0"> + <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0" Text="{x:Static prop:Resources.dontHaveAnAccountText}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.DontHaveDotcomAccountTextBlock}"> + <Hyperlink x:Name="pricingLink" ToolTip="https://github.com/pricing"><TextBlock Text="{x:Static prop:Resources.SignUpLink}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.DotcomSignUpHyperlink}" /></Hyperlink> + </TextBlock> + </StackPanel> + + <StackPanel x:Name="dotComloginControlsPanel"> + + <StackPanel Style="{StaticResource FormFieldStackPanel}"> + <ghfvs:PromptTextBox + x:Name="dotComUserNameOrEmail" + PromptText="{x:Static prop:Resources.UserNameOrEmailPromptText}" + Margin="0,0,0,10" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.DotcomUsernameEmailTextBox}" /> + + <ghfvs:SecurePasswordBox x:Name="dotComPassword" PromptText="{x:Static prop:Resources.PasswordPrompt}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.DotcomPasswordTextBox}"/> + + <Border Style="{StaticResource LoginButtonBorder}" Margin="0 16 0 0"> + <ghfvs:OcticonCircleButton x:Name="dotComLogInButton" Icon="check" Content="{x:Static prop:Resources.LoginLink}" IsDefault="True" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.DotcomSignInButton}"/> + </Border> + + <Grid Margin="0 8"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + <Rectangle Grid.Column="0" Fill="#FFDDDDDD" Height="1"/> + <TextBlock Grid.Column="1" Margin="8,0,8,4">or</TextBlock> + <Rectangle Grid.Column="2" Fill="#FFDDDDDD" Height="1"/> + </Grid> + + <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> + <ghfvs:GitHubActionLink x:Name="dotComSsaLogInButton">Sign in with your browser</ghfvs:GitHubActionLink> + <ghfvs:OcticonImage Icon="link_external" Margin="0 1 0 0"/> + </StackPanel> + + <ghfvs:UserErrorMessages x:Name="dotComErrorMessage" Margin="0,10"> + </ghfvs:UserErrorMessages> + + <ghfvs:ValidationMessage + x:Name="dotComUserNameOrEmailValidationMessage" + ValidatesControl="{Binding ElementName=dotComUserNameOrEmail}" /> + + <ghfvs:ValidationMessage + x:Name="dotComPasswordValidationMessage" + ValidatesControl="{Binding ElementName=dotComPassword}" /> + + </StackPanel> + </StackPanel> + </DockPanel> + </TabItem> + + <TabItem x:Name="enterpriseTab" Header="GitHub Enterprise" Margin="10,0,-10,0" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SignInEnterpriseHostTabItem}"> + <DockPanel Style="{StaticResource TabDockPanel}"> + <StackPanel DockPanel.Dock="Bottom"> + <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0" Text="{x:Static prop:Resources.dontHaveGitHubEnterpriseText}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.DontHaveEnterpriseTextBlock}" > + <Hyperlink x:Name="learnMoreLink" ToolTip="enterprise.github.com"><TextBlock Text="{x:Static prop:Resources.learnMoreLink}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.EnterpriseLearnMoreHyperlink}"></TextBlock></Hyperlink> + </TextBlock> + </StackPanel> + + <StackPanel + x:Name="enterpriseloginControlsPanel" + Style="{StaticResource FormFieldStackPanel}"> + + <ghfvs:PromptTextBox x:Name="enterpriseUrl" + PromptText="{x:Static prop:Resources.enterpriseUrlPromptText}" + Margin="0,0,0,10" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.EnterpriseServerAddressTextBox}"> + <ghfvs:PromptTextBox.IconContentTemplate> + <DataTemplate DataType="{x:Type ghfvs:EnterpriseProbeStatus}"> + <Grid Margin="4,0"> + <ghfvs:Spinner Margin="0,4" Visibility="{Binding Converter={ghfvs:EqualsToVisibilityConverter Checking}}"/> + <ghfvs:OcticonImage Icon="check" Visibility="{Binding Converter={ghfvs:EqualsToVisibilityConverter Valid}}"/> + <ghfvs:OcticonImage Icon="x" Visibility="{Binding Converter={ghfvs:EqualsToVisibilityConverter Invalid}}"/> + </Grid> + </DataTemplate> + </ghfvs:PromptTextBox.IconContentTemplate> + </ghfvs:PromptTextBox> + + <StackPanel Name="enterpriseUsernamePasswordPanel"> + <ghfvs:PromptTextBox + x:Name="enterpriseUserNameOrEmail" + PromptText="{x:Static prop:Resources.UserNameOrEmailPromptText}" + Margin="0,0,0,10" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.EnterpriseUsernameEmailTextBox}" /> + + <ghfvs:SecurePasswordBox x:Name="enterprisePassword" + PromptText="{x:Static prop:Resources.PasswordPrompt}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.EnterprisePasswordTextBox}" /> + </StackPanel> + + <StackPanel Name="enterpriseTokenPanel"> + <ghfvs:SecurePasswordBox x:Name="enterpriseToken" + PromptText="{x:Static prop:Resources.TokenPrompt}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.EnterprisePasswordTextBox}" /> + </StackPanel> + + <Border Style="{StaticResource LoginButtonBorder}" Margin="0 16 0 0"> + <ghfvs:OcticonCircleButton x:Name="enterpriseLogInButton" Icon="check" Content="{x:Static prop:Resources.LoginLink}" IsDefault="True" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.EnterpriseSignInButton}"/> + </Border> + + <StackPanel Name="enterpriseSsoPanel"> + <Grid Margin="0 8"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + <Rectangle Grid.Column="0" Fill="#FFDDDDDD" Height="1"/> + <TextBlock Grid.Column="1" Margin="8,0,8,4">or</TextBlock> + <Rectangle Grid.Column="2" Fill="#FFDDDDDD" Height="1"/> + </Grid> + <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> + <ghfvs:GitHubActionLink x:Name="enterpriseSsaLogInButton">Sign in with your browser</ghfvs:GitHubActionLink> + <ghfvs:OcticonImage Icon="link_external" Margin="0 1 0 0"/> + </StackPanel> + </StackPanel> + + <ghfvs:ValidationMessage + x:Name="enterpriseUserNameOrEmailValidationMessage" + ValidatesControl="{Binding ElementName=enterpriseUserNameOrEmail}"/> + + <ghfvs:ValidationMessage + x:Name="enterprisePasswordValidationMessage" + ValidatesControl="{Binding ElementName=enterprisePassword}" /> + + <ghfvs:ValidationMessage + x:Name="enterpriseUrlValidationMessage" + ValidatesControl="{Binding ElementName=enterpriseUrl}" + TextChangeThrottle="1.0"/> + + <ghfvs:UserErrorMessages x:Name="enterpriseErrorMessage" Margin="0,10"> + </ghfvs:UserErrorMessages> + </StackPanel> + </DockPanel> + </TabItem> + </TabControl> + </DockPanel> +</local:GenericLoginCredentialsView> diff --git a/src/GitHub.VisualStudio/Views/Dialog/LoginCredentialsView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/LoginCredentialsView.xaml.cs new file mode 100644 index 0000000000..17b04d827c --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/LoginCredentialsView.xaml.cs @@ -0,0 +1,136 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Input; +using GitHub.Controls; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.Dialog +{ + public class GenericLoginCredentialsView : ViewBase<ILoginCredentialsViewModel, LoginCredentialsView> + { } + + /// <summary> + /// Interaction logic for LoginControl.xaml + /// </summary> + [ExportViewFor(typeof(ILoginCredentialsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class LoginCredentialsView : GenericLoginCredentialsView + { + public LoginCredentialsView() + { + InitializeComponent(); + + this.WhenActivated(d => + { + SetupDotComBindings(d); + SetupEnterpriseBindings(d); + SetupSelectedAndVisibleTabBindings(d); + d(Disposable.Create(Deactivate)); + }); + + IsVisibleChanged += (s, e) => + { + if (IsVisible) + dotComUserNameOrEmail.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); + }; + + // Refocus VS after a SSO login attempt. + this.WhenAnyObservable( + x => x.ViewModel.GitHubLogin.LoginViaOAuth, + x => x.ViewModel.EnterpriseLogin.LoginViaOAuth) + .Subscribe(_ => Application.Current.MainWindow?.Activate()); + + hostTabControl.SelectionChanged += (s, e) => + { + foreach (var i in e.RemovedItems) + { + if (i == dotComTab) + { + ViewModel?.GitHubLogin.Deactivated(); + } + else if (i == enterpriseTab) + { + ViewModel?.EnterpriseLogin.Deactivated(); + } + } + }; + } + + void Deactivate() + { + ViewModel?.GitHubLogin.Deactivated(); + ViewModel?.EnterpriseLogin.Deactivated(); + } + + void SetupDotComBindings(Action<IDisposable> d) + { + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.IsLoggingIn, x => x.dotComloginControlsPanel.IsEnabled, x => x == false)); + + d(this.Bind(ViewModel, vm => vm.GitHubLogin.UsernameOrEmail, x => x.dotComUserNameOrEmail.Text)); + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.UsernameOrEmailValidator, v => v.dotComUserNameOrEmailValidationMessage.ReactiveValidator)); + + d(this.BindPassword(ViewModel, vm => vm.GitHubLogin.Password, v => v.dotComPassword.Text, dotComPassword)); + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.PasswordValidator, v => v.dotComPasswordValidationMessage.ReactiveValidator)); + + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.Login, v => v.dotComLogInButton.Command)); + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.LoginViaOAuth, v => v.dotComSsaLogInButton.Command)); + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.IsLoggingIn, v => v.dotComLogInButton.ShowSpinner)); + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.NavigatePricing, v => v.pricingLink.Command)); + d(this.OneWayBind(ViewModel, vm => vm.GitHubLogin.Error, v => v.dotComErrorMessage.UserError)); + } + + void SetupEnterpriseBindings(Action<IDisposable> d) + { + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.IsLoggingIn, x => x.enterpriseloginControlsPanel.IsEnabled, x => x == false)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.ProbeStatus, x => x.enterpriseUrl.IconContent)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.SupportedLoginMethods, x => x.enterpriseUsernamePasswordPanel.Visibility, x => x.HasValue && ((x & EnterpriseLoginMethods.UsernameAndPassword) != 0) ? Visibility.Visible : Visibility.Collapsed)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.SupportedLoginMethods, x => x.enterpriseTokenPanel.Visibility, x => x.HasValue && ((x & EnterpriseLoginMethods.Token) != 0) ? Visibility.Visible : Visibility.Collapsed)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.SupportedLoginMethods, x => x.enterpriseSsoPanel.Visibility, x => x.HasValue && ((x & EnterpriseLoginMethods.OAuth) != 0) ? Visibility.Visible : Visibility.Collapsed)); + + d(this.Bind(ViewModel, vm => vm.EnterpriseLogin.UsernameOrEmail, x => x.enterpriseUserNameOrEmail.Text)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.UsernameOrEmailValidator, v => v.enterpriseUserNameOrEmailValidationMessage.ReactiveValidator)); + + d(this.BindPassword(ViewModel, vm => vm.EnterpriseLogin.Password, v => v.enterprisePassword.Text, enterprisePassword)); + d(this.BindPassword(ViewModel, vm => vm.EnterpriseLogin.Password, v => v.enterpriseToken.Text, enterpriseToken)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.PasswordValidator, v => v.enterprisePasswordValidationMessage.ReactiveValidator)); + + d(this.Bind(ViewModel, vm => vm.EnterpriseLogin.EnterpriseUrl, v => v.enterpriseUrl.Text)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.EnterpriseUrlValidator, v => v.enterpriseUrlValidationMessage.ReactiveValidator)); + + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.Login, v => v.enterpriseLogInButton.Command)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.LoginViaOAuth, v => v.enterpriseSsaLogInButton.Command)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.IsLoggingIn, v => v.enterpriseLogInButton.ShowSpinner)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.NavigateLearnMore, v => v.learnMoreLink.Command)); + d(this.OneWayBind(ViewModel, vm => vm.EnterpriseLogin.Error, v => v.enterpriseErrorMessage.UserError)); + } + + void SetupSelectedAndVisibleTabBindings(Action<IDisposable> d) + { + d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) + .Select(x => x == LoginMode.DotComOrEnterprise || x == LoginMode.DotComOnly) + .BindTo(this, v => v.dotComTab.IsEnabled)); + + d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) + .Select(x => x == LoginMode.DotComOrEnterprise || x == LoginMode.EnterpriseOnly) + .BindTo(this, v => v.enterpriseTab.IsEnabled)); + + d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) + .Select(x => x == LoginMode.DotComOrEnterprise || x == LoginMode.DotComOnly) + .Where(x => x == true) + .BindTo(this, v => v.dotComTab.IsSelected)); + + d(this.WhenAny(x => x.ViewModel.LoginMode, x => x.Value) + .Select(x => x == LoginMode.EnterpriseOnly) + .Where(x => x == true) + .BindTo(this, v => v.enterpriseTab.IsSelected)); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/RepositoryCloneView.xaml b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCloneView.xaml new file mode 100644 index 0000000000..db1977c283 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCloneView.xaml @@ -0,0 +1,327 @@ +<local:GenericRepositoryCloneView x:Class="GitHub.VisualStudio.Views.Dialog.RepositoryCloneView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views.Dialog" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + d:DesignHeight="440" + d:DesignWidth="414" + Background="Transparent" + mc:Ignorable="d" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.RepositoryCloneControlCustom}" > + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:RepositoryCloneViewModelDesigner /> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel LastChildFill="True"> + <DockPanel.Resources> + <Style x:Key="repositoryBorderStyle" TargetType="Border"> + <Setter Property="BorderBrush" Value="#EAEAEA" /> + <Setter Property="BorderThickness" Value="0,0,0,1" /> + <Setter Property="VerticalAlignment" Value="Center" /> + <Setter Property="Height" Value="30" /> + <Setter Property="Margin" Value="0" /> + </Style> + + <Style x:Key="expanderDownHeaderStyle" TargetType="{x:Type ToggleButton}"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ToggleButton}"> + <Border Padding="{TemplateBinding Padding}" Style="{StaticResource repositoryBorderStyle}"> + <StackPanel Background="#F8F8F8" Orientation="Horizontal"> + <ghfvs:OcticonImage x:Name="arrow" + Height="10" + Margin="5,0,0,0" + Foreground="Black" + Icon="triangle_right" /> + <ContentPresenter Margin="0" + HorizontalAlignment="Left" + VerticalAlignment="Center" + RecognizesAccessKey="True" + SnapsToDevicePixels="True" /> + </StackPanel> + </Border> + + <ControlTemplate.Triggers> + <Trigger Property="IsChecked" Value="True"> + <Setter TargetName="arrow" Property="Icon" Value="triangle_down" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="expanderHeaderFocusVisual"> + <Setter Property="Control.Template"> + <Setter.Value> + <ControlTemplate> + <Border> + <Rectangle Margin="0" + SnapsToDevicePixels="true" + Stroke="Black" + StrokeDashArray="1 2" + StrokeThickness="1" /> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="cloneGroupExpander" TargetType="{x:Type Expander}"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> + <Setter Property="Background" Value="Transparent" /> + <Setter Property="HorizontalContentAlignment" Value="Stretch" /> + <Setter Property="VerticalContentAlignment" Value="Stretch" /> + <Setter Property="BorderBrush" Value="Transparent" /> + <Setter Property="BorderThickness" Value="0" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Expander}"> + <Border Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="3" + SnapsToDevicePixels="true"> + <DockPanel> + <ToggleButton x:Name="HeaderSite" + MinWidth="0" + MinHeight="0" + Margin="0" + HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + Content="{TemplateBinding Header}" + ContentTemplate="{TemplateBinding HeaderTemplate}" + ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" + DockPanel.Dock="Top" + FocusVisualStyle="{StaticResource expanderHeaderFocusVisual}" + FontFamily="{TemplateBinding FontFamily}" + FontSize="{TemplateBinding FontSize}" + FontStretch="{TemplateBinding FontStretch}" + FontStyle="{TemplateBinding FontStyle}" + FontWeight="{TemplateBinding FontWeight}" + Foreground="{TemplateBinding Foreground}" + IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" + Padding="{TemplateBinding Padding}" + Style="{StaticResource expanderDownHeaderStyle}" /> + <ContentPresenter x:Name="ExpandSite" + Margin="{TemplateBinding Padding}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + DockPanel.Dock="Bottom" + Focusable="false" + Visibility="Collapsed" /> + </DockPanel> + </Border> + <ControlTemplate.Triggers> + <Trigger Property="IsExpanded" Value="true"> + <Setter TargetName="ExpandSite" Property="Visibility" Value="Visible" /> + </Trigger> + <Trigger Property="IsEnabled" Value="false"> + <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> + </Trigger> + </ControlTemplate.Triggers> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </DockPanel.Resources> + + <ghfvs:FilterTextBox x:Name="filterText" + Margin="10" + DockPanel.Dock="Top" + PromptText="{x:Static prop:Resources.filterTextPromptText}" + Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" + IsEnabled="{Binding FilterTextIsEnabled, Mode=OneWay}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.SearchRepositoryTextBox}" /> + + <ghfvs:GitHubProgressBar DockPanel.Dock="Top" + Foreground="{DynamicResource GitHubAccentBrush}" + IsIndeterminate="True" + Style="{DynamicResource GitHubProgressBar}" + Visibility="{Binding IsBusy, Converter={ghfvs:BooleanToHiddenVisibilityConverter}}"/> + + <StackPanel x:Name="loadingFailedPanel" + Margin="0,4,0,4" + HorizontalAlignment="Center" + VerticalAlignment="Center" + DockPanel.Dock="Top"> + <ghfvs:ErrorMessageDisplay x:Name="loadingFailedMessage" + Margin="8,0" + Icon="stop" + Message="{x:Static prop:Resources.loadingFailedMessageMessage}" + Visibility="{Binding LoadingFailed, Mode=OneWay, Converter={ghfvs:BooleanToVisibilityConverter}}"> + <TextBlock Text="{x:Static prop:Resources.loadingFailedMessageContent}" TextWrapping="Wrap" /> + </ghfvs:ErrorMessageDisplay> + </StackPanel> + + <ghfvs:OcticonCircleButton x:Name="cloneButton" + Margin="12" + HorizontalAlignment="Center" + DockPanel.Dock="Bottom" + Icon="check" + IsDefault="True" + Command="{Binding CloneCommand}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CloneRepositoryButton}" > + <TextBlock Text="{x:Static prop:Resources.CloneLink}" /> + </ghfvs:OcticonCircleButton> + + <Border DockPanel.Dock="Bottom" Style="{StaticResource repositoryBorderStyle}"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + + <ghfvs:PromptTextBox x:Name="clonePath" + Grid.Column="1" + Margin="0,2" + Text="{Binding BaseRepositoryPath, UpdateSourceTrigger=PropertyChanged}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CreateRepositoryLocalPathTextBox}" + /> + <Button x:Name="browsePathButton" + Grid.Column="2" + Margin="4,0,4,0" + VerticalContentAlignment="Center" + Content="{x:Static prop:Resources.browsePathButtonContent}" + Padding="0" + Style="{StaticResource GitHubBlueLinkButton}" + Command="{Binding BrowseForDirectory}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CloneRepositoryLocalPathBrowsePathButton}" + /> + <Label Grid.Row="3" + Grid.Column="0" + Margin="4,0" + Content="{x:Static prop:Resources.pathText}" + Target="{Binding ElementName=clonePath}" /> + <ghfvs:ValidationMessage x:Name="pathValidationMessage" + Grid.Row="4" + Grid.Column="1" + ValidatesControl="{Binding ElementName=clonePath}" + ReactiveValidator="{Binding BaseRepositoryPathValidator, Mode=OneWay}" + /> + </Grid> + </Border> + + <Border x:Name="repositoriesListPane" + Margin="0" + BorderBrush="#EAEAEA" + BorderThickness="0,1" + FocusManager.IsFocusScope="True" + FocusVisualStyle="{x:Null}"> + <Border.Resources> + <Style BasedOn="{StaticResource GitHubTextBlock}" TargetType="{x:Type ghfvs:TrimmedTextBlock}"> + <Style.Triggers> + <Trigger Property="IsTextTrimmed" Value="True"> + <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" /> + </Trigger> + </Style.Triggers> + </Style> + <Style x:Key="cloneRepoHeaderStyle" TargetType="TextBlock"> + <Setter Property="Foreground" Value="{DynamicResource GHTextBrush}" /> + <Setter Property="Margin" Value="0,6,12,6" /> + </Style> + </Border.Resources> + <Grid Margin="0"> + <ListBox x:Name="repositoryList" + HorizontalContentAlignment="Stretch" + ItemsSource="{Binding Path=RepositoriesView, RelativeSource={RelativeSource AncestorType={x:Type local:RepositoryCloneView}}}" + SelectedItem="{Binding SelectedRepository}" + Style="{DynamicResource LightListBox}"> + <ListBox.GroupStyle> + <GroupStyle HidesIfEmpty="true"> + <GroupStyle.ContainerStyle> + <Style TargetType="{x:Type GroupItem}"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type GroupItem}"> + <Expander IsExpanded="{Binding Name.IsExpanded}" Style="{StaticResource cloneGroupExpander}"> + <Expander.Header> + <Border Style="{StaticResource repositoryBorderStyle}"> + <StackPanel Margin="0" + VerticalAlignment="Center" + Orientation="Horizontal"> + <Image x:Name="avatar" + Width="16" + Height="16" + Margin="0,0,5,0" + RenderOptions.BitmapScalingMode="HighQuality" + Source="{Binding Items[0].Owner.Avatar}" /> + <TextBlock Style="{StaticResource cloneRepoHeaderStyle}" Text="{Binding Path=Name.Header}" /> + </StackPanel> + </Border> + </Expander.Header> + <ItemsPresenter Margin="0" /> + </Expander> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </GroupStyle.ContainerStyle> + </GroupStyle> + </ListBox.GroupStyle> + <ListBox.ItemTemplate> + <DataTemplate> + <Border Style="{StaticResource repositoryBorderStyle}"> + <StackPanel Margin="0" + VerticalAlignment="Center" + Orientation="Horizontal"> + <ghfvs:OcticonImage x:Name="iconPath" + Width="16" + Height="16" + Margin="32,0,6,0" + VerticalAlignment="Center" + Foreground="#D0D0D0" + Icon="{Binding Icon}" /> + + <ghfvs:TrimmedTextBlock x:Name="label" + VerticalAlignment="Center" + Foreground="#666" + Text="{Binding Name}" + TextTrimming="CharacterEllipsis" /> + </StackPanel> + </Border> + <DataTemplate.Triggers> + <MultiDataTrigger> + <MultiDataTrigger.Conditions> + <Condition Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True" /> + <Condition Binding="{Binding Path=(Selector.IsSelectionActive), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True" /> + </MultiDataTrigger.Conditions> + <MultiDataTrigger.Setters> + <Setter TargetName="iconPath" Property="Foreground" Value="White" /> + <Setter TargetName="label" Property="Foreground" Value="White" /> + </MultiDataTrigger.Setters> + </MultiDataTrigger> + </DataTemplate.Triggers> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + <StackPanel x:Name="noRepositoriesMessage" + Margin="0,-70,0,0" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Visibility="{Binding NoRepositoriesFound, Mode=OneWay, Converter={ghfvs:BooleanToVisibilityConverter}}"> + <TextBlock HorizontalAlignment="Center" + Style="{DynamicResource GitHubH2TextBlock}" + Text="{x:Static prop:Resources.noRepositoriesMessageText}" /> + </StackPanel> + </Grid> + </Border> + </DockPanel> +</local:GenericRepositoryCloneView> diff --git a/src/GitHub.VisualStudio/Views/Dialog/RepositoryCloneView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCloneView.xaml.cs new file mode 100644 index 0000000000..6714bfe7b5 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCloneView.xaml.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Data; +using System.Windows.Input; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.UI; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.Dialog +{ + public class GenericRepositoryCloneView : ViewBase<IRepositoryCloneViewModel, RepositoryCloneView> + {} + + /// <summary> + /// Interaction logic for CloneRepoControl.xaml + /// </summary> + [ExportViewFor(typeof(IRepositoryCloneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class RepositoryCloneView : GenericRepositoryCloneView + { + readonly Dictionary<string, RepositoryGroup> groups = new Dictionary<string, RepositoryGroup>(); + + static readonly DependencyPropertyKey RepositoriesViewPropertyKey = + DependencyProperty.RegisterReadOnly( + nameof(RepositoriesView), + typeof(ICollectionView), + typeof(RepositoryCloneView), + new PropertyMetadata(null)); + + public static readonly DependencyProperty RepositoriesViewProperty = RepositoriesViewPropertyKey.DependencyProperty; + + public RepositoryCloneView() + { + InitializeComponent(); + + this.WhenActivated(d => + { + //d(repositoryList.Events().MouseDoubleClick.InvokeCommand(this, x => x.ViewModel.CloneCommand)); + }); + + IsVisibleChanged += (s, e) => + { + if (IsVisible) + this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); + }; + + this.WhenAnyValue(x => x.ViewModel.Repositories, CreateRepositoryListCollectionView).Subscribe(x => RepositoriesView = x); + } + + public ICollectionView RepositoriesView + { + get { return (ICollectionView)GetValue(RepositoriesViewProperty); } + private set { SetValue(RepositoriesViewPropertyKey, value); } + } + + ListCollectionView CreateRepositoryListCollectionView(IEnumerable<IRemoteRepositoryModel> repositories) + { + if (repositories == null) + return null; + + var view = new ListCollectionView((IList)repositories); + view.GroupDescriptions.Add(new RepositoryGroupDescription(this)); + return view; + } + + class RepositoryGroupDescription : GroupDescription + { + readonly RepositoryCloneView owner; + + public RepositoryGroupDescription(RepositoryCloneView owner) + { + Guard.ArgumentNotNull(owner, nameof(owner)); + + this.owner = owner; + } + + public override object GroupNameFromItem(object item, int level, System.Globalization.CultureInfo culture) + { + var repo = item as IRemoteRepositoryModel; + var name = repo?.Owner ?? string.Empty; + RepositoryGroup group; + + if (!owner.groups.TryGetValue(name, out group)) + { + group = new RepositoryGroup(name, owner.groups.Count == 0); + + if (owner.groups.Count == 1) + owner.groups.Values.First().IsExpanded = false; + owner.groups.Add(name, group); + } + + return group; + } + } + + class RepositoryGroup : ReactiveObject + { + bool isExpanded; + + public RepositoryGroup(string header, bool isExpanded) + { + Guard.ArgumentNotEmptyString(header, nameof(header)); + + Header = header; + this.isExpanded = isExpanded; + } + + public string Header { get; } + + public bool IsExpanded + { + get { return isExpanded; } + set { this.RaiseAndSetIfChanged(ref isExpanded, value); } + } + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/RepositoryCreationView.xaml b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCreationView.xaml new file mode 100644 index 0000000000..ae0de08433 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCreationView.xaml @@ -0,0 +1,259 @@ +<local:GenericRepositoryCreationView x:Class="GitHub.VisualStudio.Views.Dialog.RepositoryCreationView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.Dialog" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + d:DesignHeight="440" + d:DesignWidth="414" + Style="{DynamicResource DialogUserControl}" + mc:Ignorable="d" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.RepositoryCreationControlCustom}" > + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:RepositoryCreationViewModelDesigner /> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel Style="{DynamicResource DialogContainerDockPanel}"> + <ghfvs:OcticonImage Margin="0,-20,0,-10" + Panel.ZIndex="100" + DockPanel.Dock="Top" + Icon="logo_github" + Style="{DynamicResource GitHubLogo}" /> + + <ghfvs:OcticonCircleButton x:Name="createRepositoryButton" + Margin="0" + HorizontalAlignment="Center" + DockPanel.Dock="Bottom" + Icon="check" + IsDefault="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CreateRepositoryButton}" > + <TextBlock Text="{x:Static prop:Resources.CreateLink}" /> + </ghfvs:OcticonCircleButton> + + <StackPanel> + <StackPanel.Resources> + <Style BasedOn="{StaticResource {x:Type ghfvs:UserErrorMessages}}" TargetType="{x:Type ghfvs:UserErrorMessages}"> + <Setter Property="IconMargin" Value="-1,2,7,0" /> + <Setter Property="ErrorMessageFontWeight" Value="Normal" /> + <Setter Property="Icon" Value="stop" /> + <Setter Property="Margin" Value="0,5,0,0" /> + </Style> + </StackPanel.Resources> + <ghfvs:HorizontalShadowDivider /> + <Grid x:Name="loginStackPanel" + Margin="30,-10,30,10" + FocusManager.IsFocusScope="True" + FocusVisualStyle="{x:Null}"> + + <Grid.Resources> + <Style TargetType="{x:Type TextBlock}"> + <Setter Property="Margin" Value="0,3,0,3" /> + </Style> + + <Style TargetType="{x:Type Label}"> + <Setter Property="Foreground" Value="{StaticResource GHTextBrush}" /> + <Setter Property="VerticalAlignment" Value="Center" /> + <Setter Property="HorizontalAlignment" Value="Right" /> + <Setter Property="Margin" Value="0,0,10,0" /> + <Setter Property="Padding" Value="0" /> + </Style> + + <Style BasedOn="{StaticResource RoundedPromptTextBox}" TargetType="{x:Type ghfvs:PromptTextBox}"> + <Setter Property="Margin" Value="0,5" /> + </Style> + + <Style TargetType="{x:Type Button}"> + <Setter Property="Padding" Value="0" /> + <Setter Property="VerticalContentAlignment" Value="Center" /> + </Style> + </Grid.Resources> + + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <Grid.RowDefinitions> + <RowDefinition Height="35" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="35" /> + <RowDefinition Height="35" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + + <Label Grid.Row="0" + Grid.Column="0" + Content="{x:Static prop:Resources.nameText}" + Target="{Binding ElementName=nameText}" /> + <ghfvs:PromptTextBox x:Name="nameText" + Grid.Row="0" + Grid.Column="1" + MaxLength="{x:Static ghfvs:Constants.MaxRepositoryNameLength}" + Text="{Binding RepositoryName, UpdateSourceTrigger=PropertyChanged}" + SpellCheck.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.RepositoryNameTextBox}" /> + + <StackPanel Grid.Row="1" Grid.Column="1"> + <ghfvs:ValidationMessage x:Name="nameValidationMessage" ValidatesControl="{Binding ElementName=nameText}" /> + <ghfvs:ValidationMessage x:Name="safeRepositoryNameWarning" + ErrorAdornerTemplate="None" + Fill="#f39c12" + Icon="alert" + Style="{DynamicResource InlineValidationMessage}" + ValidatesControl="{Binding ElementName=nameText}" /> + </StackPanel> + + <Label Grid.Row="2" + Grid.Column="0" + Content="{x:Static prop:Resources.Description}" + Target="{Binding ElementName=description}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.RepositoryDescriptionTextBlock}" /> + <ghfvs:PromptTextBox x:Name="description" + Grid.Row="2" + Grid.Column="1" + Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" + SpellCheck.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.RepositoryDescriptionTextBox}" /> + + <Label Grid.Row="3" + Grid.Column="0" + Content="{x:Static prop:Resources.localPathText}" + Target="{Binding ElementName=localPathText}" /> + <Grid Grid.Row="3" Grid.Column="1"> + + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + + <ghfvs:PromptTextBox x:Name="localPathText" + Grid.Row="0" + Grid.Column="0" + Text="{Binding BaseRepositoryPath, UpdateSourceTrigger=PropertyChanged}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CreateRepositoryLocalPathTextBox}" /> + <Button x:Name="browsePathButton" + Grid.Row="0" + Grid.Column="1" + Margin="3,0,0,0" + VerticalContentAlignment="Center" + Content="{x:Static prop:Resources.browsePathButtonContent}" + Padding="0" + Style="{StaticResource GitHubBlueLinkButton}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.CreateRepositoryLocalPathBrowseButton}" /> + </Grid> + + <ghfvs:ValidationMessage x:Name="pathValidationMessage" + Grid.Row="4" + Grid.Column="1" + ValidatesControl="{Binding ElementName=localPathText}" /> + + <Label Grid.Row="5" + Grid.Column="0" + Content="{x:Static prop:Resources.ignoreTemplateListText}" + Target="{Binding ElementName=ignoreTemplateList}" /> + <ghfvs:FilteredComboBox x:Name="ignoreTemplateList" + Grid.Row="5" + Grid.Column="1" + ItemsSource="{Binding GitIgnoreTemplates}" + SelectedItem="{Binding SelectedGitIgnoreTemplate}" + Style="{StaticResource GitHubFilterComboBox}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GitignoreComboBox}" > + <ComboBox.ItemTemplate> + <DataTemplate> + <TextBlock x:Name="itemName" FontWeight="{Binding Recommended, Converter={ghfvs:BooleanToFontWeightConverter}}" Text="{Binding Name}" /> + <DataTemplate.Triggers> + <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBoxItem}}" Value="{x:Null}"> + <Setter TargetName="itemName" Property="FontWeight" Value="Normal" /> + </DataTrigger> + </DataTemplate.Triggers> + </DataTemplate> + </ComboBox.ItemTemplate> + </ghfvs:FilteredComboBox> + + <Label Grid.Row="6" + Grid.Column="0" + Content="{x:Static prop:Resources.licenseListText}" + Target="{Binding ElementName=licenseList}" /> + <ghfvs:FilteredComboBox x:Name="licenseList" + Grid.Row="6" + Grid.Column="1" + Style="{StaticResource GitHubFilterComboBox}" + ItemsSource="{Binding Licenses}" + SelectedItem="{Binding SelectedLicense}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.LicenseComboBox}" > + <ComboBox.ItemTemplate> + <DataTemplate> + <TextBlock x:Name="itemName" FontWeight="{Binding Recommended, Converter={ghfvs:BooleanToFontWeightConverter}}" Text="{Binding Name}" /> + <DataTemplate.Triggers> + <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBoxItem}}" Value="{x:Null}"> + <Setter TargetName="itemName" Property="FontWeight" Value="Normal" /> + </DataTrigger> + </DataTemplate.Triggers> + </DataTemplate> + </ComboBox.ItemTemplate> + </ghfvs:FilteredComboBox> + + <ghfvs:HorizontalShadowDivider Grid.Row="7" Grid.ColumnSpan="2" Margin="0,12" /> + + <ghfvs:GitHubComboBox x:Name="accountsComboBox" + Grid.Row="8" + Grid.Column="1" + Margin="0,0,0,8" + ItemsSource="{Binding Accounts}" + SelectedItem="{Binding SelectedAccount}" + Style="{StaticResource GitHubComboBox}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.AccountComboBox}" > + <ComboBox.ItemTemplate> + <DataTemplate> + <StackPanel Orientation="Horizontal"> + <Image Width="16" + Height="16" + Margin="0,0,8,0" + RenderOptions.BitmapScalingMode="HighQuality" + Source="{Binding Avatar}" /> + <TextBlock Text="{Binding Login}" /> + </StackPanel> + </DataTemplate> + </ComboBox.ItemTemplate> + </ghfvs:GitHubComboBox> + + <CheckBox x:Name="makePrivate" + Grid.Row="9" + Grid.Column="1" + Content="{x:Static prop:Resources.makePrivateContent}" + Padding="8,0,0,0" + Style="{DynamicResource BlueRoundedCheckBox}" + IsChecked="{Binding KeepPrivate}" + IsEnabled="{Binding CanKeepPrivate}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PrivateRepositoryCheckBox}" /> + </Grid> + + <ghfvs:UserErrorMessages x:Name="userErrorMessages" + Margin="0" + HorizontalAlignment="Center" + HorizontalContentAlignment="Stretch" /> + </StackPanel> + + </DockPanel> +</local:GenericRepositoryCreationView> diff --git a/src/GitHub.VisualStudio/Views/Dialog/RepositoryCreationView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCreationView.xaml.cs new file mode 100644 index 0000000000..6e1ab5f2d5 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/RepositoryCreationView.xaml.cs @@ -0,0 +1,58 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows.Input; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.UI; +using GitHub.UserErrors; +using GitHub.ViewModels.Dialog; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.Dialog +{ + public class GenericRepositoryCreationView : ViewBase<IRepositoryCreationViewModel, RepositoryCreationView> + { } + + /// <summary> + /// Interaction logic for NewRepositoryCreationView.xaml + /// </summary> + [ExportViewFor(typeof(IRepositoryCreationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class RepositoryCreationView : GenericRepositoryCreationView + { + public RepositoryCreationView() + { + InitializeComponent(); + + var clearErrorWhenChanged = this.WhenAny( + x => x.ViewModel.RepositoryName, + x => x.ViewModel.Description, + x => x.ViewModel.BaseRepositoryPath, + (x, y, z) => new { x, y, z }) + .WhereNotNull() + .Select(x => true); + + this.WhenActivated(d => + { + d(this.OneWayBind(ViewModel, vm => vm.RepositoryNameValidator, v => v.nameValidationMessage.ReactiveValidator)); + d(this.OneWayBind(ViewModel, vm => vm.SafeRepositoryNameWarningValidator, v => v.safeRepositoryNameWarning.ReactiveValidator)); + + 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)); + + d(userErrorMessages.RegisterHandler<PublishRepositoryUserError>(clearErrorWhenChanged)); + }); + IsVisibleChanged += (s, e) => + { + if (IsVisible) + this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); + }; + } + } +} diff --git a/src/GitHub.VisualStudio/Views/Dialog/RepositoryRecloneView.xaml b/src/GitHub.VisualStudio/Views/Dialog/RepositoryRecloneView.xaml new file mode 100644 index 0000000000..61b3c13a02 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/RepositoryRecloneView.xaml @@ -0,0 +1,161 @@ +<local:GenericRepositoryRecloneView x:Class="GitHub.VisualStudio.Views.Dialog.RepositoryRecloneView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views.Dialog" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + d:DesignHeight="440" + d:DesignWidth="414" + Background="Transparent" + mc:Ignorable="d"> + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:RepositoryRecloneViewModelDesigner> + <ghfvs:RepositoryRecloneViewModelDesigner.SelectedRepository> + <ghfvs:RemoteRepositoryModelDesigner> + <ghfvs:RemoteRepositoryModelDesigner.CloneUrl>https://github.com/github/VisualStudio</ghfvs:RemoteRepositoryModelDesigner.CloneUrl> + </ghfvs:RemoteRepositoryModelDesigner> + </ghfvs:RepositoryRecloneViewModelDesigner.SelectedRepository> + </ghfvs:RepositoryRecloneViewModelDesigner> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel LastChildFill="True"> + <DockPanel.Resources> + <Style x:Key="repositoryBorderStyle" TargetType="Border"> + <Setter Property="BorderBrush" Value="#EAEAEA" /> + <Setter Property="BorderThickness" Value="0,0,0,1" /> + <Setter Property="VerticalAlignment" Value="Center" /> + <Setter Property="Height" Value="30" /> + <Setter Property="Margin" Value="0" /> + </Style> + </DockPanel.Resources> + + <ghfvs:OcticonCircleButton x:Name="cloneButton" + Margin="12" + HorizontalAlignment="Center" + DockPanel.Dock="Bottom" + Icon="check" + IsDefault="True" + Command="{Binding CloneCommand}" + > + <TextBlock Text="{x:Static prop:Resources.CloneLink}" /> + </ghfvs:OcticonCircleButton> + + <Border x:Name="repositoriesListPane" + Margin="0" + BorderBrush="#EAEAEA" + BorderThickness="0,1" + FocusManager.IsFocusScope="True" + FocusVisualStyle="{x:Null}"> + <Border.Resources> + <Style BasedOn="{StaticResource GitHubTextBlock}" TargetType="{x:Type ghfvs:TrimmedTextBlock}"> + <Style.Triggers> + <Trigger Property="IsTextTrimmed" Value="True"> + <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" /> + </Trigger> + </Style.Triggers> + </Style> + <Style x:Key="cloneRepoHeaderStyle" TargetType="TextBlock"> + <Setter Property="Foreground" Value="{DynamicResource GHTextBrush}" /> + <Setter Property="Margin" Value="0,6,12,6" /> + </Style> + </Border.Resources> + <StackPanel> + <ghfvs:OcticonImage Icon="logo_github" Width="100" Height="100" VerticalAlignment="Top" Margin="0,20,0,10"/> + <ghfvs:HorizontalShadowDivider /> + <Grid HorizontalAlignment="Center" Margin="0,20,0,0" MaxWidth="400"> + <Grid.Resources> + <Style x:Key="Light" TargetType="Label"> + <Setter Property="Opacity" Value="0.6"/> + </Style> + <Style TargetType="TextBlock"> + <Setter Property="Margin" Value="5,0"/> + <Setter Property="VerticalAlignment" Value="Center"/> + </Style> + </Grid.Resources> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition/> + </Grid.ColumnDefinitions> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <Label Style="{StaticResource Light}" Grid.Row="0" HorizontalAlignment="Left" Margin="6,0,0,0">Repo Name</Label> + <Label Style="{StaticResource Light}" Grid.Row="1" HorizontalAlignment="Right">Organization</Label> + <Label Style="{StaticResource Light}" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Right">Source</Label> + <Label Grid.Row="3" + Grid.Column="0" + Style="{StaticResource Light}" + HorizontalAlignment="Right" + Margin="4,0" + Content="{x:Static prop:Resources.pathText}" + Target="{Binding ElementName=clonePath}" /> + + <ghfvs:OcticonImage Grid.Row="0" Grid.Column="1" Icon="repo"/> + <ghfvs:OcticonImage Grid.Row="1" Grid.Column="1" Icon="organization"/> + <ghfvs:OcticonImage Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Icon="link"/> + + <TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding SelectedRepository.CloneUrl.RepositoryName}"/> + <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding SelectedRepository.CloneUrl.Owner}"/> + <TextBlock Grid.Row="2" Grid.Column="2" + Margin="3" + TextWrapping="Wrap" + VerticalAlignment="Center" + Text="{Binding SelectedRepository.CloneUrl, Mode=OneWay}"/> + + <Grid Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"> + <Grid.ColumnDefinitions> + <ColumnDefinition /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + + <Grid.RowDefinitions> + <RowDefinition /> + <RowDefinition Height="*" /> + </Grid.RowDefinitions> + + <ghfvs:PromptTextBox x:Name="clonePath" + Grid.Column="0" + Margin="0,2" + Text="{Binding BaseRepositoryPath, UpdateSourceTrigger=PropertyChanged}" /> + + <Button x:Name="browsePathButton" + Grid.Column="1" + Margin="4,0,4,0" + VerticalContentAlignment="Center" + Content="{x:Static prop:Resources.browsePathButtonContent}" + Padding="0" + Style="{StaticResource GitHubBlueLinkButton}" + Command="{Binding BrowseForDirectory}" /> + + <ghfvs:ValidationMessage x:Name="pathValidationMessage" + Grid.Row="1" + Grid.ColumnSpan="2" + Grid.Column="1" + ValidatesControl="{Binding ElementName=clonePath}" + ReactiveValidator="{Binding BaseRepositoryPathValidator, Mode=OneWay}" /> + </Grid> + </Grid> + </StackPanel> + </Border> + </DockPanel> +</local:GenericRepositoryRecloneView> diff --git a/src/GitHub.VisualStudio/Views/Dialog/RepositoryRecloneView.xaml.cs b/src/GitHub.VisualStudio/Views/Dialog/RepositoryRecloneView.xaml.cs new file mode 100644 index 0000000000..4b98387a68 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/Dialog/RepositoryRecloneView.xaml.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows.Input; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.UI; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + public class GenericRepositoryRecloneView : ViewBase<IRepositoryRecloneViewModel, RepositoryRecloneView> + {} + + /// <summary> + /// Interaction logic for RepositoryRecloneView.xaml + /// </summary> + [ExportViewFor(typeof(IRepositoryRecloneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class RepositoryRecloneView : GenericRepositoryRecloneView + { + public RepositoryRecloneView() + { + InitializeComponent(); + + IsVisibleChanged += (s, e) => + { + if (IsVisible) + this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); + }; + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/DirectoryIsExpandedToImageMonikerConverter.cs b/src/GitHub.VisualStudio/Views/GitHubPane/DirectoryIsExpandedToImageMonikerConverter.cs new file mode 100644 index 0000000000..e69f4c8568 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/DirectoryIsExpandedToImageMonikerConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows.Data; +using Microsoft.VisualStudio.Imaging; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Used in XAML")] + internal sealed class DirectoryIsExpandedToImageMonikerConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? KnownMonikers.FolderOpened : KnownMonikers.FolderClosed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/FileNameToImageMonikerConverter.cs b/src/GitHub.VisualStudio/Views/GitHubPane/FileNameToImageMonikerConverter.cs new file mode 100644 index 0000000000..f89de42a53 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/FileNameToImageMonikerConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Used in XAML")] + internal sealed class FileNameToImageMonikerConverter : IValueConverter + { + private readonly IVsImageService2 imageService; + + public FileNameToImageMonikerConverter() + { + imageService = (IVsImageService2)Package.GetGlobalService(typeof(SVsImageService)); + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // In design mode, imageService will be null + if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) + return default(ImageMoniker); + + return imageService.GetImageMonikerForFile((string)value); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml new file mode 100644 index 0000000000..8bd0113072 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml @@ -0,0 +1,92 @@ +<local:GenericGitHubPaneView x:Class="GitHub.VisualStudio.Views.GitHubPane.GitHubPaneView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:views="clr-namespace:GitHub.VisualStudio.Views" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + Foreground="{DynamicResource GitHubVsWindowText}" + d:DesignHeight="300" + d:DesignWidth="300" + mc:Ignorable="d"> + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + <Style x:Key="PaneHorizontalSeparator" TargetType="{x:Type Separator}"> + <Setter Property="Background" Value="{DynamicResource GitHubHeaderSeparatorBrush}" /> + <Setter Property="Height" Value="2" /> + <Setter Property="SnapsToDevicePixels" Value="True" /> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type Separator}"> + <Border Width="{TemplateBinding Width}" + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding Background}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + <views:ViewLocator x:Key="viewLocator"/> + <DataTemplate DataType="{x:Type ghfvs:ViewModelBase}"> + <ContentControl Content="{Binding Converter={StaticResource viewLocator}}"/> + </DataTemplate> + </ResourceDictionary> + </Control.Resources> + + <DockPanel> + <ghfvs:InfoPanel Name="infoPanel" + DockPanel.Dock="Top" + MessageType="{Binding MessageType}" + Message="{Binding Message}" + VerticalAlignment="Top"/> + <StackPanel DockPanel.Dock="Top" + Margin="6,9,9,5" + Orientation="Horizontal"> + <TextBlock Margin="0,-5,0,0" + FontSize="14.7" + FontWeight="SemiBold" + Foreground="{DynamicResource GitHubPaneTitleBrush}" + Text="{Binding Title}" /> + <Separator Margin="5,-2,5,0" + Foreground="{DynamicResource GitHubPaneTitleBrush}" + Style="{StaticResource TitleVerticalSeparator}" /> + <TextBlock Margin="0,-5,0,0" + VerticalAlignment="Center" + Foreground="{DynamicResource GitHubVsGrayText}" + Text="{Binding LocalRepository.Name}" /> + </StackPanel> + + <Separator Margin="0,0,0,2" + DockPanel.Dock="Top" + Style="{StaticResource PaneHorizontalSeparator}" /> + + <Grid> + <DockPanel Visibility="{Binding ContentOverride, Converter={ghfvs:EqualsToVisibilityConverter None}}"> + <ghfvs:GitHubProgressBar DockPanel.Dock="Top" + Foreground="{DynamicResource GitHubAccentBrush}" + IsIndeterminate="True" + Style="{DynamicResource GitHubProgressBar}" + Visibility="{Binding Content.Content.IsBusy, Converter={ghfvs:BooleanToHiddenVisibilityConverter}, FallbackValue=Hidden}"/> + <ContentControl Content="{Binding Content}"/> + </DockPanel> + <ghfvs:Spinner Width="48" + Height="48" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Visibility="{Binding ContentOverride, Converter={ghfvs:EqualsToVisibilityConverter Spinner}, FallbackValue=Collapsed}"/> + <Border HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + Visibility="{Binding ContentOverride, Converter={ghfvs:EqualsToVisibilityConverter Error}, FallbackValue=Collapsed}"> + <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> + <ghfvs:OcticonImage Icon="alert" Width="32" Height="32" Margin="8"/> + <TextBlock Text="{Binding Content.Content.Error.Message}" TextAlignment="Center" TextWrapping="Wrap"/> + </StackPanel> + </Border> + </Grid> + </DockPanel> +</local:GenericGitHubPaneView> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml.cs new file mode 100644 index 0000000000..aa34ad68c4 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/GitHubPaneView.xaml.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Threading; +using GitHub.Exports; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericGitHubPaneView : ViewBase<IGitHubPaneViewModel, GitHubPaneView> + { + } + + [ExportViewFor(typeof(IGitHubPaneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class GitHubPaneView : GenericGitHubPaneView + { + [ImportingConstructor] + public GitHubPaneView(INotificationDispatcher notifications) + { + InitializeComponent(); + + this.WhenActivated(d => + { + infoPanel.Visibility = Visibility.Collapsed; + d(notifications.Listen() + .ObserveOnDispatcher(DispatcherPriority.Normal) + .Subscribe(n => + { + if (n.Type == Notification.NotificationType.Error || n.Type == Notification.NotificationType.Warning) + infoPanel.MessageType = MessageType.Warning; + else + infoPanel.MessageType = MessageType.Information; + infoPanel.Message = n.Message; + })); + }); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/LoggedOutView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/LoggedOutView.xaml new file mode 100644 index 0000000000..a3582715e8 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/LoggedOutView.xaml @@ -0,0 +1,59 @@ +<local:GenericLoggedOutView x:Class="GitHub.VisualStudio.Views.GitHubPane.LoggedOutView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + DataContext="{Binding ViewModel}" + d:DesignHeight="300" + d:DesignWidth="300" + mc:Ignorable="d" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.LoggedOutViewCustom}"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel> + <StackPanel Margin="10" Orientation="Vertical"> + <ghfvs:OcticonImage Icon="mark_github" + Foreground="{DynamicResource GitHubVsWindowText}" + Margin="0,5" + Width="48" + Height="48" /> + <Label + Foreground="{DynamicResource GitHubVsWindowText}" + HorizontalAlignment="Center" + FontSize="16" + Content="Sign in to GitHub" /> + <TextBlock + TextWrapping="Wrap" + TextAlignment="Center" + HorizontalAlignment="Center" + Text="Powerful collaboration, code review, and code management for open source and private projects." /> + <StackPanel + Margin="0,5" + HorizontalAlignment="Center" + Orientation="Horizontal"> + <TextBlock + Margin="10,0" + HorizontalAlignment="Center"> + <Hyperlink Command="{Binding SignIn}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GitHubLoggedOutSignInHyperlink}" ><TextBlock Text="{x:Static prop:Resources.SignInLink}" /></Hyperlink> + </TextBlock> + + <TextBlock + Margin="10,0" + HorizontalAlignment="Center"> + <Hyperlink Command="{Binding Register}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.GitHubLoggedOutCreateAnAccountHyperlink}" ><TextBlock Text="{x:Static prop:Resources.CreateAccountLink}" /></Hyperlink> + </TextBlock> + </StackPanel> + </StackPanel> + </DockPanel> +</local:GenericLoggedOutView> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/LoggedOutView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/LoggedOutView.xaml.cs new file mode 100644 index 0000000000..c633506849 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/LoggedOutView.xaml.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.Composition; +using GitHub.Exports; +using GitHub.UI; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericLoggedOutView : ViewBase<ILoggedOutViewModel, GenericLoggedOutView> + { + } + + [ExportViewFor(typeof(ILoggedOutViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class LoggedOutView : GenericLoggedOutView + { + public LoggedOutView() + { + this.InitializeComponent(); + this.WhenActivated(d => + { + }); + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/LoginFailedView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/LoginFailedView.xaml new file mode 100644 index 0000000000..376775543c --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/LoginFailedView.xaml @@ -0,0 +1,31 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.LoginFailedView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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" + mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <StackPanel Margin="10" Orientation="Vertical"> + <ghfvs:OcticonImage Icon="mark_github" Margin="0,5" Width="48" Height="48" /> + <Label FontSize="16" + HorizontalAlignment="Center" + Content="{Binding LoginError.ErrorMessage}"/> + <TextBlock Text="{Binding LoginError.ErrorCauseOrResolution}" + TextAlignment="Center" + TextWrapping="Wrap"/> + <ghfvs:GitHubActionLink Command="{Binding OpenTeamExplorer}" + Content="Open Team Explorer" + HorizontalAlignment="Center" + Margin="0,15"/> + </StackPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/LoginFailedView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/LoginFailedView.xaml.cs new file mode 100644 index 0000000000..7335ccabdf --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/LoginFailedView.xaml.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.Composition; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(ILoginFailedViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class LoginFailedView : UserControl + { + public LoginFailedView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitHubRepositoryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitHubRepositoryView.xaml new file mode 100644 index 0000000000..4a17410582 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitHubRepositoryView.xaml @@ -0,0 +1,48 @@ +<local:GenericNotAGitHubRepositoryView x:Class="GitHub.VisualStudio.Views.GitHubPane.NotAGitHubRepositoryView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + DataContext="{Binding ViewModel}" + d:DesignHeight="300" + d:DesignWidth="300" + mc:Ignorable="d"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel> + <StackPanel Margin="10" Orientation="Vertical"> + <ghfvs:OcticonImage Icon="mark_github" + Foreground="{DynamicResource GitHubVsWindowText}" + Margin="0,5" + Width="48" + Height="48" /> + <Label + Foreground="{DynamicResource GitHubVsWindowText}" + HorizontalAlignment="Center" + FontSize="16" + Content="{x:Static prop:Resources.NotAGitHubRepository}" /> + <TextBlock + TextWrapping="Wrap" + TextAlignment="Center" + HorizontalAlignment="Center" + Text="{x:Static prop:Resources.NotAGitHubRepositoryMessage}" /> + <Button HorizontalAlignment="Center" + Margin="0,15" + Style="{DynamicResource GitHubVsPrimaryActionButton}" + Command="{Binding Publish}" > + <TextBlock Text="{x:Static prop:Resources.GetStartedText}" /> + </Button> + </StackPanel> + </DockPanel> +</local:GenericNotAGitHubRepositoryView> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitHubRepositoryView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitHubRepositoryView.xaml.cs new file mode 100644 index 0000000000..a51b3e0f53 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitHubRepositoryView.xaml.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.Composition; +using GitHub.Exports; +using GitHub.UI; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericNotAGitHubRepositoryView : ViewBase<INotAGitHubRepositoryViewModel, GenericNotAGitHubRepositoryView> + { + } + + [ExportViewFor(typeof(INotAGitHubRepositoryViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class NotAGitHubRepositoryView : GenericNotAGitHubRepositoryView + { + public NotAGitHubRepositoryView() + { + this.InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitRepositoryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitRepositoryView.xaml new file mode 100644 index 0000000000..dd33b2d76a --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitRepositoryView.xaml @@ -0,0 +1,42 @@ +<local:GenericNotAGitRepositoryView x:Class="GitHub.VisualStudio.Views.GitHubPane.NotAGitRepositoryView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + DataContext="{Binding ViewModel}" + d:DesignHeight="300" + d:DesignWidth="300" + mc:Ignorable="d"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <DockPanel> + <StackPanel Margin="10" Orientation="Vertical"> + <ghfvs:OcticonImage Icon="mark_github" + Foreground="{DynamicResource GitHubVsWindowText}" + Margin="0,5" + Width="48" + Height="48" /> + <Label + Foreground="{DynamicResource GitHubVsWindowText}" + HorizontalAlignment="Center" + FontSize="16" + Content="{x:Static prop:Resources.NotAGitRepository}" /> + <TextBlock + TextWrapping="Wrap" + TextAlignment="Center" + HorizontalAlignment="Center" + Text="{x:Static prop:Resources.NotAGitRepositoryMessage}"/> + </StackPanel> + </DockPanel> +</local:GenericNotAGitRepositoryView> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitRepositoryView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitRepositoryView.xaml.cs new file mode 100644 index 0000000000..47510b84bf --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/NotAGitRepositoryView.xaml.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.Composition; +using GitHub.Exports; +using GitHub.UI; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericNotAGitRepositoryView : ViewBase<INotAGitRepositoryViewModel, GenericNotAGitRepositoryView> + { + } + + [ExportViewFor(typeof(INotAGitRepositoryViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + + public partial class NotAGitRepositoryView : GenericNotAGitRepositoryView + { + public NotAGitRepositoryView() + { + this.InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml new file mode 100644 index 0000000000..d04daeeda1 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml @@ -0,0 +1,54 @@ +<local:GenericPullRequestCheckView x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestCheckView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.GitHubPane" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + d:DesignWidth="600" + mc:Ignorable="d"> + + <d:DesignData.DataContext> + <ghfvs:PullRequestCheckViewModelDesigner /> + </d:DesignData.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" SharedSizeGroup="ColumnZero" /> + <!-- <ColumnDefinition Width="*" SharedSizeGroup="ColumnOne" /> --> + <ColumnDefinition Width="Auto" SharedSizeGroup="ColumnTwo" /> + <ColumnDefinition Width="*"/> + <ColumnDefinition MinWidth="50" Width="Auto" SharedSizeGroup="ColumnFour" /> + </Grid.ColumnDefinitions> + + <Grid.RowDefinitions> + <RowDefinition MaxHeight="32"/> + </Grid.RowDefinitions> + + <ghfvs:OcticonImage Grid.Column="0" Margin="0 0 4 0" Icon="check" Foreground="#2cbe4e" Visibility="{Binding Status, Converter={ghfvs:EqualsToVisibilityConverter Success}}"/> + <ghfvs:OcticonImage Grid.Column="0" Margin="0 0 4 0" Icon="x" Foreground="#cb2431" Visibility="{Binding Status, Converter={ghfvs:EqualsToVisibilityConverter Failure}}"/> + <ghfvs:OcticonImage Grid.Column="0" Margin="0 0 4 0" Icon="primitive_dot" Foreground="#f1c647" Visibility="{Binding Status, Converter={ghfvs:EqualsToVisibilityConverter Pending}}"/> + <!-- + <Image Grid.Column="1" Source="{Binding Avatar}" Height="16" Width="16" /> + --> + <Label Grid.Column="1" Foreground="{DynamicResource VsBrush.WindowText}" Content="{Binding Title}"/> + <!-- + <Label Grid.Column="3" HorizontalAlignment="Right" Content="{Binding Description}" ToolTip="{Binding Description}" /> + --> + <Label Grid.Column="3" HorizontalAlignment="Right" + Visibility="{Binding DetailsUrl, Converter={ghfvs:NullToVisibilityConverter}}"> + <Hyperlink ToolTip="{Binding DetailsUrl}" Command="{Binding OpenDetailsUrl}">Details</Hyperlink> + </Label> + </Grid> +</local:GenericPullRequestCheckView> + diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml.cs new file mode 100644 index 0000000000..0bb8fe8d92 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCheckView.xaml.cs @@ -0,0 +1,36 @@ +using System; +using System.ComponentModel.Composition; +using GitHub.Exports; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericPullRequestCheckView : ViewBase<IPullRequestCheckViewModel, GenericPullRequestCheckView> { } + + [ExportViewFor(typeof(IPullRequestCheckViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestCheckView : GenericPullRequestCheckView + { + public PullRequestCheckView() + { + InitializeComponent(); + + this.WhenActivated(d => + { + d(ViewModel.OpenDetailsUrl.Subscribe(_ => DoOpenDetailsUrl())); + }); + } + + [Import] + IVisualStudioBrowser VisualStudioBrowser { get; set; } + + void DoOpenDetailsUrl() + { + var browser = VisualStudioBrowser; + browser.OpenUrl(ViewModel.DetailsUrl); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCreationView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCreationView.xaml new file mode 100644 index 0000000000..778a9ade47 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCreationView.xaml @@ -0,0 +1,189 @@ +<local:GenericPullRequestCreationView x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestCreationView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + d:DesignHeight="450" + d:DesignWidth="300" + Background="{DynamicResource GitHubVsToolWindowBackground}" + DataContext="{Binding ViewModel}" + IsEnabled="{Binding IsBusy, Converter={ghfvs:InverseBooleanConverter}}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationViewCustom}" + mc:Ignorable="d"> + + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:PullRequestCreationViewModelDesigner /> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Style x:Key="CommitListItemContainerStyle" TargetType="{x:Type ListViewItem}"> + <Setter Property="SnapsToDevicePixels" Value="True" /> + <Setter Property="Margin" Value="5" /> + <Setter Property="Padding" Value="1" /> + + <Setter Property="HorizontalContentAlignment" Value="Stretch" /> + <Setter Property="VerticalContentAlignment" Value="Center" /> + + <Setter Property="Background" Value="Transparent" /> + <Setter Property="BorderBrush" Value="Transparent" /> + <Setter Property="BorderThickness" Value="0" /> + + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type ListViewItem}"> + <Border x:Name="Bd" + Background="{TemplateBinding Background}" + BorderThickness="{TemplateBinding BorderThickness}" + Padding="{TemplateBinding Padding}"> + <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <Style x:Key="GitHubPopupThing" TargetType="{x:Type ListBox}"> + <Setter Property="ItemTemplate"> + <Setter.Value> + <DataTemplate> + <TextBlock Text="{Binding Path=Name}" /> + </DataTemplate> + </Setter.Value> + </Setter> + </Style> + + <ghfvs:BranchNameConverter x:Key="BranchNameConverter" /> + </ResourceDictionary> + </Control.Resources> + + <Grid VerticalAlignment="Top" + MaxHeight="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GenericPullRequestCreationView}}}" + Margin="0 8 0 0"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <DockPanel Grid.Row="0"> + <Grid Margin="10,-3,10,5"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + + <StackPanel Grid.Column="0" Orientation="Horizontal"> + <ghfvs:OcticonImage Foreground="{DynamicResource GitHubVsGrayText}" Icon="git_branch" /> + <ghfvs:LinkDropDown Header="Target Branch" + ItemsSource="{Binding Branches}" + SelectedItem="{Binding TargetBranch}" + ToolTip="Select a branch" + Margin="0,1,0,0" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestTargetBranchComboBox}" > + <ghfvs:LinkDropDown.ItemTemplate> + <DataTemplate DataType="models:BranchModel"> + <TextBlock> + <TextBlock.Text> + <MultiBinding Converter="{StaticResource BranchNameConverter}"> + <Binding/> + <Binding Path="ViewModel.GitHubRepository" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:PullRequestCreationView}}" /> + </MultiBinding> + </TextBlock.Text> + </TextBlock> + </DataTemplate> + </ghfvs:LinkDropDown.ItemTemplate> + <ghfvs:LinkDropDown.LinkItemTemplate> + <DataTemplate DataType="models:BranchModel"> + <TextBlock> + <TextBlock.Text> + <MultiBinding Converter="{StaticResource BranchNameConverter}"> + <Binding/> + <Binding Path="ViewModel.GitHubRepository" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:PullRequestCreationView}}" /> + </MultiBinding> + </TextBlock.Text> + </TextBlock> + </DataTemplate> + </ghfvs:LinkDropDown.LinkItemTemplate> + </ghfvs:LinkDropDown> + <ghfvs:OcticonImage Height="13" + Margin="5,2,3,0" + VerticalAlignment="Center" + Foreground="{DynamicResource GitHubVsGrayText}" + Icon="chevron_left" /> + </StackPanel> + + <ghfvs:GitHubActionLink Margin="5,0,0,0" + VerticalAlignment="Center" + HorizontalAlignment="Left" + Content="{Binding SourceBranch.Name}" + HasDropDown="False" + Grid.Column="1" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestSourceBranchHyperlink}"/> + </Grid> + </DockPanel> + + <ghfvs:PromptTextBox x:Name="titleText" + Grid.Row="1" + Margin="10,5" + Text="{Binding PRTitle, UpdateSourceTrigger=PropertyChanged}" + PromptText="{x:Static prop:Resources.TitleRequired}" + Style="{DynamicResource GitHubVsPromptTextBox}" + SpellCheck.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationTitleTextBox}"/> + + <ghfvs:PromptTextBox Grid.Row="2" + MinHeight="100" + Margin="10,5" + AcceptsReturn="True" + PromptText="{x:Static prop:Resources.Description}" + Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" + Style="{DynamicResource GitHubVsPromptTextBox}" + VerticalScrollBarVisibility="Auto" + TextWrapping="Wrap" + SpellCheck.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationDescriptionTextBox}"/> + + <ghfvs:ValidationMessage x:Name="titleValidationMessage" + Grid.Row="3" + Fill="{StaticResource GitHubWarningBrush}" + Icon="alert" + ValidatesControl="{Binding ElementName=titleText}" + ReactiveValidator="{Binding TitleValidator, Mode=OneWay}"/> + + <DockPanel Grid.Row="4" Margin="10,10,10,20" HorizontalAlignment="Stretch"> + <Button DockPanel.Dock="Right" + Margin="6,0,0,0" + HorizontalAlignment="Right" + Command="{Binding CreatePullRequest}" + Content="Create pull request" + Style="{StaticResource GitHubVsPrimaryActionButton}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationCreateButton}"/> + <Button DockPanel.Dock="Right" + HorizontalAlignment="Right" + Command="{Binding Cancel}" + Content="Cancel" + Style="{StaticResource GitHubVsButton}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationCancelButton}"/> + + </DockPanel> + </Grid> +</local:GenericPullRequestCreationView> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCreationView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCreationView.xaml.cs new file mode 100644 index 0000000000..f9eb401d4d --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestCreationView.xaml.cs @@ -0,0 +1,21 @@ +using System; +using GitHub.Exports; +using GitHub.UI; +using System.ComponentModel.Composition; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericPullRequestCreationView : ViewBase<IPullRequestCreationViewModel, GenericPullRequestCreationView> + { } + + [ExportViewFor(typeof(IPullRequestCreationViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestCreationView : GenericPullRequestCreationView + { + public PullRequestCreationView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml new file mode 100644 index 0000000000..bb3323d01e --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml @@ -0,0 +1,278 @@ +<local:GenericPullRequestDetailView x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestDetailView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:v="clr-namespace:GitHub.VisualStudio.Views" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + xmlns:vsui="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.14.0" + xmlns:theming="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging" + Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" + theming:ImageThemingUtilities.ImageBackgroundColor="{Binding RelativeSource={RelativeSource Self}, Path=Background.Color}" + DataContext="{Binding ViewModel}" + d:DesignWidth="356" + d:DesignHeight="800" + mc:Ignorable="d"> + <d:DesignProperties.DataContext> + <Binding> + <Binding.Source> + <ghfvs:PullRequestDetailViewModelDesigner SourceBranchDisplayName="shana/error-handling-a-ridiculously-long-branch-name-because-why-not" + TargetBranchDisplayName="master-is-always-stable" + CommentCount="10"> + <ghfvs:PullRequestDetailViewModelDesigner.UpdateState> + <ghfvs:PullRequestUpdateStateDesigner CommitsAhead="0" CommitsBehind="0" UpToDate="True"/> + </ghfvs:PullRequestDetailViewModelDesigner.UpdateState> + <ghfvs:PullRequestDetailViewModelDesigner.OperationError> + Unable to connect to the internets over here! + </ghfvs:PullRequestDetailViewModelDesigner.OperationError> + </ghfvs:PullRequestDetailViewModelDesigner> + </Binding.Source> + </Binding> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Style x:Key="Separator" TargetType="Rectangle"> + <Setter Property="Fill" Value="{DynamicResource GitHubHeaderSeparatorBrush}"/> + </Style> + + <!-- TODO Fix this: here we change the color of TextBlock depending on the label. + It's a hack, it will break with localization --> + <Style x:Key="StateIndicator" TargetType="TextBlock"> + <Style.Triggers> + <Trigger Property="Text" Value="OPEN"> + <Setter Property="Foreground" Value="#6CC644"/> + </Trigger> + <Trigger Property="Text" Value="CLOSED"> + <Setter Property="Foreground" Value="#BD2C00"/> + </Trigger> + <Trigger Property="Text" Value="MERGED"> + <Setter Property="Foreground" Value="#6E5494"/> + </Trigger> + </Style.Triggers> + </Style> + + <Style x:Key="CheckoutMessage" TargetType="TextBlock"> + <Setter Property="Margin" Value="0 4"/> + </Style> + + <Style x:Key="CheckoutErrorMessage" TargetType="TextBlock"> + <Setter Property="Foreground" Value="Red" /> + </Style> + + <ghfvs:AllCapsConverter x:Key="AllCaps"/> + </ResourceDictionary> + </Control.Resources> + + <FrameworkElement.CommandBindings> + <CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" /> + </FrameworkElement.CommandBindings> + + <DockPanel Grid.IsSharedSizeScope="True"> + <StackPanel DockPanel.Dock="Top" + Orientation="Vertical" + Margin="8 0 0 0"> + + <!-- Title --> + <TextBlock Style="{DynamicResource {x:Static vsui:VsResourceKeys.TextBlockEnvironment122PercentFontSizeStyleKey}}" + TextWrapping="Wrap" + Margin="0 0 5 3" + Text="{Binding Model.Title}"/> + + <!-- State and branches --> + <StackPanel Orientation="Horizontal"> + <TextBlock FontWeight="Bold" + VerticalAlignment="Center" + Text="{Binding Model.State, Converter={StaticResource AllCaps}}" + Style="{StaticResource StateIndicator}"/> + + <Rectangle Margin="9 0" Width="1" Height="12" VerticalAlignment="Center" Style="{DynamicResource Separator}" /> + + <Border Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" CornerRadius="2" Padding="5 1" Background="{DynamicResource GitHubBranchNameBackgroundBrush}"> + <TextBlock FontFamily="Consolas" TextTrimming="CharacterEllipsis" ToolTip="{Binding TargetBranchDisplayName, Mode=OneWay}" Text="{Binding TargetBranchDisplayName, Mode=OneWay}" /> + </Border> + + <ghfvs:OcticonImage Grid.Column="2" VerticalAlignment="Center" Margin="5 3" Icon="arrow_left" /> + + <Border Grid.Column="3" HorizontalAlignment="Left" VerticalAlignment="Center" CornerRadius="2" Padding="5 1" Background="{DynamicResource GitHubBranchNameBackgroundBrush}"> + <TextBlock FontFamily="Consolas" TextTrimming="CharacterEllipsis" ToolTip="{Binding SourceBranchDisplayName, Mode=OneWay}" Text="{Binding SourceBranchDisplayName, Mode=OneWay}" /> + </Border> + + </StackPanel> + + <!-- Updated at --> + <TextBlock Opacity="0.5" + Text="{Binding Model.UpdatedAt, StringFormat={x:Static prop:Resources.UpdatedFormat}, Converter={ghfvs:DurationToStringConverter}, Mode=OneWay}"/> + + <!-- Git operation error message --> + <TextBox Foreground="Red" + Margin="-2 4" + Text="{Binding OperationError, Mode=OneWay}" + Style="{StaticResource FlatReadOnlyTextBox}" + Visibility="{Binding OperationError, Converter={ghfvs:NullToVisibilityConverter}}"/> + </StackPanel> + + <Rectangle DockPanel.Dock="Top" Style="{StaticResource Separator}" Height="2" Margin="0,5,0,0"/> + + <ScrollViewer CanContentScroll="True" + Padding="8 0 0 0" + HorizontalScrollBarVisibility="Auto" + VerticalScrollBarVisibility="Auto"> + <ghfvs:ScrollingVerticalStackPanel> + + <StackPanel Orientation="Horizontal" + Margin="0 -4 0 0" + ghfvs:ScrollingVerticalStackPanel.IsFixed="true"> + <ghfvs:GitHubActionLink Margin="0 6" Command="{Binding OpenOnGitHub}"> + View on GitHub + </ghfvs:GitHubActionLink> + + <Rectangle Margin="5 0" Width="1" Height="12" VerticalAlignment="Center" Style="{DynamicResource Separator}" /> + + <!-- Checkout pull request button --> + <ghfvs:GitHubActionLink Command="{Binding Checkout}" + Content="{Binding CheckoutState.Caption}" + VerticalAlignment="Center" + TextTrimming="CharacterEllipsis" + Visibility="{Binding CheckoutState, Converter={ghfvs:NullToVisibilityConverter}}" + ToolTip="{Binding CheckoutState.ToolTip}" + ToolTipService.ShowOnDisabled="True"/> + + <!-- Pull/push buttons --> + <StackPanel Orientation="Horizontal" VerticalAlignment="Center" + Visibility="{Binding UpdateState.UpToDate, FallbackValue=Collapsed, Converter={ghfvs:BooleanToInverseVisibilityConverter}}"> + <ghfvs:OcticonImage Icon="arrow_down"/> + <TextBlock Text="{Binding UpdateState.CommitsBehind}" VerticalAlignment="Center"/> + <ghfvs:GitHubActionLink Content="Pull" + Command="{Binding Pull}" + Margin="4,0" + ToolTip="{Binding UpdateState.PullToolTip}" + ToolTipService.ShowOnDisabled="True" + VerticalAlignment="Center"/> + <ghfvs:OcticonImage Icon="arrow_up"/> + <TextBlock Text="{Binding UpdateState.CommitsAhead}" VerticalAlignment="Center"/> + <ghfvs:GitHubActionLink Content="Push" + Command="{Binding Push}" + Margin="4,0" + ToolTip="{Binding UpdateState.PushToolTip}" + ToolTipService.ShowOnDisabled="True" + VerticalAlignment="Center"/> + <!-- Sync submodules --> + <ghfvs:OcticonImage Icon="package" + Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ghfvs:BooleanToVisibilityConverter}}" /> + <TextBlock Margin="4 0 0 0" Text="{Binding UpdateState.SubmodulesToSync}" VerticalAlignment="Center" + Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ghfvs:BooleanToVisibilityConverter}}" /> + <ghfvs:GitHubActionLink Content="Sync" + Command="{Binding SyncSubmodules}" + Margin="4 0" + ToolTip="{Binding UpdateState.SyncSubmodulesToolTip}" + Visibility="{Binding UpdateState.SyncSubmodulesEnabled, FallbackValue=Collapsed, Converter={ghfvs:BooleanToVisibilityConverter}}" /> + </StackPanel> + + <!-- Branch checked out and up-to-date --> + <TextBlock VerticalAlignment="Center" TextWrapping="Wrap" + Visibility="{Binding UpdateState.UpToDate, FallbackValue=Collapsed, Converter={ghfvs:BooleanToVisibilityConverter}}"> + <TextBlock.Style> + <Style TargetType="TextBlock" BasedOn="{StaticResource CheckoutMessage}"> + <Setter Property="Visibility" Value="Collapsed"/> + <Style.Triggers> + <MultiDataTrigger> + <MultiDataTrigger.Conditions> + <Condition Binding="{Binding UpdateState.CommitsAhead}" Value="0"/> + <Condition Binding="{Binding UpdateState.CommitsBehind}" Value="0"/> + </MultiDataTrigger.Conditions> + <MultiDataTrigger.Setters> + <Setter Property="Visibility" Value="Visible"/> + </MultiDataTrigger.Setters> + </MultiDataTrigger> + </Style.Triggers> + </Style> + </TextBlock.Style> + + <ghfvs:OcticonImage Icon="check" Foreground="#6CC644" Margin="0 0 0 -4"/> + <Run Text="{x:Static prop:Resources.LocalBranchUpToDate}"/> + </TextBlock> + </StackPanel> + + <!-- Author and open time --> + <ghfvs:SectionControl Name="descriptionSection" + HeaderText="Description" + IsExpanded="True" + ghfvs:ScrollingVerticalStackPanel.IsFixed="true"> + <StackPanel Orientation="Vertical"> + <!-- View conversation on GitHub --> + <StackPanel Orientation="Horizontal" Margin="0 4 0 0"> + <v:ActorAvatarView ViewModel="{Binding Author}" + VerticalAlignment="Bottom" + Width="16" + Height="16" + Margin="0,0,0,1"/> + + <TextBlock VerticalAlignment="Center" Margin="4 0" TextWrapping="Wrap"> + <Run FontWeight="SemiBold" Text="{Binding Model.Author.Login, Mode=OneWay}" /> + <Run Text="wrote" /> + </TextBlock> + </StackPanel> + <!-- PR Body --> + <markdig:MarkdownViewer Name="bodyMarkdown" + Margin="2 4 10 6" + Markdown="{Binding Body}"/> + </StackPanel> + </ghfvs:SectionControl> + + <ghfvs:SectionControl Name="reviewsSection" + HeaderText="Reviewers" + IsExpanded="True" + Margin="0 8 0 0" + ghfvs:ScrollingVerticalStackPanel.IsFixed="true"> + <ItemsControl ItemsSource="{Binding Reviews}" Margin="0 4 12 4"> + <ItemsControl.ItemTemplate> + <DataTemplate> + <local:PullRequestReviewSummaryView/> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </ghfvs:SectionControl> + + <ghfvs:SectionControl Name="checksSection" + HeaderText="Checks" + IsExpanded="True" + Margin="0 8 0 0" + ghfvs:ScrollingVerticalStackPanel.IsFixed="true"> + <ItemsControl ItemsSource="{Binding Checks}" Margin="0 4 12 4"> + <ItemsControl.ItemsPanel> + <ItemsPanelTemplate> + <StackPanel Grid.IsSharedSizeScope="True" /> + </ItemsPanelTemplate> + </ItemsControl.ItemsPanel> + </ItemsControl> + </ghfvs:SectionControl> + + <!-- Files changed --> + <ghfvs:SectionControl Name="changesSection" + Grid.Row="4" + IsExpanded="True" + HeaderText="{Binding Files.ChangedFilesCount, StringFormat={x:Static prop:Resources.ChangesCountFormat}}" + Margin="0 8 10 0" + ghfvs:ScrollingVerticalStackPanel.IsFixed="true"/> + + <!-- Put the changes tree outside its expander, so it can scroll horizontally + while the header remains fixed --> + <local:PullRequestFilesView DataContext="{Binding Files}" + Margin="8,0,0,0" + Visibility="{Binding ElementName=changesSection, Path=IsExpanded, Converter={ghfvs:BooleanToVisibilityConverter}}"/> + </ghfvs:ScrollingVerticalStackPanel> + </ScrollViewer> + </DockPanel> +</local:GenericPullRequestDetailView> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs new file mode 100644 index 0000000000..0ebdcc6b55 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestDetailView.xaml.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Input; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI; +using GitHub.UI.Helpers; +using GitHub.ViewModels.GitHubPane; +using GitHub.VisualStudio.UI.Helpers; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericPullRequestDetailView : ViewBase<IPullRequestDetailViewModel, GenericPullRequestDetailView> + { } + + [ExportViewFor(typeof(IPullRequestDetailViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestDetailView : GenericPullRequestDetailView + { + public PullRequestDetailView() + { + InitializeComponent(); + + bodyMarkdown.PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; + changesSection.PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; + + this.WhenActivated(d => + { + d(ViewModel.OpenOnGitHub.Subscribe(_ => DoOpenOnGitHub())); + }); + } + + [Import] + IVisualStudioBrowser VisualStudioBrowser { get; set; } + + void DoOpenOnGitHub() + { + var browser = VisualStudioBrowser; + browser.OpenUrl(ViewModel.WebUrl); + } + + void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) + { + Uri uri; + + if (Uri.TryCreate(e.Parameter?.ToString(), UriKind.Absolute, out uri)) + { + VisualStudioBrowser.OpenUrl(uri); + } + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml new file mode 100644 index 0000000000..b8032cc3cc --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml @@ -0,0 +1,85 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestFileCommentsView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views.GitHubPane" + mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> + + <d:DesignData.DataContext> + <Binding Path="FileComments"> + <Binding.Source> + <ghfvs:PullRequestReviewViewModelDesigner/> + </Binding.Source> + </Binding> + </d:DesignData.DataContext> + + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + <CollectionViewSource x:Key="FileComments" Source="{Binding}"> + <CollectionViewSource.GroupDescriptions> + <PropertyGroupDescription PropertyName="RelativePath"/> + </CollectionViewSource.GroupDescriptions> + </CollectionViewSource> + </ResourceDictionary> + </UserControl.Resources> + + <ListBox ItemsSource="{Binding Source={StaticResource FileComments}}" + Background="Transparent" + BorderThickness="0" + ScrollViewer.HorizontalScrollBarVisibility="Disabled"> + <ListBox.GroupStyle> + <GroupStyle> + <GroupStyle.ContainerStyle> + <Style TargetType="{x:Type GroupItem}"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type GroupItem}"> + <Expander Background="{DynamicResource GitHubFileExpanderHeaderBackgroundBrush}" + Foreground="{DynamicResource VsBrush.WindowText}" + IsExpanded="True" + Padding="0 4 4 4"> + <Expander.Header> + <DockPanel> + <ghfvs:OcticonImage DockPanel.Dock="Left" Icon="file_text" Margin="2 0"/> + <TextBlock DockPanel.Dock="Right" Text="{Binding Items.Count, Mode=OneWay}"/> + <ghfvs:OcticonImage DockPanel.Dock="Right" Icon="comment" Margin="2 0 2 -2"/> + <ghfvs:TrimmedPathTextBlock Grid.Column="1" + FontWeight="SemiBold" + Foreground="{DynamicResource VsBrush.WindowText}" + Text="{Binding Name}" + ToolTip="{Binding Name}"/> + </DockPanel> + </Expander.Header> + <ItemsControl ItemsSource="{Binding Items}" + Grid.IsSharedSizeScope="True"> + <ItemsControl.ItemTemplate> + <DataTemplate> + <Border BorderBrush="{DynamicResource GitHubFileExpanderHeaderBackgroundBrush}" + BorderThickness="1 0 1 1"> + <ghfvs:GitHubActionLink Command="{Binding Open}" + Content="{Binding Body, Converter={ghfvs:TrimNewlinesConverter}, Mode=OneWay}" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Margin="4" + TextTrimming="CharacterEllipsis"/> + </Border> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </Expander> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + </GroupStyle.ContainerStyle> + </GroupStyle> + </ListBox.GroupStyle> + </ListBox> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs new file mode 100644 index 0000000000..ac60ef8035 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFileCommentsView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + /// <summary> + /// Interaction logic for PullRequestFileCommentsView.xaml + /// </summary> + public partial class PullRequestFileCommentsView : UserControl + { + public PullRequestFileCommentsView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml new file mode 100644 index 0000000000..009ff5a639 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml @@ -0,0 +1,122 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestFilesView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views.GitHubPane" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" + mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" + Name="root"> + + <d:DesignProperties.DataContext> + <ghfvs:PullRequestFilesViewModelDesigner/> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <ContextMenu x:Key="FileContextMenu"> + <MenuItem Header="{x:Static prop:Resources.ViewChanges}" Command="{Binding DiffFile}"/> + <MenuItem Header="{x:Static prop:Resources.ViewFile}" Command="{Binding ViewFile}"/> + <MenuItem Header="{x:Static prop:Resources.ViewChangesInSolution}" Command="{Binding DiffFileWithWorkingDirectory}"/> + <MenuItem Header="{x:Static prop:Resources.OpenFileInSolution}" Command="{Binding OpenFileInWorkingDirectory}"/> + </ContextMenu> + </ResourceDictionary> + </Control.Resources> + + <TreeView Name="changesTree" + ItemsSource="{Binding Items}" + ContextMenu="{StaticResource FileContextMenu}" + Background="Transparent" + BorderThickness="0" + Margin="0 6 0 0" + ContextMenuOpening="changesTree_ContextMenuOpening" + KeyUp="changesTree_KeyUp" + MouseDoubleClick="changesTree_MouseDoubleClick" + MouseRightButtonDown="changesTree_MouseRightButtonDown"> + <TreeView.ItemContainerStyle> + <Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}"> + <Setter Property="IsExpanded" Value="True"/> + </Style> + </TreeView.ItemContainerStyle> + <TreeView.Resources> + <local:DirectoryIsExpandedToImageMonikerConverter x:Key="DirectoryIsExpandedToImageMonikerConverter" /> + <local:FileNameToImageMonikerConverter x:Key="FileNameToImageMonikerConverter" /> + + <HierarchicalDataTemplate DataType="{x:Type ghfvs:PullRequestDirectoryNode}" + ItemsSource="{Binding Children}"> + <StackPanel Orientation="Horizontal"> + <imaging:CrispImage Moniker="{Binding Converter={StaticResource DirectoryIsExpandedToImageMonikerConverter}, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}, Path=IsExpanded}" + Width="16" Height="16" Margin="0,0,0,2"/> + <TextBlock Text="{Binding DirectoryName}" Margin="4 2" VerticalAlignment="Center"/> + </StackPanel> + </HierarchicalDataTemplate> + + <DataTemplate DataType="{x:Type ghfvs:PullRequestFileNode}"> + <StackPanel Orientation="Horizontal" + Tag="{Binding DataContext, ElementName=root}" + KeyboardNavigation.DirectionalNavigation="None"> + + <imaging:CrispImage Moniker="{Binding Converter={StaticResource FileNameToImageMonikerConverter}, Path=FileName}" + Width="16" Height="16" Margin="0,2"/> + + <TextBlock Text="{Binding FileName}" Margin="4 2" VerticalAlignment="Center"> + <TextBlock.Style> + <Style TargetType="TextBlock"> + <Style.Triggers> + <DataTrigger Binding="{Binding Status}" Value="Removed"> + <Setter Property="TextDecorations" Value="Strikethrough"/> + </DataTrigger> + <MultiDataTrigger> + <MultiDataTrigger.Conditions> + <Condition Binding="{Binding Status}" Value="Removed"/> + <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}, Path=IsSelected}" Value="False"/> + </MultiDataTrigger.Conditions> + <Setter Property="Foreground" Value="{DynamicResource GitHubDeletedFileBrush}"/> + </MultiDataTrigger> + </Style.Triggers> + </Style> + </TextBlock.Style> + </TextBlock> + + <TextBlock Text="{Binding StatusDisplay, StringFormat=[{0}]}" VerticalAlignment="Center" Opacity="0.5"> + <TextBlock.Style> + <Style TargetType="TextBlock"> + <Style.Triggers> + <DataTrigger Binding="{Binding StatusDisplay}" Value="{x:Null}"> + <Setter Property="Visibility" Value="Collapsed"/> + </DataTrigger> + </Style.Triggers> + </Style> + </TextBlock.Style> + </TextBlock> + + <TextBlock VerticalAlignment="Center" Margin="4 0"> + <TextBlock.Style> + <Style TargetType="TextBlock"> + <Style.Triggers> + <DataTrigger Binding="{Binding CommentCount}" Value="0"> + <Setter Property="Visibility" Value="Collapsed"/> + </DataTrigger> + </Style.Triggers> + </Style> + </TextBlock.Style> + <ghfvs:OcticonImage Icon="comment_discussion" Height="10" Margin="-2 0"/> + <Hyperlink Command="{Binding DataContext.OpenFirstComment, ElementName=root}" + CommandParameter="{Binding}"> + <Run Text="{Binding CommentCount, Mode=OneWay}"/> + </Hyperlink> + </TextBlock> + </StackPanel> + </DataTemplate> + </TreeView.Resources> + </TreeView> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs new file mode 100644 index 0000000000..e70e271dc3 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestFilesView.xaml.cs @@ -0,0 +1,91 @@ +using System.ComponentModel.Composition; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using GitHub.Exports; +using GitHub.UI.Helpers; +using GitHub.ViewModels.GitHubPane; +using GitHub.VisualStudio.UI.Helpers; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestFilesViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestFilesView : UserControl + { + public PullRequestFilesView() + { + InitializeComponent(); + PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + } + + void changesTree_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + ApplyContextMenuBinding<TreeViewItem>(sender, e); + } + + void changesTree_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; + (DataContext as IPullRequestFilesViewModel)?.DiffFile.Execute(file); + } + + void changesTree_MouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType<TreeViewItem>().FirstOrDefault(); + + if (item != null) + { + // Select tree view item on right click. + item.IsSelected = true; + } + } + + void ApplyContextMenuBinding<TItem>(object sender, ContextMenuEventArgs e) where TItem : Control + { + var container = (Control)sender; + var item = (e.OriginalSource as Visual)?.GetSelfAndVisualAncestors().OfType<TItem>().FirstOrDefault(); + + e.Handled = true; + + if (item != null) + { + var fileNode = item.DataContext as IPullRequestFileNode; + + if (fileNode != null) + { + foreach (var menuItem in container.ContextMenu.Items.OfType<MenuItem>()) + { + menuItem.CommandParameter = fileNode; + } + + // HACK: MenuItem doesn't re-query ICommand.CanExecute when CommandParameter changes. Force + // this to happen by resetting its DataContext to null and then to the correct value. + container.ContextMenu.DataContext = null; + container.ContextMenu.DataContext = this.DataContext; + e.Handled = false; + } + } + } + + private void changesTree_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Return) + { + var file = (e.OriginalSource as FrameworkElement)?.DataContext as IPullRequestFileNode; + + if (file != null) + { + (DataContext as IPullRequestFilesViewModel)?.DiffFile.Execute(file); + } + } + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListItemView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListItemView.xaml new file mode 100644 index 0000000000..4d7e261552 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListItemView.xaml @@ -0,0 +1,107 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestListItemView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:views="clr-namespace:GitHub.VisualStudio.Views" + mc:Ignorable="d" d:DesignWidth="300" + Padding="0 4"> + <d:DesignData.DataContext> + <ghfvs:PullRequestListItemViewModelDesigner Number="399" + Title="Let's try doing this differently" + CommentCount="4" + IsCurrent="True" + UpdatedAt="2018-01-29" + Checks="Success"> + <ghfvs:PullRequestListItemViewModelDesigner.Author> + <ghfvs:ActorViewModelDesigner Login="shana"/> + </ghfvs:PullRequestListItemViewModelDesigner.Author> + </ghfvs:PullRequestListItemViewModelDesigner> + </d:DesignData.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml"/> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <Grid> + <Image HorizontalAlignment="Left" + Stretch="None" + Opacity="0.2" + Visibility="{Binding Converter={ghfvs:EqualsToVisibilityConverter {x:Null}}}"> + <Image.Source> + <DrawingImage> + <DrawingImage.Drawing> + <GeometryDrawing Brush="{DynamicResource GitHubVsGrayText}"> + <GeometryDrawing.Geometry> + <GeometryGroup> + <RectangleGeometry Rect="0, 6, 30, 30" RadiusX="3" RadiusY="3"/> + <RectangleGeometry Rect="40, 8, 140, 10" RadiusX="3" RadiusY="3"/> + <RectangleGeometry Rect="40, 27, 100, 10" RadiusX="3" RadiusY="3"/> + </GeometryGroup> + </GeometryDrawing.Geometry> + </GeometryDrawing> + </DrawingImage.Drawing> + </DrawingImage> + </Image.Source> + </Image> + + <Grid Visibility="{Binding Converter={ghfvs:NullToVisibilityConverter}}"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" MinWidth="40"/> + </Grid.ColumnDefinitions> + <views:ActorAvatarView + Grid.Column="0" + ViewModel="{Binding Author}" + Margin="0,1,4,0" + Width="32" + Height="32" + VerticalAlignment="Top"/> + + <StackPanel Grid.Column="1" Margin="2 0 0 0"> + <TextBlock DockPanel.Dock="Left" + TextTrimming="CharacterEllipsis" + Text="{Binding Title}"> + <TextBlock.Style> + <Style TargetType="TextBlock"> + <Style.Triggers> + <DataTrigger Binding="{Binding IsCurrent}" Value="True"> + <Setter Property="FontWeight" Value="Bold"/> + </DataTrigger> + </Style.Triggers> + </Style> + </TextBlock.Style> + </TextBlock> + + <StackPanel Orientation="Horizontal"> + <TextBlock Opacity="0.5" Margin="0 0 4 0"> + <Run Text="{Binding Number, Mode=OneWay, StringFormat=#{0}}"/> + </TextBlock> + + <TextBlock Opacity="0.5" TextTrimming="CharacterEllipsis"> + <Run Text="updated"/> + <Run Text="{Binding UpdatedAt, Converter={ghfvs:DurationToStringConverter}, Mode=OneWay}"/> + by + <Run Text="{Binding Author.Login, Mode=OneWay}"/> + </TextBlock> + </StackPanel> + </StackPanel> + + <StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="8 0"> + <TextBlock Opacity="0.5" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0 0 4 0" Visibility="{Binding CommentCount, Converter={ghfvs:CountToVisibilityConverter}}"> <ghfvs:OcticonImage Icon="comment" Width="12" Height="12" Margin="0 0 0 -2"/> + <Run Text="{Binding CommentCount, Mode=OneWay}" BaselineAlignment="Bottom"/> + </TextBlock> + <ghfvs:OcticonImage Icon="check" Foreground="#2cbe4e" Visibility="{Binding Checks, Converter={ghfvs:EqualsToVisibilityConverter Success}}"/> + <ghfvs:OcticonImage Icon="x" Foreground="#cb2431" Visibility="{Binding Checks, Converter={ghfvs:EqualsToVisibilityConverter Failure}}"/> + <ghfvs:OcticonImage Icon="primitive_dot" Foreground="#f1c647" Visibility="{Binding Checks, Converter={ghfvs:EqualsToVisibilityConverter Pending}}"/> + </StackPanel> + </Grid> + </Grid> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListItemView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListItemView.xaml.cs new file mode 100644 index 0000000000..6e0bd06da5 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListItemView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + /// <summary> + /// Interaction logic for PullRequestListItemView.xaml + /// </summary> + public partial class PullRequestListItemView : UserControl + { + public PullRequestListItemView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListView.xaml new file mode 100644 index 0000000000..7f1d2cf0c6 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListView.xaml @@ -0,0 +1,179 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestListView" + Name="root" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:v="clr-namespace:GitHub.VisualStudio.Views" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestListViewCustom}"> + <d:DesignProperties.DataContext> + <ghfvs:PullRequestListViewModelDesigner Message="None" StateCaption="3 Open"/> + </d:DesignProperties.DataContext> + + <UserControl.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </UserControl.Resources> + + <DockPanel> + <DockPanel DockPanel.Dock="Top" + Margin="8"> + <ghfvs:DropDownButton DockPanel.Dock="Left" + Cursor="Hand" + Foreground="{DynamicResource GitHubActionLinkItemBrush}"> + <ghfvs:DropDownButton.DropDownContent> + <ListBox Background="{DynamicResource VsBrush.Window}" + BorderBrush="{DynamicResource VsBrush.DropDownPopupBorder}" + BorderThickness="1" + ItemsSource="{Binding States}" + Padding="2" + MinWidth="100" + SelectedItem="{Binding SelectedState}"/> + </ghfvs:DropDownButton.DropDownContent> + <TextBlock> + <Hyperlink Foreground="{DynamicResource GitHubActionLinkItemBrush}"> + <Run Text="{Binding StateCaption, Mode=OneWay}"/> + </Hyperlink> + </TextBlock> + </ghfvs:DropDownButton> + + <Separator DockPanel.Dock="Left" Style="{StaticResource VerticalSeparator}" Margin="4 0" /> + + <ghfvs:GitHubActionLink DockPanel.Dock="Left" + Command="{Binding CreatePullRequest}"> + Create New + </ghfvs:GitHubActionLink> + + <ghfvs:DropDownButton Name="authorFilterDropDown" + DockPanel.Dock="Right" + AutoCloseOnClick="False" + Cursor="Hand" + Foreground="{DynamicResource GitHubActionLinkItemBrush}" + Padding="2" + ToolTip="Filter by Author" + VerticalAlignment="Center" + Visibility="{Binding Items.Count, Converter={ghfvs:CountToVisibilityConverter}}" + PopupOpened="authorFilterDropDown_PopupOpened"> + <ghfvs:OcticonImage Icon="person" Height="12" Width="12"/> + <ghfvs:DropDownButton.DropDownContent> + <Border Background="{DynamicResource VsBrush.Window}" + BorderBrush="{DynamicResource VsBrush.DropDownPopupBorder}" + BorderThickness="1" + Padding="2" + MinWidth="150" + MaxHeight="250"> + <v:UserFilterView x:Name="authorFilter" DataContext="{Binding AuthorFilter}"/> + </Border> + </ghfvs:DropDownButton.DropDownContent> + </ghfvs:DropDownButton> + + <ghfvs:DropDownButton DockPanel.Dock="Right" + Cursor="Hand" + Foreground="{DynamicResource GitHubActionLinkItemBrush}" + Padding="2" + ToolTip="Select Fork" + VerticalAlignment="Center" + Visibility="{Binding Forks, Converter={ghfvs:NullToVisibilityConverter}}"> + <ghfvs:OcticonImage Icon="repo_forked" Height="12" Width="12"/> + <ghfvs:DropDownButton.DropDownContent> + <ListBox Background="{DynamicResource VsBrush.Window}" + BorderBrush="{DynamicResource VsBrush.DropDownPopupBorder}" + BorderThickness="1" + ItemsSource="{Binding Forks}" + Padding="2" + MinWidth="100" + SelectedItem="{Binding RemoteRepository}"> + <ListBox.ItemTemplate> + <DataTemplate> + <TextBlock> + <Run Text="{Binding Owner, Mode=OneWay}"/>/<Run Text="{Binding Name, Mode=OneWay}"/> + </TextBlock> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </ghfvs:DropDownButton.DropDownContent> + </ghfvs:DropDownButton> + + <Rectangle/> + </DockPanel> + <Grid> + <ListBox ItemsSource="{Binding ItemsView}" + BorderThickness="0" + ScrollViewer.HorizontalScrollBarVisibility="Disabled" + Visibility="{Binding Message, Converter={ghfvs:EqualsToVisibilityConverter None}}" + ContextMenuOpening="ListBox_ContextMenuOpening" + KeyDown="ListBox_KeyDown"> + <ListBox.ContextMenu> + <ContextMenu> + <MenuItem Header="{x:Static prop:Resources.Open}" Command="{Binding OpenItem}"/> + <MenuItem Header="{x:Static prop:Resources.openInBrowser}" Command="{Binding OpenItemInBrowser}"/> + </ContextMenu> + </ListBox.ContextMenu> + <ListBox.ItemContainerStyle> + <Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}"> + <Setter Property="HorizontalContentAlignment" Value="Stretch"/> + <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/> + </Style> + </ListBox.ItemContainerStyle> + <ListBox.ItemTemplate> + <DataTemplate> + <local:PullRequestListItemView/> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + + <StackPanel Margin="10" + VerticalAlignment="Center" + Visibility="{Binding Message, Converter={ghfvs:EqualsToVisibilityConverter NoOpenItems}}"> + <ghfvs:OcticonImage Icon="git_pull_request" + Margin="0,5" + Width="48" + Height="48"/> + <TextBlock Foreground="{DynamicResource GitHubVsWindowText}" + HorizontalAlignment="Center" + FontSize="16" + Margin="4" + Text="There aren't any open pull requests"/> + <TextBlock TextWrapping="Wrap" + TextAlignment="Center" + HorizontalAlignment="Center" + Margin="4" + Opacity="0.75" + Text="Pull requests let you tell others about changes you've pushed to a repository on GitHub"/> + <TextBlock TextWrapping="Wrap" + TextAlignment="Center" + HorizontalAlignment="Center" + Margin="4" + Opacity="0.75"> + To get started you can + <Hyperlink Command="{Binding CreatePullRequest}" + Foreground="{DynamicResource GitHubActionLinkItemBrush}"> + create a pull request + </Hyperlink> + </TextBlock> + </StackPanel> + + <StackPanel Margin="10" + VerticalAlignment="Center" + Visibility="{Binding Message, Converter={ghfvs:EqualsToVisibilityConverter NoItemsMatchCriteria}}"> + <ghfvs:OcticonImage Icon="git_pull_request" + Margin="0,5" + Width="48" + Height="48"/> + <TextBlock Foreground="{DynamicResource GitHubVsWindowText}" + HorizontalAlignment="Center" + FontSize="16" + Margin="4" + Text="No results matched your search."/> + </StackPanel> + </Grid> + </DockPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListView.xaml.cs new file mode 100644 index 0000000000..6ddf3b4b70 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestListView.xaml.cs @@ -0,0 +1,138 @@ +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Services; +using GitHub.UI.Helpers; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestListViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestListView : UserControl + { + IDisposable subscription; + + [ImportingConstructor] + public PullRequestListView() + { + InitializeComponent(); + + DataContextChanged += (s, e) => + { + var vm = DataContext as IPullRequestListViewModel; + subscription?.Dispose(); + subscription = null; + + if (vm != null) + { + subscription = new CompositeDisposable( + vm.AuthorFilter.WhenAnyValue(x => x.Selected) + .Skip(1) + .Subscribe(_ => authorFilterDropDown.IsOpen = false), + vm.OpenItemInBrowser.Subscribe(x => OpenInBrowser((IPullRequestListItemViewModel)x))); + } + }; + + Unloaded += (s, e) => + { + subscription?.Dispose(); + subscription = null; + }; + } + + [Import] + IVisualStudioBrowser VisualStudioBrowser { get; set; } + + void OpenInBrowser(IPullRequestListItemViewModel item) + { + var vm = DataContext as IPullRequestListViewModel; + + if (vm != null) + { + var uri = vm.RemoteRepository.CloneUrl.ToRepositoryUrl().Append("pull/" + item.Number); + VisualStudioBrowser.OpenUrl(uri); + } + } + + void ListBox_KeyDown(object sender, KeyEventArgs e) + { + var listBox = (ListBox)sender; + + if (listBox.SelectedItem != null && e.Key == Key.Enter) + { + var vm = DataContext as IPullRequestListViewModel; + var pr = (IPullRequestListItemViewModel)listBox.SelectedItem; + vm.OpenItem.Execute(pr); + } + } + + void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + var control = sender as ListBoxItem; + var pr = control?.DataContext as IPullRequestListItemViewModel; + var vm = DataContext as IPullRequestListViewModel; + + if (pr != null && vm != null) + { + vm.OpenItem.Execute(pr); + } + } + + void authorFilterDropDown_PopupOpened(object sender, EventArgs e) + { + authorFilter.FocusSearchBox(); + } + + void ListBox_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + ApplyContextMenuBinding<ListBoxItem>(sender, e); + } + + void ApplyContextMenuBinding<TItem>(object sender, ContextMenuEventArgs e) where TItem : Control + { + var container = (Control)sender; + var item = GetVisual(e.OriginalSource)?.GetSelfAndVisualAncestors().OfType<TItem>().FirstOrDefault(); + + e.Handled = true; + + if (item?.DataContext is IPullRequestListItemViewModel listItem) + { + container.ContextMenu.DataContext = this.DataContext; + + foreach (var menuItem in container.ContextMenu.Items.OfType<MenuItem>()) + { + menuItem.CommandParameter = listItem; + } + + e.Handled = false; + } + } + + Visual GetVisual(object element) + { + if (element is Visual v) + { + return v; + } + else if (element is TextElement e) + { + return e.Parent as Visual; + } + else + { + return null; + } + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml new file mode 100644 index 0000000000..beb4471a5c --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml @@ -0,0 +1,108 @@ +<local:GenericPullRequestReviewAuthoringView x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestReviewAuthoringView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:c="clr-namespace:GitHub.VisualStudio.UI.Controls;assembly=GitHub.VisualStudio.UI" + xmlns:models="clr-namespace:GitHub.Models;assembly=GitHub.App" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:theming="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging" + Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" + theming:ImageThemingUtilities.ImageBackgroundColor="{Binding RelativeSource={RelativeSource Self}, Path=Background.Color}" + mc:Ignorable="d" d:DesignWidth="356" d:DesignHeight="800"> + <d:DesignProperties.DataContext> + <sampleData:PullRequestReviewAuthoringViewModelDesigner/> + </d:DesignProperties.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <ScrollViewer> + <StackPanel Margin="8,0"> + <TextBlock FontSize="16" Margin="0 4 0 0"> + <Run>Submit your review for</Run> + <Hyperlink Command="{Binding NavigateToPullRequest}"> + <Run>#</Run><Run Text="{Binding PullRequestModel.Number, Mode=OneWay}"/> + </Hyperlink> + </TextBlock> + <TextBlock Foreground="{DynamicResource GitHubVsGrayText}" Text="{Binding PullRequestModel.Title}" TextWrapping="Wrap"/> + <TextBlock Margin="0 12 0 4">Your review summary</TextBlock> + <TextBox AcceptsReturn="True" + Background="{DynamicResource VsBrush.BrandedUIBackground}" + Foreground="{DynamicResource VsBrush.BrandedUIText}" + BorderBrush="{DynamicResource VsBrush.ActiveBorder}" + Margin="0 4" + MinHeight="120" + Padding="0 4" + TextWrapping="Wrap" + Text="{Binding Body}" + SpellCheck.IsEnabled="True"/> + + <StackPanel Margin="0 4" Orientation="Horizontal"> + <ui:DropDownButton AutoCloseOnClick="True" Foreground="{DynamicResource GHBlueLinkButtonTextBrush}"> + <ui:GitHubActionLink>Submit review</ui:GitHubActionLink> + <ui:DropDownButton.DropDownContent> + <Border Background="{DynamicResource GitHubVsToolWindowBackground}" + BorderBrush="{DynamicResource GitHubComboBoxBorderBrush}" + BorderThickness="1" + Padding="4"> + <StackPanel Background="{DynamicResource GitHubVsToolWindowBackground}"> + <ui:GitHubActionLink Command="{Binding Comment}" Margin="4">Comment only</ui:GitHubActionLink> + <ui:GitHubActionLink Command="{Binding Approve}" + Visibility="{Binding CanApproveRequestChanges, Converter={ui:BooleanToVisibilityConverter}}" + Margin="4"> + Approve + </ui:GitHubActionLink> + <ui:GitHubActionLink Command="{Binding RequestChanges}" + Visibility="{Binding CanApproveRequestChanges, Converter={ui:BooleanToVisibilityConverter}}" + Margin="4"> + Request changes + </ui:GitHubActionLink> + </StackPanel> + </Border> + </ui:DropDownButton.DropDownContent> + </ui:DropDownButton> + <Rectangle Fill="{DynamicResource GitHubHeaderSeparatorBrush}" Width="1" Height="16" Margin="4 0"/> + <ui:GitHubActionLink Command="{Binding Cancel}" VerticalAlignment="Center">Cancel</ui:GitHubActionLink> + </StackPanel> + + <TextBox Grid.Column="0" + Grid.ColumnSpan="3" + Grid.Row="2" + Foreground="Red" + Margin="0 2 0 0" + Text="{Binding OperationError, Mode=OneWay}" + VerticalAlignment="Center" + TextWrapping="Wrap" + Style="{StaticResource FlatReadOnlyTextBox}" + Visibility="{Binding OperationError, Converter={ui:NullToVisibilityConverter}}"/> + + <TabControl Margin="0 16 0 0" Style="{DynamicResource GitHubPRDetailsTabControl}"> + <TabItem Header="{Binding Files.ChangedFilesCount}" + HeaderStringFormat="Files changed ({0})" + Style="{DynamicResource GitHubPRDetailsTabItem}"> + <local:PullRequestFilesView DataContext="{Binding Files}"/> + </TabItem> + <TabItem Header="{Binding FileComments.Count}" + HeaderStringFormat="Your file comments ({0})" + Style="{DynamicResource GitHubPRDetailsTabItem}"> + <local:PullRequestFileCommentsView DataContext="{Binding FileComments}"/> + </TabItem> + </TabControl> + </StackPanel> + </ScrollViewer> + +</local:GenericPullRequestReviewAuthoringView> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml.cs new file mode 100644 index 0000000000..56a263c11a --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewAuthoringView.xaml.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.Composition; +using GitHub.Exports; +using GitHub.UI; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericPullRequestReviewAuthoringView : ViewBase<IPullRequestReviewAuthoringViewModel, GenericPullRequestReviewAuthoringView> + { } + + [ExportViewFor(typeof(IPullRequestReviewAuthoringViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestReviewAuthoringView : GenericPullRequestReviewAuthoringView + { + public PullRequestReviewAuthoringView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml new file mode 100644 index 0000000000..7f8389fb7f --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml @@ -0,0 +1,96 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestReviewSummaryView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.GitHubPane" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:c="clr-namespace:GitHub.VisualStudio.UI.Controls;assembly=GitHub.VisualStudio.UI" + xmlns:v="clr-namespace:GitHub.VisualStudio.Views" + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:vm="clr-namespace:GitHub.ViewModels.GitHubPane;assembly=GitHub.App" + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" + xmlns:sampleData="clr-namespace:GitHub.SampleData;assembly=GitHub.App" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + mc:Ignorable="d" d:DesignWidth="300" + Name="root"> + + <d:DesignData.DataContext> + <vm:PullRequestReviewSummaryViewModel Id="1" State="Commented" FileCommentCount="2"> + <vm:PullRequestReviewSummaryViewModel.User> + <ghfvs:ActorViewModel Login="meaghanlewis"/> + </vm:PullRequestReviewSummaryViewModel.User> + </vm:PullRequestReviewSummaryViewModel> + </d:DesignData.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <cache:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <Grid> + <Border CornerRadius="2" + Visibility="{Binding Id, Converter={ui:NotEqualsToVisibilityConverter {x:Null}}}"> + <Border.Style> + <Style TargetType="Border"> + <Setter Property="Padding" Value="0 5"/> + <Style.Triggers> + <DataTrigger Binding="{Binding State}" Value="Pending"> + <Setter Property="Background" Value="{DynamicResource GitHubPendingReviewBackground}"/> + <Setter Property="Padding" Value="6 2 6 4"/> + </DataTrigger> + </Style.Triggers> + </Style> + </Border.Style> + + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto"/> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> + + <Grid.RowDefinitions> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> + + <v:ActorAvatarView Grid.Column="0" + VerticalAlignment="Top" + Margin="2" + ViewModel="{Binding User}" + Width="16" + Height="16" + Command="{Binding Path=DataContext.ShowReview, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:PullRequestDetailView}}}" + CommandParameter="{Binding}" + Cursor="Hand"/> + <ui:GitHubActionLink Grid.Column="1" FontWeight="SemiBold" Margin="2 1 2 0" + Command="{Binding Path=DataContext.ShowReview, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:PullRequestDetailView}}}" + CommandParameter="{Binding}" + Content="{Binding User.Login}" + Foreground="{DynamicResource GitHubVsToolWindowText}"/> + + <Grid Grid.Column="2"> + <ui:OcticonImage Icon="check" Foreground="#2cbe4e" Visibility="{Binding State, Converter={ui:EqualsToVisibilityConverter Approved}}"/> + <ui:OcticonImage Icon="x" Foreground="#cb2431" Visibility="{Binding State, Converter={ui:EqualsToVisibilityConverter ChangesRequested}}"/> + <ui:OcticonImage Icon="comment" Visibility="{Binding State, Converter={ui:EqualsToVisibilityConverter Commented}}"/> + <ui:OcticonImage Icon="comment" Visibility="{Binding State, Converter={ui:EqualsToVisibilityConverter Dismissed}}"/> + <ui:GitHubActionLink Command="{Binding Path=DataContext.ShowReview, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:PullRequestDetailView}}}" + CommandParameter="{Binding}" + Margin="2 1 2 0" + Content="{x:Static prop:Resources.ContinueYourReview}" + Visibility="{Binding State, Converter={ui:EqualsToVisibilityConverter Pending}}"/> + </Grid> + </Grid> + </Border> + + <ui:GitHubActionLink Command="{Binding Path=DataContext.ShowReview, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:PullRequestDetailView}}}" + CommandParameter="{Binding}" + Visibility="{Binding Id, Converter={ui:EqualsToVisibilityConverter {x:Null}}}" + Content="{x:Static prop:Resources.AddYourReview}"/> + + </Grid> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs new file mode 100644 index 0000000000..a2c397b589 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestReviewSummaryView.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public partial class PullRequestReviewSummaryView : UserControl + { + public PullRequestReviewSummaryView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml new file mode 100644 index 0000000000..21aaddeecb --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml @@ -0,0 +1,108 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.GitHubPane.PullRequestUserReviewsView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views.GitHubPane" + xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf" + mc:Ignorable="d" d:DesignHeight="500" d:DesignWidth="300"> + + <d:DesignData.DataContext> + <ghfvs:PullRequestUserReviewsViewModelDesigner/> + </d:DesignData.DataContext> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI.Reactive;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/Assets/Markdown.xaml" /> + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Control.Resources> + + <ScrollViewer VerticalScrollBarVisibility="Auto"> + <StackPanel> + <StackPanel DockPanel.Dock="Top" Margin="8 0" Orientation="Vertical"> + <TextBlock FontSize="16" VerticalAlignment="Center"> + <Run>Reviews by</Run> + <Run FontWeight="SemiBold" Text="{Binding User.Login, Mode=OneWay}"/> + <Run>for</Run> + <Hyperlink Command="{Binding NavigateToPullRequest}"> + <Run>#</Run><Run Text="{Binding PullRequestNumber, Mode=OneWay}"/> + </Hyperlink> + </TextBlock> + + <TextBlock Foreground="{DynamicResource GitHubVsGrayText}" + Margin="0" + Text="{Binding PullRequestTitle}" + TextWrapping="Wrap"/> + <StackPanel Orientation="Horizontal" Margin="0 4"> + <ghfvs:AccountAvatar Account="{Binding User}" Margin="0 0 4 0" Width="16" Height="16"/> + <TextBlock Text="{Binding Reviews.Count, StringFormat={}{0} reviews}" + VerticalAlignment="Center"/> + </StackPanel> + </StackPanel> + <ItemsControl ItemsSource="{Binding Reviews}"> + <ItemsControl.ItemTemplate> + <DataTemplate> + <StackPanel> + <Rectangle Fill="{DynamicResource GitHubHeaderSeparatorBrush}" Height="1" Margin="0 4"/> + + <Expander Margin="0 0 4 0" + Foreground="{DynamicResource GitHubVsToolWindowText}" + IsExpanded="{Binding IsExpanded, Mode=OneTime}" + IsEnabled="{Binding HasDetails}"> + <Expander.Header> + <StackPanel Orientation="Horizontal"> + <ghfvs:OcticonImage Icon="check" Foreground="#2cbe4e" Visibility="{Binding Model.State, Converter={ghfvs:EqualsToVisibilityConverter Approved}}"/> + <ghfvs:OcticonImage Icon="x" Foreground="#cb2431" Visibility="{Binding Model.State, Converter={ghfvs:EqualsToVisibilityConverter ChangesRequested}}"/> + <ghfvs:OcticonImage Icon="comment" Visibility="{Binding Model.State, Converter={ghfvs:EqualsToVisibilityConverter Commented}}"/> + <ghfvs:OcticonImage Icon="comment" Visibility="{Binding Model.State, Converter={ghfvs:EqualsToVisibilityConverter Dismissed}}"/> + + <TextBlock FontWeight="SemiBold" Margin="2 0"> + <Run Text="{Binding Model.User.Login, Mode=OneWay}"/> + <Run Text="{Binding StateDisplay, Mode=OneWay}"/> + <Run FontWeight="Normal" Text="{Binding Model.SubmittedAt, Mode=OneWay, Converter={ghfvs:DurationToStringConverter}}"/> + </TextBlock> + </StackPanel> + </Expander.Header> + <StackPanel Margin="21 4 0 4" + Visibility="{Binding HasDetails, Converter={ghfvs:BooleanToVisibilityConverter}}"> + <Expander Foreground="{DynamicResource GitHubVsToolWindowText}" + IsExpanded="True" + Margin="0 4" + Visibility="{Binding Body, Converter={ghfvs:NullToVisibilityConverter}}"> + <Expander.Header> + <TextBlock FontWeight="SemiBold">Description</TextBlock> + </Expander.Header> + <markdig:MarkdownViewer Margin="22 4 0 0" Markdown="{Binding Body}"/> + </Expander> + <Expander IsExpanded="True" + Margin="0 4" + Visibility="{Binding FileComments.Count, Converter={ghfvs:CountToVisibilityConverter}}"> + <Expander.Header> + <TextBlock FontWeight="SemiBold">Comments</TextBlock> + </Expander.Header> + <local:PullRequestFileCommentsView DataContext="{Binding FileComments}" + Margin="22 4 0 0"/> + </Expander> + <Expander Margin="0 4" + Visibility="{Binding OutdatedFileComments.Count, Converter={ghfvs:CountToVisibilityConverter}}"> + <Expander.Header> + <TextBlock FontWeight="SemiBold">Outdated comments</TextBlock> + </Expander.Header> + <local:PullRequestFileCommentsView DataContext="{Binding OutdatedFileComments}" + Margin="22 4 0 0"/> + </Expander> + </StackPanel> + </Expander> + </StackPanel> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </StackPanel> + </ScrollViewer> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs new file mode 100644 index 0000000000..f3e1a31cc5 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/GitHubPane/PullRequestUserReviewsView.xaml.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.Composition; +using System.Windows.Controls; +using GitHub.Exports; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestUserReviewsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestUserReviewsView : UserControl + { + public PullRequestUserReviewsView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio/Views/TeamExplorer/RepositoryPublishView.xaml b/src/GitHub.VisualStudio/Views/TeamExplorer/RepositoryPublishView.xaml new file mode 100644 index 0000000000..ec67e1d3e8 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/TeamExplorer/RepositoryPublishView.xaml @@ -0,0 +1,164 @@ +<local:GenericRepositoryPublishView x:Class="GitHub.VisualStudio.Views.TeamExplorer.RepositoryPublishView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:local="clr-namespace:GitHub.VisualStudio.Views.TeamExplorer" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + Margin="0" + d:DesignHeight="440" + d:DesignWidth="414" + Background="{DynamicResource GitHubVsToolWindowBackground}" + d:DataContext="{d:DesignInstance Type=ghfvs:RepositoryPublishViewModelDesigner, IsDesignTimeCreatable=True}" + mc:Ignorable="d" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerSyncGitHubRepositoryPublishCustom}" + IsEnabled="{Binding IsBusy, Converter={ghfvs:InverseBooleanConverter}}"> + + <Control.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml" /> + <ghfvs:SharedDictionaryManager Source="pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml" /> + </ResourceDictionary.MergedDictionaries> + + <Style x:Key="VSStyledCheckBox" BasedOn="{StaticResource VsCheckBoxStyleKey}" TargetType="{x:Type CheckBox}"> + <Setter Property="Foreground" Value="{DynamicResource GitHubVsToolWindowText}"/> + <Setter Property="Background" Value="{DynamicResource GitHubVsToolWindowBackground}"/> + <Style.Triggers> + <Trigger Property="UIElement.IsMouseOver" Value="true"> + <Setter Property="Background" Value="{DynamicResource GitHubVsToolWindowBackground}"/> + </Trigger> + </Style.Triggers> + </Style> + <Style x:Key="VSStyledComboBox" BasedOn="{StaticResource VsComboBoxStyleKey}" TargetType="{x:Type ComboBox}"/> + <Style TargetType="{x:Type ghfvs:PromptTextBox}" BasedOn="{StaticResource TeamExplorerPromptTextBox}" /> + + <Style BasedOn="{StaticResource VSStyledComboBox}" TargetType="{x:Type ComboBox}"> + <Setter Property="Margin" Value="0,0,0,8" /> + <Setter Property="HorizontalAlignment" Value="Stretch" /> + <Setter Property="Height" Value="23" /> + </Style> + </ResourceDictionary> + </Control.Resources> + + <StackPanel Margin="8,6,18,0" + Orientation="Vertical" + Style="{DynamicResource DialogContainerStackPanel}"> + + <DockPanel Margin="0,0,0,13" VerticalAlignment="Top"> + <ghfvs:OcticonImage x:Name="octokit" + Width="32" + Height="32" + Margin="0,6,8,0" + VerticalAlignment="Center" + DockPanel.Dock="Left" + Foreground="{DynamicResource GitHubVsToolWindowText}" + Icon="mark_github" /> + <Border Height="32" + Margin="0,6,0,0" + DockPanel.Dock="Right"> + <TextBlock HorizontalAlignment="Stretch" + VerticalAlignment="Center" + Foreground="{DynamicResource GitHubVsGrayText}" + Text="{x:Static prop:Resources.RepoDoesNotHaveRemoteText}" + TextTrimming="CharacterEllipsis" + TextWrapping="Wrap" + ToolTip="{Binding Text, RelativeSource={RelativeSource Self}}" /> + </Border> + </DockPanel> + + <ComboBox x:Name="hostsComboBox" + ItemsSource="{Binding Connections, Mode=OneWay}" + SelectedItem="{Binding SelectedConnection}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerPublishHostComboBox}" > + <ComboBox.ItemTemplate> + <DataTemplate> + <StackPanel Orientation="Horizontal"> + <TextBlock Text="{Binding HostAddress.Title}" /> + </StackPanel> + </DataTemplate> + </ComboBox.ItemTemplate> + </ComboBox> + + <ComboBox x:Name="accountsComboBox" + ItemsSource="{Binding Accounts, Mode=OneWay}" + SelectedItem="{Binding SelectedAccount}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerPublishAccountComboBox}" > + <ComboBox.ItemTemplate> + <DataTemplate> + <StackPanel Orientation="Horizontal"> + <Image Width="15" + Height="15" + Margin="0,0,8,0" + RenderOptions.BitmapScalingMode="HighQuality" + Source="{Binding Avatar}" /> + <TextBlock Text="{Binding Login}" /> + </StackPanel> + </DataTemplate> + </ComboBox.ItemTemplate> + </ComboBox> + + <Grid> + <ghfvs:PromptTextBox x:Name="nameText" + Height="23" + Margin="0" + Background="{DynamicResource GitHubVsSearchBoxBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" + MaxLength="{x:Static ghfvs:Constants.MaxRepositoryNameLength}" + Text="{Binding RepositoryName}" + PromptText="{x:Static prop:Resources.RepoNameText}" + SpellCheck.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerPublishRepositoryNameTextBox}" + VerticalContentAlignment="Center"/> + </Grid> + + <Grid> + <ghfvs:PromptTextBox x:Name="description" + Height="23" + Margin="0,8,0,0" + Background="{DynamicResource GitHubVsSearchBoxBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" + Text="{Binding Description}" + TextWrapping="WrapWithOverflow" + PromptText="{x:Static prop:Resources.DescriptionOptional}" + SpellCheck.IsEnabled="True" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerPublishRepositoryDescriptionTextBox}" + VerticalContentAlignment="Center" /> + </Grid> + + <CheckBox x:Name="makePrivate" + Margin="0,8,0,0" + IsChecked="{Binding KeepPrivate}" + IsEnabled="{Binding CanKeepPrivate}" + Style="{StaticResource VSStyledCheckBox}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerPrivateRepositoryCheckBox}" > + <TextBlock Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsToolWindowText}" + TextWrapping="Wrap"> + <TextBlock.Resources> + <Style TargetType="{x:Type TextBlock}"> + <Style.Triggers> + <Trigger Property="IsEnabled" Value="False"> + <Setter Property="Opacity" Value="0.5" /> + </Trigger> + </Style.Triggers> + </Style> + </TextBlock.Resources> + <TextBlock.Inlines> + <Run Text="{x:Static prop:Resources.makePrivateContent}" /> + </TextBlock.Inlines> + </TextBlock> + </CheckBox> + + <Button x:Name="publishRepositoryButton" + Margin="0,8,0,0" + HorizontalAlignment="Left" + IsDefault="True" + Style="{StaticResource GitHubVsButton}" + AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TeamExplorerPublishRepositoryButton}" > + <TextBlock Text="{x:Static prop:Resources.publishText}" /> + </Button> + + </StackPanel> +</local:GenericRepositoryPublishView> diff --git a/src/GitHub.VisualStudio/Views/TeamExplorer/RepositoryPublishView.xaml.cs b/src/GitHub.VisualStudio/Views/TeamExplorer/RepositoryPublishView.xaml.cs new file mode 100644 index 0000000000..1868902b7d --- /dev/null +++ b/src/GitHub.VisualStudio/Views/TeamExplorer/RepositoryPublishView.xaml.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive.Linq; +using System.Windows.Input; +using GitHub.Exports; +using GitHub.Extensions; +using GitHub.Extensions.Reactive; +using GitHub.Services; +using GitHub.UI; +using GitHub.ViewModels.TeamExplorer; +using ReactiveUI; + +namespace GitHub.VisualStudio.Views.TeamExplorer +{ + public class GenericRepositoryPublishView : ViewBase<IRepositoryPublishViewModel, RepositoryPublishView> + { } + + [ExportViewFor(typeof(IRepositoryPublishViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class RepositoryPublishView : GenericRepositoryPublishView + { + [ImportingConstructor] + public RepositoryPublishView(ITeamExplorerServices teServices, INotificationDispatcher notifications) + { + InitializeComponent(); + + this.WhenActivated(d => + { + d(this.BindCommand(ViewModel, vm => vm.PublishRepository, v => v.publishRepositoryButton)); + + ViewModel.PublishRepository.Subscribe(state => + { + if (state == ProgressState.Success) + { + teServices.ShowMessage(UI.Resources.RepositoryPublishedMessage); + } + }); + + d(notifications.Listen() + .Where(n => n.Type == Notification.NotificationType.Error) + .Subscribe(n => teServices.ShowError(n.Message))); + + d(this.WhenAny(x => x.ViewModel.SafeRepositoryNameWarningValidator.ValidationResult, x => x.Value) + .WhereNotNull() + .Select(result => result?.Message) + .Subscribe(message => + { + if (!String.IsNullOrEmpty(message)) + teServices.ShowWarning(message); + else + teServices.ClearNotifications(); + })); + }); + IsVisibleChanged += (s, e) => + { + if (IsVisible) + this.TryMoveFocus(FocusNavigationDirection.First).Subscribe(); + }; + } + } +} diff --git a/src/GitHub.VisualStudio/Views/UserFilterView.xaml b/src/GitHub.VisualStudio/Views/UserFilterView.xaml new file mode 100644 index 0000000000..66c98f10ca --- /dev/null +++ b/src/GitHub.VisualStudio/Views/UserFilterView.xaml @@ -0,0 +1,52 @@ +<UserControl x:Class="GitHub.VisualStudio.Views.UserFilterView" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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.VisualStudio.Views" + mc:Ignorable="d" d:DesignHeight="200" d:DesignWidth="150"> + <d:DesignData.DataContext> + <ghfvs:UserFilterViewModelDesigner/> + </d:DesignData.DataContext> + + <DockPanel> + <ghfvs:PromptTextBox Name="searchBox" + DockPanel.Dock="Top" + Margin="2" + PromptText="Search" + Text="{Binding Filter, Delay=300, UpdateSourceTrigger=PropertyChanged}"/> + <Button DockPanel.Dock="Top" + Background="Transparent" + BorderThickness="0" + Command="{Binding ClearSelection}" + Margin="2" + HorizontalContentAlignment="Stretch" + Visibility="{Binding Selected, Converter={ghfvs:NullToVisibilityConverter}}"> + <DockPanel> + <ghfvs:OcticonImage DockPanel.Dock="Right" Icon="x"/> + <TextBlock>Clear filter</TextBlock> + </DockPanel> + </Button> + <ListBox Background="Transparent" + BorderThickness="0" + ItemsSource="{Binding UsersView}" + ScrollViewer.HorizontalScrollBarVisibility="Disabled"> + <ListBox.ItemContainerStyle> + <Style TargetType="ListBoxItem"> + <EventSetter Event="MouseLeftButtonUp" Handler="ListBoxItem_MouseLeftButtonUp"/> + </Style> + </ListBox.ItemContainerStyle> + <ListBox.ItemTemplate> + <DataTemplate> + <DockPanel> + <local:ActorAvatarView ViewModel="{Binding}" Width="16" Height="16"/> + <TextBlock Margin="4" + Text="{Binding Login}" + VerticalAlignment="Center"/> + </DockPanel> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> + </DockPanel> +</UserControl> diff --git a/src/GitHub.VisualStudio/Views/UserFilterView.xaml.cs b/src/GitHub.VisualStudio/Views/UserFilterView.xaml.cs new file mode 100644 index 0000000000..2809872069 --- /dev/null +++ b/src/GitHub.VisualStudio/Views/UserFilterView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using GitHub.ViewModels; + +namespace GitHub.VisualStudio.Views +{ + public partial class UserFilterView : UserControl + { + public UserFilterView() + { + InitializeComponent(); + } + + public void FocusSearchBox() => searchBox.Focus(); + + private void ListBoxItem_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + var fe = e.Source as FrameworkElement; + + if (fe?.DataContext is IActorViewModel vm) + { + ((IUserFilterViewModel)DataContext).Selected = vm; + } + } + } +} diff --git a/src/GitHub.VisualStudio/Views/ViewLocator.cs b/src/GitHub.VisualStudio/Views/ViewLocator.cs new file mode 100644 index 0000000000..7cf357572a --- /dev/null +++ b/src/GitHub.VisualStudio/Views/ViewLocator.cs @@ -0,0 +1,87 @@ +using System; +using System.ComponentModel.Composition; +using System.Globalization; +using System.Linq; +using System.Windows.Data; +using GitHub.Factories; +using GitHub.Models; +using GitHub.ViewModels; +using static System.FormattableString; + +namespace GitHub.VisualStudio.Views +{ + /// <summary> + /// Locates a view for a view model. + /// </summary> + /// <remarks> + /// A converter of this type should be placed in the resources for top-level GHfVS controls and + /// is used as a default DataTemplate for finding a view for a view model. This is a variation + /// on the MVVM Convention over Configuration pattern[1], here using MEF to locate the view. + /// [1] http://stackoverflow.com/questions/768304 + /// </remarks> + public class ViewLocator : IValueConverter + { + private static IViewViewModelFactory factoryProvider; + + /// <summary> + /// Converts a view model into a view. + /// </summary> + /// <param name="value">The view model.</param> + /// <param name="targetType">Unused.</param> + /// <param name="parameter">Unused.</param> + /// <param name="culture">Unused.</param> + /// <returns> + /// A new instance of a view for the specified view model, or an error string if a view + /// could not be located. + /// </returns> + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var exportViewModelAttribute = value.GetType().GetCustomAttributes(typeof(ExportAttribute), false) + .OfType<ExportAttribute>() + .Where(x => typeof(IViewModel).IsAssignableFrom(x.ContractType)) + .FirstOrDefault(); + + if (exportViewModelAttribute != null) + { + var view = Factory?.CreateView(exportViewModelAttribute.ContractType); + + if (view != null) + { + var result = view; + result.DataContext = value; + return result; + } + } + +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Break(); + } +#endif + + return Invariant($"Could not locate view for '{value.GetType()}'"); + } + + /// <summary> + /// Not implemented in this class. + /// </summary> + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + private static IViewViewModelFactory Factory + { + get + { + if (factoryProvider == null) + { + factoryProvider = Services.GitHubServiceProvider.TryGetService<IViewViewModelFactory>(); + } + + return factoryProvider; + } + } + } +} diff --git a/src/GitHub.VisualStudio/packages.config b/src/GitHub.VisualStudio/packages.config index 082cba7a5e..c9fd83b206 100644 --- a/src/GitHub.VisualStudio/packages.config +++ b/src/GitHub.VisualStudio/packages.config @@ -1,18 +1,53 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="EditorUtils2013" version="1.4.1.1" targetFramework="net45" /> - <package id="Fody" version="1.28.0" targetFramework="net45" developmentDependency="true" /> - <package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" /> - <package id="NLog" version="3.1.0" targetFramework="net45" /> - <package id="NullGuard.Fody" version="1.4.1" targetFramework="net45" developmentDependency="true" /> + <package id="Expression.Blend.Sdk.WPF" version="1.0.1" targetFramework="net461" /> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Markdig.Signed" version="0.13.0" targetFramework="net461" /> + <package id="Markdig.Wpf.Signed" version="0.2.1" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ImageCatalog" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Imaging" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Sdk.BuildTasks.14.0" version="14.0.215" targetFramework="net461" developmentDependency="true" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Utilities" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" /> + <package id="Microsoft.VSSDK.BuildTools" version="15.0.26201" targetFramework="net461" developmentDependency="true" /> + <package id="Microsoft.VSSDK.Vsixsigntool" version="14.1.24720" targetFramework="net45" /> + <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> + <package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" /> + <package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" /> <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> - <package id="SimpleJson" version="0.38.0" targetFramework="net45" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="SerilogAnalyzer" version="0.12.0.0" targetFramework="net461" /> <package id="SQLitePCL.raw_basic" version="0.7.3.0-vs2012" targetFramework="net45" /> - <package id="Stateless" version="2.5.11" targetFramework="net45" /> - <package id="WiX.Toolset.2015" version="3.10.0.1503" targetFramework="net45" /> + <package id="Stateless" version="2.5.56.0" targetFramework="net45" /> + <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" /> </packages> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/packaging.targets b/src/GitHub.VisualStudio/packaging.targets index b2e258d187..d9755fceef 100644 --- a/src/GitHub.VisualStudio/packaging.targets +++ b/src/GitHub.VisualStudio/packaging.targets @@ -5,142 +5,28 @@ <ItemGroup> <SuppressPackaging Include="@(VSIXSourceItem)" Condition=" - '%(FileName)' == 'EnvDTE' or - '%(FileName)' == 'EnvDTE80' or - '%(FileName)' == 'Microsoft.Build.Conversion.Core' or - '%(FileName)' == 'Microsoft.Build' or - '%(FileName)' == 'Microsoft.Build.Engine' or - '%(FileName)' == 'Microsoft.Build.Framework' or - '%(FileName)' == 'Microsoft.Build.Tasks.Core' or - '%(FileName)' == 'Microsoft.Build.Utilities.Core' or - '%(FileName)' == 'Microsoft.CodeAnalysis.CSharp.Desktop' or - '%(FileName)' == 'Microsoft.CodeAnalysis.CSharp' or - '%(FileName)' == 'Microsoft.CodeAnalysis.Desktop' or - '%(FileName)' == 'Microsoft.CodeAnalysis' or - '%(FileName)' == 'Microsoft.CSharp' or - '%(FileName)' == 'Microsoft.MSXML' or - '%(FileName)' == 'Microsoft.VisualStudio.CommonIDE' or - '%(FileName)' == 'Microsoft.VisualStudio.ComponentModelHost' or - '%(FileName)' == 'Microsoft.VisualStudio.Diagnostics.Assert' or - '%(FileName)' == 'Microsoft.VisualStudio.ExtensibilityHosting' or - '%(FileName)' == 'Microsoft.VisualStudio.GraphModel' or - '%(FileName)' == 'Microsoft.VisualStudio.TemplateWizardInterface' or - '%(FileName)' == 'Microsoft.VisualStudio.Text.Internal' or - '%(FileName)' == 'microsoft.visualstudio.vcprojectengine' or - '%(FileName)' == 'Microsoft.VisualStudio.VSHelp' or - '%(FileName)' == 'Microsoft.VisualStudio.VSHelp80' or - '%(FileName)' == 'mscorlib' or - '%(FileName)' == 'stdole' or - '%(FileName)' == 'System' or - '%(FileName)' == 'System.Collections.Concurrent' or - '%(FileName)' == 'System.Collections' or - '%(FileName)' == 'System.Collections.Immutable' or - '%(FileName)' == 'System.Composition.AttributedModel' or - '%(FileName)' == 'System.Composition.Convention' or - '%(FileName)' == 'System.Composition.Hosting' or - '%(FileName)' == 'System.Composition.Runtime' or - '%(FileName)' == 'System.Composition.TypedParts' or - '%(FileName)' == 'System.Core' or - '%(FileName)' == 'System.Data.DataSetExtensions' or - '%(FileName)' == 'System.Data' or - '%(FileName)' == 'System.Net.Http' or - '%(FileName)' == 'System.Xml' or - '%(FileName)' == 'System.Xml.Linq' or - '%(FileName)' == 'System.Diagnostics.Debug' or - '%(FileName)' == 'System.Diagnostics.Tools' or - '%(FileName)' == 'System.Diagnostics.Tracing' or - '%(FileName)' == 'System.Dynamic.Runtime' or - '%(FileName)' == 'System.Globalization' or - '%(FileName)' == 'System.IO' or - '%(FileName)' == 'System.Linq' or - '%(FileName)' == 'System.Reflection' or - '%(FileName)' == 'System.Reflection.Extensions' or - '%(FileName)' == 'System.Reflection.Metadata' or - '%(FileName)' == 'System.Reflection.Primitives' or - '%(FileName)' == 'System.Resources.ResourceManager' or - '%(FileName)' == 'System.Runtime' or - '%(FileName)' == 'System.Runtime.Extensions' or - '%(FileName)' == 'System.Text.Encoding' or - '%(FileName)' == 'System.Text.Encoding.Extensions' or - '%(FileName)' == 'System.Threading' or - '%(FileName)' == 'System.Threading.Tasks.Dataflow' or - '%(FileName)' == 'System.Threading.Tasks' or - '%(FileName)' == 'VSLangProj' or - '%(FileName)' == 'VSLangProj2' or - '%(FileName)' == 'VSLangProj80' or - '%(FileName)' == 'Microsoft.VisualStudio.ProjectSystem.v14only' or - '%(FileName)' == 'Microsoft.VisualStudio.ProjectSystem.VS.V14Only' or - '%(FileName)' == 'Microsoft.VisualStudio.ProjectSystem.Utilities.v14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.ProjectSystem.Interop' or - '%(FileName)' == 'Microsoft.VisualStudio.Threading' or - '%(FileName)' == 'Microsoft.VisualStudio.Validation' or - '%(FileName)' == 'Microsoft.VisualStudio.Composition' or - '%(FileName)' == 'Microsoft.VisualStudio.Composition.Configuration' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.Interop.10.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.Interop.11.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.Interop.12.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.Interop.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.Interop' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.InteropA' or - '%(FileName)' == 'Microsoft.VisualStudio.Designer.Interfaces' or - '%(FileName)' == 'Microsoft.VisualStudio.ManagedInterfaces.9.0' or - '%(FileName)' == 'Microsoft.VisualStudio.ManagedInterfaces' or - '%(FileName)' == 'Microsoft.VisualStudio.ManagedInterfaces.WCF' or - '%(FileName)' == 'Microsoft.VisualStudio.OLE.Interop' or - '%(FileName)' == 'Microsoft.VisualStudio.ProjectAggregator' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.10.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.8.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.9.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop.10.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop.8.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop.9.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop' or - '%(FileName)' == 'Microsoft.VisualStudio.WCFReference.Interop' or - '%(FileName)' == 'Microsoft.Data.ConnectionUI' or - '%(FileName)' == 'Microsoft.VisualStudio.CoreUtility' or - '%(FileName)' == 'Microsoft.VisualStudio.Data.Core' or - '%(FileName)' == 'Microsoft.VisualStudio.Data' or - '%(FileName)' == 'Microsoft.VisualStudio.Data.Framework' or - '%(FileName)' == 'Microsoft.VisualStudio.Data.Services' or - '%(FileName)' == 'Microsoft.VisualStudio.Debugger.Engine' or - '%(FileName)' == 'Microsoft.VisualStudio.Editor' or - '%(FileName)' == 'Microsoft.VisualStudio.ImageCatalog' or - '%(FileName)' == 'Microsoft.VisualStudio.Imaging' or - '%(FileName)' == 'Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime' or - '%(FileName)' == 'Microsoft.VisualStudio.Language.Intellisense' or - '%(FileName)' == 'Microsoft.VisualStudio.Language.StandardClassification' or - '%(FileName)' == 'Microsoft.VisualStudio.Package.LanguageService.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.QualityTools.Vsip' or - '%(FileName)' == 'Microsoft.VisualStudio.Settings.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Design' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Immutable.10.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Immutable.11.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Immutable.12.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Immutable.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.11.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.12.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime' or - '%(FileName)' == 'Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime' or - '%(FileName)' == 'Microsoft.VisualStudio.Text.Data' or - '%(FileName)' == 'Microsoft.VisualStudio.Text.Logic' or - '%(FileName)' == 'Microsoft.VisualStudio.Text.UI' or - '%(FileName)' == 'Microsoft.VisualStudio.Text.UI.Wpf' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop.11.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop.12.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextManager.Interop.12.1.DesignTime' or - '%(FileName)' == 'Microsoft.VisualStudio.TextTemplating.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextTemplating.Interfaces.10.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextTemplating.Interfaces.11.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextTemplating.Interfaces.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.TextTemplating.VSHost.14.0' or - '%(FileName)' == 'Microsoft.VisualStudio.Utilities' or - '%(FileName)' == 'Microsoft.VSSDK.UnitTestLibrary' or - '%(FileName)' == 'Microsoft.Windows.Simulator.Client' + $([System.String]::Copy('%(Filename)').Contains('Microsoft.')) or + $([System.String]::Copy('%(Extension)').Contains('.dylib')) or + $([System.String]::Copy('%(Extension)').Contains('.so')) or + $([System.Text.RegularExpressions.Regex]::IsMatch('%(FullPath)', '.*\\LibGit2Sharp.NativeBinaries.*\\.*\.pdb')) "/> <VSIXSourceItem Remove="@(SuppressPackaging)" /> </ItemGroup> - <Message Text='Suppressed "%(SuppressPackaging.FileName)%(SuppressPackaging.Extension)" from being included in VSIX.' /> + <Message Importance="High" Text='Suppressed "@(SuppressPackaging)" from being included in VSIX.' /> + </Target> + + <Target Name="UpdateManifestVersion" + AfterTargets="DetokenizeVsixManifestFile" + DependsOnTargets="GetBuildVersion" + Outputs="$(IntermediateVsixManifest)"> + <Message Text="Stamping package with version '$(BuildVersion)'" /> + <XmlPoke XmlInputPath="$(IntermediateVsixManifest)" Query="/x:PackageManifest/x:Metadata/x:Identity/@Version" Value="$(BuildVersion)" Namespaces="<Namespace Prefix='x' Uri='http://schemas.microsoft.com/developer/vsx-schema/2011' />" /> + </Target> + + <!-- set AllUsers="false" and Experimental="true" when $(IsExperimental)' == 'true' --> + <Target Name="ExperimentalManifest" AfterTargets="DetokenizeVsixManifestFile" Condition=" '$(IsExperimental)' == 'true' "> + <Warning Text="NOTE: Tweaking '$(IntermediateVsixManifest)' to have AllUsers='false' and Experimental='true'" /> + <XmlPoke XmlInputPath="$(IntermediateVsixManifest)" Query="/x:PackageManifest/x:Installation/@AllUsers" Value="false" Namespaces="<Namespace Prefix='x' Uri='http://schemas.microsoft.com/developer/vsx-schema/2011' />" /> + <XmlPoke XmlInputPath="$(IntermediateVsixManifest)" Query="/x:PackageManifest/x:Installation/@Experimental" Value="true" Namespaces="<Namespace Prefix='x' Uri='http://schemas.microsoft.com/developer/vsx-schema/2011' />" /> </Target> </Project> \ No newline at end of file diff --git a/src/GitHub.VisualStudio/source.extension.vsixmanifest b/src/GitHub.VisualStudio/source.extension.vsixmanifest index bcb6aa1e5e..99c1639d68 100644 --- a/src/GitHub.VisualStudio/source.extension.vsixmanifest +++ b/src/GitHub.VisualStudio/source.extension.vsixmanifest @@ -1,28 +1,44 @@ <?xml version="1.0" encoding="utf-8"?> <PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> <Metadata> - <Identity Id="c3d3dc68-c977-411f-b3e8-03b0dccf7dfc" Version="1.0.16.1" Language="en-US" Publisher="GitHub, Inc" /> + <Identity Id="c3d3dc68-c977-411f-b3e8-03b0dccf7dfc" Version="2.5.6.0" Language="en-US" Publisher="GitHub, Inc" /> <DisplayName>GitHub Extension for Visual Studio</DisplayName> <Description xml:space="preserve">A Visual Studio Extension that brings the GitHub Flow into Visual Studio.</Description> + <PackageId>GitHub.VisualStudio</PackageId> <MoreInfo>https://visualstudio.github.com</MoreInfo> <License>LICENSE.txt</License> + <ReleaseNotes>https://visualstudio.github.com/release-notes.html</ReleaseNotes> <Icon>Resources\logo_32x32@2x.png</Icon> <PreviewImage>Resources\preview_200x200.png</PreviewImage> - <Tags>GitHub git github.com team explorer</Tags> + <Tags>GitHub;git;open source;source control;branch;pull request;team explorer;commit;publish</Tags> </Metadata> - <Installation AllUsers="true"> - <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[14.0,]" /> + <Installation AllUsers="true" Experimental="false"> + <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[14.0,15.0]" /> + <InstallationTarget Version="[15.0,16.0)" Id="Microsoft.VisualStudio.IntegratedShell" /> </Installation> <Dependencies> <Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" /> <Dependency Id="Microsoft.VisualStudio.MPF.14.0" DisplayName="Visual Studio MPF 14.0" d:Source="Installed" Version="[14.0,]" /> + <Dependency Id="Microsoft.VisualStudio.TeamFoundation.TeamExplorer.Extensions" DisplayName="Team Explorer" d:Source="Installed" Version="[14.0,16.0)" /> </Dependencies> <Assets> - <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="Rothko" Path="|Rothko|" /> + <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" /> <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.Exports" Path="|GitHub.Exports|" /> <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.Api" Path="|GitHub.Api|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.Exports.Reactive" Path="|GitHub.Exports.Reactive|" /> <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.App" Path="|GitHub.App|" /> - <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" /> - <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.Services.Vssdk" Path="|GitHub.Services.Vssdk|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.TeamFoundation.14" TargetVersion="[14.0,15.0)" Path="|GitHub.TeamFoundation.14|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.TeamFoundation.15" TargetVersion="[15.0,16.0)" Path="|GitHub.TeamFoundation.15|" /> + <!-- Sometimes the version of `ServiceHub.VSDetouredHost.exe` is used when installing for Visual Studio 2017, see https://github.com/github/VisualStudio/pull/1875 --> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.TeamFoundation.15" TargetVersion="[1.0,2.0)" Path="|GitHub.TeamFoundation.15|" /> + <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="GitHub.InlineReviewsPackage" Path="|GitHub.InlineReviews;PkgdefProjectOutputGroup|" /> + <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="GitHub.StartPage" Path="|GitHub.StartPage;PkgdefProjectOutputGroup|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="GitHub.InlineReviews" Path="|GitHub.InlineReviews|" /> + <Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="File" Path="Rothko.dll" /> </Assets> -</PackageManifest> \ No newline at end of file + <Prerequisites> + <Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0.25824.0,16.0)" DisplayName="Visual Studio core editor" /> + </Prerequisites> +</PackageManifest> diff --git a/src/GitHub.VisualStudio/versioning.targets b/src/GitHub.VisualStudio/versioning.targets new file mode 100644 index 0000000000..0d5eb330ee --- /dev/null +++ b/src/GitHub.VisualStudio/versioning.targets @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + + <UsingTask + TaskName="GenerateVersion" + TaskFactory="CodeTaskFactory" + AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> + + <ParameterGroup> + <SolutionInfoFile ParameterType="System.String" Required="true" /> + <GeneratedVersion ParameterType="System.String" Output="true" /> + </ParameterGroup> + <Task> + <Using Namespace="System" /> + <Using Namespace="System.IO" /> + <Using Namespace="System.Text.RegularExpressions" /> + <Code Type="Fragment" Language="cs"> + <![CDATA[ + var str = File.ReadAllText(this.SolutionInfoFile); + var regex = new Regex(@"Version = ""([\d]+\.[\d]+\.[\d]+.[\d]+)"""); + var v = Version.Parse(regex.Match(str).Groups[1].Value); + this.GeneratedVersion = String.Format("{0}.{1}.{2}.{3}", v.Major, v.Minor, v.Build, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); + ]]> + </Code> + </Task> + </UsingTask> + + <Target Name="GenerateVersionFile" + Condition="$(SkipPackage) != 'Skip'" + AfterTargets="CoreCompile" + Inputs="$(SolutionDir)\src\common\SolutionInfo.cs" + Outputs="$(SolutionDir)\build\version"> + <GenerateVersion SolutionInfoFile="$(SolutionDir)\src\common\SolutionInfo.cs"> + <Output TaskParameter="GeneratedVersion" PropertyName="GeneratedVersion" /> + </GenerateVersion> + + <Message Text="Generating file '$(SolutionDir)\build\version' with version '$(GeneratedVersion)'" /> + + <WriteLinesToFile File="$(SolutionDir)\build\version" Lines="$(GeneratedVersion)" Overwrite="True" /> + </Target> + + <Target Name="GetBuildVersion" Returns="$(BuildVersion)" DependsOnTargets="GenerateVersionFile"> + <ReadLinesFromFile File="$(SolutionDir)\build\version"> + <Output TaskParameter="Lines" PropertyName="BuildVersion" /> + </ReadLinesFromFile> + </Target> +</Project> \ No newline at end of file diff --git a/src/MsiInstaller/MsiInstaller.wixproj b/src/MsiInstaller/MsiInstaller.wixproj deleted file mode 100644 index 93bf7dc1b4..0000000000 --- a/src/MsiInstaller/MsiInstaller.wixproj +++ /dev/null @@ -1,79 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">x86</Platform> - <ProductVersion>3.9</ProductVersion> - <ProjectGuid>1ed83084-2a57-4f89-915c-8a2167c0d6bc</ProjectGuid> - <SchemaVersion>2.0</SchemaVersion> - <OutputName>ghfvs</OutputName> - <OutputType>Package</OutputType> - <WixTargetsPath Condition=" '$(WixTargetsPath)' == '' AND '$(MSBuildExtensionsPath32)' != '' ">$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath> - <WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets</WixTargetsPath> - <SuppressValidation>False</SuppressValidation> - <SuppressIces>ICE80</SuppressIces> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> - <OutputPath>bin\$(Configuration)\</OutputPath> - <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> - <DefineConstants>Debug</DefineConstants> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> - <OutputPath>bin\$(Configuration)\</OutputPath> - <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Publish|x86' "> - <OutputPath>..\..\build\Release\</OutputPath> - <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> - <SuppressAllWarnings>False</SuppressAllWarnings> - <Pedantic>True</Pedantic> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Publish|AnyCPU' "> - <OutputPath>..\..\build\Release\</OutputPath> - <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> - <SuppressAllWarnings>False</SuppressAllWarnings> - <Pedantic>True</Pedantic> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Publish|Any CPU' "> - <OutputPath>..\..\build\Release\</OutputPath> - <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> - <SuppressAllWarnings>False</SuppressAllWarnings> - <Pedantic>True</Pedantic> - </PropertyGroup> - <ItemGroup> - <Compile Include="Product.wxs" /> - </ItemGroup> - <ItemGroup> - <WixExtension Include="WixUtilExtension"> - <HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath> - <Name>WixUtilExtension</Name> - </WixExtension> - <WixExtension Include="WixVSExtension"> - <HintPath>$(WixExtDir)\WixVSExtension.dll</HintPath> - <Name>WixVSExtension</Name> - </WixExtension> - </ItemGroup> - <ItemGroup> - <Content Include="packages.config" /> - <Content Include="ProductInfo.wxi" /> - <Content Include="Version.wxi" /> - <Content Include="Vsix.wxi" /> - </ItemGroup> - <PropertyGroup> - <WixToolPath>$(SolutionDir)packages\WiX.Toolset.2015.3.10.0.1503\tools\wix\</WixToolPath> - <WixTargetsPath>$(WixToolPath)wix.targets</WixTargetsPath> - <WixTasksPath>$(WixToolPath)WixTasks.dll</WixTasksPath> - </PropertyGroup> - <Import Project="$(WixTargetsPath)" /> - <PropertyGroup> - <PostBuildEvent /> - </PropertyGroup> - <!-- - To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Wix.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> -</Project> \ No newline at end of file diff --git a/src/MsiInstaller/Product.wxs b/src/MsiInstaller/Product.wxs deleted file mode 100644 index 801735ab9b..0000000000 --- a/src/MsiInstaller/Product.wxs +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension" xmlns:VSExtension="http://schemas.microsoft.com/wix/VSExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> - - <?define UpgradeCode="BA510407-937D-43AB-A0D0-A23EB3EFE376" ?> - - <?include Version.wxi ?> - <?include ProductInfo.wxi ?> - <?include Vsix.wxi ?> - - <Product Id="*" Name="$(var.Name)" Language="1033" Version="$(var.VersionNumber)" - Manufacturer="$(var.Manufacturer)" UpgradeCode="$(var.UpgradeCode)"> - <Package Id="*" InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Description="$(var.Description)" Manufacturer="$(var.Manufacturer)"/> - <Icon Id="logo.ico" SourceFile="logo.ico"/> - <Property Id="ARPPRODUCTICON" Value="logo.ico" /> - - <CustomActionRef Id="ExitEarlyWithSuccess"/> - - <Upgrade Id="$(var.UpgradeCode)"> - <UpgradeVersion Minimum="$(var.VersionNumber)" Maximum="$(var.VersionNumber)" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="yes" Property="SELFFOUND" /> - <UpgradeVersion Minimum="$(var.VersionNumber)" Maximum="99.99.99" IncludeMinimum="no" IncludeMaximum="yes" OnlyDetect="yes" Property="NEWERFOUND" /> - <UpgradeVersion Maximum="$(var.VersionNumber)" IncludeMaximum="no" Property="OLDERFOUND" /> - </Upgrade> - - <MajorUpgrade MigrateFeatures="yes" AllowDowngrades="yes" /> - - <MediaTemplate EmbedCab="yes" /> - - <Feature Id="VsixFeature" Title="$(var.Name)" Level="1"> - <ComponentGroupRef Id="VsixComponents" /> - </Feature> - <Feature Id="RegistryKeyFeature" Level="1"> - <ComponentGroupRef Id="RegistryEntries"/> - </Feature> - - </Product> - - <Fragment> - <CustomAction Id="ExitEarlyWithSuccess" BinaryKey="WixCA" DllEntry="WixExitEarlyWithSuccess" Execute="immediate" Return="check" SuppressModularization="yes" /> - <InstallExecuteSequence> - <Custom Action="ExitEarlyWithSuccess" After="FindRelatedProducts" Overridable="yes">(NEWERFOUND OR SELFFOUND) AND VersionNT > 400</Custom> - </InstallExecuteSequence> - </Fragment> - - <Fragment> - <util:RegistrySearch Id="RegistryVsixVersion" Variable="InstalledVsixVersion" Root="HKCU" Key="[RegistryRoot]" Result="value" Value="Version"/> - </Fragment> - <Fragment> - <Directory Id="TARGETDIR" Name="SourceDir"> - <Directory Id="ProgramFilesFolder"> - <Directory Id="INSTALLFOLDER" Name="$(var.Name)" /> - </Directory> - </Directory> - </Fragment> - <Fragment> - <ComponentGroup Id="VsixComponents" Directory="INSTALLFOLDER"> - <Component Id="VsixComponent"> - <File Id="VsixFile" Name="$(var.VsixFile)" Source="$(var.VsixPath)\$(var.VsixFile)"> - <VSExtension:VsixPackage PackageId="$(var.VsixId)" Vital="yes" Permanent="no"/> - </File> - </Component> - </ComponentGroup> - </Fragment> - <Fragment> - <ComponentGroup Id="RegistryEntries" Directory="TARGETDIR"> -<?if $(var.Platform) = "x64"?> - <Component Id="RegistryEntries64" Guid="3146D17D-1029-4CFD-BC9E-77FA80651F68" Win64="yes"> - <RegistryKey Root="HKLM" Key="$(var.RegistryInstaller)" ForceCreateOnInstall="yes"> - <RegistryValue Name="Version" Type="string" Value="$(var.VersionNumber)" KeyPath="yes" /> - <RegistryValue Name="ProductCode" Type="string" Value="[ProductCode]" /> - </RegistryKey> - </Component> -<?endif ?> - <Component Id="RegistryEntries32" Guid="3146D17D-1029-4CFD-BC9E-77FA80651F67" Win64="no"> - <RegistryKey Root="HKLM" Key="$(var.RegistryInstaller)" ForceCreateOnInstall="yes"> - <RegistryValue Name="Version" Type="string" Value="$(var.VersionNumber)" KeyPath="yes" /> - <RegistryValue Name="ProductCode" Type="string" Value="[ProductCode]" /> - </RegistryKey> - </Component> - </ComponentGroup> - </Fragment> -</Wix> diff --git a/src/MsiInstaller/ProductInfo.wxi b/src/MsiInstaller/ProductInfo.wxi deleted file mode 100644 index 6899073da2..0000000000 --- a/src/MsiInstaller/ProductInfo.wxi +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Include> - <?define Name="GitHub Extension for Visual Studio" ?> - <?define Manufacturer="GitHub, Inc." ?> - <?define RegistryRoot="Software\GitHub\VisualStudio" ?> - <?define RegistryInstaller="Software\GitHub\VisualStudio\Installer" ?> - <?define VsixId="c3d3dc68-c977-411f-b3e8-03b0dccf7dfc" ?> - <?define DowngradeMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features." ?> - <?define Description="A Visual Studio Extension that brings the GitHub Flow into Visual Studio." ?> -</Include> diff --git a/src/MsiInstaller/Version.wxi b/src/MsiInstaller/Version.wxi deleted file mode 100644 index 15db958606..0000000000 --- a/src/MsiInstaller/Version.wxi +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Include> - <?define VersionNumber="1.0.16.1" ?> -</Include> diff --git a/src/MsiInstaller/Vsix.wxi b/src/MsiInstaller/Vsix.wxi deleted file mode 100644 index 8b6fd17aa5..0000000000 --- a/src/MsiInstaller/Vsix.wxi +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Include> - <?define VsixFile="GitHub.VisualStudio.vsix" ?> - <?define VsixPath="$(var.TargetDir)" ?> -</Include> diff --git a/src/MsiInstaller/logo.ico b/src/MsiInstaller/logo.ico deleted file mode 100644 index a6a956cf31..0000000000 Binary files a/src/MsiInstaller/logo.ico and /dev/null differ diff --git a/src/MsiInstaller/packages.config b/src/MsiInstaller/packages.config deleted file mode 100644 index d1c0c4a345..0000000000 --- a/src/MsiInstaller/packages.config +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="WiX.Toolset.2015" version="3.10.0.1503" targetFramework="net4000" /> -</packages> \ No newline at end of file diff --git a/src/TrackingCollectionTests/Properties/AssemblyInfo.cs b/src/TrackingCollectionTests/Properties/AssemblyInfo.cs deleted file mode 100644 index b5ab5888fc..0000000000 --- a/src/TrackingCollectionTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TrackingCollectionTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TrackingCollectionTests")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7b835a7d-cf94-45e8-b191-96f5a4fe26a8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/TrackingCollectionTests/packages.config b/src/TrackingCollectionTests/packages.config deleted file mode 100644 index 54b07dd61f..0000000000 --- a/src/TrackingCollectionTests/packages.config +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="NUnit" version="2.6.4" targetFramework="net452" /> - <package id="NUnit.Runners" version="2.6.4" targetFramework="net452" /> - <package id="NUnitTestAdapter" version="2.0.0" targetFramework="net452" /> - <package id="Rx-Core" version="2.2.5-custom" targetFramework="net452" /> - <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net452" /> - <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net452" /> - <package id="Rx-Main" version="2.2.5-custom" targetFramework="net452" /> - <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net452" /> - <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net452" /> -</packages> \ No newline at end of file diff --git a/src/UnitTests/Args.cs b/src/UnitTests/Args.cs deleted file mode 100644 index 711ce8e52a..0000000000 --- a/src/UnitTests/Args.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using GitHub.Api; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using LibGit2Sharp; -using Microsoft.VisualStudio.Text; -using NSubstitute; -using Octokit; - -internal static class Args -{ - public static bool Boolean { get { return Arg.Any<bool>(); } } - public static int Int32 { get { return Arg.Any<int>(); } } - public static string String { get { return Arg.Any<string>(); } } - public static Span Span { get { return Arg.Any<Span>(); } } - public static SnapshotPoint SnapshotPoint { get { return Arg.Any<SnapshotPoint>(); } } - public static NewRepository NewRepository { get { return Arg.Any<NewRepository>(); } } - public static IAccount Account { get { return Arg.Any<IAccount>(); } } - public static IApiClient ApiClient { get { return Arg.Any<IApiClient>(); } } - public static IServiceProvider ServiceProvider { get { return Arg.Any<IServiceProvider>(); } } - public static IAvatarProvider AvatarProvider { get { return Arg.Any<IAvatarProvider>(); } } - public static HostAddress HostAddress { get { return Arg.Any<HostAddress>(); } } - public static Uri Uri { get { return Arg.Any<Uri>(); } } - public static LibGit2Sharp.Branch Branch { get { return Arg.Any<LibGit2Sharp.Branch>(); } } - public static Remote Remote { get { return Arg.Any<Remote>(); } } - public static Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> - TwoFactorChallengCallback - { get { return Arg.Any<Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>> (); } } -} diff --git a/src/UnitTests/FodyWeavers.xml b/src/UnitTests/FodyWeavers.xml deleted file mode 100644 index d463f76131..0000000000 --- a/src/UnitTests/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Weavers> - <EntryExitDecorator /> -</Weavers> \ No newline at end of file diff --git a/src/UnitTests/GitHub.Api/SimpleApiClientFactoryTests.cs b/src/UnitTests/GitHub.Api/SimpleApiClientFactoryTests.cs deleted file mode 100644 index af4ff26f8b..0000000000 --- a/src/UnitTests/GitHub.Api/SimpleApiClientFactoryTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using GitHub.Api; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.VisualStudio; -using NSubstitute; -using Xunit; - -public class SimpleApiClientFactoryTests -{ - public class TheCreateMethod - { - [Fact] - public void CreatesNewInstanceOfSimpleApiClient() - { - const string url = "https://github.com/github/CreatesNewInstanceOfSimpleApiClient"; - var program = new Program(); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - var factory = new SimpleApiClientFactory( - program, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - - var client = factory.Create(url); - - Assert.Equal(url, client.OriginalUrl); - Assert.Equal(HostAddress.GitHubDotComHostAddress, client.HostAddress); - Assert.Same(client, factory.Create(url)); // Tests caching. - } - } - - public class TheClearFromCacheMethod - { - [Fact] - public void RemovesClientFromCache() - { - const string url = "https://github.com/github/RemovesClientFromCache"; - var program = new Program(); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - var factory = new SimpleApiClientFactory( - program, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - - var client = factory.Create(url); - factory.ClearFromCache(client); - - Assert.NotSame(client, factory.Create(url)); - } - } -} diff --git a/src/UnitTests/GitHub.Api/SimpleApiClientTests.cs b/src/UnitTests/GitHub.Api/SimpleApiClientTests.cs deleted file mode 100644 index 552050cb73..0000000000 --- a/src/UnitTests/GitHub.Api/SimpleApiClientTests.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Threading.Tasks; -using GitHub.Api; -using GitHub.Primitives; -using GitHub.Services; -using NSubstitute; -using Octokit; -using Xunit; - -public class SimpleApiClientTests -{ - public class TheCtor : TestBaseClass - { - public void Throws() - { - Assert.Throws<ArgumentNullException>(() => new SimpleApiClient(null, null, null, null)); - Assert.Throws<ArgumentNullException>(() => new SimpleApiClient("https://github.com/github/visualstudio", null, null, null)); - } - } - - public class TheGetRepositoryMethod - { - [Fact] - public async Task RetrievesRepositoryFromWeb() - { - var gitHubHost = HostAddress.GitHubDotComHostAddress; - var gitHubClient = Substitute.For<IGitHubClient>(); - var repository = new Repository(42); - gitHubClient.Repository.Get("github", "visualstudio").Returns(Task.FromResult(repository)); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - var client = new SimpleApiClient( - "https://github.com/github/visualstudio", - gitHubClient, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - - var result = await client.GetRepository(); - - Assert.Equal(42, result.Id); - } - - [Fact] - public async Task RetrievesCachedRepositoryForSubsequentCalls() - { - var gitHubHost = HostAddress.GitHubDotComHostAddress; - var gitHubClient = Substitute.For<IGitHubClient>(); - var repository = new Repository(42); - gitHubClient.Repository.Get("github", "visualstudio") - .Returns(_ => Task.FromResult(repository), _ => { throw new Exception("Should only be called once."); }); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - var client = new SimpleApiClient( - "https://github.com/github/visualstudio", - gitHubClient, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - await client.GetRepository(); - - var result = await client.GetRepository(); - - Assert.Equal(42, result.Id); - } - } - - public class TheHasWikiMethod - { - [Theory] - [InlineData(WikiProbeResult.Ok, true)] - [InlineData(WikiProbeResult.Failed, false)] - [InlineData(WikiProbeResult.NotFound, false)] - public async Task ReturnsTrueWhenWikiProbeReturnsOk(WikiProbeResult probeResult, bool expected) - { - var gitHubHost = HostAddress.GitHubDotComHostAddress; - var gitHubClient = Substitute.For<IGitHubClient>(); - var repository = CreateRepository(42, true); - gitHubClient.Repository.Get("github", "visualstudio").Returns(Task.FromResult(repository)); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - wikiProbe.ProbeAsync(repository) - .Returns(_ => Task.FromResult(probeResult), _ => { throw new Exception("Only call it once"); }); - var client = new SimpleApiClient( - "https://github.com/github/visualstudio", - gitHubClient, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - await client.GetRepository(); - - var result = client.HasWiki(); - - Assert.Equal(expected, result); - Assert.Equal(expected, client.HasWiki()); - } - - [Fact] - public void ReturnsFalseWhenWeHaveNotRequestedRepository() - { - var gitHubHost = HostAddress.GitHubDotComHostAddress; - var gitHubClient = Substitute.For<IGitHubClient>(); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - var client = new SimpleApiClient( - "https://github.com/github/visualstudio", - gitHubClient, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - - var result = client.HasWiki(); - - Assert.False(result); - } - } - - public class TheIsEnterpriseMethod - { - [Theory] - [InlineData(EnterpriseProbeResult.Ok, true)] - [InlineData(EnterpriseProbeResult.Failed, false)] - [InlineData(EnterpriseProbeResult.NotFound, false)] - public async Task ReturnsTrueWhenEnterpriseProbeReturnsOk(EnterpriseProbeResult probeResult, bool expected) - { - var gitHubHost = HostAddress.GitHubDotComHostAddress; - var gitHubClient = Substitute.For<IGitHubClient>(); - var repository = CreateRepository(42, true); - gitHubClient.Repository.Get("github", "visualstudio").Returns(Task.FromResult(repository)); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - enterpriseProbe.ProbeAsync(Args.Uri) - .Returns(_ => Task.FromResult(probeResult), _ => { throw new Exception("Only call it once"); }); - var wikiProbe = Substitute.For<IWikiProbe>(); - var client = new SimpleApiClient( - "https://github.com/github/visualstudio", - gitHubClient, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - await client.GetRepository(); - - var result = client.IsEnterprise(); - - Assert.Equal(expected, result); - Assert.Equal(expected, client.IsEnterprise()); - } - - [Fact] - public void ReturnsFalseWhenWeHaveNotRequestedRepository() - { - var gitHubHost = HostAddress.GitHubDotComHostAddress; - var gitHubClient = Substitute.For<IGitHubClient>(); - var enterpriseProbe = Substitute.For<IEnterpriseProbeTask>(); - var wikiProbe = Substitute.For<IWikiProbe>(); - var client = new SimpleApiClient( - "https://github.com/github/visualstudio", - gitHubClient, - new Lazy<IEnterpriseProbeTask>(() => enterpriseProbe), - new Lazy<IWikiProbe>(() => wikiProbe)); - - var result = client.IsEnterprise(); - - Assert.False(result); - } - } - - private static Repository CreateRepository(int id, bool hasWiki) - { - return new Repository("", "", "", "", "", "", "", id, new User(), "", "", "", "", "", false, false, 0, 0, 0, "", - 0, null, DateTimeOffset.Now, DateTimeOffset.Now, new RepositoryPermissions(), new User(), null, null, false, - hasWiki, false); - } -} diff --git a/src/UnitTests/GitHub.App/Caches/CredentialCacheTests.cs b/src/UnitTests/GitHub.App/Caches/CredentialCacheTests.cs deleted file mode 100644 index 1dfbfc0b00..0000000000 --- a/src/UnitTests/GitHub.App/Caches/CredentialCacheTests.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Text; -using System.Threading.Tasks; -using GitHub.Caches; -using Xunit; - -public class CredentialCacheTests : TestBaseClass -{ - public class TheGetObjectMethod : TestBaseClass - { - [Fact] - public async Task RetrievesValueWithAlternateKeys() - { - const string key = nameof(RetrievesValueWithAlternateKeys); - using (var credentialCache = new CredentialCache()) - { - try - { - var credential = Tuple.Create("somebody", "somebody's secret"); - await credentialCache.InsertObject(key, credential); - - var retrieved = await credentialCache.GetObject<Tuple<string, string>>(key); - - Assert.Equal("somebody", retrieved.Item1); - Assert.Equal("somebody's secret", retrieved.Item2); - - var retrieved2 = await credentialCache.GetObject<Tuple<string, string>>("git:" + key + "/"); - - Assert.Equal("somebody", retrieved2.Item1); - Assert.Equal("somebody's secret", retrieved2.Item2); - - var retrieved3 = await credentialCache.GetObject<Tuple<string, string>>("login:" + key + "/"); - - Assert.Equal("somebody", retrieved3.Item1); - Assert.Equal("somebody's secret", retrieved3.Item2); - } - finally - { - await credentialCache.Invalidate(key); - } - } - } - - [Fact] - public async Task ThrowsObservableInvalidOperationExceptionWhenRetrievingSomethingNotATuple() - { - using (var credentialCache = new CredentialCache()) - { - await Assert.ThrowsAsync<InvalidOperationException>( - async () => await credentialCache.GetObject<string>("_")); - } - } - - [Fact] - public async Task ThrowsObjectDisposedExceptionWhenDisposed() - { - using (var credentialCache = new CredentialCache()) - { - credentialCache.Dispose(); - await Assert.ThrowsAsync<ObjectDisposedException>( - async () => await credentialCache.GetObject<Tuple<string, string>>("_")); - } - } - } - - public class TheInsertObjectMethod : TestBaseClass - { - [Fact] - public async Task StoresCredentialForKeyAndGitKey() - { - using (var credentialCache = new CredentialCache()) - { - try - { - var credential = Tuple.Create("somebody", "somebody's secret"); - - await credentialCache.InsertObject(nameof(StoresCredentialForKeyAndGitKey), credential); - - var retrieved = await credentialCache.GetObject<Tuple<string, string>>(nameof(StoresCredentialForKeyAndGitKey)); - Assert.Equal("somebody", retrieved.Item1); - Assert.Equal("somebody's secret", retrieved.Item2); - var retrieved2 = await credentialCache.GetObject<Tuple<string, string>>("git:" + nameof(StoresCredentialForKeyAndGitKey)); - Assert.Equal("somebody", retrieved2.Item1); - Assert.Equal("somebody's secret", retrieved2.Item2); - } - finally - { - try - { - await credentialCache.Invalidate(nameof(StoresCredentialForKeyAndGitKey)); - } - catch (Exception) - { - } - } - } - } - - [Fact] - public async Task ThrowsObjectDisposedExceptionWhenDisposed() - { - using (var credentialCache = new CredentialCache()) - { - credentialCache.Dispose(); - await Assert.ThrowsAsync<ObjectDisposedException>( - async () => await credentialCache.InsertObject("_", new object())); - } - } - } - - public class TheInsertMethod : TestBaseClass - { - [Fact] - public async Task ThrowsInvalidOperationException() - { - using (var credentialCache = new CredentialCache()) - { - await Assert.ThrowsAsync<InvalidOperationException>( - async () => await credentialCache.Insert("key", new byte[] {})); - } - } - } - - public class TheGetMethod : TestBaseClass - { - [Fact] - public async Task RetrievesPasswordAsUnicodeBytes() - { - using (var credentialCache = new CredentialCache()) - { - try - { - var credential = Tuple.Create("somebody", "somebody's secret"); - await credentialCache.InsertObject(nameof(RetrievesPasswordAsUnicodeBytes), credential); - - var retrieved = await credentialCache.Get(nameof(RetrievesPasswordAsUnicodeBytes)); - - Assert.Equal("somebody's secret", Encoding.Unicode.GetString(retrieved)); - } - finally - { - await credentialCache.Invalidate(nameof(RetrievesPasswordAsUnicodeBytes)); - } - } - } - - [Fact] - public async Task ThrowsObservableKeyNotFoundExceptionWhenKeyNotFound() - { - using (var credentialCache = new CredentialCache()) - { - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await credentialCache.Get("unknownkey")); - } - } - - [Fact] - public async Task ThrowsObjectDisposedExceptionWhenDisposed() - { - using (var credentialCache = new CredentialCache()) - { - credentialCache.Dispose(); - await Assert.ThrowsAsync<ObjectDisposedException>( - async () => await credentialCache.Get("_")); - } - } - } - - public class TheInvalidateMethod : TestBaseClass - { - [Fact] - public async Task InvalidatesTheCredential() - { - const string key = "TheInvalidateMethod.InvalidatesTheCredential"; - using (var credentialCache = new CredentialCache()) - { - var credential = Tuple.Create("somebody", "somebody's secret"); - await credentialCache.InsertObject(key, credential); - await credentialCache.Invalidate(key); - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await credentialCache.Get(key)); - } - } - - [Fact] - public async Task ThrowsKeyNotFoundExceptionWhenKeyNotFound() - { - using (var credentialCache = new CredentialCache()) - { - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await credentialCache.Invalidate("git:_")); - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await credentialCache.Invalidate("_")); - } - } - - [Fact] - public async Task ThrowsObjectDisposedExceptionWhenDisposed() - { - using (var credentialCache = new CredentialCache()) - { - credentialCache.Dispose(); - await Assert.ThrowsAsync<ObjectDisposedException>( - async () => await credentialCache.Invalidate("_")); - } - } - } - - public class TheInvalidateObjectMethod : TestBaseClass - { - [Fact] - public async Task InvalidatesTheCredential() - { - const string key = "TheInvalidateObjectMethod.InvalidatesTheCredential"; - using (var credentialCache = new CredentialCache()) - { - var credential = Tuple.Create("somebody", "somebody's secret"); - await credentialCache.InsertObject(key, credential); - await credentialCache.InvalidateObject<Tuple<string, string>>(key); - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await credentialCache.Get(key)); - } - } - - [Fact] - public async Task ThrowsObjectDisposedExceptionWhenDisposed() - { - using (var credentialCache = new CredentialCache()) - { - credentialCache.Dispose(); - await Assert.ThrowsAsync<ObjectDisposedException>( - async () => await credentialCache.InvalidateObject<Tuple<string, string>>("_")); - } - } - - [Fact] - public async Task ThrowsKeyNotFoundExceptionWhenKeyNotFound() - { - using (var credentialCache = new CredentialCache()) - { - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await credentialCache.InvalidateObject<Tuple<string, string>>("git:_")); - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await credentialCache.InvalidateObject<Tuple<string, string>>("_")); - } - } - } - - public class TheFlushMethod : TestBaseClass - { - [Fact] - public async Task ThrowsObjectDisposedExceptionWhenDisposed() - { - using (var credentialCache = new CredentialCache()) - { - await credentialCache.Flush(); - - credentialCache.Dispose(); - await Assert.ThrowsAsync<ObjectDisposedException>(async () => await credentialCache.Flush()); - } - } - } - - public class TheDisposeMethod : TestBaseClass - { - [Fact] - public void SignalsShutdown() - { - bool shutdown = false; - using (var credentialCache = new CredentialCache()) - { - credentialCache.Shutdown.Subscribe(_ => shutdown = true); - } - Assert.True(shutdown); - } - } - - public class MethodsNotImplementedOnPurpose : TestBaseClass - { - [Fact] - public void ThrowNotImplementedException() - { - using (var credentialCache = new CredentialCache()) - { - Assert.Throws<NotImplementedException>(() => credentialCache.GetAllKeys()); - Assert.Throws<NotImplementedException>(() => credentialCache.GetCreatedAt("")); - Assert.Throws<NotImplementedException>(() => credentialCache.InvalidateAll()); - Assert.Throws<NotImplementedException>(() => credentialCache.InvalidateAllObjects<object>()); - Assert.Throws<NotImplementedException>(() => credentialCache.Vacuum()); - Assert.Throws<NotImplementedException>(() => credentialCache.GetAllObjects<object>()); - Assert.Throws<NotImplementedException>(() => credentialCache.GetObjectCreatedAt<object>("")); - } - } - } -} diff --git a/src/UnitTests/GitHub.App/Controllers/UIControllerTests.cs b/src/UnitTests/GitHub.App/Controllers/UIControllerTests.cs deleted file mode 100644 index fb78070f7f..0000000000 --- a/src/UnitTests/GitHub.App/Controllers/UIControllerTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Reactive.Linq; -using System.Windows.Controls; -using GitHub.Controllers; -using GitHub.Models; -using GitHub.Services; -using GitHub.UI; -using NSubstitute; -using Xunit; -using UnitTests; -using GitHub.ViewModels; -using ReactiveUI; -using System.Collections.Generic; -using GitHub.Authentication; - -public class UIControllerTests -{ - public class TheDisposeMethod : TestBaseClass - { - [Fact] - public void WithMultipleCallsDoesNotThrowException() - { - var uiProvider = Substitute.For<IUIProvider>(); - var hosts = Substitute.For<IRepositoryHosts>(); - var factory = Substitute.For<IExportFactoryProvider>(); - var cm = Substitutes.ConnectionManager; - var uiController = new UIController(uiProvider, hosts, factory, cm, LazySubstitute.For<ITwoFactorChallengeHandler>()); - - uiController.Dispose(); - uiController.Dispose(); - } - } - - public class TheStartMethod : TestBaseClass - { - IExportFactoryProvider SetupFactory(IServiceProvider provider) - { - var factory = provider.GetExportFactoryProvider(); - factory.GetViewModel(GitHub.Exports.UIViewType.Login).Returns(new ExportLifetimeContext<IViewModel>(Substitute.For<IViewModel>(), () => { })); - factory.GetView(GitHub.Exports.UIViewType.Login).Returns(new ExportLifetimeContext<IView>(Substitute.For<IView, IViewFor<ILoginControlViewModel>, SimpleViewUserControl>(), () => { })); - factory.GetViewModel(GitHub.Exports.UIViewType.TwoFactor).Returns(new ExportLifetimeContext<IViewModel>(Substitute.For<IViewModel>(), () => { })); - factory.GetView(GitHub.Exports.UIViewType.TwoFactor).Returns(new ExportLifetimeContext<IView>(Substitute.For<IView, IViewFor<ITwoFactorDialogViewModel>, SimpleViewUserControl>(), () => { })); - factory.GetViewModel(GitHub.Exports.UIViewType.Clone).Returns(new ExportLifetimeContext<IViewModel>(Substitute.For<IViewModel>(), () => { })); - factory.GetView(GitHub.Exports.UIViewType.Clone).Returns(new ExportLifetimeContext<IView>(Substitute.For<IView, IViewFor<IRepositoryCloneViewModel>, SimpleViewUserControl>(), () => { })); - return factory; - } - - [STAFact] - public void ShowingCloneDialogWithoutBeingLoggedInShowsLoginDialog() - { - var provider = Substitutes.GetFullyMockedServiceProvider(); - var hosts = provider.GetRepositoryHosts(); - var factory = SetupFactory(provider); - var loginView = factory.GetView(GitHub.Exports.UIViewType.Login); - loginView.Value.Cancel.Returns(Observable.Empty<object>()); - var cm = provider.GetConnectionManager(); - var cons = new System.Collections.ObjectModel.ObservableCollection<IConnection>(); - cm.Connections.Returns(cons); - - using (var uiController = new UIController((IUIProvider)provider, hosts, factory, cm, LazySubstitute.For<ITwoFactorChallengeHandler>())) - { - var list = new List<IView>(); - uiController.SelectFlow(UIControllerFlow.Clone) - .Subscribe(uc => list.Add(uc as IView), - () => - { - Assert.True(list.Count > 1); - Assert.IsAssignableFrom<IViewFor<ILoginControlViewModel>>(list[0]); - }); - - uiController.Start(null); - } - } - - [STAFact] - public void ShowingCloneDialogWhenLoggedInShowsCloneDialog() - { - var provider = Substitutes.GetFullyMockedServiceProvider(); - var hosts = provider.GetRepositoryHosts(); - var factory = SetupFactory(provider); - var connection = provider.GetConnection(); - connection.Login().Returns(Observable.Return(connection)); - var cm = provider.GetConnectionManager(); - var host = hosts.GitHubHost; - hosts.LookupHost(connection.HostAddress).Returns(host); - host.IsLoggedIn.Returns(true); - - using (var uiController = new UIController((IUIProvider)provider, hosts, factory, cm, LazySubstitute.For<ITwoFactorChallengeHandler>())) - { - var list = new List<IView>(); - uiController.SelectFlow(UIControllerFlow.Clone) - .Subscribe(uc => list.Add(uc as IView), - () => - { - Assert.Equal(1, list.Count); - Assert.IsAssignableFrom<IViewFor<IRepositoryCloneViewModel>>(list[0]); - }); - uiController.Start(connection); - } - } - } -} diff --git a/src/UnitTests/GitHub.App/Models/ModelServiceTests.cs b/src/UnitTests/GitHub.App/Models/ModelServiceTests.cs deleted file mode 100644 index c71cf2d126..0000000000 --- a/src/UnitTests/GitHub.App/Models/ModelServiceTests.cs +++ /dev/null @@ -1,621 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Akavache; -using GitHub.Api; -using GitHub.Caches; -using GitHub.Services; -using NSubstitute; -using Octokit; -using Xunit; -using System.Globalization; -using System.Threading; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Collections; -using ReactiveUI; - -public class ModelServiceTests -{ - public class TheGetUserFromCacheMethod : TestBaseClass - { - [Fact] - public async Task RetrievesUserFromCache() - { - var apiClient = Substitute.For<IApiClient>(); - var cache = new InMemoryBlobCache(); - await cache.InsertObject<AccountCacheItem>("user", new AccountCacheItem(CreateOctokitUser("octocat"))); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var user = await modelService.GetUserFromCache(); - - Assert.Equal("octocat", user.Login); - } - } - - public class TheInsertUserMethod : TestBaseClass - { - [Fact] - public async Task AddsUserToCache() - { - var apiClient = Substitute.For<IApiClient>(); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); - - var cached = await cache.GetObject<AccountCacheItem>("user"); - Assert.Equal("octocat", cached.Login); - } - } - - public class TheGetGitIgnoreTemplatesMethod : TestBaseClass - { - [Fact] - public async Task CanRetrieveAndCacheGitIgnores() - { - var templates = new[] { "dotnet", "peanuts", "bloomcounty" }; - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetGitIgnoreTemplates().Returns(templates.ToObservable()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetGitIgnoreTemplates(); - - Assert.Equal(4, fetched.Count); - Assert.Equal("None", fetched[0].Name); - Assert.Equal("dotnet", fetched[1].Name); - Assert.Equal("peanuts", fetched[2].Name); - Assert.Equal("bloomcounty", fetched[3].Name); - var cached = await cache.GetObject<IReadOnlyList<string>>("gitignores"); - Assert.Equal(3, cached.Count); - Assert.Equal("dotnet", cached[0]); - Assert.Equal("peanuts", cached[1]); - Assert.Equal("bloomcounty", cached[2]); - } - - [Fact] - public async Task ReturnsCollectionOnlyContainingTheNoneOptionnWhenGitIgnoreEndpointNotFound() - { - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetGitIgnoreTemplates() - .Returns(Observable.Throw<string>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetGitIgnoreTemplates(); - - Assert.Equal(1, fetched.Count); - Assert.Equal("None", fetched[0].Name); - } - - [Fact] - public async Task ReturnsCollectionOnlyContainingTheNoneOptionIfCacheReadFails() - { - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetGitIgnoreTemplates() - .Returns(Observable.Throw<string>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); - var cache = Substitute.For<IBlobCache>(); - cache.Get(Args.String) - .Returns(Observable.Throw<byte[]>(new InvalidOperationException("Unknown"))); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetGitIgnoreTemplates(); - - Assert.Equal(1, fetched.Count); - Assert.Equal("None", fetched[0].Name); - } - } - - public class TheGetLicensesMethod : TestBaseClass - { - [Fact] - public async Task CanRetrieveAndCacheLicenses() - { - var licenses = new[] - { - new LicenseMetadata("mit", "MIT", new Uri("https://github.com/")), - new LicenseMetadata("apache", "Apache", new Uri("https://github.com/")) - }; - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetLicenses().Returns(licenses.ToObservable()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetLicenses(); - - Assert.Equal(3, fetched.Count); - Assert.Equal("None", fetched[0].Name); - Assert.Equal("MIT", fetched[1].Name); - Assert.Equal("Apache", fetched[2].Name); - var cached = await cache.GetObject<IReadOnlyList<ModelService.LicenseCacheItem>>("licenses"); - Assert.Equal(2, cached.Count); - Assert.Equal("mit", cached[0].Key); - Assert.Equal("apache", cached[1].Key); - } - - [Fact] - public async Task ReturnsCollectionOnlyContainingTheNoneOptionWhenLicenseApiNotFound() - { - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetLicenses() - .Returns(Observable.Throw<LicenseMetadata>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetLicenses(); - - Assert.Equal(1, fetched.Count); - Assert.Equal("None", fetched[0].Name); - } - - [Fact] - public async Task ReturnsCollectionOnlyContainingTheNoneOptionIfCacheReadFails() - { - var apiClient = Substitute.For<IApiClient>(); - var cache = Substitute.For<IBlobCache>(); - cache.Get(Args.String) - .Returns(Observable.Throw<byte[]>(new InvalidOperationException("Unknown"))); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetLicenses(); - - Assert.Equal(1, fetched.Count); - Assert.Equal("None", fetched[0].Name); - } - } - - public class TheGetAccountsMethod : TestBaseClass - { - [Fact] - public async Task CanRetrieveAndCacheUserAndAccounts() - { - var orgs = new[] - { - CreateOctokitOrganization("github"), - CreateOctokitOrganization("fake") - }; - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("snoopy"))); - apiClient.GetOrganizations().Returns(orgs.ToObservable()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - await modelService.InsertUser(new AccountCacheItem { Login = "snoopy" }); - - var fetched = await modelService.GetAccounts(); - - Assert.Equal(3, fetched.Count); - Assert.Equal("snoopy", fetched[0].Login); - Assert.Equal("github", fetched[1].Login); - Assert.Equal("fake", fetched[2].Login); - var cachedOrgs = await cache.GetObject<IReadOnlyList<AccountCacheItem>>("snoopy|orgs"); - Assert.Equal(2, cachedOrgs.Count); - Assert.Equal("github", cachedOrgs[0].Login); - Assert.Equal("fake", cachedOrgs[1].Login); - var cachedUser = await cache.GetObject<AccountCacheItem>("user"); - Assert.Equal("snoopy", cachedUser.Login); - } - - [Fact] - public async Task CanRetrieveUserFromCacheAndAccountsFromApi() - { - var orgs = new[] - { - CreateOctokitOrganization("github"), - CreateOctokitOrganization("fake") - }; - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetOrganizations().Returns(orgs.ToObservable()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); - - var fetched = await modelService.GetAccounts(); - - Assert.Equal(3, fetched.Count); - Assert.Equal("octocat", fetched[0].Login); - Assert.Equal("github", fetched[1].Login); - Assert.Equal("fake", fetched[2].Login); - var cachedOrgs = await cache.GetObject<IReadOnlyList<AccountCacheItem>>("octocat|orgs"); - Assert.Equal(2, cachedOrgs.Count); - Assert.Equal("github", cachedOrgs[0].Login); - Assert.Equal("fake", cachedOrgs[1].Login); - var cachedUser = await cache.GetObject<AccountCacheItem>("user"); - Assert.Equal("octocat", cachedUser.Login); - } - - [Fact] - public async Task OnlyRetrievesOneUserEvenIfCacheOrApiReturnsMoreThanOne() - { - // This should be impossible, but let's pretend it does happen. - var users = new[] - { - CreateOctokitUser("peppermintpatty"), - CreateOctokitUser("peppermintpatty") - }; - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetUser().Returns(users.ToObservable()); - apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - var fetched = await modelService.GetAccounts(); - - Assert.Equal(1, fetched.Count); - Assert.Equal("peppermintpatty", fetched[0].Login); - } - } - - public class TheGetRepositoriesMethod : TestBaseClass - { - [Fact] - public async Task CanRetrieveAndCacheRepositoriesForUserAndOrganizations() - { - var orgs = new[] - { - CreateOctokitOrganization("github"), - CreateOctokitOrganization("octokit") - }; - var ownedRepos = new[] - { - CreateRepository("haacked", "seegit"), - CreateRepository("haacked", "codehaacks") - }; - var memberRepos = new[] - { - CreateRepository("mojombo", "semver"), - CreateRepository("ninject", "ninject"), - CreateRepository("jabbr", "jabbr"), - CreateRepository("fody", "nullguard") - }; - var githubRepos = new[] - { - CreateRepository("github", "visualstudio") - }; - var octokitRepos = new[] - { - CreateRepository("octokit", "octokit.net"), - CreateRepository("octokit", "octokit.rb"), - CreateRepository("octokit", "octokit.objc") - }; - var apiClient = Substitute.For<IApiClient>(); - apiClient.GetOrganizations().Returns(orgs.ToObservable()); - apiClient.GetUserRepositories(RepositoryType.Owner).Returns(ownedRepos.ToObservable()); - apiClient.GetUserRepositories(RepositoryType.Member).Returns(memberRepos.ToObservable()); - apiClient.GetRepositoriesForOrganization("github").Returns(githubRepos.ToObservable()); - apiClient.GetRepositoriesForOrganization("octokit").Returns(octokitRepos.ToObservable()); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - await modelService.InsertUser(new AccountCacheItem { Login = "opus" }); - - var fetched = await modelService.GetRepositories().ToList(); - - Assert.Equal(4, fetched.Count); - Assert.Equal(2, fetched[0].Count); - Assert.Equal(4, fetched[1].Count); - Assert.Equal(1, fetched[2].Count); - Assert.Equal(3, fetched[3].Count); - Assert.Equal("seegit", fetched[0][0].Name); - Assert.Equal("codehaacks", fetched[0][1].Name); - Assert.Equal("semver", fetched[1][0].Name); - Assert.Equal("ninject", fetched[1][1].Name); - Assert.Equal("jabbr", fetched[1][2].Name); - Assert.Equal("nullguard", fetched[1][3].Name); - Assert.Equal("visualstudio", fetched[2][0].Name); - Assert.Equal("octokit.net", fetched[3][0].Name); - Assert.Equal("octokit.rb", fetched[3][1].Name); - Assert.Equal("octokit.objc", fetched[3][2].Name); - var cachedOwnerRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|Owner:repos"); - Assert.Equal(2, cachedOwnerRepositories.Count); - Assert.Equal("seegit", cachedOwnerRepositories[0].Name); - Assert.Equal("haacked", cachedOwnerRepositories[0].Owner.Login); - Assert.Equal("codehaacks", cachedOwnerRepositories[1].Name); - Assert.Equal("haacked", cachedOwnerRepositories[1].Owner.Login); - var cachedMemberRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|Member:repos"); - Assert.Equal(4, cachedMemberRepositories.Count); - Assert.Equal("semver", cachedMemberRepositories[0].Name); - Assert.Equal("mojombo", cachedMemberRepositories[0].Owner.Login); - Assert.Equal("ninject", cachedMemberRepositories[1].Name); - Assert.Equal("ninject", cachedMemberRepositories[1].Owner.Login); - Assert.Equal("jabbr", cachedMemberRepositories[2].Name); - Assert.Equal("jabbr", cachedMemberRepositories[2].Owner.Login); - Assert.Equal("nullguard", cachedMemberRepositories[3].Name); - Assert.Equal("fody", cachedMemberRepositories[3].Owner.Login); - var cachedGitHubRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|github|repos"); - Assert.Equal(1, cachedGitHubRepositories.Count); - Assert.Equal("seegit", cachedOwnerRepositories[0].Name); - Assert.Equal("haacked", cachedOwnerRepositories[0].Owner.Login); - Assert.Equal("codehaacks", cachedOwnerRepositories[1].Name); - Assert.Equal("haacked", cachedOwnerRepositories[1].Owner.Login); - var cachedOctokitRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|octokit|repos"); - Assert.Equal("octokit.net", cachedOctokitRepositories[0].Name); - Assert.Equal("octokit", cachedOctokitRepositories[0].Owner.Login); - Assert.Equal("octokit.rb", cachedOctokitRepositories[1].Name); - Assert.Equal("octokit", cachedOctokitRepositories[1].Owner.Login); - Assert.Equal("octokit.objc", cachedOctokitRepositories[2].Name); - Assert.Equal("octokit", cachedOctokitRepositories[2].Owner.Login); - } - - [Fact] - public async Task WhenNotLoggedInReturnsEmptyCollection() - { - var apiClient = Substitute.For<IApiClient>(); - var modelService = new ModelService(apiClient, new InMemoryBlobCache(), Substitute.For<IAvatarProvider>()); - - var repos = await modelService.GetRepositories(); - - Assert.Equal(0, repos.Count); - } - - [Fact] - public async Task WhenLoggedInDoesNotBlowUpOnUnexpectedNetworkProblems() - { - var apiClient = Substitute.For<IApiClient>(); - var modelService = new ModelService(apiClient, new InMemoryBlobCache(), Substitute.For<IAvatarProvider>()); - apiClient.GetOrganizations() - .Returns(Observable.Throw<Organization>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); - - var repos = await modelService.GetRepositories(); - - Assert.Equal(0, repos.Count); - } - } - - public class TheInvalidateAllMethod : TestBaseClass - { - [Fact] - public async Task InvalidatesTheCache() - { - var apiClient = Substitute.For<IApiClient>(); - var cache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); - Assert.Equal(1, (await cache.GetAllObjects<AccountCacheItem>()).Count()); - - await modelService.InvalidateAll(); - - Assert.Equal(0, (await cache.GetAllObjects<AccountCacheItem>()).Count()); - } - - [Fact] - public async Task VaccumsTheCache() - { - var apiClient = Substitute.For<IApiClient>(); - var cache = Substitute.For<IBlobCache>(); - cache.InvalidateAll().Returns(Observable.Return(Unit.Default)); - var received = false; - cache.Vacuum().Returns(x => - { - received = true; - return Observable.Return(Unit.Default); - }); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - - await modelService.InvalidateAll(); - Assert.True(received); - } - } - - public class TheGetPullRequestsMethod : TestBaseClass - { - [Fact] - public async Task NonExpiredIndexReturnsCache() - { - var expected = 5; - - var username = "octocat"; - var reponame = "repo"; - - var cache = new InMemoryBlobCache(); - var apiClient = Substitute.For<IApiClient>(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - var user = CreateOctokitUser(username); - apiClient.GetUser().Returns(Observable.Return(user)); - apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); - var act = modelService.GetAccounts().ToEnumerable().First().First(); - - var repo = Substitute.For<ISimpleRepositoryModel>(); - repo.Name.Returns(reponame); - repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame)); - - var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name); - - var prcache = Enumerable.Range(1, expected) - .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)); - - // seed the cache - prcache - .Select(item => new ModelService.PullRequestCacheItem(item)) - .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First()) - .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable()) - .ToList(); - - var prlive = Observable.Range(1, expected) - .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)) - .DelaySubscription(TimeSpan.FromMilliseconds(10)); - - apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive); - - await modelService.InsertUser(new AccountCacheItem(user)); - var col = modelService.GetPullRequests(repo); - col.ProcessingDelay = TimeSpan.Zero; - - var count = 0; - var evt = new ManualResetEvent(false); - col.Subscribe(t => - { - if (++count == expected) - evt.Set(); - }, () => { }); - - - evt.WaitOne(); - evt.Reset(); - - Assert.Collection(col, col.Select(x => new Action<IPullRequestModel>(t => Assert.True(x.Title.StartsWith("Cache")))).ToArray()); - } - - [Fact] - public async Task ExpiredIndexReturnsLive() - { - var expected = 5; - - var username = "octocat"; - var reponame = "repo"; - - var cache = new InMemoryBlobCache(); - var apiClient = Substitute.For<IApiClient>(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - var user = CreateOctokitUser(username); - apiClient.GetUser().Returns(Observable.Return(user)); - apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); - var act = modelService.GetAccounts().ToEnumerable().First().First(); - - var repo = Substitute.For<ISimpleRepositoryModel>(); - repo.Name.Returns(reponame); - repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame)); - - var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name); - - var prcache = Enumerable.Range(1, expected) - .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)); - - // seed the cache - prcache - .Select(item => new ModelService.PullRequestCacheItem(item)) - .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First()) - .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable()) - .ToList(); - - // expire the index - var indexobj = await cache.GetObject<CacheIndex>(indexKey); - indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6); - await cache.InsertObject(indexKey, indexobj); - - var prlive = Observable.Range(1, expected) - .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)) - .DelaySubscription(TimeSpan.FromMilliseconds(10)); - - apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive); - - await modelService.InsertUser(new AccountCacheItem(user)); - var col = modelService.GetPullRequests(repo); - col.ProcessingDelay = TimeSpan.Zero; - - var count = 0; - var evt = new ManualResetEvent(false); - col.Subscribe(t => - { - if (++count == expected * 2) - evt.Set(); - }, () => { }); - - - evt.WaitOne(); - evt.Reset(); - - Assert.Collection(col, col.Select(x => new Action<IPullRequestModel>(t => Assert.True(x.Title.StartsWith("Live")))).ToArray()); - } - - [Fact] - public async Task ExpiredIndexClearsItems() - { - var expected = 5; - - var username = "octocat"; - var reponame = "repo"; - - var cache = new InMemoryBlobCache(); - var apiClient = Substitute.For<IApiClient>(); - var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); - var user = CreateOctokitUser(username); - apiClient.GetUser().Returns(Observable.Return(user)); - apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); - var act = modelService.GetAccounts().ToEnumerable().First().First(); - - var repo = Substitute.For<ISimpleRepositoryModel>(); - repo.Name.Returns(reponame); - repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame)); - - var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name); - - var prcache = Enumerable.Range(1, expected) - .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)); - - // seed the cache - prcache - .Select(item => new ModelService.PullRequestCacheItem(item)) - .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First()) - .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable()) - .ToList(); - - // expire the index - var indexobj = await cache.GetObject<CacheIndex>(indexKey); - indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6); - await cache.InsertObject(indexKey, indexobj); - - var prlive = Observable.Range(5, expected) - .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)) - .DelaySubscription(TimeSpan.FromMilliseconds(10)); - - apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive); - - await modelService.InsertUser(new AccountCacheItem(user)); - var col = modelService.GetPullRequests(repo); - col.ProcessingDelay = TimeSpan.Zero; - - var count = 0; - var evt = new ManualResetEvent(false); - col.Subscribe(t => - { - // we get all the items from the cache (items 1-5), all the items from the live (items 5-9), - // and 4 deletions (items 1-4) because the cache expired the items that were not - // a part of the live data - if (++count == 14) - evt.Set(); - }, () => { }); - - - evt.WaitOne(); - evt.Reset(); - - Assert.Equal(5, col.Count); - Assert.Collection(col, - t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(5, t.Number); }, - t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(6, t.Number); }, - t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(7, t.Number); }, - t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(8, t.Number); }, - t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(9, t.Number); } - ); - } - } - - static User CreateOctokitUser(string login) - { - return new User("https://url", "", "", 1, "GitHub", DateTimeOffset.UtcNow, 0, "email", 100, 100, true, "http://url", 10, 42, "somewhere", login, "Who cares", 1, new Plan(), 1, 1, 1, "https://url", false); - } - - static Organization CreateOctokitOrganization(string login) - { - return new Organization("https://url", "", "", 1, "GitHub", DateTimeOffset.UtcNow, 0, "email", 100, 100, true, "http://url", 10, 42, "somewhere", login, "Who cares", 1, new Plan(), 1, 1, 1, "https://url", "billing"); - } - - static Repository CreateRepository(string owner, string name) - { - return new Repository("https://url", "https://url", "https://url", "https://url", "https://url", "https://url", "https://url", 1, CreateOctokitUser(owner), name, "fullname", "description", "https://url", "c#", false, false, 0, 0, 0, "master", 0, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, new RepositoryPermissions(), null, null, null, true, false, false); - } - - static PullRequest CreatePullRequest(User user, int id, ItemState state, string title, - DateTimeOffset createdAt, DateTimeOffset updatedAt, int commentCount) - { - var uri = new Uri("https://url"); - return new PullRequest(uri, uri, uri, uri, uri, uri, - id, state, title, "", createdAt, updatedAt, - null, null, null, null, user, "", false, null, null, commentCount, 0, 0, 0, - 0); - } -} diff --git a/src/UnitTests/GitHub.App/Models/RepositoryHostTests.cs b/src/UnitTests/GitHub.App/Models/RepositoryHostTests.cs deleted file mode 100644 index dbfde2a5f2..0000000000 --- a/src/UnitTests/GitHub.App/Models/RepositoryHostTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Akavache; -using GitHub.Api; -using GitHub.Authentication; -using GitHub.Caches; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using NSubstitute; -using Octokit; -using UnitTests.Helpers; -using Xunit; - -public class RepositoryHostTests -{ - public class TheLoginMethod : TestBaseClass - { - [Fact] - public async Task LogsTheUserInSuccessfullyAndCachesRelevantInfo() - { - var apiClient = Substitute.For<IApiClient>(); - apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); - apiClient.GetOrCreateApplicationAuthenticationCode( - Args.TwoFactorChallengCallback, Args.String, Args.Boolean) - .Returns(Observable.Return(new ApplicationAuthorization("S3CR3TS"))); - apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("baymax"))); - var hostCache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>()); - var loginCache = new TestLoginCache(); - var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>()); - - var result = await host.LogIn("baymax", "aPassword"); - - Assert.Equal(AuthenticationResult.Success, result); - var user = await hostCache.GetObject<AccountCacheItem>("user"); - Assert.NotNull(user); - Assert.Equal("baymax", user.Login); - var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress); - Assert.Equal("baymax", loginInfo.UserName); - Assert.Equal("S3CR3TS", loginInfo.Password); - Assert.True(host.IsLoggedIn); - } - - [Fact] - public async Task DoesNotLogInWhenRetrievingOauthTokenFails() - { - var apiClient = Substitute.For<IApiClient>(); - apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); - apiClient.GetOrCreateApplicationAuthenticationCode( - Args.TwoFactorChallengCallback, Args.String, Args.Boolean) - .Returns(Observable.Throw<ApplicationAuthorization>(new NotFoundException("", HttpStatusCode.BadGateway))); - apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("jiminy"))); - var hostCache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>()); - var loginCache = new TestLoginCache(); - var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>()); - - await Assert.ThrowsAsync<NotFoundException>(async () => await host.LogIn("jiminy", "cricket")); - - await Assert.ThrowsAsync<KeyNotFoundException>( - async () => await hostCache.GetObject<AccountCacheItem>("user")); - var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress); - Assert.Equal("jiminy", loginInfo.UserName); - Assert.Equal("cricket", loginInfo.Password); - Assert.False(host.IsLoggedIn); - } - - [Fact] - public async Task UsesUsernameAndPasswordInsteadOfAuthorizationTokenWhenEnterpriseAndAPIReturns404() - { - var enterpriseHostAddress = HostAddress.Create("https://enterprise.example.com/"); - var apiClient = Substitute.For<IApiClient>(); - apiClient.HostAddress.Returns(enterpriseHostAddress); - // Throw a 404 on the first try with the new scopes - apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, false, true) - .Returns(Observable.Throw<ApplicationAuthorization>(new NotFoundException("Not there", HttpStatusCode.NotFound))); - // Throw a 404 on the retry with the old scopes: - apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, true, false) - .Returns(Observable.Throw<ApplicationAuthorization>(new NotFoundException("Also not there", HttpStatusCode.NotFound))); - apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("Cthulu"))); - var hostCache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>()); - var loginCache = new TestLoginCache(); - var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>()); - - var result = await host.LogIn("Cthulu", "aPassword"); - - Assert.Equal(AuthenticationResult.Success, result); - // Only username and password were saved, never an authorization token: - var loginInfo = await loginCache.GetLoginAsync(enterpriseHostAddress); - Assert.Equal("Cthulu", loginInfo.UserName); - Assert.Equal("aPassword", loginInfo.Password); - Assert.True(host.IsLoggedIn); - } - - // Since we're now catching a 401 to detect a bad authoriation token, we need a test to make sure - // real 2FA failures (like putting in a bad code) still make it through (they're also a 401, but - // shouldn't be caught): - [Fact] - public async Task DoesNotFallBackToOldScopesWhenGitHubAndTwoFactorAuthFailsAndErasesLogin() - { - var apiClient = Substitute.For<IApiClient>(); - apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); - bool received1 = false, received2 = false; - apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, false, true) - .Returns(_ => - { - received1 = true; - return Observable.Throw<ApplicationAuthorization>(new TwoFactorChallengeFailedException()); - }); - - apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, - Args.String, - true, - Args.Boolean) - .Returns(_ => - { - received2 = true; - return Observable.Throw<ApplicationAuthorization>(new TwoFactorChallengeFailedException()); - }); - apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("jiminy"))); - var hostCache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>()); - var loginCache = new TestLoginCache(); - var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>()); - - await host.LogIn("aUsername", "aPassowrd"); - - Assert.True(received1); - Assert.False(received2); - Assert.False(host.IsLoggedIn); - var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress); - Assert.Equal("", loginInfo.UserName); - Assert.Equal("", loginInfo.Password); - } - - [Fact] - public async Task RetriesUsingOldScopeWhenAuthenticationFailsAndIsEnterprise() - { - var enterpriseHostAddress = HostAddress.Create("https://enterprise.example.com/"); - var apiClient = Substitute.For<IApiClient>(); - apiClient.HostAddress.Returns(enterpriseHostAddress); - apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, false, true) - .Returns(Observable.Throw<ApplicationAuthorization>(new ApiException("Bad scopes", (HttpStatusCode)422))); - apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, true, false) - .Returns(Observable.Return(new ApplicationAuthorization("T0k3n"))); - apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("jiminy"))); - var hostCache = new InMemoryBlobCache(); - var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>()); - var loginCache = new TestLoginCache(); - var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>()); - - await host.LogIn("jiminy", "aPassowrd"); - - Assert.True(host.IsLoggedIn); - var loginInfo = await loginCache.GetLoginAsync(enterpriseHostAddress); - Assert.Equal("jiminy", loginInfo.UserName); - Assert.Equal("T0k3n", loginInfo.Password); - } - } - - static User CreateOctokitUser(string login) - { - return new User("https://url", "", "", 1, "GitHub", DateTimeOffset.UtcNow, 0, "email", 100, 100, true, "http://url", 10, 42, "somewhere", login, "Who cares", 1, new Plan(), 1, 1, 1, "https://url", false); - } - - static Organization CreateOctokitOrganization(string login) - { - return new Organization("https://url", "", "", 1, "GitHub", DateTimeOffset.UtcNow, 0, "email", 100, 100, true, "http://url", 10, 42, "somewhere", login, "Who cares", 1, new Plan(), 1, 1, 1, "https://url", "billing"); - } -} diff --git a/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs b/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs deleted file mode 100644 index 64577d00b8..0000000000 --- a/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.VisualStudio; -using LibGit2Sharp; -using NSubstitute; -using UnitTests; -using Xunit; - -public class RepositoryModelTests -{ - public class ComparisonTests : TestBaseClass - { - [Theory] - [InlineData("a name", "https://github.com/github/VisualStudio", null, "a name", "https://github.com/github/VisualStudio", null)] - [InlineData("a name", "https://github.com/github/VisualStudio", @"C:\some\path", "a name", "https://github.com/github/VisualStudio", @"C:\some\path")] - [InlineData("a name", "https://github.com/github/VisualStudio", @"c:\some\path", "a name", "https://github.com/github/VisualStudio", @"C:\some\path")] - [InlineData("a name", "https://github.com/github/VisualStudio", @"C:\some\path", "a name", "https://github.com/github/VisualStudio", @"c:\some\path")] - [InlineData("a name", "https://github.com/github/VisualStudio", @"C:\some\path\", "a name", "https://github.com/github/VisualStudio", @"c:\some\path")] - [InlineData("a name", "https://github.com/github/VisualStudio", @"C:\some\path", "a name", "https://github.com/github/VisualStudio", @"c:\some\path\")] - [InlineData("a name", "https://github.com/github/VisualStudio", @"C:\some\path\", "a name", "https://github.com/github/VisualStudio", @"c:\some\path\")] - public void SameContentEqualsTrue(string name1, string url1, string path1, string name2, string url2, string path2) - { - var a = new SimpleRepositoryModel(name1, new UriString(url1), path1); - var b = new SimpleRepositoryModel(name2, new UriString(url2), path2); - Assert.Equal(a, b); - Assert.False(a == b); - Assert.Equal(a.GetHashCode(), b.GetHashCode()); - } - - [Theory] - [InlineData("a name", "https://github.com/github/VisualStudio", "a name", "https://github.com/github/VisualStudio")] - public void SameContentEqualsTrue2(string name1, string url1, string name2, string url2) - { - var account = Substitute.For<IAccount>(); - var a = new RepositoryModel(name1, new UriString(url1), false, false, account); - var b = new RepositoryModel(name2, new UriString(url2), false, false, account); - Assert.Equal(a, b); - Assert.False(a == b); - Assert.Equal(a.GetHashCode(), b.GetHashCode()); - } - - [Theory] - [InlineData("a name1", "https://github.com/github/VisualStudio", "a name", "https://github.com/github/VisualStudio")] - public void DifferentContentEqualsFalse(string name1, string url1, string name2, string url2) - { - var account = Substitute.For<IAccount>(); - var a = new RepositoryModel(name1, new UriString(url1), false, false, account); - var b = new RepositoryModel(name2, new UriString(url2), false, false, account); - Assert.NotEqual(a, b); - Assert.False(a == b); - Assert.NotEqual(a.GetHashCode(), b.GetHashCode()); - } - } - - public class PathConstructorTests : TempFileBaseClass - { - [Fact] - public void NoRemoteUrl() - { - var provider = Substitutes.ServiceProvider; - Services.PackageServiceProvider = provider; - var gitservice = provider.GetGitService(); - var repo = Substitute.For<IRepository>(); - var path = Directory.CreateSubdirectory("repo-name"); - gitservice.GetUri(path.FullName).Returns((UriString)null); - var model = new SimpleRepositoryModel(path.FullName); - Assert.Equal("repo-name", model.Name); - } - - [Fact] - public void WithRemoteUrl() - { - var provider = Substitutes.ServiceProvider; - Services.PackageServiceProvider = provider; - var gitservice = provider.GetGitService(); - var repo = Substitute.For<IRepository>(); - var path = Directory.CreateSubdirectory("repo-name"); - gitservice.GetUri(path.FullName).Returns(new UriString("https://github.com/user/repo-name")); - var model = new SimpleRepositoryModel(path.FullName); - Assert.Equal("user/repo-name", model.Name); - } - } - - public class HostAddressTests : TestBaseClass - { - [Theory] - [InlineData("https://github.com/owner/repo")] - [InlineData("https://anotherurl.com/foo/bar")] - public void SameContentEqualsTrue(string url) - { - var a = HostAddress.Create(url); - var b = HostAddress.Create(url); - Assert.Equal(a, b); - Assert.Equal(a.GetHashCode(), b.GetHashCode()); - } - } -} diff --git a/src/UnitTests/GitHub.App/Services/GitClientTests.cs b/src/UnitTests/GitHub.App/Services/GitClientTests.cs deleted file mode 100644 index 9fab30f5aa..0000000000 --- a/src/UnitTests/GitHub.App/Services/GitClientTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Threading.Tasks; -using GitHub.Services; -using LibGit2Sharp; -using NSubstitute; -using Xunit; - -public class GitClientTests -{ - public class ThePushMethod : TestBaseClass - { - [Fact] - public async Task PushesToDefaultOrigin() - { - var origin = Substitute.For<Remote>(); - var head = Substitute.For<Branch>(); - head.Commits.Returns(new FakeCommitLog { Substitute.For<Commit>() }); - var repository = Substitute.For<IRepository>(); - repository.Head.Returns(head); - repository.Network.Remotes["origin"].Returns(origin); - var gitClient = new GitClient(Substitute.For<IGitHubCredentialProvider>()); - - await gitClient.Push(repository, "master", "origin"); - - repository.Network.Received().Push(origin,"HEAD", @"refs/heads/master", Arg.Any<PushOptions>()); - } - - [Fact] - public async Task DoesNotPushEmptyRepository() - { - var repository = Substitute.For<IRepository>(); - var gitClient = new GitClient(Substitute.For<IGitHubCredentialProvider>()); - - await gitClient.Push(repository, "master", "origin"); - - repository.Network.DidNotReceive() - .Push(Args.Remote, Args.String, Args.String); - } - } - - public class TheSetRemoteMethod : TestBaseClass - { - [Fact] - public async Task SetsTheConfigToTheRemoteBranch() - { - var config = Substitute.For<Configuration>(); - var repository = Substitute.For<IRepository>(); - repository.Config.Returns(config); - var gitClient = new GitClient(Substitute.For<IGitHubCredentialProvider>()); - - await gitClient.SetRemote(repository, "origin", new Uri("https://github.com/foo/bar")); - - config.Received().Set<string>("remote.origin.url", "https://github.com/foo/bar"); - config.Received().Set<string>("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"); - } - } - - public class TheSetTrackingMethod : TestBaseClass - { - [Fact] - public async Task SetsTheRemoteTrackingBranch() - { - var config = Substitute.For<Configuration>(); - var origin = Substitute.For<Remote>(); - var branches = Substitute.For<BranchCollection>(); - var repository = Substitute.For<IRepository>(); - repository.Config.Returns(config); - repository.Branches.Returns(branches); - repository.Network.Remotes["origin"].Returns(origin); - var localBranch = Substitute.For<Branch>(); - var remoteBranch = Substitute.For<Branch>(); ; - branches["refs/heads/master"].Returns(localBranch); - branches["refs/remotes/origin/master"].Returns(remoteBranch); - - var gitClient = new GitClient(Substitute.For<IGitHubCredentialProvider>()); - - await gitClient.SetTrackingBranch(repository, "master", "origin"); - - branches.Received().Update(localBranch, Arg.Any<Action<BranchUpdater>>()); - } - } -} diff --git a/src/UnitTests/GitHub.App/Services/RepositoryCloneServiceTests.cs b/src/UnitTests/GitHub.App/Services/RepositoryCloneServiceTests.cs deleted file mode 100644 index 6b1ec10527..0000000000 --- a/src/UnitTests/GitHub.App/Services/RepositoryCloneServiceTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Threading.Tasks; -using GitHub.Services; -using Microsoft.TeamFoundation.Git.Controls.Extensibility; -using NSubstitute; -using Rothko; -using Xunit; -using UnitTests; - -public class RepositoryCloneServiceTests -{ - public class TheCloneRepositoryMethod : TestBaseClass - { - [Fact] - public async Task ClonesToRepositoryPath() - { - var serviceProvider = Substitutes.ServiceProvider; - var operatingSystem = serviceProvider.GetOperatingSystem(); - var vsservices = serviceProvider.GetVSServices(); - var cloneService = serviceProvider.GetRepositoryCloneService(); - - await cloneService.CloneRepository("https://github.com/foo/bar", "bar", @"c:\dev"); - - operatingSystem.Directory.Received().CreateDirectory(@"c:\dev\bar"); - vsservices.Received().Clone("https://github.com/foo/bar", @"c:\dev\bar", true); - } - } -} diff --git a/src/UnitTests/GitHub.App/ViewModels/LoginToGitHubViewModelTests.cs b/src/UnitTests/GitHub.App/ViewModels/LoginToGitHubViewModelTests.cs deleted file mode 100644 index 7bd1479bb0..0000000000 --- a/src/UnitTests/GitHub.App/ViewModels/LoginToGitHubViewModelTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Net; -using System.Reactive.Linq; -using GitHub.Authentication; -using GitHub.Info; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.ViewModels; -using NSubstitute; -using Octokit; -using Xunit; - -public class LoginToGitHubViewModelTests -{ - public class TheLoginCommand : TestBaseClass - { - [Fact] - public void ShowsHelpfulTooltipWhenForbiddenResponseReceived() - { - var response = Substitute.For<IResponse>(); - response.StatusCode.Returns(HttpStatusCode.Forbidden); - var repositoryHosts = Substitute.For<IRepositoryHosts>(); - repositoryHosts.LogIn(HostAddress.GitHubDotComHostAddress, Args.String, Args.String) - .Returns(_ => Observable.Throw<AuthenticationResult>(new ForbiddenException(response))); - var browser = Substitute.For<IVisualStudioBrowser>(); - var loginViewModel = new LoginToGitHubViewModel(repositoryHosts, browser); - - loginViewModel.Login.Execute(null); - - Assert.True(loginViewModel.ShowLogInFailedError); - Assert.Equal("Make sure to use your password and not a Personal Access token to log in.", - loginViewModel.LoginFailedMessage); - } - } - - public class TheSignupCommand : TestBaseClass - { - [Fact] - public void LaunchesBrowserToSignUpPage() - { - var repositoryHosts = Substitute.For<IRepositoryHosts>(); - var gitHubHost = Substitute.For<IRepositoryHost>(); - gitHubHost.Address.Returns(HostAddress.GitHubDotComHostAddress); - repositoryHosts.GitHubHost.Returns(gitHubHost); - var browser = Substitute.For<IVisualStudioBrowser>(); - var loginViewModel = new LoginToGitHubViewModel(repositoryHosts, browser); - - loginViewModel.SignUp.Execute(null); - - browser.Received().OpenUrl(GitHubUrls.Plans); - } - } - - public class TheForgotPasswordCommand : TestBaseClass - { - [Fact] - public void LaunchesBrowserToForgotPasswordPage() - { - var repositoryHosts = Substitute.For<IRepositoryHosts>(); - var gitHubHost = Substitute.For<IRepositoryHost>(); - gitHubHost.Address.Returns(HostAddress.GitHubDotComHostAddress); - repositoryHosts.GitHubHost.Returns(gitHubHost); - var browser = Substitute.For<IVisualStudioBrowser>(); - var loginViewModel = new LoginToGitHubViewModel(repositoryHosts, browser); - - loginViewModel.NavigateForgotPassword.Execute(null); - - browser.Received().OpenUrl(new Uri(HostAddress.GitHubDotComHostAddress.WebUri, GitHubUrls.ForgotPasswordPath)); - } - } -} - \ No newline at end of file diff --git a/src/UnitTests/GitHub.App/ViewModels/RepositoryCloneViewModelTests.cs b/src/UnitTests/GitHub.App/ViewModels/RepositoryCloneViewModelTests.cs deleted file mode 100644 index 9564b3de0f..0000000000 --- a/src/UnitTests/GitHub.App/ViewModels/RepositoryCloneViewModelTests.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; -using GitHub.Models; -using GitHub.Services; -using GitHub.ViewModels; -using NSubstitute; -using Rothko; -using Xunit; - -public class RepositoryCloneViewModelTests -{ - public class TheLoadRepositoriesCommand : TestBaseClass - { - [Fact] - public async Task LoadsRepositories() - { - var repos = new IRepositoryModel[] - { - Substitute.For<IRepositoryModel>(), - Substitute.For<IRepositoryModel>(), - Substitute.For<IRepositoryModel>() - }; - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(Observable.Return(repos)); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - - await vm.LoadRepositoriesCommand.ExecuteAsync(); - - Assert.Equal(3, vm.FilteredRepositories.Count); - } - } - - public class TheIsLoadingProperty : TestBaseClass - { - [Fact] - public void StartsTrueBecomesFalseWhenCompleted() - { - var repoSubject = new Subject<IRepositoryModel[]>(); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - - Assert.False(vm.IsLoading); - - vm.LoadRepositoriesCommand.ExecuteAsync().Subscribe(); - - Assert.True(vm.IsLoading); - - repoSubject.OnNext(new[] { Substitute.For<IRepositoryModel>() }); - repoSubject.OnNext(new[] { Substitute.For<IRepositoryModel>() }); - - Assert.True(vm.IsLoading); - - repoSubject.OnCompleted(); - - Assert.False(vm.IsLoading); - } - - [Fact] - public void IsFalseWhenLoadingReposFailsImmediately() - { - var repoSubject = Observable.Throw<IRepositoryModel[]>(new InvalidOperationException("Doh!")); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - - vm.LoadRepositoriesCommand.ExecuteAsync().Subscribe(); - - Assert.True(vm.LoadingFailed); - Assert.False(vm.IsLoading); - } - } - - public class TheNoRepositoriesFoundProperty : TestBaseClass - { - [Fact] - public void IsTrueInitially() - { - var repoSubject = new Subject<IRepositoryModel[]>(); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - - Assert.True(vm.NoRepositoriesFound); - } - - [Fact] - public void IsFalseWhenLoadingAndCompletedWithRepository() - { - var repoSubject = new Subject<IRepositoryModel[]>(); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - vm.LoadRepositoriesCommand.ExecuteAsync().Subscribe(); - - repoSubject.OnNext(new[] { Substitute.For<IRepositoryModel>() }); - - Assert.False(vm.NoRepositoriesFound); - - repoSubject.OnCompleted(); - - Assert.Equal(1, vm.FilteredRepositories.Count); - Assert.False(vm.NoRepositoriesFound); - } - - [Fact] - public void IsFalseWhenFailed() - { - var repoSubject = new Subject<IRepositoryModel[]>(); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - vm.LoadRepositoriesCommand.ExecuteAsync().Subscribe(); - - repoSubject.OnError(new InvalidOperationException()); - - Assert.False(vm.NoRepositoriesFound); - } - - [Fact] - public void IsTrueWhenLoadingCompleteNotFailedAndNoRepositories() - { - var repoSubject = new Subject<IRepositoryModel[]>(); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - vm.LoadRepositoriesCommand.ExecuteAsync().Subscribe(); - - repoSubject.OnCompleted(); - - Assert.True(vm.NoRepositoriesFound); - } - } - - public class TheLoadingFailedProperty : TestBaseClass - { - [Fact] - public void IsTrueIfLoadingReposFails() - { - var repoSubject = new Subject<IRepositoryModel[]>(); - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetRepositories().Returns(repoSubject); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - vm.LoadRepositoriesCommand.ExecuteAsync().Subscribe(); - - Assert.False(vm.LoadingFailed); - - repoSubject.OnError(new InvalidOperationException("Doh!")); - - Assert.True(vm.LoadingFailed); - Assert.False(vm.IsLoading); - } - } - - public class TheCloneCommand : TestBaseClass - { - [Fact] - public void IsEnabledWhenRepositorySelectedAndPathValid() - { - var repositoryHost = Substitute.For<IRepositoryHost>(); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - Assert.False(vm.CloneCommand.CanExecute(null)); - - vm.BaseRepositoryPath = @"c:\fake\path"; - vm.SelectedRepository = Substitute.For<IRepositoryModel>(); - - Assert.True(vm.CloneCommand.CanExecute(null)); - } - - [Fact] - public void IsNotEnabledWhenPathIsNotValid() - { - var repositoryHost = Substitute.For<IRepositoryHost>(); - var cloneService = Substitute.For<IRepositoryCloneService>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - Substitute.For<IVSServices>()); - vm.BaseRepositoryPath = @"c:|fake\path"; - Assert.False(vm.CloneCommand.CanExecute(null)); - - vm.SelectedRepository = Substitute.For<IRepositoryModel>(); - - Assert.False(vm.CloneCommand.CanExecute(null)); - } - - [Fact] - public async Task DisplaysErrorMessageWhenExceptionOccurs() - { - var repositoryHost = Substitute.For<IRepositoryHost>(); - var cloneService = Substitute.For<IRepositoryCloneService>(); - cloneService.CloneRepository(Args.String, Args.String, Args.String) - .Returns(Observable.Throw<Unit>(new InvalidOperationException("Oh my! That was bad."))); - var vsServices = Substitute.For<IVSServices>(); - var vm = new RepositoryCloneViewModel( - repositoryHost, - cloneService, - Substitute.For<IOperatingSystem>(), - vsServices); - vm.BaseRepositoryPath = @"c:\fake"; - var repository = Substitute.For<IRepositoryModel>(); - repository.Name.Returns("octokit"); - vm.SelectedRepository = repository; - - await vm.CloneCommand.ExecuteAsync(null); - - vsServices.Received().ShowError(@"Failed to clone the repository 'octokit' -Email support@github.com if you continue to have problems."); - } - } -} diff --git a/src/UnitTests/GitHub.App/ViewModels/RepositoryCreationViewModelTests.cs b/src/UnitTests/GitHub.App/ViewModels/RepositoryCreationViewModelTests.cs deleted file mode 100644 index b8da481568..0000000000 --- a/src/UnitTests/GitHub.App/ViewModels/RepositoryCreationViewModelTests.cs +++ /dev/null @@ -1,629 +0,0 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using GitHub.Extensions.Reactive; -using GitHub.Models; -using GitHub.Services; -using GitHub.ViewModels; -using NSubstitute; -using Octokit; -using ReactiveUI; -using Rothko; -using UnitTests; -using Xunit; -using GitHub.Extensions; - -public class RepositoryCreationViewModelTests -{ - static object DefaultInstance = new object(); - - static IRepositoryCreationViewModel GetMeAViewModel( - IServiceProvider provider = null, - IRepositoryCreationService creationService = null) - { - if (provider == null) - provider = Substitutes.ServiceProvider; - var repositoryHost = provider.GetRepositoryHosts().GitHubHost; - var os = provider.GetOperatingSystem(); - creationService = creationService ?? provider.GetRepositoryCreationService(); - var avatarProvider = provider.GetAvatarProvider(); - var connection = provider.GetConnection(); - - return new RepositoryCreationViewModel(repositoryHost, os, creationService, avatarProvider); - } - - public class TheSafeRepositoryNameProperty : TestBaseClass - { - [Fact] - public void IsTheSameAsTheRepositoryNameWhenTheInputIsSafe() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake\"; - vm.RepositoryName = "this-is-bad"; - - Assert.Equal(vm.RepositoryName, vm.SafeRepositoryName); - } - - [Fact] - public void IsConvertedWhenTheRepositoryNameIsNotSafe() - { - var vm = GetMeAViewModel(); - - vm.RepositoryName = "this is bad"; - - Assert.Equal("this-is-bad", vm.SafeRepositoryName); - } - - [Fact] - public void IsNullWhenRepositoryNameIsNull() - { - var vm = GetMeAViewModel(); - Assert.Null(vm.SafeRepositoryName); - vm.RepositoryName = "not-null"; - vm.RepositoryName = null; - - Assert.Null(vm.SafeRepositoryName); - } - } - - public class TheBrowseForDirectoryCommand : TestBaseClass - { - [Fact] - public async Task SetsTheBaseRepositoryPathWhenUserChoosesADirectory() - { - var provider = Substitutes.ServiceProvider; - var windows = provider.GetOperatingSystem(); - windows.Dialog.BrowseForDirectory(@"c:\fake\dev", Args.String) - .Returns(new BrowseDirectoryResult(@"c:\fake\foo")); - var vm = GetMeAViewModel(provider); - - vm.BaseRepositoryPath = @"c:\fake\dev"; - - await vm.BrowseForDirectory.ExecuteAsync(); - - Assert.Equal(@"c:\fake\foo", vm.BaseRepositoryPath); - } - - [Fact] - public async Task DoesNotChangeTheBaseRepositoryPathWhenUserDoesNotChooseResult() - { - var provider = Substitutes.ServiceProvider; - var windows = provider.GetOperatingSystem(); - windows.Dialog.BrowseForDirectory(@"c:\fake\dev", Args.String) - .Returns(BrowseDirectoryResult.Failed); - var vm = GetMeAViewModel(provider); - vm.BaseRepositoryPath = @"c:\fake\dev"; - - await vm.BrowseForDirectory.ExecuteAsync(); - - Assert.Equal(@"c:\fake\dev", vm.BaseRepositoryPath); - } - } - - public class TheBaseRepositoryPathProperty : TestBaseClass - { - [Fact] - public void IsSetFromTheRepositoryCreationService() - { - var repositoryCreationService = Substitute.For<IRepositoryCreationService>(); - repositoryCreationService.DefaultClonePath.Returns(@"c:\fake\default"); - - var vm = GetMeAViewModel(creationService: repositoryCreationService); - - Assert.Equal(@"c:\fake\default", vm.BaseRepositoryPath); - } - } - - public class TheBaseRepositoryPathValidatorProperty : TestBaseClass - { - [Fact] - public void IsFalseWhenPathEmpty() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = ""; - vm.RepositoryName = "foo"; - - Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a repository path", vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - - [Fact] - public void IsFalseWhenPathHasInvalidCharacters() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake!!>\"; - vm.RepositoryName = "foo"; - - Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal("Path contains invalid characters", - vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - - [Fact] - public void IsFalseWhenLotsofInvalidCharactersInPath() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake???\sajoisfaoia\afsofsafs::::\"; - vm.RepositoryName = "foo"; - - Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal("Path contains invalid characters", - vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - - [Fact] - public void IsValidWhenUserAccidentallyUsesForwardSlashes() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake\sajoisfaoia/afsofsafs/"; - vm.RepositoryName = "foo"; - - Assert.True(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - } - - [Fact] - public void IsFalseWhenPathIsNotRooted() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = "fake"; - vm.RepositoryName = "foo"; - - Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a valid path", vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - - [Fact] - public void IsFalseWhenAfterBeingTrue() - { - var vm = GetMeAViewModel(); - vm.BaseRepositoryPath = @"c:\fake\"; - vm.RepositoryName = "repo"; - - Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Empty(vm.RepositoryNameValidator.ValidationResult.Message); - - vm.BaseRepositoryPath = ""; - - Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a repository path", vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - - [Fact] - public void IsTrueWhenRepositoryNameAndPathIsValid() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake\"; - vm.RepositoryName = "thisisfine"; - - Assert.True(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Empty(vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - - [Fact] - public void IsTrueWhenSetToValidQuotedPath() - { - var vm = GetMeAViewModel(); - - vm.RepositoryName = "thisisfine"; - vm.BaseRepositoryPath = @"""c:\fake"""; - - Assert.True(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal(@"c:\fake", vm.BaseRepositoryPath); - } - - [Fact] - public void ReturnsCorrectMessageWhenPathTooLong() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"C:\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"; - - Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); - Assert.Equal("Path too long", vm.BaseRepositoryPathValidator.ValidationResult.Message); - } - } - - public class TheRepositoryNameValidatorProperty : TestBaseClass - { - [Fact] - public void IsFalseWhenRepoNameEmpty() - { - var vm = GetMeAViewModel(); - vm.BaseRepositoryPath = @"c:\fake\"; - - vm.RepositoryName = ""; - - Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a repository name", vm.RepositoryNameValidator.ValidationResult.Message); - } - - [Fact] - public void IsFalseWhenAfterBeingTrue() - { - var vm = GetMeAViewModel(); - vm.BaseRepositoryPath = @"c:\fake\"; - vm.RepositoryName = "repo"; - - Assert.True(vm.CreateRepository.CanExecute(null)); - Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Empty(vm.RepositoryNameValidator.ValidationResult.Message); - - vm.RepositoryName = ""; - - Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a repository name", vm.RepositoryNameValidator.ValidationResult.Message); - } - - [Fact] - public void IsTrueWhenRepositoryNameAndPathIsValid() - { - var vm = GetMeAViewModel(); - - vm.RepositoryName = "thisisfine"; - - Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Empty(vm.RepositoryNameValidator.ValidationResult.Message); - } - - [Theory] - [InlineData(true, false)] - [InlineData(false, true)] - public void IsFalseWhenRepositoryAlreadyExists(bool exists, bool expected) - { - var provider = Substitutes.ServiceProvider; - var operatingSystem = provider.GetOperatingSystem(); - operatingSystem.File.Exists(@"c:\fake\foo\.git\HEAD").Returns(exists); - var vm = GetMeAViewModel(provider); - vm.BaseRepositoryPath = @"c:\fake\"; - - vm.RepositoryName = "foo"; - - Assert.Equal(expected, vm.RepositoryNameValidator.ValidationResult.IsValid); - if (!expected) - Assert.Equal("Repository with same name already exists at this location", - vm.RepositoryNameValidator.ValidationResult.Message); - } - } - - public class TheSafeRepositoryNameWarningValidatorProperty : TestBaseClass - { - [Fact] - public void IsTrueWhenRepoNameIsSafe() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake\"; - vm.RepositoryName = "this-is-bad"; - - Assert.True(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); - } - - [Fact] - public void IsFalseWhenRepoNameIsNotSafe() - { - var vm = GetMeAViewModel(); - - vm.BaseRepositoryPath = @"c:\fake\"; - vm.RepositoryName = "this is bad"; - - Assert.False(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); - Assert.Equal("Will be created as this-is-bad", vm.SafeRepositoryNameWarningValidator.ValidationResult.Message); - } - } - - public class TheAccountsProperty : TestBaseClass - { - [Fact] - public void IsPopulatedByTheRepositoryHost() - { - var accounts = new ReactiveList<IAccount>() { Substitute.For<IAccount>(), Substitute.For<IAccount>() }; - var repositoryHost = Substitute.For<IRepositoryHost>(); - repositoryHost.ModelService.GetAccounts().Returns(Observable.Return(accounts)); - var vm = new RepositoryCreationViewModel( - repositoryHost, - Substitute.For<IOperatingSystem>(), - Substitute.For<IRepositoryCreationService>(), - Substitute.For<IAvatarProvider>()); - - Assert.Equal(vm.Accounts[0], vm.SelectedAccount); - Assert.Equal(2, vm.Accounts.Count); - } - } - - public class TheGitIgnoreTemplatesProperty : TestBaseClass - { - [Fact] - public void IsPopulatedByTheApiAndSortedWithRecommendedFirst() - { - var gitIgnoreTemplates = new[] - { - "None", - "VisualStudio", - "Node", - "Waf", - "WordPress" - }.Select(GitIgnoreItem.Create); - var provider = Substitutes.ServiceProvider; - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService - .GetGitIgnoreTemplates() - .Returns(gitIgnoreTemplates.ToObservable().ToReadOnlyList()); - var vm = GetMeAViewModel(provider); - - var result = vm.GitIgnoreTemplates; - - Assert.Equal(5, result.Count); - Assert.Equal("None", result[0].Name); - Assert.True(result[0].Recommended); - Assert.Equal("VisualStudio", result[1].Name); - Assert.True(result[1].Recommended); - Assert.Equal("Node", result[2].Name); - Assert.True(result[2].Recommended); - Assert.Equal("Waf", result[3].Name); - Assert.False(result[3].Recommended); - Assert.Equal("WordPress", result[4].Name); - Assert.False(result[4].Recommended); - } - } - - public class TheLicensesProperty : TestBaseClass - { - [Fact] - public void IsPopulatedByTheModelService() - { - var licenses = new[] - { - LicenseItem.None, - new LicenseItem("apache-2.0", "Apache License 2.0"), - new LicenseItem("mit", "MIT License"), - new LicenseItem("agpl-3.0", "GNU Affero GPL v3.0"), - new LicenseItem("artistic-2.0", "Artistic License 2.0") - }; - var provider = Substitutes.ServiceProvider; - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService - .GetLicenses() - .Returns(licenses.ToObservable().ToReadOnlyList()); - var vm = GetMeAViewModel(provider); - - var result = vm.Licenses; - - Assert.Equal(5, result.Count); - Assert.Equal("", result[0].Key); - Assert.Equal("None", result[0].Name); - Assert.True(result[0].Recommended); - Assert.Equal("apache-2.0", result[1].Key); - Assert.True(result[1].Recommended); - Assert.Equal("mit", result[2].Key); - Assert.True(result[2].Recommended); - Assert.Equal("agpl-3.0", result[3].Key); - Assert.False(result[3].Recommended); - Assert.Equal("artistic-2.0", result[4].Key); - Assert.False(result[4].Recommended); - Assert.Equal(result[0], vm.SelectedLicense); - } - } - - public class TheSelectedGitIgnoreProperty : TestBaseClass - { - [Fact] - public void DefaultsToVisualStudio() - { - var gitignores = new[] - { - GitIgnoreItem.None, - GitIgnoreItem.Create("C++"), - GitIgnoreItem.Create("Node"), - GitIgnoreItem.Create("VisualStudio"), - }; - var provider = Substitutes.ServiceProvider; - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService - .GetGitIgnoreTemplates() - .Returns(gitignores.ToObservable().ToReadOnlyList()); - var vm = GetMeAViewModel(provider); - - Assert.Equal("VisualStudio", vm.SelectedGitIgnoreTemplate.Name); - } - - [Fact] - public void DefaultsToNoneIfVisualStudioIsMissingSomehow() - { - var gitignores = new[] - { - GitIgnoreItem.None, - GitIgnoreItem.Create("C++"), - GitIgnoreItem.Create("Node"), - }; - var provider = Substitutes.ServiceProvider; - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService - .GetGitIgnoreTemplates() - .Returns(gitignores.ToObservable().ToReadOnlyList()); - var vm = GetMeAViewModel(provider); - - Assert.Equal("None", vm.SelectedGitIgnoreTemplate.Name); - } - } - - public class TheCreateRepositoryCommand : TestBaseClass - { - [Fact] - public async Task DisplaysUserErrorWhenCreationFails() - { - var creationService = Substitutes.RepositoryCreationService; - var provider = Substitutes.GetServiceProvider(creationService: creationService); - - creationService.CreateRepository(Args.NewRepository, Args.Account, Args.String, Args.ApiClient) - .Returns(Observable.Throw<Unit>(new InvalidOperationException("Could not create a repository on GitHub"))); - var vm = GetMeAViewModel(provider); - - vm.RepositoryName = "my-repo"; - - using (var handlers = ReactiveTestHelper.OverrideHandlersForTesting()) - { - await vm.CreateRepository.ExecuteAsync().Catch(Observable.Return(Unit.Default)); - - Assert.Equal("Could not create a repository on GitHub", handlers.LastError.ErrorMessage); - } - } - - [Fact] - public void CreatesARepositoryUsingTheCreationService() - { - var creationService = Substitutes.RepositoryCreationService; - var provider = Substitutes.GetServiceProvider(creationService: creationService); - - var account = Substitute.For<IAccount>(); - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount> { account })); - var vm = GetMeAViewModel(provider); - vm.RepositoryName = "Krieger"; - vm.BaseRepositoryPath = @"c:\dev"; - vm.SelectedAccount = account; - vm.KeepPrivate = true; - - vm.CreateRepository.Execute(null); - - creationService - .Received() - .CreateRepository( - Arg.Is<NewRepository>(r => r.Name == "Krieger" - && r.Private == true - && r.AutoInit == null - && r.LicenseTemplate == null - && r.GitignoreTemplate == null), - account, - @"c:\dev", - Args.ApiClient); - } - - [Fact] - public void SetsAutoInitToTrueWhenLicenseSelected() - { - var creationService = Substitutes.RepositoryCreationService; - var provider = Substitutes.GetServiceProvider(creationService: creationService); - var account = Substitute.For<IAccount>(); - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount> { account })); - var vm = GetMeAViewModel(provider); - vm.RepositoryName = "Krieger"; - vm.BaseRepositoryPath = @"c:\dev"; - vm.SelectedAccount = account; - vm.KeepPrivate = false; - vm.SelectedLicense = new LicenseItem("mit", "MIT"); - - vm.CreateRepository.Execute(null); - - creationService - .Received() - .CreateRepository( - Arg.Is<NewRepository>(r => r.Name == "Krieger" - && r.Private == false - && r.AutoInit == true - && r.LicenseTemplate == "mit" - && r.GitignoreTemplate == null), - account, - @"c:\dev", - Args.ApiClient); - } - - [Fact] - public void SetsAutoInitToTrueWhenGitIgnore() - { - var creationService = Substitutes.RepositoryCreationService; - var provider = Substitutes.GetServiceProvider(creationService: creationService); - var account = Substitute.For<IAccount>(); - var hosts = provider.GetRepositoryHosts(); - var host = hosts.GitHubHost; - hosts.LookupHost(Args.HostAddress).Returns(host); - host.ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount> { account })); - var vm = GetMeAViewModel(provider); - vm.RepositoryName = "Krieger"; - vm.BaseRepositoryPath = @"c:\dev"; - vm.SelectedAccount = account; - vm.KeepPrivate = false; - vm.SelectedGitIgnoreTemplate = GitIgnoreItem.Create("VisualStudio"); - - vm.CreateRepository.Execute(null); - - creationService - .Received() - .CreateRepository( - Arg.Is<NewRepository>(r => r.Name == "Krieger" - && r.Private == false - && r.AutoInit == true - && r.LicenseTemplate == null - && r.GitignoreTemplate == "VisualStudio"), - account, - @"c:\dev", - Args.ApiClient); - } - - [Theory] - [InlineData("", "", false)] - [InlineData("", @"c:\dev", false)] - [InlineData("blah", @"c:\|dev", false)] - [InlineData("blah", @"c:\dev", true)] - public void CannotCreateWhenRepositoryNameOrBasePathIsInvalid( - string repositoryName, - string baseRepositoryPath, - bool expected) - { - var vm = GetMeAViewModel(); - vm.RepositoryName = repositoryName; - vm.BaseRepositoryPath = baseRepositoryPath; - var reactiveCommand = vm.CreateRepository as ReactiveCommand<Unit>; - - bool result = reactiveCommand.CanExecute(null); - - Assert.Equal(expected, result); - } - } - - public class TheCanKeepPrivateProperty : TestBaseClass - { - [Theory] - [InlineData(true, false, false, false)] - [InlineData(true, false, true, false)] - [InlineData(false, false, true, false)] - [InlineData(true, true, true, true)] - [InlineData(false, false, false, true)] - public void IsOnlyTrueWhenUserIsEntepriseOrNotOnFreeAccountThatIsNotMaxedOut( - bool isFreeAccount, - bool isEnterprise, - bool isMaxedOut, - bool expected) - { - var selectedAccount = Substitute.For<IAccount>(); - selectedAccount.IsOnFreePlan.Returns(isFreeAccount); - selectedAccount.IsEnterprise.Returns(isEnterprise); - selectedAccount.HasMaximumPrivateRepositories.Returns(isMaxedOut); - var vm = GetMeAViewModel(); - vm.SelectedAccount = selectedAccount; - - Assert.Equal(expected, vm.CanKeepPrivate); - } - } -} diff --git a/src/UnitTests/GitHub.App/ViewModels/RepositoryPublishViewModelTests.cs b/src/UnitTests/GitHub.App/ViewModels/RepositoryPublishViewModelTests.cs deleted file mode 100644 index 502a69ad4f..0000000000 --- a/src/UnitTests/GitHub.App/ViewModels/RepositoryPublishViewModelTests.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System.Reactive.Linq; -using GitHub.Models; -using GitHub.Services; -using GitHub.ViewModels; -using NSubstitute; -using ReactiveUI; -using Xunit; -using UnitTests; -using Microsoft.Reactive.Testing; -using ReactiveUI.Testing; -using System.Reactive; -using System.Threading.Tasks; -using System; -using GitHub.Primitives; -using GitHub.Info; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -public class RepositoryPublishViewModelTests -{ - public static class Helpers - { - public static IRepositoryPublishViewModel GetViewModel(IRepositoryPublishService service = null) - { - return GetViewModel(null, service, null); - } - - public static IRepositoryPublishViewModel GetViewModel( - IRepositoryHosts hosts = null, - IRepositoryPublishService service = null, - IVSServices vsServices = null, - IConnectionManager connectionManager = null) - { - hosts = hosts ?? Substitutes.RepositoryHosts; - service = service ?? Substitute.For<IRepositoryPublishService>(); - vsServices = vsServices ?? Substitute.For<IVSServices>(); - connectionManager = connectionManager ?? Substitutes.ConnectionManager; - return new RepositoryPublishViewModel(hosts, service, vsServices, connectionManager); - } - - public static void SetupConnections(IRepositoryHosts hosts, IConnectionManager cm, - List<HostAddress> adds, List<IConnection> conns, List<IRepositoryHost> hsts, - string uri) - { - var add = HostAddress.Create(new Uri(uri)); - var host = Substitute.For<IRepositoryHost>(); - var conn = Substitute.For<IConnection>(); - host.Address.Returns(add); - conn.HostAddress.Returns(add); - adds.Add(add); - hsts.Add(host); - conns.Add(conn); - - if (add.IsGitHubDotCom()) - hosts.GitHubHost.Returns(host); - else - hosts.EnterpriseHost.Returns(host); - hosts.LookupHost(Arg.Is(add)).Returns(host); - } - - public static IRepositoryPublishViewModel SetupConnectionsAndViewModel( - IRepositoryHosts hosts = null, - IRepositoryPublishService service = null, - IVSServices vs = null, - IConnectionManager cm = null, - string uri = GitHubUrls.GitHub) - { - cm = cm ?? Substitutes.ConnectionManager; - hosts = hosts ?? Substitute.For<IRepositoryHosts>(); - var adds = new List<HostAddress>(); - var hsts = new List<IRepositoryHost>(); - var conns = new List<IConnection>(); - SetupConnections(hosts, cm, adds, conns, hsts, uri); - hsts[0].ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount>())); - cm.Connections.Returns(new ObservableCollection<IConnection>(conns)); - return GetViewModel(hosts, service, vs, cm); - } - - public static string[] GetArgs(params string[] args) - { - var ret = new List<string>(); - foreach (var arg in args) - if (arg != null) - ret.Add(arg); - return ret.ToArray(); - } - - } - - public class TheConnectionsProperty : TestBaseClass - { - [Theory] - [InlineData(GitHubUrls.GitHub, "https://ghe.io" )] - [InlineData("https://ghe.io", null)] - [InlineData(GitHubUrls.GitHub, null)] - public void ConnectionsMatchHosts(string arg1, string arg2) - { - var args = Helpers.GetArgs(arg1, arg2); - - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var adds = new List<HostAddress>(); - var hsts = new List<IRepositoryHost>(); - var conns = new List<IConnection>(); - foreach (var uri in args) - Helpers.SetupConnections(hosts, cm, adds, conns, hsts, uri); - - foreach(var host in hsts) - host.ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount>())); - - cm.Connections.Returns(new ObservableCollection<IConnection>(conns)); - - var vm = Helpers.GetViewModel(hosts: hosts, connectionManager: cm); - - var connections = vm.Connections; - - Assert.Equal(args.Length, connections.Count); - for (int i = 0; i < conns.Count; i++) - { - Assert.Same(hsts[i], hosts.LookupHost(conns[i].HostAddress)); - } - } - } - - public class TheSelectedConnectionProperty : TestBaseClass - { - [Theory] - [InlineData(GitHubUrls.GitHub, "https://ghe.io")] - [InlineData("https://ghe.io", GitHubUrls.GitHub)] - public void DefaultsToGitHub(string arg1, string arg2) - { - var args = Helpers.GetArgs(arg1, arg2); - - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var adds = new List<HostAddress>(); - var hsts = new List<IRepositoryHost>(); - var conns = new List<IConnection>(); - foreach (var uri in args) - Helpers.SetupConnections(hosts, cm, adds, conns, hsts, uri); - - foreach (var host in hsts) - host.ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount>())); - - cm.Connections.Returns(new ObservableCollection<IConnection>(conns)); - var vm = Helpers.GetViewModel(hosts, connectionManager: cm); - - Assert.Same(adds.First(x => x.IsGitHubDotCom()), vm.SelectedConnection.HostAddress); - Assert.Same(conns.First(x => x.HostAddress.IsGitHubDotCom()), vm.SelectedConnection); - Assert.Same(hsts.First(x => x.Address.IsGitHubDotCom()), hosts.LookupHost(vm.SelectedConnection.HostAddress)); - } - } - - public class TheAccountsProperty : TestBaseClass - { - [Fact] - public void IsPopulatedByTheAccountsForTheSelectedHost() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var adds = new List<HostAddress>(); - var hsts = new List<IRepositoryHost>(); - var conns = new List<IConnection>(); - Helpers.SetupConnections(hosts, cm, adds, conns, hsts, GitHubUrls.GitHub); - Helpers.SetupConnections(hosts, cm, adds, conns, hsts, "https://ghe.io"); - - var gitHubAccounts = new ReactiveList<IAccount> { Substitute.For<IAccount>(), Substitute.For<IAccount>() }; - var enterpriseAccounts = new ReactiveList<IAccount> { Substitute.For<IAccount>() }; - - hsts.First(x => x.Address.IsGitHubDotCom()).ModelService.GetAccounts().Returns(Observable.Return(gitHubAccounts)); - hsts.First(x => !x.Address.IsGitHubDotCom()).ModelService.GetAccounts().Returns(Observable.Return(enterpriseAccounts)); - - cm.Connections.Returns(new ObservableCollection<IConnection>(conns)); - var vm = Helpers.GetViewModel(hosts, connectionManager: cm); - - Assert.Equal(2, vm.Accounts.Count); - Assert.Same(gitHubAccounts[0], vm.SelectedAccount); - - vm.SelectedConnection = conns.First(x => !x.HostAddress.IsGitHubDotCom()); - - Assert.Equal(1, vm.Accounts.Count); - Assert.Same(enterpriseAccounts[0], vm.SelectedAccount); - } - } - - public class TheSafeRepositoryNameProperty : TestBaseClass - { - [Fact] - public void IsTheSameAsTheRepositoryNameWhenTheInputIsSafe() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = "this-is-bad"; - - Assert.Equal(vm.RepositoryName, vm.SafeRepositoryName); - } - - [Fact] - public void IsConvertedWhenTheRepositoryNameIsNotSafe() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = "this is bad"; - - Assert.Equal("this-is-bad", vm.SafeRepositoryName); - } - - [Fact] - public void IsNullWhenRepositoryNameIsNull() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - Assert.Null(vm.SafeRepositoryName); - vm.RepositoryName = "not-null"; - vm.RepositoryName = null; - - Assert.Null(vm.SafeRepositoryName); - } - } - - public class TheRepositoryNameValidatorProperty : TestBaseClass - { - [Fact] - public void IsFalseWhenRepoNameEmpty() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = ""; - - Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a repository name", vm.RepositoryNameValidator.ValidationResult.Message); - } - - [Fact] - public void IsFalseWhenAfterBeingTrue() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = "repo"; - - Assert.True(vm.PublishRepository.CanExecute(null)); - Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Empty(vm.RepositoryNameValidator.ValidationResult.Message); - - vm.RepositoryName = ""; - - Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Equal("Please enter a repository name", vm.RepositoryNameValidator.ValidationResult.Message); - } - - [Fact] - public void IsTrueWhenRepositoryNameIsValid() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = "thisisfine"; - - Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); - Assert.Empty(vm.RepositoryNameValidator.ValidationResult.Message); - } - } - - public class TheSafeRepositoryNameWarningValidatorProperty : TestBaseClass - { - [Fact] - public void IsTrueWhenRepoNameIsSafe() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = "this-is-bad"; - - Assert.True(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); - } - - [Fact] - public void IsFalseWhenRepoNameIsNotSafe() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, cm: cm); - - vm.RepositoryName = "this is bad"; - - Assert.False(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); - Assert.Equal("Will be created as this-is-bad", vm.SafeRepositoryNameWarningValidator.ValidationResult.Message); - } - - [Fact] - public void DisplaysWarningWhenRepoNameNotSafeAndClearsItWhenSafeAgain() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vsServices = Substitute.For<IVSServices>(); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, vs: vsServices, cm: cm); - - vsServices.DidNotReceive().ShowWarning(Args.String); - - vm.RepositoryName = "this is bad"; - Assert.Equal("this-is-bad", vm.SafeRepositoryName); - - vsServices.Received().ShowWarning("Will be created as this-is-bad"); - vsServices.DidNotReceive().ClearNotifications(); - - vm.RepositoryName = "this"; - - vsServices.Received().ClearNotifications(); - } - } - - public class ThePublishRepositoryCommand : TestBaseClass - { - [Fact] - public async Task DisplaysSuccessMessageWhenCompletedWithoutError() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vsServices = Substitute.For<IVSServices>(); - - var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); - repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) - .Returns(Observable.Return(new Octokit.Repository())); - - var vm = Helpers.SetupConnectionsAndViewModel(hosts, repositoryPublishService, vsServices, cm); - - vm.RepositoryName = "repo-name"; - - await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Success)); - - vsServices.Received().ShowMessage("Repository published successfully."); - vsServices.DidNotReceive().ShowError(Args.String); - } - - [Fact] - public async Task DisplaysRepositoryExistsErrorWithVisualStudioNotifications() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var vsServices = Substitute.For<IVSServices>(); - - var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); - repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) - .Returns(Observable.Throw<Octokit.Repository>(new Octokit.RepositoryExistsException("repo-name", new Octokit.ApiValidationException()))); - var vm = Helpers.SetupConnectionsAndViewModel(hosts, repositoryPublishService, vsServices, cm); - vm.RepositoryName = "repo-name"; - - await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Fail)); - - vsServices.DidNotReceive().ShowMessage(Args.String); - vsServices.Received().ShowError("There is already a repository named 'repo-name' for the current account."); - } - - [Fact] - public async Task ClearsErrorsWhenSwitchingHosts() - { - var args = Helpers.GetArgs(GitHubUrls.GitHub, "https://ghe.io"); - - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var adds = new List<HostAddress>(); - var hsts = new List<IRepositoryHost>(); - var conns = new List<IConnection>(); - foreach (var uri in args) - Helpers.SetupConnections(hosts, cm, adds, conns, hsts, uri); - - foreach (var host in hsts) - host.ModelService.GetAccounts().Returns(Observable.Return(new ReactiveList<IAccount>())); - - cm.Connections.Returns(new ObservableCollection<IConnection>(conns)); - - var vsServices = Substitute.For<IVSServices>(); - - var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); - repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) - .Returns(Observable.Throw<Octokit.Repository>(new Octokit.RepositoryExistsException("repo-name", new Octokit.ApiValidationException()))); - var vm = Helpers.GetViewModel(hosts, repositoryPublishService, vsServices, cm); - - vm.RepositoryName = "repo-name"; - - await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Fail)); - - vm.SelectedConnection = conns.First(x => x != vm.SelectedConnection); - - vsServices.Received().ClearNotifications(); - } - - [Fact] - public async Task ClearsErrorsWhenSwitchingAccounts() - { - var cm = Substitutes.ConnectionManager; - var hosts = Substitute.For<IRepositoryHosts>(); - var adds = new List<HostAddress>(); - var hsts = new List<IRepositoryHost>(); - var conns = new List<IConnection>(); - Helpers.SetupConnections(hosts, cm, adds, conns, hsts, GitHubUrls.GitHub); - - var accounts = new ReactiveList<IAccount> { Substitute.For<IAccount>(), Substitute.For<IAccount>() }; - hsts[0].ModelService.GetAccounts().Returns(Observable.Return(accounts)); - - cm.Connections.Returns(new ObservableCollection<IConnection>(conns)); - - var vsServices = Substitute.For<IVSServices>(); - - var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); - repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) - .Returns(Observable.Throw<Octokit.Repository>(new Octokit.RepositoryExistsException("repo-name", new Octokit.ApiValidationException()))); - var vm = Helpers.GetViewModel(hosts, repositoryPublishService, vsServices, cm); - - vm.RepositoryName = "repo-name"; - - await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Fail)); - - vm.SelectedAccount = accounts[1]; - - vsServices.Received().ClearNotifications(); - } - } -} diff --git a/src/UnitTests/GitHub.Exports.Reactive/Caches/AccountCacheItemTests.cs b/src/UnitTests/GitHub.Exports.Reactive/Caches/AccountCacheItemTests.cs deleted file mode 100644 index 3f79d96dbd..0000000000 --- a/src/UnitTests/GitHub.Exports.Reactive/Caches/AccountCacheItemTests.cs +++ /dev/null @@ -1,30 +0,0 @@ - -using System; -using GitHub.Caches; -using Octokit; -using Xunit; - -public class AccountCacheItemTests -{ - public class TheConstructor : TestBaseClass - { - [Theory] - [InlineData("https://foo.com", true)] - [InlineData("https://notgithub.com", true)] - [InlineData("https://github.com", false)] - [InlineData("https://api.github.com", false)] - [InlineData("GARBAGE", false)] - public void SetsIsEnterpriseCorrectly(string htmlUrl, bool expected) - { - var apiAccount = CreateOctokitUser(htmlUrl); - var cachedAccount = new AccountCacheItem(apiAccount); - - Assert.Equal(expected, cachedAccount.IsEnterprise); - } - } - - static User CreateOctokitUser(string url) - { - return new User("https://url", "", "", 1, "GitHub", DateTimeOffset.UtcNow, 0, "email", 100, 100, true, url, 10, 42, "somewhere", "foo", "Who cares", 1, new Plan(), 1, 1, 1, "https://url", false); - } -} diff --git a/src/UnitTests/GitHub.Exports/GitServiceTests.cs b/src/UnitTests/GitHub.Exports/GitServiceTests.cs deleted file mode 100644 index 52e40b92be..0000000000 --- a/src/UnitTests/GitHub.Exports/GitServiceTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using GitHub.Services; -using LibGit2Sharp; -using NSubstitute; -using Xunit; -using System.Linq; -using System.Collections; -using System.Collections.Generic; - -public class GitServiceTests : TestBaseClass -{ - [Theory] - [InlineData("asdf", null)] - [InlineData("", null)] - [InlineData(null, null)] - [InlineData("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] - [InlineData("http://example.com/", "http://example.com/")] - [InlineData("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")] - [InlineData("https://github.com/github/Windows", "https://github.com/github/Windows")] - [InlineData("https://github.com/github/Windows.git", "https://github.com/github/Windows")] - [InlineData("https://haacked@github.com/github/Windows.git", "https://github.com/github/Windows")] - [InlineData("http://example.com:4000/github/Windows", "http://example.com:4000/github/Windows")] - [InlineData("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/github/Windows")] - [InlineData("git@example.com:org/repo.git", "https://example.com/org/repo")] - [InlineData("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")] - [InlineData("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")] - public void GetUriShouldNotThrow(string url, string expected) - { - var origin = Substitute.For<Remote>(); - origin.Url.Returns(url); - var repository = Substitute.For<IRepository>(); - repository.Network.Remotes["origin"].Returns(origin); - - var gitservice = new GitService(); - Assert.Equal(expected, gitservice.GetUri(repository)?.ToString()); - } -} diff --git a/src/UnitTests/GitHub.Exports/VSServicesTests.cs b/src/UnitTests/GitHub.Exports/VSServicesTests.cs deleted file mode 100644 index b87206f967..0000000000 --- a/src/UnitTests/GitHub.Exports/VSServicesTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using GitHub.Services; -using Microsoft.TeamFoundation.Git.Controls.Extensibility; -using NSubstitute; -using Xunit; - -public class VSServicesTests -{ - public class TheCloneMethod : TestBaseClass - { - [Theory] - [InlineData(true, CloneOptions.RecurseSubmodule)] - [InlineData(false, CloneOptions.None)] - public void CallsCloneOnVsProvidedCloneService(bool recurseSubmodules, CloneOptions expectedCloneOptions) - { - var provider = Substitute.For<IUIProvider>(); - var gitRepositoriesExt = Substitute.For<IGitRepositoriesExt>(); - provider.GetService(typeof(IGitRepositoriesExt)).Returns(gitRepositoriesExt); - var vsServices = new VSServices(provider); - - vsServices.Clone("https://github.com/github/visualstudio", @"c:\fake\ghfvs", recurseSubmodules); - - gitRepositoriesExt.Received() - .Clone("https://github.com/github/visualstudio", @"c:\fake\ghfvs", expectedCloneOptions); - } - } -} diff --git a/src/UnitTests/GitHub.Extensions/UriExtensionTests.cs b/src/UnitTests/GitHub.Extensions/UriExtensionTests.cs deleted file mode 100644 index c964709023..0000000000 --- a/src/UnitTests/GitHub.Extensions/UriExtensionTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using GitHub.Extensions; -using Xunit; - -public class UriExtensionTests -{ - public class TheAppendMethod : TestBaseClass - { - [Theory] - [InlineData("https://github.com/foo/bar", "graphs", "https://github.com/foo/bar/graphs")] - [InlineData("https://github.com/foo/bar/", "graphs", "https://github.com/foo/bar/graphs")] - [InlineData("https://github.com", "bippety/boppety", "https://github.com/bippety/boppety")] - [InlineData("https://github.com/", "bippety/boppety", "https://github.com/bippety/boppety")] - [InlineData("https://github.com/foo/bar", "bippety/boppety", "https://github.com/foo/bar/bippety/boppety")] - public void AppendsRelativePath(string url, string relativePath, string expected) - { - var uri = new Uri(url, UriKind.Absolute); - var expectedUri = new Uri(expected, UriKind.Absolute); - - var result = uri.Append(relativePath); - - Assert.Equal(expectedUri, result); - } - } -} diff --git a/src/UnitTests/GitHub.Primitives/UriStringTests.cs b/src/UnitTests/GitHub.Primitives/UriStringTests.cs deleted file mode 100644 index b9c33e2f5b..0000000000 --- a/src/UnitTests/GitHub.Primitives/UriStringTests.cs +++ /dev/null @@ -1,306 +0,0 @@ -using System; -using System.Collections.Generic; -using GitHub.Primitives; -using Xunit; - -public class UriStringTests -{ - public class TheConstructor : TestBaseClass - { - [Theory] - [InlineData("http://192.168.1.3/foo/bar.git", "192.168.1.3", "foo", "bar")] - [InlineData("http://haacked@example.com/foo/bar", "example.com", "foo", "bar")] - [InlineData("http://haacked:password@example.com/foo/bar", "example.com", "foo", "bar")] - [InlineData("http://example.com/foo/bar.git", "example.com", "foo", "bar")] - [InlineData("http://example.com/foo/bar", "example.com", "foo", "bar")] - [InlineData("http://example.com:1234/foo/bar.git", "example.com", "foo", "bar")] - [InlineData("https://github.com/github/Windows.git", "github.com", "github", "Windows")] - [InlineData("https://github.com/libgit2/libgit2.github.com", "github.com", "libgit2", "libgit2.github.com")] - [InlineData("https://github.com/github/Windows", "github.com", "github", "Windows")] - [InlineData("git@192.168.1.2:github/Windows.git", "192.168.1.2", "github", "Windows")] - [InlineData("git@github.com:github/Windows.git", "github.com", "github", "Windows")] - [InlineData("git@github.com:github/Windows", "github.com", "github", "Windows")] - [InlineData("git@example.com:org/repo.git", "example.com", "org", "repo")] - [InlineData("ssh://git@github.com:443/benstraub/libgit2", "github.com", "benstraub", "libgit2")] - [InlineData("git://git@github.com:443/benstraub/libgit2", "github.com", "benstraub", "libgit2")] - [InlineData("jane;fingerprint=9e:1a:5e:27:16:4d:2a:13:90:2c:64:41:bd:25:fd:35@foo.com:github/Windows.git", - "foo.com", "github", "Windows")] - [InlineData("https://haacked@bitbucket.org/haacked/test-mytest.git", "bitbucket.org", "haacked", "test-mytest")] - [InlineData("https://git01.codeplex.com/nuget", "git01.codeplex.com", null, "nuget")] - [InlineData("https://example.com/vpath/foo/bar", "example.com", "foo", "bar")] - [InlineData("https://example.com/vpath/foo/bar.git", "example.com", "foo", "bar")] - [InlineData("https://github.com/github/Windows.git?pr=24&branch=pr/23&filepath=relative/to/the/path.md", - "github.com", "github", "Windows")] - public void ParsesWellFormedUrlComponents(string url, string expectedHost, string owner, string repositoryName) - { - var cloneUrl = new UriString(url); - - Assert.Equal(cloneUrl.Host, expectedHost); - Assert.Equal(cloneUrl.Owner, owner); - Assert.Equal(cloneUrl.RepositoryName, repositoryName); - Assert.False(cloneUrl.IsFileUri); - } - - [Theory] - [InlineData(@"..\bar\foo")] - [InlineData(@"..\..\foo")] - [InlineData(@"../..\foo")] - [InlineData(@"../../foo")] - [InlineData(@"..\..\foo.git")] - [InlineData(@"c:\dev\exp\foo")] - [InlineData(@"c:\dev\bar\..\exp\foo")] - [InlineData("c:/dev/exp/foo")] - [InlineData(@"c:\dev\exp\foo.git")] - [InlineData(@"c:\dev\exp\foo.git")] - [InlineData("c:/dev/exp/foo.git")] - [InlineData("c:/dev/exp/bar/../foo.git")] - [InlineData("file:///C:/dev/exp/foo")] - [InlineData("file://C:/dev/exp/foo")] - [InlineData("file://C:/dev/exp/foo.git")] - public void ParsesLocalFileUris(string path) - { - var cloneUrl = new UriString(path); - - Assert.Equal(cloneUrl.Host, ""); - Assert.Equal(cloneUrl.Owner, ""); - Assert.Equal(cloneUrl.RepositoryName, "foo"); - Assert.Equal(cloneUrl.ToString(), path.Replace('\\', '/')); - Assert.True(cloneUrl.IsFileUri); - } - - [Theory] - [InlineData("complete garbage", "", "", null)] - [InlineData(@"..\other_folder", "", "", "other_folder")] - [InlineData("http://example.com", "example.com", null, null)] - [InlineData("http://example.com?bar", "example.com", null, null)] - [InlineData("https://example.com?bar", "example.com", null, null)] - [InlineData("ssh://git@example.com/Windows.git", "example.com", null, "Windows")] - [InlineData("blah@bar.com:/", "bar.com", null, null)] - [InlineData("blah@bar.com/", "bar.com", null, null)] - [InlineData("blah@bar.com", "bar.com", null, null)] - [InlineData("blah@bar.com:/Windows.git", "bar.com", null, "Windows")] - [InlineData("blah@baz.com/Windows.git", "baz.com", null, "Windows")] - [InlineData("ssh://git@github.com:github/Windows.git", "github.com", "github", "Windows")] - [InlineData("ssh://git@example.com/Windows.git", "example.com", null, "Windows")] - public void ParsesWeirdUrlsAsWellAsPossible(string url, string expectedHost, string owner, string repositoryName) - { - var cloneUrl = new UriString(url); - - Assert.Equal(cloneUrl.Host, expectedHost); - Assert.Equal(cloneUrl.Owner, owner); - Assert.Equal(cloneUrl.RepositoryName, repositoryName); - } - - [Theory] - [InlineData(@"http:\\example.com/bar\baz")] - [InlineData(@"http://example.com/bar/baz")] - public void NormalizesSeparator(string uriString) - { - var path = new UriString(uriString); - new UriString("http://example.com/bar/baz").Equals(path); - } - - [Fact] - public void EnsuresArgumentNotNull() - { - Assert.Throws<ArgumentNullException>(() => new UriString(null)); - } - } - - public class TheNameWithOwnerProperty : TestBaseClass - { - [Theory] - [InlineData("http://192.168.1.3/foo/bar.git", "foo/bar")] - [InlineData("http://192.168.1.3/foo/bar", "foo/bar")] - [InlineData("http://192.168.1.3/foo/bar/baz/qux", "baz/qux")] - [InlineData("https://github.com/github/Windows.git", "github/Windows")] - [InlineData("https://github.com/github/", "github")] - [InlineData("blah@bar.com:/Windows.git", "Windows")] - [InlineData("git@github.com:github/Windows.git", "github/Windows")] - public void DependsOnOwnerAndRepoNameNotBeingNull(string url, string expectedNameWithOwner) - { - var cloneUrl = new UriString(url); - - Assert.Equal(cloneUrl.NameWithOwner, expectedNameWithOwner); - } - } - - public class TheCombineMethod : TestBaseClass - { - [Theory] - [InlineData("http://example.com", "foo/bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", "foo/bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", "/foo/bar/", @"http://example.com/foo/bar/")] - [InlineData("http://example.com/foo", "bar/", @"http://example.com/foo/bar/")] - [InlineData("http://example.com", @"foo\bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", @"foo\bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", @"/foo\bar/", @"http://example.com/foo/bar/")] - [InlineData("http://example.com/foo", @"bar\", @"http://example.com/foo/bar/")] - [InlineData("http://example.com/foo?bar", @"baz", @"http://example.com/foo?bar&baz")] - [InlineData("http://example.com/foo?bar", @"&baz", @"http://example.com/foo?bar&baz")] - [InlineData("http://example.com/foo?", @"bar", @"http://example.com/foo?bar")] - [InlineData("http://example.com/foo?", @"&bar", @"http://example.com/foo?&bar")] - public void ComparesHostInsensitively(string uriString, string path, string expected) - { - Assert.Equal(new UriString(uriString).Combine(path), (UriString)expected); - } - } - - public class TheIsValidUriProperty : TestBaseClass - { - [Theory] - [InlineData("http://example.com/", true)] - [InlineData("file:///C:/dev/exp/foo", true)] - [InlineData("garbage", false)] - [InlineData("git@192.168.1.2:github/Windows.git", false)] - public void ReturnWhetherTheUriIsParseableByUri(string uriString, bool expected) - { - Assert.Equal(new UriString(uriString).IsValidUri, expected); - } - } - - public class TheToRepositoryUrlMethod : TestBaseClass - { - [Theory] - [InlineData("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] - [InlineData("http://example.com/", "http://example.com/")] - [InlineData("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")] - [InlineData("https://github.com/github/Windows", "https://github.com/github/Windows")] - [InlineData("https://github.com/github/Windows.git", "https://github.com/github/Windows")] - [InlineData("https://haacked@github.com/github/Windows.git", "https://github.com/github/Windows")] - [InlineData("http://example.com:4000/github/Windows", "http://example.com:4000/github/Windows")] - [InlineData("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/github/Windows")] - [InlineData("git@example.com:org/repo.git", "https://example.com/org/repo")] - [InlineData("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")] - [InlineData("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")] - public void ConvertsToWebUrl(string uriString, string expected) - { - Assert.Equal(new Uri(expected), new UriString(uriString).ToRepositoryUrl()); - } - - [Theory] - [InlineData("asdf", null)] - [InlineData("", null)] - [InlineData("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] - [InlineData("http://example.com/", "http://example.com/")] - [InlineData("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")] - [InlineData("https://github.com/github/Windows", "https://github.com/github/Windows")] - [InlineData("https://github.com/github/Windows.git", "https://github.com/github/Windows")] - [InlineData("https://haacked@github.com/github/Windows.git", "https://github.com/github/Windows")] - [InlineData("http://example.com:4000/github/Windows", "http://example.com:4000/github/Windows")] - [InlineData("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/github/Windows")] - [InlineData("git@example.com:org/repo.git", "https://example.com/org/repo")] - [InlineData("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")] - [InlineData("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")] - public void ShouldNeverThrow(string url, string expected) - { - Uri uri; - Uri.TryCreate(expected, UriKind.Absolute, out uri); - Assert.Equal(uri, new UriString(url).ToRepositoryUrl()); - } - } - - public class TheAdditionOperator : TestBaseClass - { - [Theory] - [InlineData("http://example.com", "foo/bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", "foo/bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", "/foo/bar/", @"http://example.com/foo/bar/")] - [InlineData("http://example.com/foo", "bar/", @"http://example.com/foo/bar/")] - [InlineData("http://example.com", @"foo\bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", @"foo\bar", @"http://example.com/foo/bar")] - [InlineData("http://example.com/", @"/foo\bar/", @"http://example.com/foo/bar/")] - [InlineData("http://example.com/foo", @"bar\", @"http://example.com/foo/bar/")] - [InlineData("http://example.com/foo?bar", @"baz", @"http://example.com/foo?bar&baz")] - [InlineData("http://example.com/foo?bar", @"&baz", @"http://example.com/foo?bar&baz")] - [InlineData("http://example.com/foo?", @"bar", @"http://example.com/foo?bar")] - [InlineData("http://example.com/foo?", @"&bar", @"http://example.com/foo?&bar")] - public void CombinesPaths(string uriString, string addition, string expected) - { - UriString path = uriString; - var newPath = path + addition; - Assert.Equal(newPath, (UriString)expected); - } - } - - public class ImplicitConversionToString : TestBaseClass - { - [Fact] - public void ConvertsBackToString() - { - var uri = new UriString("http://github.com/foo/bar/"); - string cloneUri = uri; - Assert.Equal(cloneUri, "http://github.com/foo/bar/"); - } - - [Fact] - public void ConvertsNullToNull() - { - UriString uri = null; - Assert.Null(uri); - string cloneUri = uri; - Assert.Null(cloneUri); - } - } - - public class ImplicitConversionFromString : TestBaseClass - { - [Fact] - public void ConvertsToCloneUri() - { - UriString cloneUri = "http://github.com/foo/bar/"; - Assert.Equal(cloneUri.Host, "github.com"); - } - - [Fact] - public void ConvertsNullToNull() - { - UriString cloneUri = (string)null; - Assert.Null(cloneUri); - } - } - - public class TheIsHypertextTransferProtocolProperty : TestBaseClass - { - [Theory] - [InlineData("http://example.com", true)] - [InlineData("HTTP://example.com", true)] - [InlineData("https://example.com", true)] - [InlineData("HTTPs://example.com", true)] - [InlineData("ftp://example.com", false)] - [InlineData("c:/example.com", false)] - [InlineData("git@github.com:github/Windows", false)] - public void IsTrueOnlyForHttpAndHttps(string url, bool expected) - { - var uri = new UriString(url); - Assert.Equal(uri.IsHypertextTransferProtocol, expected); - } - } - - public class TheEqualsMethod : TestBaseClass - { - [Theory] - [InlineData("https://github.com/foo/bar", "https://github.com/foo/bar", true)] - [InlineData("https://github.com/foo/bar", "https://github.com/foo/BAR", false)] - [InlineData("https://github.com/foo/bar", "https://github.com/foo/bar/", false)] - [InlineData("https://github.com/foo/bar", null, false)] - public void ReturnsTrueForCaseSensitiveEquality(string source, string compare, bool expected) - { - Assert.Equal(expected, source.Equals(compare)); - Assert.Equal(expected, EqualityComparer<UriString>.Default.Equals(source, compare)); - } - - [Fact] - public void MakesUriStringSuitableForDictionaryKey() - { - var dictionary = new Dictionary<UriString, string> - { - { new UriString("https://github.com/foo/bar"), "whatever" } - }; - - Assert.False(dictionary.ContainsKey("https://github.com/foo/not-bar")); - Assert.True(dictionary.ContainsKey("https://github.com/foo/bar")); - Assert.True(dictionary.ContainsKey(new UriString("https://github.com/foo/bar"))); - } - } -} diff --git a/src/UnitTests/GitHub.UI/TwoFactorInputTests.cs b/src/UnitTests/GitHub.UI/TwoFactorInputTests.cs deleted file mode 100644 index cb74a5a9c1..0000000000 --- a/src/UnitTests/GitHub.UI/TwoFactorInputTests.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using GitHub.UI; -using Xunit; - -public class TwoFactorInputTests -{ - public class TheTextProperty : TestBaseClass - { - [STAFact] - public void SetsTextBoxesToIndividualCharacters() - { - var twoFactorInput = new TwoFactorInput(); - var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); - - twoFactorInput.Text = "012345"; - - Assert.Equal("012345", twoFactorInput.Text); - Assert.Equal("0", textBoxes[0].Text); - Assert.Equal("1", textBoxes[1].Text); - Assert.Equal("2", textBoxes[2].Text); - Assert.Equal("3", textBoxes[3].Text); - Assert.Equal("4", textBoxes[4].Text); - Assert.Equal("5", textBoxes[5].Text); - } - - [STAFact] - public void IgnoresNonDigitCharacters() - { - var twoFactorInput = new TwoFactorInput(); - var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); - - twoFactorInput.Text = "01xyz2345"; - - Assert.Equal("012345", twoFactorInput.Text); - Assert.Equal("0", textBoxes[0].Text); - Assert.Equal("1", textBoxes[1].Text); - Assert.Equal("2", textBoxes[2].Text); - Assert.Equal("3", textBoxes[3].Text); - Assert.Equal("4", textBoxes[4].Text); - Assert.Equal("5", textBoxes[5].Text); - } - - [STAFact] - public void HandlesNotEnoughCharacters() - { - var twoFactorInput = new TwoFactorInput(); - var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); - - twoFactorInput.Text = "012"; - - Assert.Equal("012", twoFactorInput.Text); - Assert.Equal("0", textBoxes[0].Text); - Assert.Equal("1", textBoxes[1].Text); - Assert.Equal("2", textBoxes[2].Text); - Assert.Equal("", textBoxes[3].Text); - Assert.Equal("", textBoxes[4].Text); - Assert.Equal("", textBoxes[5].Text); - } - - [STATheory] - [InlineData(null, null)] - [InlineData("", "")] - [InlineData("xxxx", "")] - public void HandlesNullAndStringsWithNoDigits(string input, string expected) - { - var twoFactorInput = new TwoFactorInput(); - var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); - - twoFactorInput.Text = input; - - Assert.Equal(expected, twoFactorInput.Text); - Assert.Equal("", textBoxes[0].Text); - Assert.Equal("", textBoxes[1].Text); - Assert.Equal("", textBoxes[2].Text); - Assert.Equal("", textBoxes[3].Text); - Assert.Equal("", textBoxes[4].Text); - Assert.Equal("", textBoxes[5].Text); - } - - static IEnumerable<FrameworkElement> GetChildrenRecursive(FrameworkElement element) - { - yield return element; - foreach (var child in LogicalTreeHelper.GetChildren(element) - .Cast<FrameworkElement>() - .SelectMany(GetChildrenRecursive)) - { - yield return child; - } - } - } -} diff --git a/src/UnitTests/GitHub.VisualStudio/Services/ConnectionManagerTests.cs b/src/UnitTests/GitHub.VisualStudio/Services/ConnectionManagerTests.cs deleted file mode 100644 index 808562d867..0000000000 --- a/src/UnitTests/GitHub.VisualStudio/Services/ConnectionManagerTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Text; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.Services; -using GitHub.VisualStudio; -using NSubstitute; -using Rothko; -using Xunit; -using UnitTests; - -public class ConnectionManagerTests -{ - public class TheConnectionsProperty : TestBaseClass - { - [Fact] - public void IsLoadedFromCache() - { - const string cacheData = @"{""connections"":[{""HostUrl"":""https://github.com"", ""UserName"":""shana""},{""HostUrl"":""https://ghe.io"", ""UserName"":""haacked""}]}"; - var program = Substitute.For<IProgram>(); - program.ApplicationName.Returns("GHfVS"); - var operatingSystem = Substitute.For<IOperatingSystem>(); - operatingSystem.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) - .Returns(@"c:\fake"); - operatingSystem.File.Exists(@"c:\fake\GHfVS\ghfvs.connections").Returns(true); - operatingSystem.File.ReadAllText(@"c:\fake\GHfVS\ghfvs.connections", Encoding.UTF8).Returns(cacheData); - var manager = new ConnectionManager(program, operatingSystem, Substitutes.IVSServices); - - var connections = manager.Connections; - - Assert.Equal(2, connections.Count); - Assert.Equal(new Uri("https://api.github.com"), connections[0].HostAddress.ApiUri); - Assert.Equal(new Uri("https://ghe.io/api/v3/"), connections[1].HostAddress.ApiUri); - } - - [Theory] - [InlineData("|!This ain't no JSON what even is this?")] - [InlineData(null)] - [InlineData("")] - [InlineData("{}")] - [InlineData(@"{""connections"":null}")] - [InlineData(@"{""connections"":{}}")] - public void IsEmptyWhenCacheCorrupt(string cacheJson) - { - var program = Substitute.For<IProgram>(); - program.ApplicationName.Returns("GHfVS"); - var operatingSystem = Substitute.For<IOperatingSystem>(); - operatingSystem.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) - .Returns(@"c:\fake"); - operatingSystem.File.Exists(@"c:\fake\GHfVS\ghfvs.connections").Returns(true); - operatingSystem.File.ReadAllText(@"c:\fake\GHfVS\ghfvs.connections", Encoding.UTF8).Returns(cacheJson); - var manager = new ConnectionManager(program, operatingSystem, Substitutes.IVSServices); - - var connections = manager.Connections; - - Assert.Equal(0, connections.Count); - operatingSystem.File.Received().Delete(@"c:\fake\GHfVS\ghfvs.connections"); - } - - [Fact] - public void IsSavedToDiskWhenConnectionAdded() - { - var program = Substitute.For<IProgram>(); - program.ApplicationName.Returns("GHfVS"); - var operatingSystem = Substitute.For<IOperatingSystem>(); - operatingSystem.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) - .Returns(@"c:\fake"); - operatingSystem.File.Exists(@"c:\fake\GHfVS\ghfvs.connections").Returns(true); - operatingSystem.File.ReadAllText(@"c:\fake\GHfVS\ghfvs.connections", Encoding.UTF8).Returns(""); - var manager = new ConnectionManager(program, operatingSystem, Substitutes.IVSServices); - - manager.Connections.Add(new Connection(manager, HostAddress.GitHubDotComHostAddress, "coolio")); - - Assert.Equal(1, manager.Connections.Count); - operatingSystem.File.Received().WriteAllText(@"c:\fake\GHfVS\ghfvs.connections", - @"{""connections"":[{""HostUrl"":""https://github.com/"",""UserName"":""coolio""}]}"); - } - } -} diff --git a/src/UnitTests/GitHub.VisualStudio/Services/RepositoryPublishServiceTests.cs b/src/UnitTests/GitHub.VisualStudio/Services/RepositoryPublishServiceTests.cs deleted file mode 100644 index a616682948..0000000000 --- a/src/UnitTests/GitHub.VisualStudio/Services/RepositoryPublishServiceTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Threading.Tasks; -using GitHub.Api; -using GitHub.Models; -using GitHub.Services; -using Microsoft.VisualStudio.Shell.Interop; -using NSubstitute; -using Xunit; - -public class RepositoryPublishServiceTests -{ - public class ThePublishMethod : TestBaseClass - { - [Fact] - public async Task CreatesRepositoryAndPushesLocalToIt() - { - var solution = Substitute.For<IVsSolution>(); - var gitClient = Substitute.For<IGitClient>(); - var service = new RepositoryPublishService(gitClient, Substitute.For<IVSServices>()); - var newRepository = new Octokit.NewRepository("test"); - var account = Substitute.For<IAccount>(); - account.Login.Returns("monalisa"); - account.IsUser.Returns(true); - var gitHubRepository = CreateOctokitRepository("https://github.com/monalisa/test"); - var apiClient = Substitute.For<IApiClient>(); - apiClient.CreateRepository(newRepository, "monalisa", true).Returns(Observable.Return(gitHubRepository)); - - var repository = await service.PublishRepository(newRepository, account, apiClient); - - Assert.Equal("https://github.com/monalisa/test", repository.CloneUrl); - } - - static Octokit.Repository CreateOctokitRepository(string cloneUrl) - { - var notCloneUrl = cloneUrl + "x"; - return new Octokit.Repository(notCloneUrl, notCloneUrl, cloneUrl, notCloneUrl, notCloneUrl, notCloneUrl, notCloneUrl, 1, null, null, null, null, null, null, false, false, 0, 0, 0, "master", 0, null, DateTimeOffset.Now, DateTimeOffset.Now, null, null, null, null, false, false, false); - } - } -} diff --git a/src/UnitTests/GitHub.VisualStudio/TeamExplorer/Home/GraphsNavigationItemTests.cs b/src/UnitTests/GitHub.VisualStudio/TeamExplorer/Home/GraphsNavigationItemTests.cs deleted file mode 100644 index 51755f7f9b..0000000000 --- a/src/UnitTests/GitHub.VisualStudio/TeamExplorer/Home/GraphsNavigationItemTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using GitHub.Api; -using GitHub.Services; -using GitHub.VisualStudio.TeamExplorer.Home; -using NSubstitute; -using Xunit; - -public class GraphsNavigationItemTests -{ - public class TheExecuteMethod : TestBaseClass - { - [Theory] - [InlineData("https://github.com/foo/bar.git", "https://github.com/foo/bar/graphs")] - [InlineData("https://haacked@github.com/foo/bar.git", "https://github.com/foo/bar/graphs")] - [InlineData("https://github.com/foo/bar", "https://github.com/foo/bar/graphs")] - [InlineData("https://github.com/foo/bar/", "https://github.com/foo/bar/graphs")] - public void BrowsesToTheCorrectURL(string origin, string expectedUrl) - { - var apiFactory = Substitute.For<ISimpleApiClientFactory>(); - var browser = Substitute.For<IVisualStudioBrowser>(); - var lazyBrowser = new Lazy<IVisualStudioBrowser>(() => browser); - var holder = Substitute.For<ITeamExplorerServiceHolder>(); - var graphsNavigationItem = new GraphsNavigationItem(apiFactory, lazyBrowser, holder) - { - ActiveRepoUri = origin - }; - - graphsNavigationItem.Execute(); - - browser.Received().OpenUrl(new Uri(expectedUrl)); - } - } -} diff --git a/src/UnitTests/GitHubPackageTests.cs b/src/UnitTests/GitHubPackageTests.cs deleted file mode 100644 index a42d0af36c..0000000000 --- a/src/UnitTests/GitHubPackageTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.ComponentModel.Design; -using GitHub.VisualStudio; -using NSubstitute; -using Xunit; - -public class GitHubPackageTests -{ - public class TheInitializeMethod - { - public void AddsTopLevelMenuItems() - { - var menuService = new FakeMenuCommandService(); - var serviceProvider = Substitute.For<IServiceProvider>(); - serviceProvider.GetService(typeof(IMenuCommandService)).Returns(menuService); - var package = new GitHubPackageTestWrapper(serviceProvider); - - package.CallInitialize(); - - Assert.Equal(2, menuService.AddedCommands.Count); - } - } - - public class GitHubPackageTestWrapper : GitHubPackage - { - public GitHubPackageTestWrapper(IServiceProvider serviceProvider) : base(serviceProvider) - { - } - - public void CallInitialize() - { - Initialize(); - } - } -} diff --git a/src/UnitTests/Helpers/TestBaseClass.cs b/src/UnitTests/Helpers/TestBaseClass.cs deleted file mode 100644 index c1aa49a638..0000000000 --- a/src/UnitTests/Helpers/TestBaseClass.cs +++ /dev/null @@ -1,42 +0,0 @@ -using EntryExitDecoratorInterfaces; -using System.IO; - -/// <summary> -/// This base class will get its methods called by the most-derived -/// classes. The calls are injected by the EntryExitMethodDecorator Fody -/// addin, so don't be surprised if you don't see any calls in the code. -/// </summary> -public class TestBaseClass : IEntryExitDecorator -{ - public virtual void OnEntry() - { - // Ensure that every test has the InUnitTestRunner flag - // set, so threading doesn't go nuts. - Splat.ModeDetector.Current.SetInUnitTestRunner(true); - } - - public virtual void OnExit() - { - } -} - -public class TempFileBaseClass : TestBaseClass -{ - public DirectoryInfo Directory { get; set; } - - public override void OnEntry() - { - var f = Path.GetTempFileName(); - var name = Path.GetFileNameWithoutExtension(f); - File.Delete(f); - Directory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), name)); - Directory.Create(); - base.OnEntry(); - } - - public override void OnExit() - { - Directory.Delete(true); - base.OnExit(); - } -} \ No newline at end of file diff --git a/src/UnitTests/Helpers/TestLoginCache.cs b/src/UnitTests/Helpers/TestLoginCache.cs deleted file mode 100644 index 1eeb8bb233..0000000000 --- a/src/UnitTests/Helpers/TestLoginCache.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Reactive; -using Akavache; -using GitHub.Caches; -using GitHub.Primitives; - -namespace UnitTests.Helpers -{ - public class TestLoginCache : ILoginCache - { - readonly LoginCache loginCacheDelegate = new LoginCache(new TestSharedCache()); - - public void Dispose() - { - loginCacheDelegate.Dispose(); - } - - public IObservable<LoginInfo> GetLoginAsync(HostAddress hostAddress) - { - return loginCacheDelegate.GetLoginAsync(hostAddress); - } - - public IObservable<Unit> SaveLogin(string user, string password, HostAddress hostAddress) - { - return loginCacheDelegate.SaveLogin(user, password, hostAddress); - } - - public IObservable<Unit> EraseLogin(HostAddress hostAddress) - { - return loginCacheDelegate.EraseLogin(hostAddress); - } - - public IObservable<Unit> Flush() - { - return loginCacheDelegate.Flush(); - } - } -} diff --git a/src/UnitTests/Properties/AssemblyInfo.cs b/src/UnitTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 59f209e0db..0000000000 --- a/src/UnitTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("UnitTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UnitTests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7c0cb7e5-6c7b-4f11-8454-9e1a4747641c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/UnitTests/Substitutes.cs b/src/UnitTests/Substitutes.cs deleted file mode 100644 index 003587c60e..0000000000 --- a/src/UnitTests/Substitutes.cs +++ /dev/null @@ -1,176 +0,0 @@ -using GitHub.Authentication; -using GitHub.Models; -using GitHub.Services; -using Microsoft.TeamFoundation.Git.Controls.Extensibility; -using Microsoft.VisualStudio.ComponentModelHost; -using NSubstitute; -using Rothko; -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.ComponentModel.Composition.Hosting; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace UnitTests -{ - internal static class Substitutes - { - public static IGitRepositoriesExt IGitRepositoriesExt { get { return Substitute.For<IGitRepositoriesExt>(); } } - public static IGitService IGitService { get { return Substitute.For<IGitService>(); } } - - public static IVSServices IVSServices - { - get - { - var ret = Substitute.For<IVSServices>(); - ret.GetLocalClonePathFromGitProvider().Returns(@"c:\foo\bar"); - return ret; - } - } - - public static IOperatingSystem OperatingSystem - { - get - { - var ret = Substitute.For<IOperatingSystem>(); - // this expansion happens when the GetLocalClonePathFromGitProvider call is setup by default - // see IVSServices property above - ret.Environment.ExpandEnvironmentVariables(Args.String).Returns(x => x[0]); - return ret; - } - } - - public static IExportFactoryProvider ExportFactoryProvider { get { return Substitute.For<IExportFactoryProvider>(); } } - - public static IRepositoryCreationService RepositoryCreationService { get { return Substitute.For<IRepositoryCreationService>(); } } - public static IRepositoryCloneService RepositoryCloneService { get { return Substitute.For<IRepositoryCloneService>(); } } - - public static IRepositoryHosts RepositoryHosts { get { return Substitute.For<IRepositoryHosts>(); } } - public static IConnection Connection { get { return Substitute.For<IConnection>(); } } - public static IConnectionManager ConnectionManager { get { return Substitute.For<IConnectionManager>(); } } - public static ITwoFactorChallengeHandler TwoFactorChallengeHandler { get { return Substitute.For<ITwoFactorChallengeHandler>(); } } - - /// <summary> - /// This returns a service provider with everything mocked except for - /// RepositoryCloneService and RepositoryCreationService, which are real - /// instances. - /// </summary> - public static IServiceProvider ServiceProvider { get { return GetServiceProvider(); } } - - /// <summary> - /// This returns a service provider with mocked IRepositoryCreationService and - /// IRepositoryCloneService as well as all other services mocked. The regular - /// GetServiceProvider method (and ServiceProvider property return a IServiceProvider - /// with real RepositoryCloneService and RepositoryCreationService instances. - /// </summary> - /// <returns></returns> - public static IServiceProvider GetFullyMockedServiceProvider() - { - return GetServiceProvider(RepositoryCloneService, RepositoryCreationService); - } - - /// <summary> - /// This returns a service provider with everything mocked except for - /// RepositoryCloneService and RepositoryCreationService, which are real - /// instances. - /// </summary> - /// <param name="cloneService"></param> - /// <param name="creationService"></param> - /// <returns></returns> - public static IServiceProvider GetServiceProvider( - IRepositoryCloneService cloneService = null, - IRepositoryCreationService creationService = null, - IAvatarProvider avatarProvider = null) - { - var ret = Substitute.For<IServiceProvider, IUIProvider>(); - - var gitservice = IGitService; - var cm = Substitute.For<SComponentModel, IComponentModel>(); - var cc = new CompositionContainer(CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); - cc.ComposeExportedValue(gitservice); - ((IComponentModel)cm).DefaultExportProvider.Returns(cc); - ret.GetService(typeof(SComponentModel)).Returns(cm); - - var os = OperatingSystem; - var vs = IVSServices; - var clone = cloneService ?? new RepositoryCloneService(os, vs); - var create = creationService ?? new RepositoryCreationService(clone); - avatarProvider = avatarProvider ?? Substitute.For<IAvatarProvider>(); - ret.GetService(typeof(IGitRepositoriesExt)).Returns(IGitRepositoriesExt); - ret.GetService(typeof(IGitService)).Returns(gitservice); - ret.GetService(typeof(IVSServices)).Returns(vs); - ret.GetService(typeof(IOperatingSystem)).Returns(os); - ret.GetService(typeof(IRepositoryCloneService)).Returns(clone); - ret.GetService(typeof(IRepositoryCreationService)).Returns(create); - ret.GetService(typeof(IRepositoryHosts)).Returns(RepositoryHosts); - ret.GetService(typeof(IExportFactoryProvider)).Returns(ExportFactoryProvider); - ret.GetService(typeof(IConnection)).Returns(Connection); - ret.GetService(typeof(IConnectionManager)).Returns(ConnectionManager); - ret.GetService(typeof(IAvatarProvider)).Returns(avatarProvider); - ret.GetService(typeof(ITwoFactorChallengeHandler)).Returns(TwoFactorChallengeHandler); - return ret; - } - - public static IGitRepositoriesExt GetGitExt(this IServiceProvider provider) - { - return provider.GetService(typeof(IGitRepositoriesExt)) as IGitRepositoriesExt; - } - - public static IVSServices GetVSServices(this IServiceProvider provider) - { - return provider.GetService(typeof(IVSServices)) as IVSServices; - } - - public static IGitService GetGitService(this IServiceProvider provider) - { - return provider.GetService(typeof(IGitService)) as IGitService; - } - - public static IOperatingSystem GetOperatingSystem(this IServiceProvider provider) - { - return provider.GetService(typeof(IOperatingSystem)) as IOperatingSystem; - } - - public static IRepositoryCloneService GetRepositoryCloneService(this IServiceProvider provider) - { - return provider.GetService(typeof(IRepositoryCloneService)) as IRepositoryCloneService; - } - - public static IRepositoryCreationService GetRepositoryCreationService(this IServiceProvider provider) - { - return provider.GetService(typeof(IRepositoryCreationService)) as IRepositoryCreationService; - } - - public static IRepositoryHosts GetRepositoryHosts(this IServiceProvider provider) - { - return provider.GetService(typeof(IRepositoryHosts)) as IRepositoryHosts; - } - - public static IExportFactoryProvider GetExportFactoryProvider(this IServiceProvider provider) - { - return provider.GetService(typeof(IExportFactoryProvider)) as IExportFactoryProvider; - } - - public static IConnection GetConnection(this IServiceProvider provider) - { - return provider.GetService(typeof(IConnection)) as IConnection; - } - - public static IConnectionManager GetConnectionManager(this IServiceProvider provider) - { - return provider.GetService(typeof(IConnectionManager)) as IConnectionManager; - } - - public static IAvatarProvider GetAvatarProvider(this IServiceProvider provider) - { - return provider.GetService(typeof(IAvatarProvider)) as IAvatarProvider; - } - - public static ITwoFactorChallengeHandler GetTwoFactorChallengeHandler(this IServiceProvider provider) - { - return provider.GetService(typeof(ITwoFactorChallengeHandler)) as ITwoFactorChallengeHandler; - } - } -} diff --git a/src/UnitTests/TestDoubles/FakeCommitLog.cs b/src/UnitTests/TestDoubles/FakeCommitLog.cs deleted file mode 100644 index b580159d92..0000000000 --- a/src/UnitTests/TestDoubles/FakeCommitLog.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using LibGit2Sharp; - -public class FakeCommitLog : List<Commit>, ICommitLog -{ - public CommitSortStrategies SortedBy - { - get - { - return CommitSortStrategies.Topological; - } - } -} diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj deleted file mode 100644 index 1d23e42d8d..0000000000 --- a/src/UnitTests/UnitTests.csproj +++ /dev/null @@ -1,287 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props')" /> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{596595A6-2A3C-469E-9386-9E3767D863A5}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>UnitTests</RootNamespace> - <AssemblyName>UnitTests</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <NuGetPackageImportStamp> - </NuGetPackageImportStamp> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>pdbonly</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <ItemGroup> - <Reference Include="EntryExitDecoratorInterfaces, Version=0.3.0.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\..\packages\EntryExitDecorator.Fody.0.3.0\lib\net45\EntryExitDecoratorInterfaces.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Testing.2.2.5-custom\lib\net45\Microsoft.Reactive.Testing.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="Microsoft.TeamFoundation.Controls, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Controls.dll</HintPath> - </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Controls, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Controls.dll</HintPath> - </Reference> - <Reference Include="Microsoft.TeamFoundation.Git.Provider"> - <HintPath>..\..\lib\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> - </Reference> - <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> - <Reference Include="Microsoft.VisualStudio.OLE.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0"> - <EmbedInteropTypes>true</EmbedInteropTypes> - </Reference> - <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0"> - <EmbedInteropTypes>true</EmbedInteropTypes> - </Reference> - <Reference Include="Microsoft.VisualStudio.TextManager.Interop" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0" /> - <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0" /> - <Reference Include="NSubstitute, Version=1.8.1.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\NSubstitute.1.8.1.0\lib\net45\NSubstitute.dll</HintPath> - </Reference> - <Reference Include="PresentationCore" /> - <Reference Include="PresentationFramework" /> - <Reference Include="System" /> - <Reference Include="System.ComponentModel.Composition" /> - <Reference Include="System.Core" /> - <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Reactive.Windows.Threading, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> - <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="System.Xaml" /> - <Reference Include="System.Xml.Linq" /> - <Reference Include="System.Data.DataSetExtensions" /> - <Reference Include="Microsoft.CSharp" /> - <Reference Include="System.Data" /> - <Reference Include="System.Xml" /> - <Reference Include="WindowsBase" /> - <Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath> - </Reference> - <Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> - <HintPath>..\..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> - <HintPath>..\..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> - <HintPath>..\..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath> - <Private>True</Private> - </Reference> - </ItemGroup> - <ItemGroup> - <Compile Include="Args.cs" /> - <Compile Include="GitHub.Api\SimpleApiClientFactoryTests.cs" /> - <Compile Include="GitHub.Api\SimpleApiClientTests.cs" /> - <Compile Include="GitHub.App\Caches\CredentialCacheTests.cs" /> - <Compile Include="GitHub.App\Caches\ImageCacheTests.cs" /> - <Compile Include="GitHub.App\Models\ModelServiceTests.cs" /> - <Compile Include="GitHub.App\Controllers\UIControllerTests.cs" /> - <Compile Include="GitHub.App\Models\PullRequestModelTests.cs" /> - <Compile Include="GitHub.App\Models\RepositoryHostTests.cs" /> - <Compile Include="GitHub.App\Models\RepositoryModelTests.cs" /> - <Compile Include="GitHub.App\Services\AvatarProviderTests.cs" /> - <Compile Include="GitHub.App\Services\GitClientTests.cs" /> - <Compile Include="GitHub.App\Services\RepositoryCloneServiceTests.cs" /> - <Compile Include="GitHub.App\Services\RepositoryCreationServiceTests.cs" /> - <Compile Include="GitHub.App\ViewModels\LoginToGitHubViewModelTests.cs" /> - <Compile Include="GitHub.App\ViewModels\RepositoryCloneViewModelTests.cs" /> - <Compile Include="GitHub.App\ViewModels\RepositoryCreationViewModelTests.cs" /> - <Compile Include="GitHub.App\ViewModels\RepositoryPublishViewModelTests.cs" /> - <Compile Include="GitHub.Exports.Reactive\Caches\AccountCacheItemTests.cs" /> - <Compile Include="GitHub.Exports\GitServiceTests.cs" /> - <Compile Include="GitHub.Exports\VSServicesTests.cs" /> - <Compile Include="GitHub.Extensions\UriExtensionTests.cs" /> - <Compile Include="GitHub.Primitives\UriStringTests.cs" /> - <Compile Include="GitHub.UI\TwoFactorInputTests.cs" /> - <Compile Include="GitHub.VisualStudio\Services\ConnectionManagerTests.cs" /> - <Compile Include="GitHub.VisualStudio\Services\RepositoryPublishServiceTests.cs" /> - <Compile Include="GitHub.VisualStudio\TeamExplorer\Home\GraphsNavigationItemTests.cs" /> - <Compile Include="GitHubPackageTests.cs" /> - <Compile Include="Helpers\CommandTestHelpers.cs" /> - <Compile Include="Helpers\LazySubstitute.cs" /> - <Compile Include="Helpers\ReactiveTestHelper.cs" /> - <Compile Include="Helpers\TestBaseClass.cs" /> - <Compile Include="Helpers\TestLoginCache.cs" /> - <Compile Include="Helpers\TestSharedCache.cs" /> - <Compile Include="Substitutes.cs" /> - <Compile Include="TestDoubles\FakeCommitLog.cs" /> - <Compile Include="TestDoubles\FakeMenuCommandService.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Helpers\TestImageCache.cs" /> - <Compile Include="XUnit\ConditionalFactAttribute.cs" /> - <Compile Include="XUnit\DelayedMessageBus.cs" /> - <Compile Include="XUnit\RetryFactAttribute.cs" /> - <Compile Include="XUnit\RetryFactDiscoverer.cs" /> - <Compile Include="XUnit\RetryTestCase.cs" /> - <Compile Include="XUnit\STAFactAttribute.cs" /> - <Compile Include="XUnit\STAFactDiscoverer.cs" /> - <Compile Include="XUnit\STATestCase.cs" /> - <Compile Include="XUnit\STATheoryAttribute.cs" /> - <Compile Include="XUnit\STATheoryDiscoverer.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\..\submodules\akavache\Akavache\Akavache_Net45.csproj"> - <Project>{B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}</Project> - <Name>Akavache_Net45</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\octokit.net\Octokit.Reactive\Octokit.Reactive.csproj"> - <Project>{674B69B8-0780-4D54-AE2B-C15821FA51CB}</Project> - <Name>Octokit.Reactive</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> - <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> - <Name>Octokit</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI.Testing\ReactiveUI.Testing_Net45.csproj"> - <Project>{dd99fd0f-82f6-4c30-930e-4a1d0df01d65}</Project> - <Name>ReactiveUI.Testing_Net45</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> - <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> - <Name>ReactiveUI_Net45</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\Rothko\src\Rothko.csproj"> - <Project>{4a84e568-ca86-4510-8cd0-90d3ef9b65f9}</Project> - <Name>Rothko</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> - <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> - <Name>Splat-Net45</Name> - </ProjectReference> - <ProjectReference Include="..\..\submodules\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj"> - <Project>{EE6ED99F-CB12-4683-B055-D28FC7357A34}</Project> - <Name>LibGit2Sharp</Name> - <Private>True</Private> - </ProjectReference> - <ProjectReference Include="..\DesignTimeStyleHelper\DesignTimeStyleHelper.csproj"> - <Project>{b1f5c227-456f-437d-bd5f-4c11b7a8d1a0}</Project> - <Name>DesignTimeStyleHelper</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj"> - <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> - <Name>GitHub.Api</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.App\GitHub.App.csproj"> - <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> - <Name>GitHub.App</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> - <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> - <Name>GitHub.Exports.Reactive</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> - <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> - <Name>GitHub.Exports</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Extensions.Reactive\GitHub.Extensions.Reactive.csproj"> - <Project>{6559e128-8b40-49a5-85a8-05565ed0c7e3}</Project> - <Name>GitHub.Extensions.Reactive</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> - <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> - <Name>GitHub.Extensions</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj"> - <Project>{158B05E8-FDBC-4D71-B871-C96E28D5ADF5}</Project> - <Name>GitHub.UI.Reactive</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj"> - <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> - <Name>GitHub.UI</Name> - </ProjectReference> - <ProjectReference Include="..\GitHub.VisualStudio\GitHub.VisualStudio.csproj"> - <Project>{11569514-5ae5-4b5b-92a2-f10b0967de5f}</Project> - <Name>GitHub.VisualStudio</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> - </ItemGroup> - <ItemGroup> - <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> - </ItemGroup> - <ItemGroup> - <Content Include="FodyWeavers.xml" /> - </ItemGroup> - <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>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}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Fody.1.28.3\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Fody.1.28.3\build\Fody.targets'))" /> - <Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.1.0\build\net20\xunit.runner.visualstudio.props'))" /> - </Target> - <Import Project="..\..\packages\Fody.1.28.3\build\Fody.targets" Condition="Exists('..\..\packages\Fody.1.28.3\build\Fody.targets')" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> -</Project> \ No newline at end of file diff --git a/src/UnitTests/XUnit/ConditionalFactAttribute.cs b/src/UnitTests/XUnit/ConditionalFactAttribute.cs deleted file mode 100644 index 37a636be0b..0000000000 --- a/src/UnitTests/XUnit/ConditionalFactAttribute.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using Xunit; - -namespace GitHub.Tests.Helpers -{ - public class ConditionalFactAttribute : FactAttribute - { - public bool RunOnJanky { get; set; } - - public override string Skip - { - get - { - if (!RunOnJanky && IsJanky()) - { - return Message ?? "This test may not be run on Janky"; - } - return base.Skip; - } - set { base.Skip = value; } - } - - public string Message { get; set; } - - static bool IsJanky() - { - return !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("JANKY_SHA1")); - } - } - - public class ConditionalTheoryAttribute : TheoryAttribute - { - public bool RunOnJanky { get; set; } - - public override string Skip - { - get - { - if (!RunOnJanky && IsJanky()) - { - return Message ?? "This test may not be run on Janky"; - } - return base.Skip; - } - set { base.Skip = value; } - } - - public string Message { get; set; } - - static bool IsJanky() - { - return !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("JANKY_SHA1")); - } - } -} diff --git a/src/UnitTests/XUnit/DelayedMessageBus.cs b/src/UnitTests/XUnit/DelayedMessageBus.cs deleted file mode 100644 index 067c7cb786..0000000000 --- a/src/UnitTests/XUnit/DelayedMessageBus.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using Xunit.Abstractions; -using Xunit.Sdk; - -/// <summary> -/// Used to capture messages to potentially be forwarded later. Messages are forwarded by -/// disposing of the message bus. -/// </summary> -public class DelayedMessageBus : IMessageBus -{ - readonly IMessageBus innerBus; - readonly List<IMessageSinkMessage> messages = new List<IMessageSinkMessage>(); - - public DelayedMessageBus(IMessageBus innerBus) - { - this.innerBus = innerBus; - } - - public bool QueueMessage(IMessageSinkMessage message) - { - lock (messages) - messages.Add(message); - - // No way to ask the inner bus if they want to cancel without sending them the message, so - // we just go ahead and continue always. - return true; - } - - public void Dispose() - { - foreach (var message in messages) - innerBus.QueueMessage(message); - } -} \ No newline at end of file diff --git a/src/UnitTests/XUnit/RetryFactAttribute.cs b/src/UnitTests/XUnit/RetryFactAttribute.cs deleted file mode 100644 index 73107f34b4..0000000000 --- a/src/UnitTests/XUnit/RetryFactAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Xunit; -using Xunit.Sdk; - -/// <summary> -/// Works just like [Fact] except that failures are retried (by default, 3 times). -/// </summary> -[XunitTestCaseDiscoverer("RetryFactDiscoverer", "GitHub.Tests.Utils")] -public class RetryFactAttribute : FactAttribute -{ - /// <summary> - /// Number of retries allowed for a failed test. If unset (or set less than 1), will - /// default to 3 attempts. - /// </summary> - public int MaxRetries { get; set; } -} \ No newline at end of file diff --git a/src/UnitTests/XUnit/RetryFactDiscoverer.cs b/src/UnitTests/XUnit/RetryFactDiscoverer.cs deleted file mode 100644 index cfbd680fb3..0000000000 --- a/src/UnitTests/XUnit/RetryFactDiscoverer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using Xunit.Abstractions; -using Xunit.Sdk; - -public class RetryFactDiscoverer : IXunitTestCaseDiscoverer -{ - readonly IMessageSink diagnosticMessageSink; - - public RetryFactDiscoverer(IMessageSink diagnosticMessageSink) - { - this.diagnosticMessageSink = diagnosticMessageSink; - } - - public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - var maxRetries = factAttribute.GetNamedArgument<int>("MaxRetries"); - if (maxRetries < 1) - maxRetries = 3; - - yield return new RetryTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, maxRetries); - } -} - diff --git a/src/UnitTests/XUnit/RetryTestCase.cs b/src/UnitTests/XUnit/RetryTestCase.cs deleted file mode 100644 index 1b5a20156d..0000000000 --- a/src/UnitTests/XUnit/RetryTestCase.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.ComponentModel; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - - -[Serializable] -public class RetryTestCase : XunitTestCase -{ - int maxRetries; - - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Called by the de-serializer", true)] - public RetryTestCase() - { - } - - public RetryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay testMethodDisplay, ITestMethod testMethod, int maxRetries) - : base(diagnosticMessageSink, testMethodDisplay, testMethod, testMethodArguments: null) - { - this.maxRetries = maxRetries; - } - - // This method is called by the xUnit test framework classes to run the test case. We will do the - // loop here, forwarding on to the implementation in XunitTestCase to do the heavy lifting. We will - // continue to re-run the test until the aggregator has an error (meaning that some internal error - // condition happened), or the test runs without failure, or we've hit the maximum number of tries. - public override async Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - var runCount = 0; - - while (true) - { - // This is really the only tricky bit: we need to capture and delay messages (since those will - // contain run status) until we know we've decided to accept the final result; - var delayedMessageBus = new DelayedMessageBus(messageBus); - - var summary = await base.RunAsync(diagnosticMessageSink, delayedMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) - { - delayedMessageBus.Dispose(); // Sends all the delayed messages - return summary; - } - - diagnosticMessageSink.OnMessage(new DiagnosticMessage("Execution of '{0}' failed (attempt #{1}), retrying...", DisplayName, runCount)); - } - } - - public override void Serialize(IXunitSerializationInfo data) - { - base.Serialize(data); - - data.AddValue("MaxRetries", maxRetries); - } - - public override void Deserialize(IXunitSerializationInfo data) - { - base.Deserialize(data); - - maxRetries = data.GetValue<int>("MaxRetries"); - } -} \ No newline at end of file diff --git a/src/UnitTests/XUnit/STAFactAttribute.cs b/src/UnitTests/XUnit/STAFactAttribute.cs deleted file mode 100644 index 674e3743dc..0000000000 --- a/src/UnitTests/XUnit/STAFactAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using Xunit; -using Xunit.Sdk; - -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -[XunitTestCaseDiscoverer("STAFactDiscoverer", "UnitTests")] -public class STAFactAttribute : FactAttribute -{ -} \ No newline at end of file diff --git a/src/UnitTests/XUnit/STAFactDiscoverer.cs b/src/UnitTests/XUnit/STAFactDiscoverer.cs deleted file mode 100644 index dcd1a420c8..0000000000 --- a/src/UnitTests/XUnit/STAFactDiscoverer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -public class STAFactDiscoverer : IXunitTestCaseDiscoverer -{ - readonly FactDiscoverer factDiscoverer; - - public STAFactDiscoverer(IMessageSink diagnosticMessageSink) - { - factDiscoverer = new FactDiscoverer(diagnosticMessageSink); - } - - public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - return factDiscoverer.Discover(discoveryOptions, testMethod, factAttribute) - .Select(testCase => new STATestCase(testCase)); - } -} diff --git a/src/UnitTests/XUnit/STATestCase.cs b/src/UnitTests/XUnit/STATestCase.cs deleted file mode 100644 index a5cc5ad849..0000000000 --- a/src/UnitTests/XUnit/STATestCase.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -/// <summary> -/// Wraps test cases for FactAttribute and TheoryAttribute so the test case runs in the STA Thread -/// </summary> -[DebuggerDisplay(@"\{ class = {TestMethod.TestClass.Class.Name}, method = {TestMethod.Method.Name}, display = {DisplayName}, skip = {SkipReason} \}")] -public class STATestCase : LongLivedMarshalByRefObject, IXunitTestCase -{ - IXunitTestCase testCase; - - public STATestCase(IXunitTestCase testCase) - { - this.testCase = testCase; - } - - /// <summary/> - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Called by the de-serializer", error: true)] - public STATestCase() - { - } - - public IMethodInfo Method - { - get { return testCase.Method; } - } - - public Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, - ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) - { - var tcs = new TaskCompletionSource<RunSummary>(); - var thread = new Thread(() => - { - try - { - var testCaseTask = testCase.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, - cancellationTokenSource); - tcs.SetResult(testCaseTask.Result); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - return tcs.Task; - } - - public string DisplayName - { - get { return testCase.DisplayName; } - } - - public string SkipReason - { - get { return testCase.SkipReason; } - } - - public ISourceInformation SourceInformation - { - get { return testCase.SourceInformation; } - set { testCase.SourceInformation = value; } - } - - public ITestMethod TestMethod - { - get { return testCase.TestMethod; } - } - - public object[] TestMethodArguments - { - get { return testCase.TestMethodArguments; } - } - - public Dictionary<string, List<string>> Traits - { - get { return testCase.Traits; } - } - - public string UniqueID - { - get { return testCase.UniqueID; } - } - - public void Deserialize(IXunitSerializationInfo info) - { - testCase = info.GetValue<IXunitTestCase>("InnerTestCase"); - } - - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("InnerTestCase", testCase); - } -} \ No newline at end of file diff --git a/src/UnitTests/XUnit/STATheoryAttribute.cs b/src/UnitTests/XUnit/STATheoryAttribute.cs deleted file mode 100644 index 5ba2c5f865..0000000000 --- a/src/UnitTests/XUnit/STATheoryAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using Xunit; -using Xunit.Sdk; - -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -[XunitTestCaseDiscoverer("STATheoryDiscoverer", "UnitTests")] -public class STATheoryAttribute : TheoryAttribute -{ -} diff --git a/src/UnitTests/XUnit/STATheoryDiscoverer.cs b/src/UnitTests/XUnit/STATheoryDiscoverer.cs deleted file mode 100644 index 6ce49f3185..0000000000 --- a/src/UnitTests/XUnit/STATheoryDiscoverer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -public class STATheoryDiscoverer : IXunitTestCaseDiscoverer -{ - readonly TheoryDiscoverer theoryDiscoverer; - - public STATheoryDiscoverer(IMessageSink diagnosticMessageSink) - { - theoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink); - } - - public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - return theoryDiscoverer.Discover(discoveryOptions, testMethod, factAttribute) - .Select(testCase => new STATestCase(testCase)); - } -} \ No newline at end of file diff --git a/src/UnitTests/packages.config b/src/UnitTests/packages.config deleted file mode 100644 index 9d17975aa0..0000000000 --- a/src/UnitTests/packages.config +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="EntryExitDecorator.Fody" version="0.3.0" targetFramework="net45" /> - <package id="Fody" version="1.28.3" targetFramework="net45" /> - <package id="NSubstitute" version="1.8.1.0" targetFramework="net45" /> - <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> - <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> - <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> - <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> - <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> - <package id="Rx-Testing" version="2.2.5-custom" targetFramework="net45" /> - <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> - <package id="xunit" version="2.1.0" targetFramework="net45" /> - <package id="xunit.abstractions" version="2.0.0" targetFramework="net45" /> - <package id="xunit.assert" version="2.1.0" targetFramework="net45" /> - <package id="xunit.core" version="2.1.0" targetFramework="net45" /> - <package id="xunit.extensibility.core" version="2.1.0" targetFramework="net45" /> - <package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net45" /> - <package id="xunit.runner.console" version="2.1.0" targetFramework="net45" /> - <package id="xunit.runner.visualstudio" version="2.1.0" targetFramework="net45" /> -</packages> \ No newline at end of file diff --git a/src/common/GitHubVS.ruleset b/src/common/GitHubVS.ruleset index ebf59de5c0..63f2a176eb 100644 --- a/src/common/GitHubVS.ruleset +++ b/src/common/GitHubVS.ruleset @@ -2,8 +2,11 @@ <RuleSet Name="GitHub" Description="This ruleset only includes the rules we care about. I'll be adding new ones as we fix our codebase." ToolsVersion="11.0"> <IncludeAll Action="Error" /> <Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed"> + <Rule Id="CA1000" Action="None" /> <Rule Id="CA1002" Action="None" /> + <Rule Id="CA1004" Action="Warning" /> <Rule Id="CA1006" Action="None" /> + <Rule Id="CA1009" Action="None" /> <Rule Id="CA1014" Action="None" /> <Rule Id="CA1017" Action="None" /> <Rule Id="CA1020" Action="None" /> @@ -39,6 +42,7 @@ <Rule Id="CA1506" Action="None" /> <Rule Id="CA1701" Action="None" /> <Rule Id="CA1702" Action="None" /> + <Rule Id="CA1703" Action="None" /> <Rule Id="CA1704" Action="None" /> <Rule Id="CA1707" Action="None" /> <Rule Id="CA1708" Action="None" /> @@ -58,8 +62,10 @@ <Rule Id="CA1724" Action="None" /> <Rule Id="CA1725" Action="None" /> <Rule Id="CA1726" Action="None" /> + <Rule Id="CA1800" Action="None" /> <Rule Id="CA1810" Action="None" /> <Rule Id="CA1811" Action="None" /> + <Rule Id="CA1822" Action="None" /> <Rule Id="CA2000" Action="None" /> <Rule Id="CA2118" Action="None" /> <Rule Id="CA2122" Action="None" /> diff --git a/src/common/SolutionInfo.cs b/src/common/SolutionInfo.cs index aa3c30bc87..58d7bb878e 100644 --- a/src/common/SolutionInfo.cs +++ b/src/common/SolutionInfo.cs @@ -1,13 +1,15 @@ +using System; using System.Reflection; using System.Resources; using System.Runtime.InteropServices; [assembly: AssemblyProduct("GitHub Extension for Visual Studio")] -[assembly: AssemblyVersion("1.0.16.1")] -[assembly: AssemblyFileVersion("1.0.16.1")] +[assembly: AssemblyVersion(AssemblyVersionInformation.Version)] +[assembly: AssemblyFileVersion(AssemblyVersionInformation.Version)] +[assembly: AssemblyInformationalVersion(AssemblyVersionInformation.Version)] [assembly: ComVisible(false)] [assembly: AssemblyCompany("GitHub, Inc.")] -[assembly: AssemblyCopyright("Copyright � GitHub, Inc. 2014-2015")] +[assembly: AssemblyCopyright("Copyright � GitHub, Inc. 2014-2016")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -16,6 +18,6 @@ namespace System { internal static class AssemblyVersionInformation { - internal const string Version = "1.0.16.1"; + internal const string Version = "2.5.6.0"; } } diff --git a/src/common/settings.json b/src/common/settings.json new file mode 100644 index 0000000000..3bab5c422f --- /dev/null +++ b/src/common/settings.json @@ -0,0 +1,35 @@ +{ + "settings": [ + { + "name": "CollectMetrics", + "type": "bool", + "default": 'true' + }, + { + "name": "EditorComments", + "type": "bool", + "default": "false" + }, + { + "name": "ForkButton", + "type": "bool", + "default": "false" + }, + { + "name": "UIState", + "type": "object", + "typename": "UIState", + "default": "null" + }, + { + "name": "HideTeamExplorerWelcomeMessage", + "type": "bool", + "default": "false" + }, + { + "name": "EnableTraceLogging", + "type": "bool", + "default": "false" + } + ] +} \ No newline at end of file diff --git a/src/common/signing.props b/src/common/signing.props new file mode 100644 index 0000000000..30f68c51e7 --- /dev/null +++ b/src/common/signing.props @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <BuildType Condition="Exists('..\..\script\key.snk')">Internal</BuildType> + </PropertyGroup> + + <PropertyGroup Condition="'$(BuildType)' == 'Internal'"> + <AssemblyOriginatorKeyFile>..\..\script\key.snk</AssemblyOriginatorKeyFile> + <SignAssembly>true</SignAssembly> + <DelaySign>false</DelaySign> + </PropertyGroup> + + <PropertyGroup Condition="'$(BuildType)' != 'Internal' and Exists('..\..\signingkey')"> + <AssemblyOriginatorKeyFile>..\..\signingkey.snk</AssemblyOriginatorKeyFile> + <SignAssembly>true</SignAssembly> + <DelaySign>false</DelaySign> + </PropertyGroup> + + <ItemGroup> + <None Include="..\..\script\key.snk" Condition="'$(BuildType)' == 'Internal'"> + <Link>key.snk</Link> + </None> + + <None Include="..\..\signingkey" Condition="'$(BuildType)' != 'Internal' and Exists('..\..\signingkey')"> + <Link>signingkey</Link> + </None> + </ItemGroup> +</Project> diff --git a/src/common/t4.targets b/src/common/t4.targets new file mode 100644 index 0000000000..505af9b1f5 --- /dev/null +++ b/src/common/t4.targets @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(SolutionDir)\lib\Microsoft.TextTemplating.targets" /> + <PropertyGroup> + <TransformOnBuild>false</TransformOnBuild> + <TransformOutOfDateOnly>true</TransformOutOfDateOnly> + <PackageDir>$(SolutionDir)\packages</PackageDir> + </PropertyGroup> +<!-- Tell the MSBuild T4 task to make the property available: --> + <ItemGroup> + <T4ParameterValues Include="PackageDir"> + <Value>$(PackageDir)</Value> + </T4ParameterValues> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/submodules/akavache b/submodules/akavache index ba21a0f71f..1ea55dfb7e 160000 --- a/submodules/akavache +++ b/submodules/akavache @@ -1 +1 @@ -Subproject commit ba21a0f71f4b3b6c5261be8b5eb5ce2c0cc3219c +Subproject commit 1ea55dfb7e36ee0737291bf013b09c895fffa624 diff --git a/submodules/libgit2sharp b/submodules/libgit2sharp deleted file mode 160000 index af6f3b8804..0000000000 --- a/submodules/libgit2sharp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit af6f3b880464b1e98b5c9e7b4871c341ffd208eb diff --git a/submodules/octokit.net b/submodules/octokit.net index ae23de8682..d807d06526 160000 --- a/submodules/octokit.net +++ b/submodules/octokit.net @@ -1 +1 @@ -Subproject commit ae23de8682134eda4a12881346c52e15717d7f15 +Subproject commit d807d065263f140575d4e5e755c40f459a72c262 diff --git a/submodules/reactiveui b/submodules/reactiveui index 7d86f7d045..85e05b16bd 160000 --- a/submodules/reactiveui +++ b/submodules/reactiveui @@ -1 +1 @@ -Subproject commit 7d86f7d04569ff4c092bbc197576b0bb48b6a167 +Subproject commit 85e05b16bdcd01b0fb5ea84b76bcb1069d8c7037 diff --git a/submodules/rothko b/submodules/rothko deleted file mode 160000 index 581164318b..0000000000 --- a/submodules/rothko +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 581164318b73b52617a20f0184135246872dcc9e diff --git a/submodules/splat b/submodules/splat index cab725b8df..53551f73f4 160000 --- a/submodules/splat +++ b/submodules/splat @@ -1 +1 @@ -Subproject commit cab725b8dfa654a4217f8fe1fdc4462bb15d196d +Subproject commit 53551f73f4ee0e921e149e5136d7f5cc05967530 diff --git a/test.cmd b/test.cmd new file mode 100644 index 0000000000..275a8d1a43 --- /dev/null +++ b/test.cmd @@ -0,0 +1,2 @@ +@if "%1" == "" echo Please specify Debug or Release && EXIT /B +powershell -ExecutionPolicy Unrestricted scripts\test.ps1 -Config:%1 diff --git a/test/GitHub.Api.UnitTests/Args.cs b/test/GitHub.Api.UnitTests/Args.cs new file mode 100644 index 0000000000..2a04705cde --- /dev/null +++ b/test/GitHub.Api.UnitTests/Args.cs @@ -0,0 +1,35 @@ +using System; +using GitHub.Api; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using LibGit2Sharp; +using Microsoft.VisualStudio.Text; +using NSubstitute; +using Octokit; + +internal static class Args +{ + public static bool Boolean { get { return Arg.Any<bool>(); } } + public static int Int32 { get { return Arg.Any<int>(); } } + public static string String { get { return Arg.Any<string>(); } } + public static Span Span { get { return Arg.Any<Span>(); } } + public static SnapshotPoint SnapshotPoint { get { return Arg.Any<SnapshotPoint>(); } } + public static NewRepository NewRepository { get { return Arg.Any<NewRepository>(); } } + public static IAccount Account { get { return Arg.Any<IAccount>(); } } + public static IApiClient ApiClient { get { return Arg.Any<IApiClient>(); } } + public static IServiceProvider ServiceProvider { get { return Arg.Any<IServiceProvider>(); } } + public static IAvatarProvider AvatarProvider { get { return Arg.Any<IAvatarProvider>(); } } + public static HostAddress HostAddress { get { return Arg.Any<HostAddress>(); } } + public static Uri Uri { get { return Arg.Any<Uri>(); } } + public static LibGit2Sharp.IRepository LibGit2Repo { get { return Arg.Any<LibGit2Sharp.IRepository>(); } } + public static LibGit2Sharp.Branch LibGit2Branch { get { return Arg.Any<LibGit2Sharp.Branch>(); } } + public static Remote LibgGit2Remote { get { return Arg.Any<Remote>(); } } + public static ILocalRepositoryModel LocalRepositoryModel { get { return Arg.Any<ILocalRepositoryModel>(); } } + public static IRemoteRepositoryModel RemoteRepositoryModel { get { return Arg.Any<IRemoteRepositoryModel>(); } } + public static IBranch Branch { get { return Arg.Any<IBranch>(); } } + public static IGitService GitService { get { return Arg.Any<IGitService>(); } } + public static Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> + TwoFactorChallengCallback + { get { return Arg.Any<Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>> (); } } +} diff --git a/test/GitHub.Api.UnitTests/GitHub.Api.UnitTests.csproj b/test/GitHub.Api.UnitTests/GitHub.Api.UnitTests.csproj new file mode 100644 index 0000000000..2474ea6e02 --- /dev/null +++ b/test/GitHub.Api.UnitTests/GitHub.Api.UnitTests.csproj @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\NUnit.3.10.1\build\NUnit.props" Condition="Exists('..\..\packages\NUnit.3.10.1\build\NUnit.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{EFDE0798-ACDB-431D-B7F1-548A7231C853}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.Api.UnitTests</RootNamespace> + <AssemblyName>GitHub.Api.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Xaml" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="Args.cs" /> + <Compile Include="LoginManagerTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SimpleApiClientFactoryTests.cs" /> + <Compile Include="SimpleApiClientTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.InlineReviews\GitHub.InlineReviews.csproj"> + <Project>{7f5ed78b-74a3-4406-a299-70cfb5885b8b}</Project> + <Name>GitHub.InlineReviews</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project> \ No newline at end of file diff --git a/test/GitHub.Api.UnitTests/LoginManagerTests.cs b/test/GitHub.Api.UnitTests/LoginManagerTests.cs new file mode 100644 index 0000000000..58af41afd5 --- /dev/null +++ b/test/GitHub.Api.UnitTests/LoginManagerTests.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Primitives; +using NSubstitute; +using Octokit; +using NUnit.Framework; + +public class LoginManagerTests +{ + static readonly HostAddress host = HostAddress.GitHubDotComHostAddress; + static readonly HostAddress enterprise = HostAddress.Create("https://enterprise.hub"); + static readonly string[] scopes = { "user", "repo", "gist", "write:public_key" }; + + public class TheLoginMethod + { + [Test] + public async Task LoginTokenIsSavedToCache() + { + var client = CreateClient(); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns(new ApplicationAuthorization("123abc")); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + await target.Login(host, client, "foo", "bar"); + + await keychain.Received().Save("foo", "123abc", host); + } + + [Test] + public async Task LoggedInUserIsReturned() + { + var user = new User(); + var client = CreateClient(user); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns(new ApplicationAuthorization("123abc")); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + var result = await target.Login(host, client, "foo", "bar"); + + Assert.That(user, Is.SameAs(result)); + } + + [Test] + public async Task DeletesExistingAuthenticationIfNullTokenReturned() + { + // If GetOrCreateApplicationAuthentication is called and a matching token already exists, + // the returned token will be null because it is assumed that the token will be stored + // locally. In this case, the existing token should be first deleted. + var client = CreateClient(); + var user = new User(); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns( + new ApplicationAuthorization(string.Empty), + new ApplicationAuthorization("123abc")); + client.User.Current().Returns(user); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + var result = await target.Login(host, client, "foo", "bar"); + + await client.Authorization.Received(2).GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()); + await client.Authorization.Received(1).Delete(0); + await keychain.Received().Save("foo", "123abc", host); + } + + [Test] + public async Task TwoFactorExceptionIsPassedToHandler() + { + var client = CreateClient(); + var exception = new TwoFactorChallengeFailedException(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw exception; }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>(), "123456") + .Returns(new ApplicationAuthorization("123abc")); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + tfa.Value.HandleTwoFactorException(exception).Returns(new TwoFactorChallengeResult("123456")); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + await target.Login(host, client, "foo", "bar"); + + await client.Authorization.Received().GetOrCreateApplicationAuthentication( + "id", + "secret", + Arg.Any<NewAuthorization>(), + "123456"); + } + + [Test] + public async Task Failed2FACodeResultsInRetry() + { + var client = CreateClient(); + var exception = new TwoFactorChallengeFailedException(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw exception; }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>(), "111111") + .Returns<ApplicationAuthorization>(_ => { throw exception; }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>(), "123456") + .Returns(new ApplicationAuthorization("123abc")); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + tfa.Value.HandleTwoFactorException(exception).Returns( + new TwoFactorChallengeResult("111111"), + new TwoFactorChallengeResult("123456")); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + await target.Login(host, client, "foo", "bar"); + + await client.Authorization.Received(1).GetOrCreateApplicationAuthentication( + "id", + "secret", + Arg.Any<NewAuthorization>(), + "111111"); + await client.Authorization.Received(1).GetOrCreateApplicationAuthentication( + "id", + "secret", + Arg.Any<NewAuthorization>(), + "123456"); + } + + [Test] + public async Task HandlerNotifiedOfExceptionIn2FAChallengeResponse() + { + var client = CreateClient(); + var twoFaException = new TwoFactorChallengeFailedException(); + var forbiddenResponse = Substitute.For<IResponse>(); + forbiddenResponse.StatusCode.Returns(HttpStatusCode.Forbidden); + var loginAttemptsException = new LoginAttemptsExceededException(forbiddenResponse); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw twoFaException; }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>(), "111111") + .Returns<ApplicationAuthorization>(_ => { throw loginAttemptsException; }); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + tfa.Value.HandleTwoFactorException(twoFaException).Returns( + new TwoFactorChallengeResult("111111"), + new TwoFactorChallengeResult("123456")); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + Assert.ThrowsAsync<LoginAttemptsExceededException>(async () => await target.Login(host, client, "foo", "bar")); + + await client.Authorization.Received(1).GetOrCreateApplicationAuthentication( + "id", + "secret", + Arg.Any<NewAuthorization>(), + "111111"); + await tfa.Value.Received(1).ChallengeFailed(loginAttemptsException); + } + + [Test] + public async Task RequestResendCodeResultsInRetryingLogin() + { + var client = CreateClient(); + var exception = new TwoFactorChallengeFailedException(); + var user = new User(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw exception; }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>(), "123456") + .Returns(new ApplicationAuthorization("456def")); + client.User.Current().Returns(user); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + tfa.Value.HandleTwoFactorException(exception).Returns( + TwoFactorChallengeResult.RequestResendCode, + new TwoFactorChallengeResult("123456")); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + await target.Login(host, client, "foo", "bar"); + + await client.Authorization.Received(2).GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()); + } + + [Test] + public async Task UsesUsernameAndPasswordInsteadOfAuthorizationTokenWhenEnterpriseAndAPIReturns404() + { + var client = CreateClient(); + var user = new User(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => + { + throw new NotFoundException("Not there", HttpStatusCode.NotFound); + }); + client.User.Current().Returns(user); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + await target.Login(enterprise, client, "foo", "bar"); + + await keychain.Received().Save("foo", "bar", enterprise); + } + + [Test] + public async Task ErasesLoginWhenUnauthorized() + { + var client = CreateClient(); + var user = new User(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw new AuthorizationException(); }); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + Assert.ThrowsAsync<AuthorizationException>(async () => await target.Login(enterprise, client, "foo", "bar")); + + await keychain.Received().Delete(enterprise); + } + + [Test] + public async Task ErasesLoginWhenNonOctokitExceptionThrown() + { + var client = CreateClient(); + var user = new User(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw new InvalidOperationException(); }); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + Assert.ThrowsAsync<InvalidOperationException>(async () => await target.Login(host, client, "foo", "bar")); + + + await keychain.Received().Delete(host); + } + + [Test] + public async Task ErasesLoginWhenNonOctokitExceptionThrownIn2FA() + { + var client = CreateClient(); + var user = new User(); + var exception = new TwoFactorChallengeFailedException(); + + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns<ApplicationAuthorization>(_ => { throw exception; }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>(), "123456") + .Returns<ApplicationAuthorization>(_ => { throw new InvalidOperationException(); }); + client.User.Current().Returns(user); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + tfa.Value.HandleTwoFactorException(exception).Returns(new TwoFactorChallengeResult("123456")); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + Assert.ThrowsAsync<InvalidOperationException>(async () => await target.Login(host, client, "foo", "bar")); + + await keychain.Received().Delete(host); + } + + [Test] + public void InvalidResponseScopesCauseException() + { + var client = CreateClient(responseScopes: new[] { "user", "repo" }); + client.Authorization.GetOrCreateApplicationAuthentication("id", "secret", Arg.Any<NewAuthorization>()) + .Returns(new ApplicationAuthorization("123abc")); + + var keychain = Substitute.For<IKeychain>(); + var tfa = new Lazy<ITwoFactorChallengeHandler>(() => Substitute.For<ITwoFactorChallengeHandler>()); + var oauthListener = Substitute.For<IOAuthCallbackListener>(); + + var target = new LoginManager(keychain, tfa, oauthListener, "id", "secret", scopes); + + Assert.ThrowsAsync<IncorrectScopesException>(() => target.Login(host, client, "foo", "bar")); + } + + IGitHubClient CreateClient(User user = null, string[] responseScopes = null) + { + var result = Substitute.For<IGitHubClient>(); + var userResponse = Substitute.For<IApiResponse<User>>(); + userResponse.HttpResponse.Headers.Returns(new Dictionary<string, string> + { + { "X-OAuth-Scopes", string.Join(",", responseScopes ?? scopes) } + }); + userResponse.Body.Returns(user ?? new User()); + result.Connection.Get<User>(new Uri("user", UriKind.Relative), null, null).Returns(userResponse); + return result; + } + } + + public class TheScopesMatchMethod + { + [Test] + public void ReturnsFalseWhenMissingScopes() + { + var received = new[] { "user", "repo", "write:public_key" }; + + Assert.False(LoginManager.ScopesMatch(scopes, received)); + } + + [Test] + public void ReturnsTrueWhenScopesEqual() + { + var received = new[] { "user", "repo", "gist", "write:public_key" }; + + Assert.True(LoginManager.ScopesMatch(scopes, received)); + } + + [Test] + public void ReturnsTrueWhenExtraScopesReturned() + { + var received = new[] { "user", "repo", "gist", "foo", "write:public_key" }; + + Assert.True(LoginManager.ScopesMatch(scopes, received)); + } + + [Test] + public void ReturnsTrueWhenAdminScopeReturnedInsteadOfWrite() + { + var received = new[] { "user", "repo", "gist", "foo", "admin:public_key" }; + + Assert.True(LoginManager.ScopesMatch(scopes, received)); + } + } +} \ No newline at end of file diff --git a/test/GitHub.Api.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.Api.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e40839b161 --- /dev/null +++ b/test/GitHub.Api.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.Api.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTestProject1")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("efde0798-acdb-431d-b7f1-548a7231c853")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.Api.UnitTests/SimpleApiClientFactoryTests.cs b/test/GitHub.Api.UnitTests/SimpleApiClientFactoryTests.cs new file mode 100644 index 0000000000..7782c3ac98 --- /dev/null +++ b/test/GitHub.Api.UnitTests/SimpleApiClientFactoryTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.Models; +using NSubstitute; +using Octokit; +using NUnit.Framework; + +public class SimpleApiClientFactoryTests +{ + public class TheCreateMethod + { + [Test] + public async Task CreatesNewInstanceOfSimpleApiClient() + { + const string url = "https://github.com/github/CreatesNewInstanceOfSimpleApiClient"; + var program = CreateProgram(); + var keychain = Substitute.For<IKeychain>(); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + var factory = new SimpleApiClientFactory( + program, + CreateKeychain(), + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + + var client = await factory.Create(url); + + Assert.That(url, Is.EqualTo(client.OriginalUrl)); + Assert.That(HostAddress.GitHubDotComHostAddress, Is.EqualTo(client.HostAddress)); + Assert.That(client, Is.SameAs(await factory.Create(url))); // Tests caching. + } + } + + public class TheClearFromCacheMethod + { + [Test] + public async Task RemovesClientFromCache() + { + const string url = "https://github.com/github/RemovesClientFromCache"; + var program = CreateProgram(); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + var factory = new SimpleApiClientFactory( + program, + CreateKeychain(), + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + + var client = await factory.Create(url); + factory.ClearFromCache(client); + + Assert.That(client, Is.Not.SameAs(factory.Create(url))); + } + } + + static IProgram CreateProgram() + { + var program = Substitute.For<IProgram>(); + program.ProductHeader.Returns(new ProductHeaderValue("ProductName")); + return program; + } + + static IKeychain CreateKeychain() + { + var result = Substitute.For<IKeychain>(); + result.Load(null).ReturnsForAnyArgs(Tuple.Create("user", "pass")); + return result; + } +} diff --git a/test/GitHub.Api.UnitTests/SimpleApiClientTests.cs b/test/GitHub.Api.UnitTests/SimpleApiClientTests.cs new file mode 100644 index 0000000000..34679192a8 --- /dev/null +++ b/test/GitHub.Api.UnitTests/SimpleApiClientTests.cs @@ -0,0 +1,243 @@ +using System; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Primitives; +using GitHub.Services; +using NSubstitute; +using Octokit; +using NUnit.Framework; + +public class SimpleApiClientTests +{ + public class TheCtor : TestBaseClass + { + public void Throws() + { + Assert.Throws<ArgumentNullException>(() => new SimpleApiClient(null, null, null, null)); + Assert.Throws<ArgumentNullException>(() => new SimpleApiClient("https://github.com/github/visualstudio", null, null, null)); + } + } + + public class TheGetRepositoryMethod + { + [Test] + public async Task RetrievesRepositoryFromWeb() + { + var gitHubHost = HostAddress.GitHubDotComHostAddress; + var gitHubClient = Substitute.For<IGitHubClient>(); + var repository = new Repository(42); + gitHubClient.Repository.Get("github", "visualstudio").Returns(Task.FromResult(repository)); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + + var result = await client.GetRepository(); + + Assert.That(42, Is.EqualTo(result.Id)); + } + + [Test] + public async Task RetrievesCachedRepositoryForSubsequentCalls() + { + var gitHubHost = HostAddress.GitHubDotComHostAddress; + var gitHubClient = Substitute.For<IGitHubClient>(); + var repository = new Repository(42); + gitHubClient.Repository.Get("github", "visualstudio") + .Returns(_ => Task.FromResult(repository), _ => { throw new Exception("Should only be called once."); }); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + await client.GetRepository(); + + var result = await client.GetRepository(); + + Assert.That(42, Is.EqualTo(result.Id)); + } + } + + public class TheHasWikiMethod + { + [TestCase(WikiProbeResult.Ok, true)] + [TestCase(WikiProbeResult.Failed, false)] + [TestCase(WikiProbeResult.NotFound, false)] + public async Task ReturnsTrueWhenWikiProbeReturnsOk(WikiProbeResult probeResult, bool expected) + { + var gitHubHost = HostAddress.GitHubDotComHostAddress; + var gitHubClient = Substitute.For<IGitHubClient>(); + var repository = CreateRepository(42, true); + gitHubClient.Repository.Get("github", "visualstudio").Returns(Task.FromResult(repository)); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + wikiProbe.ProbeAsync(repository) + .Returns(_ => Task.FromResult(probeResult), _ => { throw new Exception("Only call it once"); }); + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + await client.GetRepository(); + + var result = client.HasWiki(); + + Assert.That(expected, Is.EqualTo(result)); + Assert.That(expected, Is.EqualTo(client.HasWiki())); + } + + [Test] + public void ReturnsFalseWhenWeHaveNotRequestedRepository() + { + var gitHubHost = HostAddress.GitHubDotComHostAddress; + var gitHubClient = Substitute.For<IGitHubClient>(); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + + var result = client.HasWiki(); + + Assert.False(result); + } + } + + public class TheIsEnterpriseMethod + { + [TestCase(EnterpriseProbeResult.Ok, true)] + [TestCase(EnterpriseProbeResult.Failed, false)] + [TestCase(EnterpriseProbeResult.NotFound, false)] + public async Task ReturnsTrueWhenEnterpriseProbeReturnsOk(EnterpriseProbeResult probeResult, bool expected) + { + var gitHubHost = HostAddress.GitHubDotComHostAddress; + var gitHubClient = Substitute.For<IGitHubClient>(); + var repository = CreateRepository(42, true); + gitHubClient.Repository.Get("github", "visualstudio").Returns(Task.FromResult(repository)); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + enterpriseProbe.Probe(Args.Uri) + .Returns(_ => Task.FromResult(probeResult), _ => { throw new Exception("Only call it once"); }); + var wikiProbe = Substitute.For<IWikiProbe>(); + var client = new SimpleApiClient( + "https://github.enterprise/github/visualstudio", + gitHubClient, + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + await client.GetRepository(); + + var result = await client.IsEnterprise(); + + Assert.That(expected, Is.EqualTo(result)); + Assert.That(expected, Is.EqualTo(await client.IsEnterprise())); + } + + [Test] + public void ReturnsFalseWhenWeHaveNotRequestedRepository() + { + var gitHubHost = HostAddress.GitHubDotComHostAddress; + var gitHubClient = Substitute.For<IGitHubClient>(); + var enterpriseProbe = Substitute.For<IEnterpriseProbe>(); + var wikiProbe = Substitute.For<IWikiProbe>(); + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + new Lazy<IEnterpriseProbe>(() => enterpriseProbe), + new Lazy<IWikiProbe>(() => wikiProbe)); + + var result = client.IsEnterprise().Result; + + Assert.False(result); + } + } + + public class TheIsIsAuthenticatedMethod + { + [Test] + public void ReturnsFalseWhenCredentialsNotSet() + { + var gitHubClient = Substitute.For<IGitHubClient>(); + gitHubClient.Connection.Credentials.Returns((Credentials)null); + + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + null, + null); + + var result = client.IsAuthenticated(); + Assert.False(result); + } + + [Test] + public void ReturnsFalseWhenAuthenicationTypeIsAnonymous() + { + var connection = Substitute.For<IConnection>(); + connection.Credentials=Credentials.Anonymous; + + var gitHubClient = Substitute.For<IGitHubClient>(); + gitHubClient.Connection.Returns(connection); + + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + null, + null); + + var result = client.IsAuthenticated(); + Assert.False(result); + } + + [Test] + public void ReturnsTrueWhenLoginIsSetToBasicAuth() + { + var connection = Substitute.For<IConnection>(); + connection.Credentials.Returns(new Credentials("username", "password")); + + var gitHubClient = Substitute.For<IGitHubClient>(); + gitHubClient.Connection.Returns(connection); + + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + null, + null); + + var result = client.IsAuthenticated(); + Assert.True(result); + } + + [Test] + public void ReturnsTrueWhenLoginIsSetToOAuth() + { + var connection = Substitute.For<IConnection>(); + connection.Credentials.Returns(new Credentials("token")); + + var gitHubClient = Substitute.For<IGitHubClient>(); + gitHubClient.Connection.Returns(connection); + + var client = new SimpleApiClient( + "https://github.com/github/visualstudio", + gitHubClient, + null, + null); + + var result = client.IsAuthenticated(); + Assert.True(result); + } + } + + private static Repository CreateRepository(int id, bool hasWiki) + { + return new Repository("", "", "", "", "", "", "", + id, new User(), "", "", "", "", "", false, false, 0, 0, "", + 0, null, DateTimeOffset.Now, DateTimeOffset.Now, new RepositoryPermissions(), null, null, null, false, + hasWiki, false, false, 0, 0, null, null, null); + } +} diff --git a/test/GitHub.Api.UnitTests/packages.config b/test/GitHub.Api.UnitTests/packages.config new file mode 100644 index 0000000000..b2455522fe --- /dev/null +++ b/test/GitHub.Api.UnitTests/packages.config @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.App.UnitTests/Args.cs b/test/GitHub.App.UnitTests/Args.cs new file mode 100644 index 0000000000..2a04705cde --- /dev/null +++ b/test/GitHub.App.UnitTests/Args.cs @@ -0,0 +1,35 @@ +using System; +using GitHub.Api; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using LibGit2Sharp; +using Microsoft.VisualStudio.Text; +using NSubstitute; +using Octokit; + +internal static class Args +{ + public static bool Boolean { get { return Arg.Any<bool>(); } } + public static int Int32 { get { return Arg.Any<int>(); } } + public static string String { get { return Arg.Any<string>(); } } + public static Span Span { get { return Arg.Any<Span>(); } } + public static SnapshotPoint SnapshotPoint { get { return Arg.Any<SnapshotPoint>(); } } + public static NewRepository NewRepository { get { return Arg.Any<NewRepository>(); } } + public static IAccount Account { get { return Arg.Any<IAccount>(); } } + public static IApiClient ApiClient { get { return Arg.Any<IApiClient>(); } } + public static IServiceProvider ServiceProvider { get { return Arg.Any<IServiceProvider>(); } } + public static IAvatarProvider AvatarProvider { get { return Arg.Any<IAvatarProvider>(); } } + public static HostAddress HostAddress { get { return Arg.Any<HostAddress>(); } } + public static Uri Uri { get { return Arg.Any<Uri>(); } } + public static LibGit2Sharp.IRepository LibGit2Repo { get { return Arg.Any<LibGit2Sharp.IRepository>(); } } + public static LibGit2Sharp.Branch LibGit2Branch { get { return Arg.Any<LibGit2Sharp.Branch>(); } } + public static Remote LibgGit2Remote { get { return Arg.Any<Remote>(); } } + public static ILocalRepositoryModel LocalRepositoryModel { get { return Arg.Any<ILocalRepositoryModel>(); } } + public static IRemoteRepositoryModel RemoteRepositoryModel { get { return Arg.Any<IRemoteRepositoryModel>(); } } + public static IBranch Branch { get { return Arg.Any<IBranch>(); } } + public static IGitService GitService { get { return Arg.Any<IGitService>(); } } + public static Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> + TwoFactorChallengCallback + { get { return Arg.Any<Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>> (); } } +} diff --git a/src/UnitTests/GitHub.App/Caches/ImageCacheTests.cs b/test/GitHub.App.UnitTests/Caches/ImageCacheTests.cs similarity index 81% rename from src/UnitTests/GitHub.App/Caches/ImageCacheTests.cs rename to test/GitHub.App.UnitTests/Caches/ImageCacheTests.cs index 49233b4a29..9a88528ed3 100644 --- a/src/UnitTests/GitHub.App/Caches/ImageCacheTests.cs +++ b/test/GitHub.App.UnitTests/Caches/ImageCacheTests.cs @@ -11,14 +11,14 @@ using GitHub.Services; using NSubstitute; using Rothko; -using Xunit; +using NUnit.Framework; public class ImageCacheTests { public class TheGetImageBytesMethod : TestBaseClass { - [Fact] - public async Task RetrievesImageFromCacheAndDoesNotFetchIt() + [Test] + public async Task RetrievesImageFromCacheAndDoesNotFetchItAsync() { var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=="); var cache = new InMemoryBlobCache(); @@ -32,12 +32,12 @@ public async Task RetrievesImageFromCacheAndDoesNotFetchIt() var retrieved = await imageCache.GetImage(new Uri("https://fake/")).FirstAsync(); Assert.NotNull(retrieved); - Assert.Equal(32, retrieved.PixelWidth); - Assert.Equal(32, retrieved.PixelHeight); + Assert.That(32, Is.EqualTo(retrieved.PixelWidth)); + Assert.That(32, Is.EqualTo(retrieved.PixelHeight)); } - [Fact] - public async Task WhenLoadingFromCacheFailsInvalidatesCacheEntry() + [Test] + public async Task WhenLoadingFromCacheFailsInvalidatesCacheEntryAsync() { var cache = new InMemoryBlobCache(); await cache.Insert("https://fake/", new byte[] { 0, 0, 0 }); @@ -52,12 +52,12 @@ public async Task WhenLoadingFromCacheFailsInvalidatesCacheEntry() .Catch(Observable.Return<BitmapSource>(null)) .FirstAsync(); - Assert.Null(retrieved); - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await cache.Get("https://fake/")); + Assert.That(retrieved, Is.Null); + Assert.ThrowsAsync<KeyNotFoundException>(async () => await cache.Get("https://fake/")); } - - [Fact] - public async Task DownloadsImageWhenMissingAndCachesIt() + + [Test] + public async Task DownloadsImageWhenMissingAndCachesItAsync() { var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=="); @@ -70,13 +70,13 @@ public async Task DownloadsImageWhenMissingAndCachesIt() var retrieved = await imageCache.GetImage(imageUri).FirstAsync(); - Assert.NotNull(retrieved); - Assert.Equal(32, retrieved.PixelWidth); - Assert.Equal(32, retrieved.PixelHeight); + Assert.That(retrieved, Is.Not.Null); + Assert.That(32, Is.EqualTo(retrieved.PixelWidth)); + Assert.That(32, Is.EqualTo(retrieved.PixelHeight)); } - [Fact] - public async Task ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchThrowsException() + [Test] + public void ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchThrowsException() { var imageUri = new Uri("https://example.com/poop.gif"); var cacheFactory = Substitute.For<IBlobCacheFactory>(); @@ -86,12 +86,12 @@ public async Task ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchThrow var imageCache = new ImageCache(cacheFactory, Substitute.For<IEnvironment>(), new Lazy<IImageDownloader>(() => imageDownloader)); - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await + Assert.ThrowsAsync<KeyNotFoundException>(async () => await imageCache.GetImage(imageUri).FirstAsync()); } - [Fact] - public async Task ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchReturnsEmpty() + [Test] + public void ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchReturnsEmpty() { var imageUri = new Uri("https://example.com/poop.gif"); var cache = new InMemoryBlobCache(); @@ -102,11 +102,11 @@ public async Task ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchRetur var imageCache = new ImageCache(cacheFactory, Substitute.For<IEnvironment>(), new Lazy<IImageDownloader>(() => imageDownloader)); - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await - imageCache.GetImage(imageUri).FirstAsync()); + Assert.ThrowsAsync<KeyNotFoundException>(async () => await + imageCache.GetImage(imageUri).FirstAsync()); } - [Fact] + [Test] public void OnlyDownloadsAndDecodesOnceForConcurrentOperations() { var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=="); @@ -126,21 +126,21 @@ public void OnlyDownloadsAndDecodesOnceForConcurrentOperations() var sub1 = imageCache.GetImage(uri).Subscribe(x => res1 = x); var sub2 = imageCache.GetImage(uri).Subscribe(x => res2 = x); - Assert.Null(res1); - Assert.Null(res2); + Assert.That(res1, Is.Null); + Assert.That(res2, Is.Null); subj.OnNext(singlePixel); subj.OnCompleted(); - Assert.NotNull(res1); - Assert.Equal(res1, res2); + Assert.That(res1, Is.Not.Null); + Assert.That(res1, Is.EqualTo(res2)); } } public class TheInvalidateMethod : TestBaseClass { - [Fact] - public async Task RemovesImageFromCache() + [Test] + public async Task RemovesImageFromCacheAsync() { var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=="); var cache = new InMemoryBlobCache(); @@ -151,14 +151,14 @@ public async Task RemovesImageFromCache() await imageCache.Invalidate(new Uri("https://fake/")); - await Assert.ThrowsAsync<KeyNotFoundException>(async () => await cache.Get("https://fake/")); + Assert.ThrowsAsync<KeyNotFoundException>(async () => await cache.Get("https://fake/")); } } public class TheSeedImageMethod : TestBaseClass { - [Fact] - public async Task AddsImageDirectlyToCache() + [Test] + public async Task AddsImageDirectlyToCacheAsync() { var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw=="); var cache = new InMemoryBlobCache(); @@ -169,7 +169,7 @@ public async Task AddsImageDirectlyToCache() await imageCache.SeedImage(new Uri("https://fake/"), singlePixel, DateTimeOffset.MaxValue); var retrieved = await cache.Get("https://fake/"); - Assert.Equal(singlePixel, retrieved); + Assert.That(singlePixel, Is.EqualTo(retrieved)); } } } diff --git a/test/GitHub.App.UnitTests/Collections/SequentialListSourceTests.cs b/test/GitHub.App.UnitTests/Collections/SequentialListSourceTests.cs new file mode 100644 index 0000000000..a219b76fdd --- /dev/null +++ b/test/GitHub.App.UnitTests/Collections/SequentialListSourceTests.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using GitHub.Collections; +using GitHub.Models; +using NUnit.Framework; + +namespace GitHub.App.UnitTests.Collections +{ + public class SequentialListSourceTests + { + [Test] + public async Task GetCount_Should_Load_First_Page() + { + var target = new TestSource(); + + Assert.That(target.PagesLoaded, Is.Empty); + + var count = await target.GetCount(); + + Assert.That(count, Is.EqualTo(100)); + Assert.That(target.PagesLoaded, Is.EqualTo(new[] { 0 })); + } + + [Test] + public async Task GetPage_Should_Load_Pages() + { + var target = new TestSource(); + + Assert.That(target.PagesLoaded, Is.Empty); + + var count = await target.GetPage(3); + + Assert.That(target.PagesLoaded, Is.EqualTo(new[] { 0, 1, 2, 3 })); + } + + [Test] + public void GetPage_Should_Stop_Loading_Pages_When_LoadPage_Throws() + { + var target = new TestSource(2); + + Assert.That(target.PagesLoaded, Is.Empty); + + Assert.ThrowsAsync<AggregateException>(() => target.GetPage(3)); + Assert.That(target.PagesLoaded, Is.EqualTo(new[] { 0, 1 })); + } + + [Test] + public void IsLoading_Should_Be_Set_To_False_When_LoadPage_Throws() + { + var target = new TestSource(2); + + Assert.That(target.PagesLoaded, Is.Empty); + + Assert.ThrowsAsync<AggregateException>(() => target.GetPage(3)); + Assert.That(target.IsLoading, Is.False); + } + + [Test] + public async Task Should_Not_Load_Duplicate_Pages() + { + var trigger = new Subject<Unit>(); + var target = new TestSource(loadTrigger: trigger); + + var task1 = target.GetPage(1); + var task2 = target.GetPage(1); + + Assert.That(target.PagesLoaded, Is.Empty); + + trigger.OnNext(Unit.Default); + trigger.OnNext(Unit.Default); + + await task1; + await task2; + + Assert.That(target.PagesLoaded, Is.EqualTo(new[] { 0, 1 })); + } + + class TestSource : SequentialListSource<string, string> + { + const int PageCount = 10; + readonly int? throwAtPage; + readonly ISubject<Unit> loadTrigger; + + public TestSource( + int? throwAtPage = null, + ISubject<Unit> loadTrigger = null) + { + this.throwAtPage = throwAtPage; + this.loadTrigger = loadTrigger; + } + + public override int PageSize => 10; + public List<int> PagesLoaded { get; private set; } = new List<int>(); + + protected override string CreateViewModel(string model) + { + return model + " loaded"; + } + + protected override async Task<Page<string>> LoadPage(string after) + { + var page = after != null ? int.Parse(after) : 0; + + if (loadTrigger != null) + { + await loadTrigger.Take(1).ToTask().ConfigureAwait(false); + } + + if (page == throwAtPage) + { + throw new GitHubLogicException("Thrown."); + } + + PagesLoaded.Add(page); + + return new Page<string> + { + EndCursor = (page + 1).ToString(), + HasNextPage = page < PageCount, + Items = Enumerable.Range(page * PageSize, PageSize).Select(x => "Item " + x).ToList(), + TotalCount = PageSize * PageCount, + }; + } + } + } +} diff --git a/test/GitHub.App.UnitTests/Factories/ModelServiceFactoryTests.cs b/test/GitHub.App.UnitTests/Factories/ModelServiceFactoryTests.cs new file mode 100644 index 0000000000..948eb21e7f --- /dev/null +++ b/test/GitHub.App.UnitTests/Factories/ModelServiceFactoryTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Caches; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.Factories +{ + public class ModelServiceFactoryTests : TestBaseClass + { + public class TheCreateAsyncMethod + { + [Test] + public async Task ShouldCreateDifferentModelServiceForDifferentHostAsync() + { + var target = CreateTarget(); + var instance1 = await target.CreateAsync(CreateConnection("https://github.com")); + var instance2 = await target.CreateAsync(CreateConnection("https://another.com")); + + Assert.That(instance1, Is.Not.SameAs(instance2)); + } + + [Test] + public async Task ShouldCreateDifferentModelServiceForDifferentConnectionsWithSameAddressAsync() + { + var target = CreateTarget(); + var instance1 = await target.CreateAsync(CreateConnection("https://github.com")); + var instance2 = await target.CreateAsync(CreateConnection("https://github.com")); + + Assert.That(instance1, Is.Not.SameAs(instance2)); + } + + [Test] + public async Task ShouldCacheModelServiceForHostAsync() + { + var target = CreateTarget(); + var connection = CreateConnection("https://github.com"); + var instance1 = await target.CreateAsync(connection); + var instance2 = await target.CreateAsync(connection); + + Assert.That(instance1, Is.SameAs(instance2)); + } + + [Test] + public async Task ShouldInsertUserAsync() + { + var hostCacheFactory = Substitute.For<IHostCacheFactory>(); + var target = CreateTarget(hostCacheFactory: hostCacheFactory); + var connection = CreateConnection("https://github.com"); + var hostCache = await hostCacheFactory.Create(connection.HostAddress); + var modelService = await target.CreateAsync(connection); + + hostCache.Received().Insert("GitHub.Caches.AccountCacheItem___user", Arg.Any<byte[]>()); + } + } + + static ModelServiceFactory CreateTarget( + IHostCacheFactory hostCacheFactory = null) + { + var apiClientFactory = Substitute.For<IApiClientFactory>(); + var graphQLClientFactory = Substitute.For<IGraphQLClientFactory>(); + var avatarProvider = Substitute.For<IAvatarProvider>(); + + hostCacheFactory = hostCacheFactory ?? Substitute.For<IHostCacheFactory>(); + + return new ModelServiceFactory( + apiClientFactory, + hostCacheFactory, + avatarProvider); + } + + static IConnection CreateConnection(string address, string login = "user") + { + var result = Substitute.For<IConnection>(); + var user = CreateOctokitUser(login, address); + result.HostAddress.Returns(HostAddress.Create(address)); + result.User.Returns(user); + return result; + } + } +} diff --git a/test/GitHub.App.UnitTests/GitHub.App.UnitTests.csproj b/test/GitHub.App.UnitTests/GitHub.App.UnitTests.csproj new file mode 100644 index 0000000000..72d56259cc --- /dev/null +++ b/test/GitHub.App.UnitTests/GitHub.App.UnitTests.csproj @@ -0,0 +1,256 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props" Condition="Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{3525D819-6AEC-4879-89FB-56B41F026571}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.App.UnitTests</RootNamespace> + <AssemblyName>GitHub.App.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + <HintPath>..\..\packages\EnvDTE.8.0.0\lib\net10\EnvDTE.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Testing.2.2.5-custom\lib\net45\Microsoft.Reactive.Testing.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=15.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.15.3.15\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + </Reference> + <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Octokit.GraphQL, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL.Core, Version=0.1.1.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="rothko, Version=0.0.3.0, Culture=neutral, PublicKeyToken=9f664c41f503810a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rothko.0.0.3-ghfvs\lib\net45\rothko.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="stdole, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\stdole.7.0.3300\lib\net10\stdole.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\CommandTestHelpers.cs" /> + <Compile Include="..\Helpers\LazySubstitute.cs" /> + <Compile Include="..\Helpers\ReactiveTestHelper.cs" /> + <Compile Include="..\Helpers\RepositoryHelpers.cs" /> + <Compile Include="..\Helpers\SimpleJson.cs" /> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="..\Helpers\TestImageCache.cs" /> + <Compile Include="..\Helpers\TestSharedCache.cs" /> + <Compile Include="Args.cs" /> + <Compile Include="Caches\ImageCacheTests.cs" /> + <Compile Include="Collections\SequentialListSourceTests.cs" /> + <Compile Include="Factories\ModelServiceFactoryTests.cs" /> + <Compile Include="Models\AccountModelTests.cs" /> + <Compile Include="Models\ModelServiceTests.cs" /> + <Compile Include="Models\PullRequestModelTests.cs" /> + <Compile Include="Models\RepositoryModelTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Services\AvatarProviderTests.cs" /> + <Compile Include="Services\GitClientTests.cs" /> + <Compile Include="Services\GitHubContextServiceTests.cs" /> + <Compile Include="Services\ImageDownloaderTests.cs" /> + <Compile Include="Services\OAuthCallbackListenerTests.cs" /> + <Compile Include="Services\PullRequestServiceTests.cs" /> + <Compile Include="Services\RepositoryCloneServiceTests.cs" /> + <Compile Include="Services\RepositoryCreationServiceTests.cs" /> + <Compile Include="Services\TeamExplorerContextTests.cs" /> + <Compile Include="TestDoubles\FakeCommitLog.cs" /> + <Compile Include="TestDoubles\FakeMenuCommandService.cs" /> + <Compile Include="ViewModels\Dialog\GistCreationViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\GitHubDialogWindowViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\Login2FaViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\LoginCredentialsViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\LoginToGitHubForEnterpriseViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\LoginToGitHubViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\PullRequestCreationViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\RepositoryCloneViewModelTests.cs" /> + <Compile Include="ViewModels\Dialog\RepositoryCreationViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\GitHubPaneViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\IssueListViewModelBaseTests.cs" /> + <Compile Include="ViewModels\GitHubPane\NavigationViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestDetailViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestFilesViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestListViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestReviewAuthoringViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestReviewViewModelTests.cs" /> + <Compile Include="ViewModels\GitHubPane\PullRequestUserReviewsViewModelTests.cs" /> + <Compile Include="ViewModels\TeamExplorer\RepositoryPublishViewModelTests.cs" /> + <Compile Include="Substitutes.cs" /> + <Compile Include="ViewModels\UserFilterViewModelTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\akavache\Akavache\Akavache_Net45.csproj"> + <Project>{B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}</Project> + <Name>Akavache_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="GitHub.App.UnitTests.dll.config"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\LibGit2Sharp.NativeBinaries.1.0.164\build\LibGit2Sharp.NativeBinaries.props'))" /> + </Target> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.App.UnitTests/GitHub.App.UnitTests.dll.config b/test/GitHub.App.UnitTests/GitHub.App.UnitTests.dll.config new file mode 100644 index 0000000000..c86d85c954 --- /dev/null +++ b/test/GitHub.App.UnitTests/GitHub.App.UnitTests.dll.config @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <runtime> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <dependentAssembly> + <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30AD4FE6B2A6AEED" culture="neutral"/> + <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0"/> + </dependentAssembly> + </assemblyBinding> + </runtime> +</configuration> \ No newline at end of file diff --git a/test/GitHub.App.UnitTests/Models/AccountModelTests.cs b/test/GitHub.App.UnitTests/Models/AccountModelTests.cs new file mode 100644 index 0000000000..cbbffe7d6e --- /dev/null +++ b/test/GitHub.App.UnitTests/Models/AccountModelTests.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Windows.Media.Imaging; +using GitHub.Collections; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; +using NUnit.Framework; +using Serilog; + +namespace UnitTests.GitHub.App.Models +{ + public class AccountModelTests : TestBaseClass + { + [Test] + public void CopyFromDoesNotLoseAvatar() + { + var userImage = AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"); + var orgImage = AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_org_avatar.png"); + + var initialBitmapImageSubject = new Subject<BitmapImage>(); + + var collectionEvent = new ManualResetEvent(false); + var avatarPropertyEvent = new ManualResetEvent(false); + + //Creating an initial account with an observable that returns immediately + const string login = "foo"; + const int initialOwnedPrivateRepositoryCount = 1; + + var initialAccount = new Account(login, true, false, initialOwnedPrivateRepositoryCount, 0, null, initialBitmapImageSubject); + + //Creating the test collection + var col = new TrackingCollection<IAccount>(Observable.Empty<IAccount>(), OrderedComparer<IAccount>.OrderByDescending(x => x.Login).Compare); + col.Subscribe(account => + { + collectionEvent.Set(); + }, () => { }); + + //Adding that account to the TrackingCollection + col.AddItem(initialAccount); + + //Waiting for the collection add the item + collectionEvent.WaitOne(); + collectionEvent.Reset(); + + //Checking some initial properties + Assert.That(login, Is.EqualTo(col[0].Login)); + Assert.That(initialOwnedPrivateRepositoryCount, Is.EqualTo(col[0].OwnedPrivateRepos)); + + //Demonstrating that the avatar is not yet present + Assert.That(col[0].Avatar, Is.Null); + + //Adding a listener to check for the changing of the Avatar property + initialAccount.Changed.Subscribe(args => + { + if (args.PropertyName == "Avatar") + { + avatarPropertyEvent.Set(); + } + }); + + //Providing the first avatar + initialBitmapImageSubject.OnNext(userImage); + initialBitmapImageSubject.OnCompleted(); + + //Waiting for the avatar to be added + avatarPropertyEvent.WaitOne(); + avatarPropertyEvent.Reset(); + + //Demonstrating that the avatar is present + Assert.That(col[0].Avatar, Is.Not.Null); + Assert.True(BitmapSourcesAreEqual(col[0].Avatar, userImage)); + Assert.False(BitmapSourcesAreEqual(col[0].Avatar, orgImage)); + + //Creating an account update + const int updatedOwnedPrivateRepositoryCount = 2; + var updatedBitmapImageSubject = new Subject<BitmapImage>(); + var updatedAccount = new Account(login, true, false, updatedOwnedPrivateRepositoryCount, 0, null, updatedBitmapImageSubject); + + //Updating the account in the collection + col.AddItem(updatedAccount); + + //Waiting for the collection to process the update + collectionEvent.WaitOne(); + collectionEvent.Reset(); + + //Providing the second avatar + updatedBitmapImageSubject.OnNext(orgImage); + updatedBitmapImageSubject.OnCompleted(); + + //Waiting for the delayed bitmap image observable + avatarPropertyEvent.WaitOne(); + avatarPropertyEvent.Reset(); + + //Login is the id, so that should be the same + Assert.That(login, Is.EqualTo(col[0].Login)); + + //CopyFrom() should have updated this field + Assert.That(updatedOwnedPrivateRepositoryCount, Is.EqualTo(col[0].OwnedPrivateRepos)); + + //CopyFrom() should not cause a race condition here + Assert.That(col[0].Avatar, Is.Not.Null); + Assert.True(BitmapSourcesAreEqual(col[0].Avatar, orgImage)); + Assert.False(BitmapSourcesAreEqual(col[0].Avatar, userImage)); + } + + public static bool BitmapSourcesAreEqual(BitmapSource image1, BitmapSource image2) + { + if (image1 == null || image2 == null) + { + return false; + } + + return BitmapSourceToBytes(image1).SequenceEqual(BitmapSourceToBytes(image2)); + } + + public static byte[] BitmapSourceToBytes(BitmapSource image) + { + byte[] data = new byte[] { }; + if (image != null) + { + try + { + var encoder = new BmpBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(image)); + using (MemoryStream ms = new MemoryStream()) + { + encoder.Save(ms); + data = ms.ToArray(); + } + return data; + } + catch (Exception ex) + { + Log.Error(ex, "Error'"); + } + } + + return data; + } + } +} diff --git a/test/GitHub.App.UnitTests/Models/ModelServiceTests.cs b/test/GitHub.App.UnitTests/Models/ModelServiceTests.cs new file mode 100644 index 0000000000..13e307a2f8 --- /dev/null +++ b/test/GitHub.App.UnitTests/Models/ModelServiceTests.cs @@ -0,0 +1,588 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Akavache; +using GitHub.Api; +using GitHub.Caches; +using GitHub.Services; +using NSubstitute; +using Octokit; +using NUnit.Framework; +using System.Globalization; +using System.Reactive.Subjects; +using System.Threading; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Collections; +using ReactiveUI; +using static GitHub.Services.ModelService; + +public class ModelServiceTests +{ + const int Timeout = 2000; + public class TheGetCurrentUserMethod : TestBaseClass + { + [Test] + public async Task RetrievesCurrentUserAsync() + { + var cache = new InMemoryBlobCache(); + await cache.InsertObject<AccountCacheItem>("user", new AccountCacheItem(CreateOctokitUser("octocat"))); + var modelService = CreateTarget(hostCache: cache); + + var user = await modelService.GetCurrentUser(); + + Assert.That("octocat", Is.EqualTo(user.Login)); + } + } + + public class TheInsertUserMethod : TestBaseClass + { + [Test] + public async Task AddsUserToCacheAsync() + { + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(hostCache: cache); + + var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); + + var cached = await cache.GetObject<AccountCacheItem>("user"); + Assert.That("octocat", Is.EqualTo(cached.Login)); + } + } + + public class TheGetGitIgnoreTemplatesMethod : TestBaseClass + { + [Test] + public async Task CanRetrieveAndCacheGitIgnoresAsync() + { + var data = new[] { "dotnet", "peanuts", "bloomcounty" }; + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetGitIgnoreTemplates().Returns(data.ToObservable()); + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + + var fetched = await modelService.GetGitIgnoreTemplates().ToList(); + + Assert.That(3, Is.EqualTo(fetched.Count)); + for (int i = 0; i < data.Length; i++) + Assert.That(data[i], Is.EqualTo(fetched[i].Name)); + + var indexKey = CacheIndex.GitIgnoresPrefix; + var cached = await cache.GetObject<CacheIndex>(indexKey); + Assert.That(3, Is.EqualTo(cached.Keys.Count)); + + var items = await cache.GetObjects<GitIgnoreCacheItem>(cached.Keys).Take(1); + for (int i = 0; i < data.Length; i++) + Assert.That(data[i], Is.EqualTo(items[indexKey + "|" + data[i]].Name)); + } + } + + public class TheGetLicensesMethod : TestBaseClass + { + [Test] + public async Task CanRetrieveAndCacheLicensesAsync() + { + var data = new[] + { + new LicenseMetadata("mit", "MIT", "foo", "https://github.com/", false), + new LicenseMetadata("apache", "Apache", "foo", "https://github.com/", false) + }; + + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetLicenses().Returns(data.ToObservable()); + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + + var fetched = await modelService.GetLicenses().ToList(); + + Assert.That(2, Is.EqualTo(fetched.Count)); + for (int i = 0; i < data.Length; i++) + Assert.That(data[i].Name, Is.EqualTo(fetched[i].Name)); + + var indexKey = CacheIndex.LicensesPrefix; + var cached = await cache.GetObject<CacheIndex>(indexKey); + Assert.That(2, Is.EqualTo(cached.Keys.Count)); + + var items = await cache.GetObjects<LicenseCacheItem>(cached.Keys).Take(1); + for (int i = 0; i < data.Length; i++) + Assert.That(data[i].Name, Is.EqualTo(items[indexKey + "|" + data[i].Key].Name)); + } + + [Test] + public async Task ReturnsEmptyIfLicenseApiNotFoundAsync() + { + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetLicenses() + .Returns(Observable.Throw<LicenseMetadata>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); + var modelService = CreateTarget(apiClient: apiClient); + + var fetched = await modelService.GetLicenses().ToList(); + + Assert.That(0, Is.EqualTo(fetched.Count)); + } + + [Test] + public async Task ReturnsEmptyIfCacheReadFailsAsync() + { + var apiClient = Substitute.For<IApiClient>(); + var cache = Substitute.For<IBlobCache>(); + cache.Get(Args.String) + .Returns(Observable.Throw<byte[]>(new InvalidOperationException("Unknown"))); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + + var fetched = await modelService.GetLicenses().ToList(); + + Assert.That(0, Is.EqualTo(fetched.Count)); + } + } + + public class TheGetAccountsMethod : TestBaseClass + { + [Test] + [Ignore("Skip this test as it will no longer be relevant with the GraphQL migration")] + public async Task CanRetrieveAndCacheUserAndAccountsAsync() + { + var orgs = new[] + { + CreateOctokitOrganization("github"), + CreateOctokitOrganization("fake") + }; + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("snoopy"))); + apiClient.GetOrganizations().Returns(orgs.ToObservable()); + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + await modelService.InsertUser(new AccountCacheItem { Login = "snoopy" }); + + var fetched = await modelService.GetAccounts(); + + Assert.That(3, Is.EqualTo(fetched.Count)); + Assert.That("snoopy", Is.EqualTo(fetched[0].Login)); + Assert.That("github", Is.EqualTo(fetched[1].Login)); + Assert.That("fake", Is.EqualTo(fetched[2].Login)); + var cachedOrgs = await cache.GetObject<IReadOnlyList<AccountCacheItem>>("snoopy|orgs"); + Assert.That(2, Is.EqualTo(cachedOrgs.Count)); + Assert.That("github", Is.EqualTo(cachedOrgs[0].Login)); + Assert.That("fake", Is.EqualTo(cachedOrgs[1].Login)); + var cachedUser = await cache.GetObject<AccountCacheItem>("user"); + Assert.That("snoopy", Is.EqualTo(cachedUser.Login)); + } + + [Test] + [Ignore("Skip this test as it will no longer be relevant with the GraphQL migration")] + public async Task CanRetrieveUserFromCacheAndAccountsFromApiAsync() + { + var orgs = new[] + { + CreateOctokitOrganization("github"), + CreateOctokitOrganization("fake") + }; + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetOrganizations().Returns(orgs.ToObservable()); + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); + + var fetched = await modelService.GetAccounts(); + + Assert.That(3, Is.EqualTo(fetched.Count)); + Assert.That("octocat", Is.EqualTo(fetched[0].Login)); + Assert.That("github", Is.EqualTo(fetched[1].Login)); + Assert.That("fake", Is.EqualTo(fetched[2].Login)); + var cachedOrgs = await cache.GetObject<IReadOnlyList<AccountCacheItem>>("octocat|orgs"); + Assert.That(2, Is.EqualTo(cachedOrgs.Count)); + Assert.That("github", Is.EqualTo(cachedOrgs[0].Login)); + Assert.That("fake", Is.EqualTo(cachedOrgs[1].Login)); + var cachedUser = await cache.GetObject<AccountCacheItem>("user"); + Assert.That("octocat", Is.EqualTo(cachedUser.Login)); + } + + [Test] + public async Task OnlyRetrievesOneUserEvenIfCacheOrApiReturnsMoreThanOneAsync() + { + // This should be impossible, but let's pretend it does happen. + var users = new[] + { + CreateOctokitUser("peppermintpatty"), + CreateOctokitUser("peppermintpatty") + }; + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetUser().Returns(users.ToObservable()); + apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); + var modelService = CreateTarget(apiClient: apiClient); + + var fetched = await modelService.GetAccounts(); + + Assert.That(1, Is.EqualTo(fetched.Count)); + Assert.That("peppermintpatty", Is.EqualTo(fetched[0].Login)); + } + } + + public class TheGetRepositoriesMethod : TestBaseClass + { + [Test] + [Ignore("Skip this test as it will no longer be relevant with the GraphQL migration")] + public async Task CanRetrieveAndCacheRepositoriesForUserAndOrganizationsAsync() + { + var orgs = new[] + { + CreateOctokitOrganization("github"), + CreateOctokitOrganization("octokit") + }; + var ownedRepos = new[] + { + CreateRepository("haacked", "seegit"), + CreateRepository("haacked", "codehaacks") + }; + var memberRepos = new[] + { + CreateRepository("mojombo", "semver"), + CreateRepository("ninject", "ninject"), + CreateRepository("jabbr", "jabbr"), + CreateRepository("fody", "nullguard") + }; + var githubRepos = new[] + { + CreateRepository("github", "visualstudio") + }; + var octokitRepos = new[] + { + CreateRepository("octokit", "octokit.net"), + CreateRepository("octokit", "octokit.rb"), + CreateRepository("octokit", "octokit.objc") + }; + var apiClient = Substitute.For<IApiClient>(); + apiClient.GetOrganizations().Returns(orgs.ToObservable()); + apiClient.GetUserRepositories(RepositoryType.Owner).Returns(ownedRepos.ToObservable()); + apiClient.GetUserRepositories(RepositoryType.Member).Returns(memberRepos.ToObservable()); + apiClient.GetRepositoriesForOrganization("github").Returns(githubRepos.ToObservable()); + apiClient.GetRepositoriesForOrganization("octokit").Returns(octokitRepos.ToObservable()); + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + await modelService.InsertUser(new AccountCacheItem { Login = "opus" }); + + var fetched = await modelService.GetRepositories().ToList(); + + Assert.That(4, Is.EqualTo(fetched.Count)); + Assert.That(2, Is.EqualTo(fetched[0].Count)); + Assert.That(4, Is.EqualTo(fetched[1].Count)); + Assert.That(1, Is.EqualTo(fetched[2].Count)); + Assert.That(3, Is.EqualTo(fetched[3].Count)); + Assert.That("seegit", Is.EqualTo(fetched[0][0].Name)); + Assert.That("codehaacks", Is.EqualTo(fetched[0][1].Name)); + Assert.That("semver", Is.EqualTo(fetched[1][0].Name)); + Assert.That("ninject", Is.EqualTo(fetched[1][1].Name)); + Assert.That("jabbr", Is.EqualTo(fetched[1][2].Name)); + Assert.That("nullguard", Is.EqualTo(fetched[1][3].Name)); + Assert.That("visualstudio", Is.EqualTo(fetched[2][0].Name)); + Assert.That("octokit.net", Is.EqualTo(fetched[3][0].Name)); + Assert.That("octokit.rb", Is.EqualTo(fetched[3][1].Name)); + Assert.That("octokit.objc", Is.EqualTo(fetched[3][2].Name)); + var cachedOwnerRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|Owner:repos"); + Assert.That(2, Is.EqualTo(cachedOwnerRepositories.Count)); + Assert.That("seegit", Is.EqualTo(cachedOwnerRepositories[0].Name)); + Assert.That("haacked", Is.EqualTo(cachedOwnerRepositories[0].Owner.Login)); + Assert.That("codehaacks", Is.EqualTo(cachedOwnerRepositories[1].Name)); + Assert.That("haacked", Is.EqualTo(cachedOwnerRepositories[1].Owner.Login)); + var cachedMemberRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|Member:repos"); + Assert.That(4, Is.EqualTo(cachedMemberRepositories.Count)); + Assert.That("semver", Is.EqualTo(cachedMemberRepositories[0].Name)); + Assert.That("mojombo", Is.EqualTo(cachedMemberRepositories[0].Owner.Login)); + Assert.That("ninject", Is.EqualTo(cachedMemberRepositories[1].Name)); + Assert.That("ninject", Is.EqualTo(cachedMemberRepositories[1].Owner.Login)); + Assert.That("jabbr", Is.EqualTo(cachedMemberRepositories[2].Name)); + Assert.That("jabbr", Is.EqualTo(cachedMemberRepositories[2].Owner.Login)); + Assert.That("nullguard", Is.EqualTo(cachedMemberRepositories[3].Name)); + Assert.That("fody", Is.EqualTo(cachedMemberRepositories[3].Owner.Login)); + var cachedGitHubRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|github|repos"); + Assert.That(1, Is.EqualTo(cachedGitHubRepositories.Count)); + Assert.That("seegit", Is.EqualTo(cachedOwnerRepositories[0].Name)); + Assert.That("haacked", Is.EqualTo(cachedOwnerRepositories[0].Owner.Login)); + Assert.That("codehaacks", Is.EqualTo(cachedOwnerRepositories[1].Name)); + Assert.That("haacked", Is.EqualTo(cachedOwnerRepositories[1].Owner.Login)); + var cachedOctokitRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|octokit|repos"); + Assert.That("octokit.net", Is.EqualTo(cachedOctokitRepositories[0].Name)); + Assert.That("octokit", Is.EqualTo(cachedOctokitRepositories[0].Owner.Login)); + Assert.That("octokit.rb", Is.EqualTo(cachedOctokitRepositories[1].Name)); + Assert.That("octokit", Is.EqualTo(cachedOctokitRepositories[1].Owner.Login)); + Assert.That("octokit.objc", Is.EqualTo(cachedOctokitRepositories[2].Name)); + Assert.That("octokit", Is.EqualTo(cachedOctokitRepositories[2].Owner.Login)); + } + + [Test] + public async Task WhenNotLoggedInReturnsEmptyCollectionAsync() + { + var apiClient = Substitute.For<IApiClient>(); + var modelService = CreateTarget(apiClient: apiClient); + + var repos = await modelService.GetRepositories(); + + Assert.That(0, Is.EqualTo(repos.Count)); + } + + [Test] + public async Task WhenLoggedInDoesNotBlowUpOnUnexpectedNetworkProblemsAsync() + { + var apiClient = Substitute.For<IApiClient>(); + var modelService = CreateTarget(apiClient: apiClient); + apiClient.GetOrganizations() + .Returns(Observable.Throw<Organization>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); + + var repos = await modelService.GetRepositories(); + + Assert.That(0, Is.EqualTo(repos.Count)); + } + } + + public class TheInvalidateAllMethod : TestBaseClass + { + [Test] + public async Task InvalidatesTheCacheAsync() + { + var apiClient = Substitute.For<IApiClient>(); + var cache = new InMemoryBlobCache(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat"))); + //Assert.Single((await cache.GetAllObjects<AccountCacheItem>())); + + await modelService.InvalidateAll(); + + //Assert.That((cache.GetAllObjects<AccountCacheItem>(), Is.Empty)); + } + + [Test] + public async Task VaccumsTheCacheAsync() + { + var apiClient = Substitute.For<IApiClient>(); + var cache = Substitute.For<IBlobCache>(); + cache.InvalidateAll().Returns(Observable.Return(Unit.Default)); + var received = false; + cache.Vacuum().Returns(x => + { + received = true; + return Observable.Return(Unit.Default); + }); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + + await modelService.InvalidateAll(); + Assert.True(received); + } + } + + public class TheGetPullRequestsMethod : TestBaseClass + { + [Test] + [Ignore("Pull requests always refresh from the server now. Migrate this test to data that doesn't require constant refreshing.")] + public async Task NonExpiredIndexReturnsCacheAsync() + { + var expected = 5; + + var username = "octocat"; + var reponame = "repo"; + + var cache = new InMemoryBlobCache(); + var apiClient = Substitute.For<IApiClient>(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + var user = CreateOctokitUser(username); + apiClient.GetUser().Returns(Observable.Return(user)); + apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); + var act = modelService.GetAccounts().ToEnumerable().First().First(); + + var repo = Substitute.For<ILocalRepositoryModel>(); + repo.Name.Returns(reponame); + repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame)); + + var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name); + + var prcache = Enumerable.Range(1, expected) + .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow)); + + // seed the cache + prcache + .Select(item => new PullRequestCacheItem(item)) + .Select(item => item.Save<PullRequestCacheItem>(cache, indexKey).ToEnumerable().First()) + .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable()) + .ToList(); + + var prlive = Observable.Range(1, expected) + .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow)) + .DelaySubscription(TimeSpan.FromMilliseconds(10)); + + apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive); + + await modelService.InsertUser(new AccountCacheItem(user)); + + ITrackingCollection<IPullRequestModel> col = new TrackingCollection<IPullRequestModel>(); + modelService.GetPullRequests(repo, col); + col.ProcessingDelay = TimeSpan.Zero; + + col.Subscribe(); + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout));; + + Assert.That(expected, Is.EqualTo(col.Count)); + //Assert.Collection(col, col.Select(x => new Action<IPullRequestModel>(t => Assert.That("Cache", StartsWith(x.Title)))).ToArray()); + } + + [Test] + public async Task ExpiredIndexReturnsLiveAsync() + { + var expected = 5; + + var username = "octocat"; + var reponame = "repo"; + + var cache = new InMemoryBlobCache(); + var apiClient = Substitute.For<IApiClient>(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + var user = CreateOctokitUser(username); + apiClient.GetUser().Returns(Observable.Return(user)); + apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); + var act = modelService.GetAccounts().ToEnumerable().First().First(); + + var repo = Substitute.For<ILocalRepositoryModel>(); + repo.Name.Returns(reponame); + repo.Owner.Returns(user.Login); + repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame)); + + var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name); + + var prcache = Enumerable.Range(1, expected) + .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow)); + + // seed the cache + prcache + .Select(item => new ModelService.PullRequestCacheItem(item)) + .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First()) + .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable()) + .ToList(); + + // expire the index + var indexobj = await cache.GetObject<CacheIndex>(indexKey); + indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6); + await cache.InsertObject(indexKey, indexobj); + + var prlive = Observable.Range(1, expected) + .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow)) + .DelaySubscription(TimeSpan.FromMilliseconds(10)); + + apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive); + + await modelService.InsertUser(new AccountCacheItem(user)); + + ITrackingCollection<IPullRequestModel> col = new TrackingCollection<IPullRequestModel>(); + modelService.GetPullRequests(repo, col); + col.ProcessingDelay = TimeSpan.Zero; + + var count = 0; + var done = new ReplaySubject<Unit>(); + done.OnNext(Unit.Default); + done.Subscribe(); + + col.Subscribe(t => + { + if (++count == expected * 2) + { + done.OnCompleted(); + } + }, () => { }); + + await done; + + //Assert.Collection(col, col.Select(x => new Action<IPullRequestModel>(t => Assert.StartsWith("Live", x.Title))).ToArray()); + } + + [Test] + public async Task ExpiredIndexClearsItemsAsync() + { + var expected = 5; + + var username = "octocat"; + var reponame = "repo"; + + var cache = new InMemoryBlobCache(); + var apiClient = Substitute.For<IApiClient>(); + var modelService = CreateTarget(apiClient: apiClient, hostCache: cache); + var user = CreateOctokitUser(username); + apiClient.GetUser().Returns(Observable.Return(user)); + apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); + var act = modelService.GetAccounts().ToEnumerable().First().First(); + + var repo = Substitute.For<ILocalRepositoryModel>(); + repo.Name.Returns(reponame); + repo.Owner.Returns(user.Login); + repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame)); + + var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name); + + var prcache = Enumerable.Range(1, expected) + .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow)); + + // seed the cache + prcache + .Select(item => new ModelService.PullRequestCacheItem(item)) + .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First()) + .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable()) + .ToList(); + + // expire the index + var indexobj = await cache.GetObject<CacheIndex>(indexKey); + indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6); + await cache.InsertObject(indexKey, indexobj); + + var prlive = Observable.Range(5, expected) + .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0)) + .DelaySubscription(TimeSpan.FromMilliseconds(10)); + + apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive); + + await modelService.InsertUser(new AccountCacheItem(user)); + + ITrackingCollection<IPullRequestModel> col = new TrackingCollection<IPullRequestModel>(); + modelService.GetPullRequests(repo, col); + col.ProcessingDelay = TimeSpan.Zero; + + var count = 0; + var done = new ReplaySubject<Unit>(); + done.OnNext(Unit.Default); + done.Subscribe(); + + col.Subscribe(t => + { + // we get all the items from the cache (items 1-5), all the items from the live (items 5-9), + // and 4 deletions (items 1-4) because the cache expired the items that were not + // a part of the live data + if (++count == 14) + { + done.OnCompleted(); + } + }, () => { }); + + await done; + + Assert.That(5, Is.EqualTo(col.Count)); + /**Assert.Collection(col, + t => { Assert.StartsWith("Live", t.Title); Assert.Equal(5, t.Number); }, + t => { Assert.StartsWith("Live", t.Title); Assert.Equal(6, t.Number); }, + t => { Assert.StartsWith("Live", t.Title); Assert.Equal(7, t.Number); }, + t => { Assert.StartsWith("Live", t.Title); Assert.Equal(8, t.Number); }, + t => { Assert.StartsWith("Live", t.Title); Assert.Equal(9, t.Number); } + );*/ + } + } + + static ModelService CreateTarget( + IApiClient apiClient = null, + Octokit.IConnection graphql = null, + IBlobCache hostCache = null, + IAvatarProvider avatarProvider = null) + { + return new ModelService( + apiClient ?? Substitute.For<IApiClient>(), + hostCache ?? new InMemoryBlobCache(), + Substitute.For<IAvatarProvider>()); + } +} diff --git a/src/UnitTests/GitHub.App/Models/PullRequestModelTests.cs b/test/GitHub.App.UnitTests/Models/PullRequestModelTests.cs similarity index 93% rename from src/UnitTests/GitHub.App/Models/PullRequestModelTests.cs rename to test/GitHub.App.UnitTests/Models/PullRequestModelTests.cs index 30b968319f..4ef1e035bf 100644 --- a/src/UnitTests/GitHub.App/Models/PullRequestModelTests.cs +++ b/test/GitHub.App.UnitTests/Models/PullRequestModelTests.cs @@ -13,14 +13,14 @@ using NSubstitute; using Octokit; using UnitTests.Helpers; -using Xunit; +using NUnit.Framework; using UnitTests; public class PullRequestModelTests : TestBaseClass { protected DateTimeOffset Now = new DateTimeOffset(0, TimeSpan.FromTicks(0)); - [Fact] + [Test] public void ComparisonNullEqualsNull() { PullRequestModel left = null; @@ -31,7 +31,7 @@ public void ComparisonNullEqualsNull() Assert.False(left < right); } - [Fact] + [Test] public void ComparisonBiggerThanNull() { PullRequestModel left = new PullRequestModel(0, "", Substitute.For<IAccount>(), Now, Now); @@ -42,7 +42,7 @@ public void ComparisonBiggerThanNull() Assert.False(left < right); } - [Fact] + [Test] public void ComparisonNullLowerThan() { PullRequestModel left = null; @@ -53,7 +53,7 @@ public void ComparisonNullLowerThan() Assert.True(left < right); } - [Fact] + [Test] public void ComparisonLowerThan() { PullRequestModel left = new PullRequestModel(0, "", Substitute.For<IAccount>(), Now, Now + TimeSpan.FromMilliseconds(1)); @@ -64,7 +64,7 @@ public void ComparisonLowerThan() Assert.True(left < right); } - [Fact] + [Test] public void ComparisonGreaterThan() { PullRequestModel left = new PullRequestModel(0, "", Substitute.For<IAccount>(), Now, Now + TimeSpan.FromMilliseconds(3)); @@ -75,20 +75,19 @@ public void ComparisonGreaterThan() Assert.False(left < right); } - [Fact] + [Test] public void ComparisonEquals() { PullRequestModel left = new PullRequestModel(1, "", Substitute.For<IAccount>(), Now, Now + TimeSpan.FromMilliseconds(1)); PullRequestModel right = new PullRequestModel(1, "", Substitute.For<IAccount>(), Now, Now + TimeSpan.FromMilliseconds(1)); - Assert.True(left == right); - Assert.False(left != right); + Assert.False(left == right); + Assert.True(left != right); Assert.False(left > right); Assert.False(left < right); } - [Theory] - [InlineData(1, 1, 1, 2)] - [InlineData(1, 1, 2, 1)] + [TestCase(1, 1, 1, 2)] + [TestCase(1, 1, 2, 1)] public void ComparisonNotEquals(int id1, int ms1, int id2, int ms2) { PullRequestModel left = new PullRequestModel(id1, "", Substitute.For<IAccount>(), Now, Now + TimeSpan.FromMilliseconds(ms1)); diff --git a/test/GitHub.App.UnitTests/Models/RepositoryModelTests.cs b/test/GitHub.App.UnitTests/Models/RepositoryModelTests.cs new file mode 100644 index 0000000000..fc048b5c95 --- /dev/null +++ b/test/GitHub.App.UnitTests/Models/RepositoryModelTests.cs @@ -0,0 +1,98 @@ +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.VisualStudio; +using LibGit2Sharp; +using NSubstitute; +using UnitTests; +using NUnit.Framework; + +public class RepositoryModelTests +{ + public class ComparisonTests : TestBaseClass + { + [TestCase("a name", "https://github.com/github/VisualStudio", @"C:\some\path", "a name", "https://github.com/github/VisualStudio", @"C:\some\path")] + [TestCase("a name", "https://github.com/github/VisualStudio", @"c:\some\path", "a name", "https://github.com/github/VisualStudio", @"C:\some\path")] + [TestCase("a name", "https://github.com/github/VisualStudio", @"C:\some\path", "a name", "https://github.com/github/VisualStudio", @"c:\some\path")] + [TestCase("a name", "https://github.com/github/VisualStudio", @"C:\some\path\", "a name", "https://github.com/github/VisualStudio", @"c:\some\path")] + [TestCase("a name", "https://github.com/github/VisualStudio", @"C:\some\path", "a name", "https://github.com/github/VisualStudio", @"c:\some\path\")] + [TestCase("a name", "https://github.com/github/VisualStudio", @"C:\some\path\", "a name", "https://github.com/github/VisualStudio", @"c:\some\path\")] + public void SameContentEqualsTrue(string name1, string url1, string path1, string name2, string url2, string path2) + { + var gitService = Substitute.For<IGitService>(); + var a = new LocalRepositoryModel(name1, new UriString(url1), path1, gitService); + var b = new LocalRepositoryModel(name2, new UriString(url2), path2, gitService); + Assert.That(a, Is.EqualTo(b)); + Assert.False(a == b); + Assert.That(a.GetHashCode(), Is.EqualTo(b.GetHashCode())); + } + + [TestCase(1, "a name", "https://github.com/github/VisualStudio", 1, "a name", "https://github.com/github/VisualStudio")] + public void SameContentEqualsTrue2(long id1, string name1, string url1, long id2, string name2, string url2) + { + var account = Substitute.For<IAccount>(); + var a = new RemoteRepositoryModel(id1, name1, new UriString(url1), false, false, account, null); + var b = new RemoteRepositoryModel(id2, name2, new UriString(url2), false, false, account, null); + Assert.That(a, Is.EqualTo(b)); + Assert.False(a == b); + Assert.That(a.GetHashCode(), Is.EqualTo(b.GetHashCode())); + } + + [TestCase(1, "a name1", "https://github.com/github/VisualStudio", 2, "a name", "https://github.com/github/VisualStudio")] + public void DifferentContentEqualsFalse(long id1, string name1, string url1, long id2, string name2, string url2) + { + var account = Substitute.For<IAccount>(); + var a = new RemoteRepositoryModel(id1, name1, new UriString(url1), false, false, account, null); + var b = new RemoteRepositoryModel(id2, name2, new UriString(url2), false, false, account, null); + Assert.That(a, Is.Not.EqualTo(b)); + Assert.False(a == b); + Assert.That(a.GetHashCode(), Is.Not.EqualTo(b.GetHashCode())); + } + } + + //[Collection("PackageServiceProvider global data tests")] + public class PathConstructorTests : TestBaseClass + { + [Test] + public void NoRemoteUrl() + { + using (var temp = new TempDirectory()) + { + var gitService = Substitute.For<IGitService>(); + var repo = Substitute.For<IRepository>(); + var path = temp.Directory.CreateSubdirectory("repo-name"); + gitService.GetUri(path.FullName).Returns((UriString)null); + var model = new LocalRepositoryModel(path.FullName, gitService); + Assert.That("repo-name", Is.EqualTo(model.Name)); + } + } + + [Test] + public void WithRemoteUrl() + { + using (var temp = new TempDirectory()) + { + var gitService = Substitute.For<IGitService>(); + var repo = Substitute.For<IRepository>(); + var path = temp.Directory.CreateSubdirectory("repo-name"); + gitService.GetUri(path.FullName).Returns(new UriString("https://github.com/user/repo-name")); + var model = new LocalRepositoryModel(path.FullName, gitService); + Assert.That("repo-name", Is.EqualTo(model.Name)); + Assert.That("user", Is.EqualTo(model.Owner)); + } + } + } + + public class HostAddressTests : TestBaseClass + { + [TestCase("https://github.com/owner/repo")] + [TestCase("https://anotherurl.com/foo/bar")] + public void SameContentEqualsTrue(string url) + { + var a = HostAddress.Create(url); + var b = HostAddress.Create(url); + Assert.That(a, Is.EqualTo(b)); + Assert.That(a.GetHashCode(), Is.EqualTo(b.GetHashCode())); + } + } +} diff --git a/test/GitHub.App.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.App.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1e26caae28 --- /dev/null +++ b/test/GitHub.App.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.App.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.App.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3525d819-6aec-4879-89fb-56b41f026571")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/UnitTests/GitHub.App/Services/AvatarProviderTests.cs b/test/GitHub.App.UnitTests/Services/AvatarProviderTests.cs similarity index 87% rename from src/UnitTests/GitHub.App/Services/AvatarProviderTests.cs rename to test/GitHub.App.UnitTests/Services/AvatarProviderTests.cs index 182d03f2bd..ccae7a7092 100644 --- a/src/UnitTests/GitHub.App/Services/AvatarProviderTests.cs +++ b/test/GitHub.App.UnitTests/Services/AvatarProviderTests.cs @@ -11,14 +11,14 @@ using GitHub.Services; using NSubstitute; using UnitTests.Helpers; -using Xunit; +using NUnit.Framework; public class AvatarProviderTests { public class TheDefaultOrgBitmapImageProperty : TestBaseClass { - [STAFact] - public async Task CanBeAccessedFromMultipleThreads() + [Test] + public async Task CanBeAccessedFromMultipleThreadsAsync() { var blobCache = new InMemoryBlobCache(); var sharedCache = Substitute.For<ISharedCache>(); @@ -35,15 +35,15 @@ public async Task CanBeAccessedFromMultipleThreads() return avatarProvider.DefaultOrgBitmapImage.ToString(); }); - Assert.Equal(expected, actual); - Assert.NotEqual(mainThreadId, otherThreadId); + Assert.That(expected, Is.EqualTo(actual)); + Assert.That(mainThreadId, Is.Not.EqualTo(otherThreadId)); } } public class TheDefaultUserBitmapImageProperty : TestBaseClass { - [STAFact] - public async Task CanBeAccessedFromMultipleThreads() + [Test] + public async Task CanBeAccessedFromMultipleThreadsAsync() { var blobCache = new InMemoryBlobCache(); var sharedCache = Substitute.For<ISharedCache>(); @@ -60,15 +60,15 @@ public async Task CanBeAccessedFromMultipleThreads() return avatarProvider.DefaultUserBitmapImage.ToString(); }); - Assert.Equal(expected, actual); - Assert.NotEqual(mainThreadId, otherThreadId); + Assert.That(expected, Is.EqualTo(actual)); + Assert.That(mainThreadId, Is.Not.EqualTo(otherThreadId)); } } public class TheGetAvatarMethod : TestBaseClass { - [STAFact] - public async Task GetsAvatarFromCache() + [Test] + public async Task GetsAvatarFromCacheAsync() { var expectedImage = AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_org_avatar.png"); var avatarUrl = new Uri("https://avatars.githubusercontent.com/u/e?email=me@test.com&s=140"); @@ -86,8 +86,8 @@ public async Task GetsAvatarFromCache() AssertSameImage(expectedImage, retrieved); } - [STAFact] - public async Task RetrievesGitHubAvatar() + [Test] + public async Task RetrievesGitHubAvatarAsync() { var expectedImage = AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_org_avatar.png"); var avatarUrl = new Uri("https://avatars.githubusercontent.com/u/e?email=me@test.com&s=140"); @@ -108,7 +108,7 @@ public async Task RetrievesGitHubAvatar() static void AssertSameImage(byte[] expected, BitmapSource imageSource) { var actualBytes = ImageCache.GetBytesFromBitmapImage(imageSource); - Assert.Equal(expected, actualBytes); + Assert.That(expected, Is.EqualTo(actualBytes)); } static void AssertSameImage(BitmapSource expected, BitmapSource actual) @@ -120,13 +120,13 @@ static void AssertSameImage(BitmapSource expected, BitmapSource actual) // TODO: This is probably not correct, but we've manually verified the code we're testing. // We need a way to test similarity of images. const int bytesToCompare = 19; - Assert.Equal(expectedBytes.Take(bytesToCompare), actualBytes.Take(bytesToCompare)); + Assert.That(expectedBytes.Take(bytesToCompare), Is.EqualTo(actualBytes.Take(bytesToCompare))); } } public class TheInvalidateAvatarMethod : TestBaseClass { - [Fact] + [Test] public void DoesNotThrowOnNullUserOrAvatarUrl() { var blobStore = Substitute.For<IBlobCache>(); @@ -143,7 +143,7 @@ public void DoesNotThrowOnNullUserOrAvatarUrl() public class TheGetBytesFromBitmapImageMethod : TestBaseClass { - [Fact] + [Test] public void GetsBytesFromImage() { var image = AvatarProvider.CreateBitmapImage("pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"); @@ -157,7 +157,7 @@ public void GetsBytesFromImage() bytes = ms.ToArray(); } - Assert.NotNull(bytes); + Assert.That(bytes, Is.Not.Null); Assert.True(bytes.Length > 256); } } diff --git a/test/GitHub.App.UnitTests/Services/GitClientTests.cs b/test/GitHub.App.UnitTests/Services/GitClientTests.cs new file mode 100644 index 0000000000..1efe891a1a --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/GitClientTests.cs @@ -0,0 +1,398 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Services; +using LibGit2Sharp; +using NSubstitute; +using NUnit.Framework; +using GitHub.Primitives; +using System.Collections.Generic; + +public class GitClientTests +{ + public class TheIsModifiedMethod + { + [TestCase(FileStatus.Unaltered, false)] + [TestCase(FileStatus.ModifiedInIndex, true)] + [TestCase(FileStatus.ModifiedInWorkdir, true)] + public async Task RetrieveStatusAsync(FileStatus fileStatus, bool expect) + { + var path = "path"; + var repo = Substitute.For<IRepository>(); + repo.RetrieveStatus(path).Returns(fileStatus); + repo.Head.Returns(Substitute.For<Branch>()); + var treeEntry = null as TreeEntry; + repo.Head[path].Returns(treeEntry); + var gitClient = CreateGitClient(); + + var modified = await gitClient.IsModified(repo, path, null); + + Assert.That(expect, Is.EqualTo(modified)); + } + + [Test] + public async Task TreeEntry_Null_False_Async() + { + var path = "path"; + var repo = Substitute.For<IRepository>(); + repo.RetrieveStatus(path).Returns(FileStatus.Unaltered); + repo.Head.Returns(Substitute.For<Branch>()); + var treeEntry = null as TreeEntry; + repo.Head[path].Returns(treeEntry); + var gitClient = CreateGitClient(); + + var modified = await gitClient.IsModified(repo, path, null); + + Assert.False(modified); + } + + [Test] + public async Task TreeEntryTarget_GitLink_False_Async() + { + var path = "path"; + var repo = Substitute.For<IRepository>(); + repo.RetrieveStatus(path).Returns(FileStatus.Unaltered); + repo.Head.Returns(Substitute.For<Branch>()); + var treeEntry = Substitute.For<TreeEntry>(); + treeEntry.TargetType.Returns(TreeEntryTargetType.GitLink); + treeEntry.Target.Returns(Substitute.For<GitLink>()); + repo.Head[path].Returns(treeEntry); + var gitClient = CreateGitClient(); + + var modified = await gitClient.IsModified(repo, path, null); + + Assert.False(modified); + } + + [TestCase(0, 0, false)] + [TestCase(1, 0, true)] + [TestCase(0, 1, true)] + [TestCase(1, 1, true)] + public async Task ContentChangesAsync(int linesAdded, int linesDeleted, bool expected) + { + var path = "path"; + var repo = Substitute.For<IRepository>(); + repo.RetrieveStatus(path).Returns(FileStatus.Unaltered); + repo.Head.Returns(Substitute.For<Branch>()); + var treeEntry = Substitute.For<TreeEntry>(); + treeEntry.TargetType.Returns(TreeEntryTargetType.Blob); + treeEntry.Target.Returns(Substitute.For<Blob>()); + repo.Head[path].Returns(treeEntry); + var changes = Substitute.For<ContentChanges>(); + changes.LinesAdded.Returns(linesAdded); + changes.LinesDeleted.Returns(linesDeleted); + repo.Diff.Compare(null, null).ReturnsForAnyArgs(changes); + var gitClient = CreateGitClient(); + + var modified = await gitClient.IsModified(repo, path, null); + + Assert.That(expected, Is.EqualTo(modified)); + } + } + + public class TheIsHeadPushedMethod : TestBaseClass + { + [TestCase(0, true)] + [TestCase(2, false)] + [TestCase(null, false)] + public async Task IsHeadPushedAsync(int? aheadBy, bool expected) + { + var gitClient = CreateGitClient(); + var repository = MockTrackedBranchRepository(aheadBy); + + var isHeadPushed = await gitClient.IsHeadPushed(repository); + + Assert.That(expected, Is.EqualTo(isHeadPushed)); + } + + static IRepository MockTrackedBranchRepository(int? aheadBy) + { + var headBranch = Substitute.For<Branch>(); + var trackingDetails = Substitute.For<BranchTrackingDetails>(); + trackingDetails.AheadBy.Returns(aheadBy); + headBranch.TrackingDetails.Returns(trackingDetails); + var repository = Substitute.For<IRepository>(); + repository.Head.Returns(headBranch); + return repository; + } + } + + public class ThePushMethod : TestBaseClass + { + [Test] + public async Task PushesToDefaultOriginAsync() + { + var origin = Substitute.For<Remote>(); + var head = Substitute.For<Branch>(); + head.Commits.Returns(new FakeCommitLog { Substitute.For<Commit>() }); + var repository = Substitute.For<IRepository>(); + repository.Head.Returns(head); + repository.Network.Remotes["origin"].Returns(origin); + var gitClient = CreateGitClient(); + + await gitClient.Push(repository, "master", "origin"); + + repository.Network.Received().Push(origin, "HEAD", @"refs/heads/master", Arg.Any<PushOptions>()); + } + + [Test] + public async Task DoesNotPushEmptyRepositoryAsync() + { + var repository = Substitute.For<IRepository>(); + var gitClient = CreateGitClient(); + + await gitClient.Push(repository, "master", "origin"); + + repository.Network.DidNotReceive() + .Push(Args.LibgGit2Remote, Args.String, Args.String); + } + } + + public class TheSetRemoteMethod : TestBaseClass + { + [Test] + public async Task SetsTheConfigToTheRemoteBranchAsync() + { + var config = Substitute.For<Configuration>(); + var repository = Substitute.For<IRepository>(); + repository.Config.Returns(config); + var gitClient = CreateGitClient(); + + await gitClient.SetRemote(repository, "origin", new Uri("https://github.com/foo/bar")); + + config.Received().Set<string>("remote.origin.url", "https://github.com/foo/bar"); + config.Received().Set<string>("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"); + } + } + + public class TheSetTrackingMethod : TestBaseClass + { + [Test] + public async Task SetsTheRemoteTrackingBranchAsync() + { + var config = Substitute.For<Configuration>(); + var origin = Substitute.For<Remote>(); + var branches = Substitute.For<BranchCollection>(); + var repository = Substitute.For<IRepository>(); + repository.Config.Returns(config); + repository.Branches.Returns(branches); + repository.Network.Remotes["origin"].Returns(origin); + var localBranch = Substitute.For<Branch>(); + var remoteBranch = Substitute.For<Branch>(); ; + branches["refs/heads/master"].Returns(localBranch); + branches["refs/remotes/origin/master"].Returns(remoteBranch); + + var gitClient = CreateGitClient(); + + await gitClient.SetTrackingBranch(repository, "master", "origin"); + + branches.Received().Update(localBranch, Arg.Any<Action<BranchUpdater>>()); + } + } + + public class TheFetchMethod : TestBaseClass + { + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo")] + [TestCase("git@github.com:github/VisualStudioBuildScripts", "https://github.com/github/VisualStudioBuildScripts")] + public async Task FetchUsingHttpsAsync(string repoUrl, string expectFetchUrl) + { + var repo = Substitute.For<IRepository>(); + var uri = new UriString(repoUrl); + var refSpec = "refSpec"; + var gitClient = CreateGitClient(); + var expectUrl = UriString.ToUriString(uri.ToRepositoryUrl()); + + await gitClient.Fetch(repo, uri, refSpec); + + repo.Network.Remotes.Received(1).Add(Arg.Any<string>(), expectFetchUrl); + } + + [TestCase("https://github.com/owner/repo", "origin", "https://github.com/owner/repo", null)] + [TestCase("https://github.com/fetch/repo", "origin", "https://github.com/origin/repo", "https://github.com/fetch/repo")] + [TestCase("git@github.com:owner/repo", "origin", "git@github.com:owner/repo", "https://github.com/owner/repo", Description = "Only use http style urls")] + [TestCase("https://github.com/jcansdale/repo", "jcansdale", "https://github.com/jcansdale/repo", null, Description = "Use existing remote")] + [TestCase("https://github.com/jcansdale/repo.git", "jcansdale", "https://github.com/jcansdale/repo", null, Description = "Ignore trailing .git")] + [TestCase("https://github.com/JCANSDALE/REPO", "jcansdale", "https://github.com/jcansdale/repo", null, Description = "Ignore different case")] + public async Task UseExistingRemoteWhenPossible(string fetchUrl, string remoteName, string remoteUrl, string addUrl = null) + { + var repo = CreateRepository(remoteName, remoteUrl); + var fetchUri = new UriString(fetchUrl); + var refSpec = "refSpec"; + var gitClient = CreateGitClient(); + + await gitClient.Fetch(repo, fetchUri, refSpec); + + if (addUrl != null) + { + repo.Network.Remotes.Received().Add(Arg.Any<string>(), addUrl); + } + else + { + repo.Network.Remotes.DidNotReceiveWithAnyArgs().Add(null, null); + } + } + + [TestCase("https://github.com/upstream_owner/repo", "origin", "https://github.com/origin_owner/repo", + "upstream_owner", "https://github.com/upstream_owner/repo")] + public async Task CreateRemoteWithNameOfOwner(string fetchUrl, string remoteName, string remoteUrl, + string expectRemoteName, string expectRemoteUrl) + { + var repo = CreateRepository(remoteName, remoteUrl); + var fetchUri = new UriString(fetchUrl); + var refSpec = "refSpec"; + var gitClient = CreateGitClient(); + + await gitClient.Fetch(repo, fetchUri, refSpec); + + repo.Network.Remotes.Received(1).Add(expectRemoteName, expectRemoteUrl); + repo.Network.Remotes.Received(0).Remove(Arg.Any<string>()); + } + + [TestCase("https://github.com/same_name/repo", "same_name", "https://github.com/different_name/repo", + "same_name", "https://github.com/same_name/repo")] + public async Task UseTemporaryRemoteWhenSameRemoteWithDifferentUrlExists(string fetchUrl, string remoteName, string remoteUrl, + string expectRemoteName, string expectRemoteUrl) + { + var repo = CreateRepository(remoteName, remoteUrl); + var fetchUri = new UriString(fetchUrl); + var refSpec = "refSpec"; + var gitClient = CreateGitClient(); + + await gitClient.Fetch(repo, fetchUri, refSpec); + + repo.Network.Remotes.Received(0).Add(expectRemoteName, expectRemoteUrl); + repo.Network.Remotes.Received(1).Add(Arg.Any<string>(), expectRemoteUrl); + repo.Network.Remotes.Received(1).Remove(Arg.Any<string>()); + } + + [TestCase("https://github.com/owner/repo", "origin", "https://github.com/owner/repo", "origin")] + [TestCase("https://github.com/owner/repo", "not_origin", "https://github.com/owner/repo", "not_origin")] + public async Task FetchFromExistingRemote(string fetchUrl, string remoteName, string remoteUrl, string expectRemoteName) + { + var repo = CreateRepository(remoteName, remoteUrl); + var fetchUri = new UriString(fetchUrl); + var refSpec = "refSpec"; + var gitClient = CreateGitClient(); + + await gitClient.Fetch(repo, fetchUri, refSpec); + + var remote = repo.Network.Remotes[expectRemoteName]; +#pragma warning disable 0618 // TODO: Replace `Network.Fetch` with `Commands.Fetch`. + repo.Network.Received(1).Fetch(remote, Arg.Any<string[]>(), Arg.Any<FetchOptions>()); +#pragma warning restore 0618 + } + + static IRepository CreateRepository(string remoteName, string remoteUrl) + { + var remote = Substitute.For<Remote>(); + remote.Name.Returns(remoteName); + remote.Url.Returns(remoteUrl); + var remotes = new List<Remote> { remote }; + var repo = Substitute.For<IRepository>(); + repo.Network.Remotes[remoteName].Returns(remote); + repo.Network.Remotes.GetEnumerator().Returns(_ => remotes.GetEnumerator()); + return repo; + } + } + + public class TheGetPullRequestMergeBaseMethod : TestBaseClass + { + [Test] + public async Task LocalBaseHeadAndMergeBase_DontFetchAsync() + { + var targetCloneUrl = new UriString("https://github.com/owner/repo"); + var baseSha = "baseSha"; + var headSha = "headSha"; + var expectMergeBaseSha = "mergeBaseSha"; + var baseRef = "master"; + var pullNumber = 0; + var repo = MockRepo(baseSha, headSha, expectMergeBaseSha); + var gitClient = CreateGitClient(); + + var mergeBaseSha = await gitClient.GetPullRequestMergeBase(repo, targetCloneUrl, baseSha, headSha, baseRef, pullNumber); + +#pragma warning disable 618 // Type or member is obsolete + repo.Network.DidNotReceiveWithAnyArgs().Fetch(null as Remote, null, null as FetchOptions); +#pragma warning restore 618 // Type or member is obsolete + Assert.That(expectMergeBaseSha, Is.EqualTo(mergeBaseSha)); + } + + [TestCase("baseSha", "headSha", "mergeBaseSha", 0)] + [TestCase(null, "headSha", "mergeBaseSha", 1)] + [TestCase("baseSha", null, "mergeBaseSha", 1)] + [TestCase("baseSha", "headSha", null, 0)] + public async Task WhenToFetchAsync(string baseSha, string headSha, string mergeBaseSha, int receivedFetch) + { + var targetCloneUri = new UriString("https://github.com/owner/repo"); + var baseRef = "master"; + var pullNumber = 0; + var repo = MockRepo(baseSha, headSha, mergeBaseSha); + var remote = Substitute.For<Remote>(); + repo.Network.Remotes.Add(null, null).ReturnsForAnyArgs(remote); + var gitClient = CreateGitClient(); + + try + { + await gitClient.GetPullRequestMergeBase(repo, targetCloneUri, baseSha, headSha, baseRef, pullNumber); + } + catch (NotFoundException) { /* We're interested in calls to Fetch even if it throws */ } + +#pragma warning disable 618 // Type or member is obsolete + repo.Network.Received(receivedFetch).Fetch(Arg.Any<Remote>(), Arg.Any<string[]>(), Arg.Any<FetchOptions>()); +#pragma warning restore 618 // Type or member is obsolete + } + + [TestCase("baseSha", null, "mergeBaseSha", "baseRef", 777, "refs/pull/777/head")] + [TestCase(null, "headSha", "mergeBaseSha", "baseRef", 777, "baseRef")] + + // PR base might not exist, so we must fetch `refs/pull/<PR>/head` first. + [TestCase(null, null, "mergeBaseSha", "baseRef", 777, "refs/pull/777/head")] + public async Task WhatToFetchAsync(string baseSha, string headSha, string mergeBaseSha, string baseRef, int pullNumber, + string expectRefSpec) + { + var repo = MockRepo(baseSha, headSha, mergeBaseSha); + var targetCloneUri = new UriString("https://github.com/owner/repo"); + var gitClient = CreateGitClient(); + + try + { + await gitClient.GetPullRequestMergeBase(repo, targetCloneUri, baseSha, headSha, baseRef, pullNumber); + } + catch (NotFoundException) { /* We're interested in calls to Fetch even if it throws */ } + +#pragma warning disable 618 // Type or member is obsolete + repo.Network.Received(1).Fetch(Arg.Any<Remote>(), Arg.Is<IEnumerable<string>>(x => x.Contains(expectRefSpec)), Arg.Any<FetchOptions>()); +#pragma warning restore 618 // Type or member is obsolete + } + + static IRepository MockRepo(string baseSha, string headSha, string mergeBaseSha) + { + var repo = Substitute.For<IRepository>(); + var baseCommit = Substitute.For<Commit>(); + var headCommit = Substitute.For<Commit>(); + var mergeBaseCommit = Substitute.For<Commit>(); + mergeBaseCommit.Sha.Returns(mergeBaseSha); + + if (baseSha != null) + { + repo.Lookup<Commit>(baseSha).Returns(baseSha != null ? baseCommit : null); + } + + if (headSha != null) + { + repo.Lookup<Commit>(headSha).Returns(headSha != null ? headCommit : null); + } + + repo.ObjectDatabase.FindMergeBase(baseCommit, headCommit).Returns(mergeBaseCommit); + return repo; + } + } + + static GitClient CreateGitClient() + { + return new GitClient( + Substitute.For<IGitHubCredentialProvider>(), + Substitute.For<IGitService>()); + } +} diff --git a/test/GitHub.App.UnitTests/Services/GitHubContextServiceTests.cs b/test/GitHub.App.UnitTests/Services/GitHubContextServiceTests.cs new file mode 100644 index 0000000000..01fc6ba496 --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/GitHubContextServiceTests.cs @@ -0,0 +1,505 @@ +using System; +using System.Linq; +using GitHub.Exports; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; +using LibGit2Sharp; + +public class GitHubContextServiceTests +{ + public class TheFindContextFromUrlMethod + { + [TestCase("https://github.com", null)] + [TestCase("https://github.com/github", "github")] + [TestCase("https://github.com/github/VisualStudio", "github")] + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", "github")] + public void Owner(string url, string expectOwner) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.Owner, Is.EqualTo(expectOwner)); + } + + [TestCase("https://github.com", null)] + [TestCase("https://github.com/github", null)] + [TestCase("https://github.com/github/VisualStudio", "VisualStudio")] + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", "VisualStudio")] + public void RepositoryName(string url, string expectRepositoryName) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.RepositoryName, Is.EqualTo(expectRepositoryName)); + } + + [TestCase("https://github.com", "github.com")] + [TestCase("https://github.com/github", "github.com")] + [TestCase("https://github.com/github/VisualStudio", "github.com")] + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", "github.com")] + public void Host(string url, string expectHost) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.Host, Is.EqualTo(expectHost)); + } + + [TestCase("https://github.com", null)] + [TestCase("https://github.com/github", null)] + [TestCase("https://github.com/github/VisualStudio", null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", null)] + [TestCase("https://github.com/github/VisualStudio/pull/1763", 1763)] + [TestCase("https://github.com/github/VisualStudio/pull/1763/commits", 1763)] + [TestCase("https://github.com/github/VisualStudio/pull/1763/files#diff-7384294e6c288e13bad0293bae232754R1", 1763)] + [TestCase("https://github.com/github/VisualStudio/pull/NaN", null)] + public void PullRequest(string url, int? expectPullRequest) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context?.PullRequest, Is.EqualTo(expectPullRequest)); + } + + [TestCase("https://github.com/github/VisualStudio/blob/master", null, null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/foo.cs", "master", "foo.cs")] + [TestCase("https://github.com/github/VisualStudio/blob/master/path/foo.cs", "master/path", "foo.cs")] + [TestCase("https://github.com/github/VisualStudio/blob/ee863ce265fc6217f589e66766125fed1b5b8256/foo.cs", "ee863ce265fc6217f589e66766125fed1b5b8256", "foo.cs")] + [TestCase("https://github.com/github/VisualStudio/blob/ee863ce265fc6217f589e66766125fed1b5b8256/path/foo.cs", "ee863ce265fc6217f589e66766125fed1b5b8256/path", "foo.cs")] + [TestCase("https://github.com/github/VisualStudio/blob/master/bar.cs#stuff", "master", "bar.cs")] + public void Blob(string url, string expectTreeishPath, string expectBlobName) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.TreeishPath, Is.EqualTo(expectTreeishPath)); + Assert.That(context.BlobName, Is.EqualTo(expectBlobName)); + } + + [TestCase("https://github.com", null)] + [TestCase("https://github.com/github", null)] + [TestCase("https://github.com/github/VisualStudio", null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md#notices", null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/src/GitHub.VisualStudio/GitHubPackage.cs#L38", 38)] + [TestCase("https://github.com/github/VisualStudio/blob/0d264d50c57d701fa62d202f481075a6c6dbdce8/src/Code.cs#L86", 86)] + public void Line(string url, int? expectLine) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.Line, Is.EqualTo(expectLine)); + } + + [TestCase("https://github.com/github/VisualStudio", null, null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/Code.cs#L115", 115, null)] + [TestCase("https://github.com/github/VisualStudio/blob/master/Code.cs#L115-L116", 115, 116)] + public void LineEnd(string url, int? expectLine, int? expectLineEnd) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.Line, Is.EqualTo(expectLine)); + Assert.That(context.LineEnd, Is.EqualTo(expectLineEnd)); + } + + [TestCase("foo", true)] + [TestCase("ssh://git@github.com:443/benstraub/libgit2", true)] + [TestCase("https://github.com/github/VisualStudio", false)] + public void IsNull(string url, bool expectNull) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context, expectNull ? Is.Null : Is.Not.Null); + } + + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", "https://github.com/github/VisualStudio/blob/master/README.md")] + public void Url_EqualTo(string url, string expectUrl) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.Url?.ToString(), Is.EqualTo(expectUrl)); + } + + [TestCase("https://github.com/github/VisualStudio/blob/master/README.md", LinkType.Blob)] + [TestCase("https://github.com/github/VisualStudio/unknown/master/README.md", LinkType.Unknown)] + public void LinkType_EqualTo(string url, LinkType expectLinkType) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromUrl(url); + + Assert.That(context.LinkType, Is.EqualTo(expectLinkType)); + } + } + + public class TheToMethod + { + [Test] + public void DefaultGitHubDotCom() + { + var context = new GitHubContext { Host = "github.com", Owner = "github", RepositoryName = "VisualStudio" }; + var target = CreateGitHubContextService(); + + var uri = target.ToRepositoryUrl(context); + + Assert.That(uri, Is.EqualTo(new Uri("https://github.com/github/VisualStudio"))); + } + } + + public class TheFindContextFromWindowTitleMethod + { + [TestCase("github/0123456789: Description - Google Chrome", "0123456789")] + [TestCase("github/abcdefghijklmnopqrstuvwxyz: Description - Google Chrome", "abcdefghijklmnopqrstuvwxyz")] + [TestCase("github/ABCDEFGHIJKLMNOPQRSTUVWXYZ: Description - Google Chrome", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [TestCase("github/_: Description - Google Chrome", "_")] + [TestCase("github/.: Description - Google Chrome", ".")] + [TestCase("github/-: Description - Google Chrome", "-")] + [TestCase("github/$: Description - Google Chrome", null, Description = "Must contain only letters, numbers, `_`, `.` or `-`")] + public void RepositoryName(string windowTitle, string expectRepositoryName) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context?.RepositoryName, Is.EqualTo(expectRepositoryName)); + } + + [TestCase("0123456789/Repository: Description - Google Chrome", "0123456789")] + [TestCase("abcdefghijklmnopqrstuvwxyz/Repository: Description - Google Chrome", "abcdefghijklmnopqrstuvwxyz")] + [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ/Repository: Description - Google Chrome", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] + [TestCase("a_/Repository: Description - Google Chrome", "a_")] + [TestCase("a-/Repository: Description - Google Chrome", "a-")] + [TestCase("_/Repository: Description - Google Chrome", null, Description = "Must start with letter or number")] + [TestCase("-/Repository: Description - Google Chrome", null, Description = "Must start with letter or number")] + public void Owner(string windowTitle, string expectOwner) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context?.Owner, Is.EqualTo(expectOwner)); + } + + // They can include slash / for hierarchical (directory) grouping + [TestCase("a/b", "a/b", Description = "")] + [TestCase("aaa/bbb", "aaa/bbb", Description = "")] + + // They cannot have space, tilde ~, caret ^, or colon : anywhere. + [TestCase("a b", null)] + [TestCase("a~b", null)] + [TestCase("a^b", null)] + [TestCase("a:b", null)] + + // They cannot have question-mark ?, asterisk *, or open bracket [ anywhere. + [TestCase("a?b", null)] + [TestCase("a*b", null)] + [TestCase("a[b", null)] + + [TestCase(@"a\b", null, Description = @"They cannot contain a \")] + + // Simple case + [TestCase("master", "master")] + + // There are many symbols they can contain + [TestCase("!@#$%&()_+-=", "!@#$%&()_+-=")] + + [TestCase("/a", null, Description = "They cannot begin a slash")] + [TestCase("a/", null, Description = "They cannot end with a slash")] + [TestCase("../b", null, Description = "no slash-separated component can begin with a dot")] + [TestCase(".a/b", null, Description = "no slash-separated component can begin with a dot")] + [TestCase("a/.b", null, Description = "no slash-separated component can begin with a dot")] + + // There are some checks we aren't doing, see https://git-scm.com/docs/git-check-ref-format + // They cannot have ASCII control characters(i.e.bytes whose values are lower than \040, or \177 DEL) + // [TestCase("a/b.lock", null, Description = "or end with the sequence.lock")] + // [TestCase("a..b", null, Description = "They cannot have two consecutive dots..anywhere")] + // [TestCase("a.", null, Description = "They cannot end with a dot")] + // [TestCase("@{a", null, Description = "They cannot contain a sequence @{")] + // [TestCase("@", null, Description = "They cannot be the single character @")] + public void Branch(string branch, string expectBranch) + { + var windowTitle = $"VisualStudio/src/GitHub.VisualStudio/Resources/icons at {branch} · github/VisualStudio - Google Chrome"; + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context?.BranchName, Is.EqualTo(expectBranch)); + } + + [TestCase("github/VisualStudio: GitHub Extension for Visual Studio - Google Chrome", "github", "VisualStudio", null)] + [TestCase("Branches · github/VisualStudio - Google Chrome", "github", "VisualStudio", null)] + [TestCase("github/VisualStudio at build/appveyor-fixes - Google Chrome", "github", "VisualStudio", "build/appveyor-fixes")] + [TestCase("[spike] Open from GitHub URL by jcansdale · Pull Request #1763 · github/VisualStudio - Google Chrome", "github", "VisualStudio", null)] + [TestCase("Consider adding C# code style preferences to editorconfig · Issue #1750 · github/VisualStudio - Google Chrome", "github", "VisualStudio", null)] + [TestCase("VisualStudio/mark_github.xaml at master · github/VisualStudio - Google Chrome", "github", "VisualStudio", "master")] + [TestCase("VisualStudio/src/GitHub.VisualStudio/Resources/icons at master · github/VisualStudio - Google Chrome", "github", "VisualStudio", "master")] + [TestCase("VisualStudio/GitHub.Exports.csproj at 89484dc25a3a475d3253afdc3bd3ddd6c6999c3b · github/VisualStudio - Google Chrome", "github", "VisualStudio", "89484dc25a3a475d3253afdc3bd3ddd6c6999c3b")] + public void OwnerRepositoryBranch(string windowTitle, string expectOwner, string expectRepositoryName, string expectBranch) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context.Owner, Is.EqualTo(expectOwner)); + Assert.That(context.RepositoryName, Is.EqualTo(expectRepositoryName)); + Assert.That(context.BranchName, Is.EqualTo(expectBranch)); + } + + [TestCase("github/VisualStudio at build/appveyor-fixes - Google Chrome", "github", "VisualStudio", "build/appveyor-fixes", Description = "Chrome")] + [TestCase("GitHub - github/VisualStudio at refactor/pr-list - Mozilla Firefox", "github", "VisualStudio", "refactor/pr-list", Description = "Firefox")] + public void TreeBranch(string windowTitle, string expectOwner, string expectRepositoryName, string expectBranch) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context.Owner, Is.EqualTo(expectOwner)); + Assert.That(context.RepositoryName, Is.EqualTo(expectRepositoryName)); + Assert.That(context.BranchName, Is.EqualTo(expectBranch)); + } + + [TestCase("Branches · github/VisualStudio - Google Chrome", "github", "VisualStudio", Description = "Chrome")] + [TestCase("Branches · github/VisualStudio · GitHub - Mozilla Firefox", "github", "VisualStudio", Description = "Firefox")] + public void Branches(string windowTitle, string expectOwner, string expectRepositoryName) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context.Owner, Is.EqualTo(expectOwner)); + Assert.That(context.RepositoryName, Is.EqualTo(expectRepositoryName)); + } + + [TestCase("Description · Pull Request #1763 · github/VisualStudio - Google Chrome", 1763)] + [TestCase("Description · Pull Request #1763 · github/VisualStudio · GitHub - Mozilla Firefox", 1763, Description = "Firefox")] + public void PullRequest(string windowTitle, int expectPullRequest) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context.PullRequest, Is.EqualTo(expectPullRequest)); + } + + [TestCase("Consider adding C# code style preferences to editorconfig · Issue #1750 · github/VisualStudio - Google Chrome", 1750)] + [TestCase("Scrape browser titles · Issue #4 · jcansdale/VisualStudio · GitHub - Mozilla Firefox", 4, Description = "Firefox")] + public void Issue(string windowTitle, int expectIssue) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context.Issue, Is.EqualTo(expectIssue)); + } + + [TestCase("VisualStudio/mark_github.xaml at master · github/VisualStudio - Google Chrome", "mark_github.xaml", "master")] + [TestCase("VisualStudio/src/GitHub.VisualStudio/Resources/icons at master · github/VisualStudio - Google Chrome", null, "master")] + [TestCase("VisualStudio/src at master · github/VisualStudio - Google Chrome", "src", "master", Description = "Can't differentiate between single level tree and blob")] + [TestCase("VisualStudio/README.md at master · jcansdale/VisualStudio · GitHub - Mozilla Firefox", "README.md", "master", Description = "Firefox")] + public void Blob(string windowTitle, string expectBlobName, string expectBranchName) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context?.BlobName, Is.EqualTo(expectBlobName)); + Assert.That(context?.BranchName, Is.EqualTo(expectBranchName)); + } + + [TestCase("VisualStudio/src/GitHub.VisualStudio/Resources/icons at master · github/VisualStudio - Google Chrome", "master/src/GitHub.VisualStudio/Resources/icons", "master")] + public void Tree(string windowTitle, string expectTreeish, string expectBranch) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context?.TreeishPath, Is.EqualTo(expectTreeish)); + Assert.That(context?.BranchName, Is.EqualTo(expectBranch)); + } + + [TestCase("jcansdale/VisualStudio: GitHub Extension for Visual Studio - Google Chrome", "jcansdale", "VisualStudio", Description = "Chrome")] + [TestCase("GitHub - jcansdale/VisualStudio: GitHub Extension for Visual Studio - Mozilla Firefox", "jcansdale", "VisualStudio", Description = "Firefox")] + [TestCase("jcansdale/GhostAssemblies - Google Chrome", "jcansdale", "GhostAssemblies", Description = "No description, Chrome")] + [TestCase("GitHub - jcansdale/GhostAssemblies - Mozilla Firefox", "jcansdale", "GhostAssemblies", Description = "No description, Firefox")] + public void RepositoryHome(string windowTitle, string expectOwner, string expectRepositoryName) + { + var target = CreateGitHubContextService(); + + var context = target.FindContextFromWindowTitle(windowTitle); + + Assert.That(context?.Owner, Is.EqualTo(expectOwner)); + Assert.That(context?.RepositoryName, Is.EqualTo(expectRepositoryName)); + } + } + + public class TheResolveBlobMethod + { + const string CommitSha = "36d6b0bb6e319337180d523281c42d9611744e66"; + + [TestCase("https://github.com/github/VisualStudio/blob/master/foo.cs", "refs/remotes/origin/master", "refs/remotes/origin/master:foo.cs", "refs/remotes/origin/master", "foo.cs", CommitSha)] + [TestCase("https://github.com/github/VisualStudio/blob/master/src/foo.cs", "refs/remotes/origin/master", "refs/remotes/origin/master:src/foo.cs", "refs/remotes/origin/master", "src/foo.cs", CommitSha)] + [TestCase("https://github.com/github/VisualStudio/blob/branch-name/src/foo.cs", "refs/remotes/origin/branch-name", "refs/remotes/origin/branch-name:src/foo.cs", "refs/remotes/origin/branch-name", "src/foo.cs", CommitSha)] + [TestCase("https://github.com/github/VisualStudio/blob/fixes/666-bug/src/foo.cs", "refs/remotes/origin/fixes/666-bug", "refs/remotes/origin/fixes/666-bug:src/foo.cs", "refs/remotes/origin/fixes/666-bug", "src/foo.cs", CommitSha)] + [TestCase("https://github.com/github/VisualStudio/blob/fixes/666-bug/A/B/foo.cs", "refs/remotes/origin/fixes/666-bug", "refs/remotes/origin/fixes/666-bug:A/B/foo.cs", "refs/remotes/origin/fixes/666-bug", "A/B/foo.cs", CommitSha)] + [TestCase("https://github.com/github/VisualStudio/blob/master/foo.cs", "refs/remotes/origin/master", null, "refs/remotes/origin/master", null, CommitSha, Description = "Resolve commit only")] + [TestCase("https://github.com/github/VisualStudio/blob/36d6b0bb6e319337180d523281c42d9611744e66/src/code.cs", CommitSha, CommitSha + ":src/code.cs", CommitSha, "src/code.cs", CommitSha, Description = "Resolve commit only")] + [TestCase("https://github.com/github/VisualStudio/commit/8cf9a268c497adb4fc0a14572253165e179dd11e", "8cf9a268c497adb4fc0a14572253165e179dd11e", null, null, null, null)] + [TestCase("https://github.com/github/VisualStudio/blob/v2.5.3.2888/build.cmd", "refs/tags/v2.5.3.2888", "refs/tags/v2.5.3.2888:build.cmd", "refs/tags/v2.5.3.2888", "build.cmd", CommitSha)] + public void ResolveBlob(string url, string commitish, string objectish, string expectCommitish, string expectPath, string expectCommitSha) + { + var repositoryDir = "repositoryDir"; + var repository = Substitute.For<IRepository>(); + var commit = Substitute.For<Commit>(); + commit.Sha.Returns(expectCommitSha); + var blob = Substitute.For<Blob>(); + repository.Lookup(commitish).Returns(commit); + repository.Lookup(objectish).Returns(blob); + if (ObjectId.TryParse(commitish, out ObjectId objectId)) + { + // If it looks like a SHA, allow lookup using its ObjectId + repository.Lookup(objectId).Returns(blob); + } + var target = CreateGitHubContextService(repositoryDir, repository); + var context = target.FindContextFromUrl(url); + + var (resolvedCommitish, resolvedPath, commitSha) = target.ResolveBlob(repositoryDir, context); + + Assert.That(resolvedCommitish, Is.EqualTo(expectCommitish)); + Assert.That(resolvedPath, Is.EqualTo(expectPath)); + Assert.That(commitSha, Is.EqualTo(expectCommitSha)); + } + } + + public class TheResolveBlobFromCommitsMethod + { + [Test] + public void FlatTree() + { + var objectish = "12345678"; + var expectCommitSha = "2434215c5489db2bfa2e5249144a3bc532465f97"; + var expectBlobPath = "Class1.cs"; + var repositoryDir = "repositoryDir"; + var blob = Substitute.For<Blob>(); + var treeEntry = CreateTreeEntry(TreeEntryTargetType.Blob, blob, expectBlobPath); + var commit = CreateCommit(expectCommitSha, treeEntry); + var repository = CreateRepository(commit); + repository.Lookup<Blob>(objectish).Returns(blob); + var target = CreateGitHubContextService(repositoryDir, repository); + + var (commitSha, blobPath) = target.ResolveBlobFromHistory(repositoryDir, objectish); + + Assert.That((commitSha, blobPath), Is.EqualTo((expectCommitSha, expectBlobPath))); + } + + [Test] + public void NestedTree() + { + var objectish = "12345678"; + var expectCommitSha = "2434215c5489db2bfa2e5249144a3bc532465f97"; + var expectBlobPath = @"AnnotateFileTests\Class1.cs"; + var repositoryDir = "repositoryDir"; + var blob = Substitute.For<Blob>(); + var blobTreeEntry = CreateTreeEntry(TreeEntryTargetType.Blob, blob, expectBlobPath); + var childTree = CreateTree(blobTreeEntry); + var treeTreeEntry = CreateTreeEntry(TreeEntryTargetType.Tree, childTree, "AnnotateFileTests"); + var commit = CreateCommit(expectCommitSha, treeTreeEntry); + var repository = CreateRepository(commit); + repository.Lookup<Blob>(objectish).Returns(blob); + var target = CreateGitHubContextService(repositoryDir, repository); + + var (commitSha, blobPath) = target.ResolveBlobFromHistory(repositoryDir, objectish); + + Assert.That((commitSha, blobPath), Is.EqualTo((expectCommitSha, expectBlobPath))); + } + + [Test] + public void MissingBlob() + { + var objectish = "12345678"; + var repositoryDir = "repositoryDir"; + var treeEntry = Substitute.For<TreeEntry>(); + var repository = CreateRepository(); + var target = CreateGitHubContextService(repositoryDir, repository); + + var (commitSha, blobPath) = target.ResolveBlobFromHistory(repositoryDir, objectish); + + Assert.That((commitSha, blobPath), Is.EqualTo((null as string, null as string))); + } + + static IRepository CreateRepository(params Commit[] commits) + { + var repository = Substitute.For<IRepository>(); + var enumerator = commits.ToList().GetEnumerator(); + repository.Commits.GetEnumerator().Returns(enumerator); + return repository; + } + + static Commit CreateCommit(string sha, params TreeEntry[] treeEntries) + { + var commit = Substitute.For<Commit>(); + commit.Sha.Returns(sha); + var tree = CreateTree(treeEntries); + commit.Tree.Returns(tree); + return commit; + } + + static TreeEntry CreateTreeEntry(TreeEntryTargetType targetType, GitObject target, string path) + { + var treeEntry = Substitute.For<TreeEntry>(); + treeEntry.TargetType.Returns(targetType); + treeEntry.Target.Returns(target); + treeEntry.Path.Returns(path); + return treeEntry; + } + + static Tree CreateTree(params TreeEntry[] treeEntries) + { + var tree = Substitute.For<Tree>(); + var enumerator = treeEntries.ToList().GetEnumerator(); + tree.GetEnumerator().Returns(enumerator); + return tree; + } + } + + public class TheFindBlobShaForTextViewMethod + { + [TestCase(@"C:\Users\me\AppData\Local\Temp\TFSTemp\vctmp21996_181282.IOpenFromClipboardCommand.783ac965.cs", "783ac965")] + [TestCase(@"\TFSTemp\File.12345678.ext", "12345678")] + [TestCase(@"\TFSTemp\File.abcdefab.ext", "abcdefab")] + [TestCase(@"\TFSTemp\.12345678.", "12345678")] + [TestCase(@"\TFSTemp\File.ABCDEFAB.ext", null)] + [TestCase(@"\TFSTemp\File.1234567.ext", null)] + [TestCase(@"\TFSTemp\File.123456789.ext", null)] + [TestCase(@"\TFSTemp\File.12345678.ext\\", null)] + public void FindObjectishForTFSTempFile(string path, string expectObjectish) + { + var target = CreateGitHubContextService(); + + var objectish = target.FindObjectishForTFSTempFile(path); + + Assert.That(objectish, Is.EqualTo(expectObjectish)); + } + } + + static GitHubContextService CreateGitHubContextService(string repositoryDir = null, IRepository repository = null) + { + var sp = Substitute.For<IGitHubServiceProvider>(); + var gitService = Substitute.For<IGitService>(); + gitService.GetRepository(repositoryDir).Returns(repository); + + return new GitHubContextService(sp, gitService); + } +} diff --git a/test/GitHub.App.UnitTests/Services/ImageDownloaderTests.cs b/test/GitHub.App.UnitTests/Services/ImageDownloaderTests.cs new file mode 100644 index 0000000000..6e383ceabc --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/ImageDownloaderTests.cs @@ -0,0 +1,125 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Reactive.Linq; +using GitHub.Services; +using Octokit; +using Octokit.Internal; +using NSubstitute; +using NUnit.Framework; + +public class ImageDownloaderTests +{ + public class TheDownloadImageBytesMethod + { + [Test] + public async Task HttpStatusCode_OK_Async() + { + var url = new Uri("http://foo.bar"); + var httpClient = Substitute.For<IHttpClient>(); + var response = Substitute.For<IResponse>(); + response.StatusCode.Returns(HttpStatusCode.OK); + response.ContentType.Returns("image/xxx"); + response.Body.Returns(Array.Empty<byte>()); + httpClient.Send(null, default(CancellationToken)).ReturnsForAnyArgs(Task.FromResult(response)); + var target = new ImageDownloader(new Lazy<IHttpClient>(() => httpClient)); + + var bytes = await target.DownloadImageBytes(url); + + Assert.IsEmpty(bytes); + } + + [Test] + public void ContentTypeText_ThrowsHttpRequestException() + { + var url = new Uri("http://foo.bar"); + var httpClient = Substitute.For<IHttpClient>(); + var response = Substitute.For<IResponse>(); + response.StatusCode.Returns(HttpStatusCode.OK); + response.ContentType.Returns("text/plain"); + response.Body.Returns(Array.Empty<byte>()); + httpClient.Send(null, default(CancellationToken)).ReturnsForAnyArgs(Task.FromResult(response)); + var target = new ImageDownloader(new Lazy<IHttpClient>(() => httpClient)); + + Assert.ThrowsAsync<NonImageContentException>(async () => await target.DownloadImageBytes(url)); + } + + [Test] + public void HttpStatusCode_NotFound404_ThrowsHttpRequestException() + { + var url = new Uri("http://foo.bar"); + var httpClient = Substitute.For<IHttpClient>(); + var response = Substitute.For<IResponse>(); + response.StatusCode.Returns(HttpStatusCode.NotFound); + httpClient.Send(null, default(CancellationToken)).ReturnsForAnyArgs(Task.FromResult(response)); + var target = new ImageDownloader(new Lazy<IHttpClient>(() => httpClient)); + + Assert.ThrowsAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url)); + } + + [Test] + public void NotFoundTwiceForSameHost_CouldNotDownloadExceptionMessage() + { + var host = "flaky404.githubusercontent.com"; + var url = new Uri("https://" + host + "/u/00000000?v=4"); + var expectMessage = ImageDownloader.CouldNotDownloadExceptionMessage(url); + var httpClient = Substitute.For<IHttpClient>(); + var response = Substitute.For<IResponse>(); + response.StatusCode.Returns(HttpStatusCode.NotFound); + httpClient.Send(null, default(CancellationToken)).ReturnsForAnyArgs(Task.FromResult(response)); + var target = new ImageDownloader(new Lazy<IHttpClient>(() => httpClient)); + + var ex1 = Assert.CatchAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url)); + var ex2 = Assert.CatchAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url)); + + Assert.That(ex1?.Message, Is.EqualTo(expectMessage)); + Assert.That(ex2?.Message, Is.EqualTo(expectMessage)); + } + + [Test] + public void NonImageContentForSameHost_ThrowsCachedHttpRequestException() + { + var host = "host"; + var url = new Uri("https://" + host + "/image"); + var contentType = "text/html"; + var expectMessage1 = ImageDownloader.NonImageContentExceptionMessage(contentType); + var expectMessage2 = ImageDownloader.CachedExceptionMessage(host); + var httpClient = Substitute.For<IHttpClient>(); + var response = Substitute.For<IResponse>(); + response.StatusCode.Returns(HttpStatusCode.OK); + response.ContentType.Returns(contentType); + httpClient.Send(null, default(CancellationToken)).ReturnsForAnyArgs(Task.FromResult(response)); + var target = new ImageDownloader(new Lazy<IHttpClient>(() => httpClient)); + + var ex1 = Assert.CatchAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url)); + var ex2 = Assert.CatchAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url)); + + Assert.That(ex1?.Message, Is.EqualTo(expectMessage1)); + Assert.That(ex2?.Message, Is.EqualTo(expectMessage2)); + } + + [Test] + public void NonImageContentForDifferentHosts_DoesNotThrowCachedHttpRequestException() + { + var url1 = new Uri("https://host1/image"); + var url2 = new Uri("https://host2/image"); + var contentType = "text/html"; + var expectMessage1 = ImageDownloader.NonImageContentExceptionMessage(contentType); + var expectMessage2 = ImageDownloader.NonImageContentExceptionMessage(contentType); + var httpClient = Substitute.For<IHttpClient>(); + var response = Substitute.For<IResponse>(); + response.StatusCode.Returns(HttpStatusCode.OK); + response.ContentType.Returns(contentType); + httpClient.Send(null, default(CancellationToken)).ReturnsForAnyArgs(Task.FromResult(response)); + var target = new ImageDownloader(new Lazy<IHttpClient>(() => httpClient)); + + var ex1 = Assert.CatchAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url1)); + var ex2 = Assert.CatchAsync<HttpRequestException>(async () => await target.DownloadImageBytes(url2)); + + Assert.That(ex1?.Message, Is.EqualTo(expectMessage1)); + Assert.That(ex2?.Message, Is.EqualTo(expectMessage2)); + } + } +} diff --git a/test/GitHub.App.UnitTests/Services/LocalRepositoriesTests.cs b/test/GitHub.App.UnitTests/Services/LocalRepositoriesTests.cs new file mode 100644 index 0000000000..16d43caaf9 --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/LocalRepositoriesTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GitHub.App.Services; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; + +public class LocalRepositoriesTests : TestBaseClass +{ + const string GitHubAddress = "https://github.com"; + + [Test] + public void RepositoriesShouldInitiallyBeEmpty() + { + var service = CreateVSGitServices("repo1", "repo2"); + var target = new LocalRepositories(service); + + Assert.That(target.Repositories, Is.Empty); + } + + [Test] + public async Task RefreshShouldLoadRepositories() + { + var service = CreateVSGitServices("repo1", "repo2"); + var target = new LocalRepositories(service); + + await target.Refresh(); + + Assert.That( + new[] { "repo1", "repo2" }, + Is.EqualTo(target.Repositories.Select(x => x.Name).ToList())); + } + + [Test] + public async Task RefreshShouldAddNewRepository() + { + var service = CreateVSGitServices("repo1", "repo2"); + var target = new LocalRepositories(service); + + await target.Refresh(); + + Assert.That(2, Is.EqualTo(target.Repositories.Count)); + + var existing = service.GetKnownRepositories(); + var newRepo = CreateRepository("new"); + service.GetKnownRepositories().Returns(existing.Concat(new[] { newRepo })); + + await target.Refresh(); + + Assert.That( + new[] { "repo1", "repo2", "new" }, + Is.EqualTo(target.Repositories.Select(x => x.Name).ToList())); + } + + [Test] + public async Task RefreshShouldRemoveRepository() + { + var service = CreateVSGitServices("repo1", "repo2"); + var target = new LocalRepositories(service); + + await target.Refresh(); + + Assert.That(2, Is.EqualTo(target.Repositories.Count)); + + var existing = service.GetKnownRepositories(); + service.GetKnownRepositories().Returns(existing.Skip(1).Take(1)); + + await target.Refresh(); + + Assert.That( + new[] { "repo2" }, + Is.EqualTo(target.Repositories.Select(x => x.Name).ToList())); + } + + [Test] + public async Task GetRepositoriesForAddressShouldFilterRepositories() + { + var service = CreateVSGitServices( + Tuple.Create("repo1", GitHubAddress), + Tuple.Create("repo2", GitHubAddress), + Tuple.Create("repo2", "https://another.com")); + var target = new LocalRepositories(service); + + await target.Refresh(); + + Assert.That(3, Is.EqualTo(target.Repositories.Count)); + + var result = target.GetRepositoriesForAddress(HostAddress.Create(GitHubAddress)); + + Assert.That(2, Is.EqualTo(result.Count)); + } + + [Test] + public async Task GetRepositoriesForAddressShouldSortRepositories() + { + var service = CreateVSGitServices("c", "a", "b"); + var target = new LocalRepositories(service); + + await target.Refresh(); + var result = target.GetRepositoriesForAddress(HostAddress.Create(GitHubAddress)); + + Assert.That( + new[] { "a", "b", "c" }, + Is.EqualTo(result.Select(x => x.Name).ToList())); + } + + static IVSGitServices CreateVSGitServices(params string[] names) + { + return CreateVSGitServices(names.Select(x => Tuple.Create(x, GitHubAddress)).ToArray()); + } + + static IVSGitServices CreateVSGitServices(params Tuple<string, string>[] namesAndAddresses) + { + var result = Substitute.For<IVSGitServices>(); + var repositories = new List<ILocalRepositoryModel>(namesAndAddresses.Select(CreateRepository)); + result.GetKnownRepositories().Returns(repositories); + return result; + } + + static ILocalRepositoryModel CreateRepository(string name) + { + return CreateRepository(Tuple.Create(name, "https://github.com")); + } + + static ILocalRepositoryModel CreateRepository(Tuple<string, string> nameAndAddress) + { + var result = Substitute.For<ILocalRepositoryModel>(); + result.Name.Returns(nameAndAddress.Item1); + result.CloneUrl.Returns(new UriString(nameAndAddress.Item2)); + return result; + } +} diff --git a/test/GitHub.App.UnitTests/Services/OAuthCallbackListenerTests.cs b/test/GitHub.App.UnitTests/Services/OAuthCallbackListenerTests.cs new file mode 100644 index 0000000000..4308bfa0b1 --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/OAuthCallbackListenerTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Services; +using NSubstitute; +using Rothko; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.Services +{ + public class OAuthCallbackListenerTests + { + [Test] + public void ListenStartsHttpListener() + { + var httpListener = CreateHttpListener("id1"); + var target = new OAuthCallbackListener(httpListener); + + target.Listen("id1", CancellationToken.None).Forget(); + + httpListener.Prefixes.Received(1).Add("http://localhost:42549/"); + httpListener.Received(1).Start(); + } + + [Test] + public async Task ListenStopsHttpListenerAsync() + { + var httpListener = CreateHttpListener("id1"); + var target = new OAuthCallbackListener(httpListener); + + await target.Listen("id1", CancellationToken.None); + + httpListener.Received(1).Stop(); + } + + [Test] + public void CancelStopsHttpListener() + { + var httpListener = CreateHttpListener(null); + var cts = new CancellationTokenSource(); + var target = new OAuthCallbackListener(httpListener); + + var task = target.Listen("id1", cts.Token); + httpListener.Received(0).Stop(); + + cts.Cancel(); + httpListener.Received(1).Stop(); + } + + [Test] + public void CallingListenWhenAlreadyListeningCancelsFirstListen() + { + var httpListener = CreateHttpListener(null); + + var target = new OAuthCallbackListener(httpListener); + var task1 = target.Listen("id1", CancellationToken.None); + var task2 = target.Listen("id2", CancellationToken.None); + + httpListener.Received(1).Stop(); + } + + [Test] + public async Task SuccessfulResponseClosesResponseAsync() + { + var httpListener = CreateHttpListener("id1"); + var context = await httpListener.GetContextAsync(); + var target = new OAuthCallbackListener(httpListener); + + await target.Listen("id1", CancellationToken.None); + + context.Response.Received(1).Close(); + } + + IHttpListener CreateHttpListener(string id) + { + var result = Substitute.For<IHttpListener>(); + result.When(x => x.Start()).Do(_ => result.IsListening.Returns(true)); + + if (id != null) + { + var context = Substitute.For<IHttpListenerContext>(); + context.Request.Url.Returns(new Uri($"https://localhost:42549?code=1234&state={id}")); + result.GetContext().Returns(context); + result.GetContextAsync().Returns(context); + } + else + { + var tcs = new TaskCompletionSource<IHttpListenerContext>(); + result.GetContextAsync().Returns(tcs.Task); + } + + return result; + } + } +} diff --git a/test/GitHub.App.UnitTests/Services/PullRequestServiceTests.cs b/test/GitHub.App.UnitTests/Services/PullRequestServiceTests.cs new file mode 100644 index 0000000000..c8e437cb8d --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/PullRequestServiceTests.cs @@ -0,0 +1,1121 @@ +using System; +using System.IO; +using System.Text; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using LibGit2Sharp; +using NSubstitute; +using Rothko; +using UnitTests; +using NUnit.Framework; +using GitHub.Api; + +public class PullRequestServiceTests : TestBaseClass +{ + public class TheIsWorkingDirectoryCleanMethod + { + [Test] + public async Task NewRepo_True_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.True(isClean); + } + } + + [Test] + public async Task UntrackedFile_True_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var file = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); + File.WriteAllText(file, "contents"); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.True(isClean); + } + } + + + [Test] + public async Task CommitFile_True_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var file = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, file); + repo.Commit("foo", Author, Author); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.True(isClean); + } + } + + [Test] + public async Task AddedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.False(isClean); + } + } + + [Test] + public async Task ModifiedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.WriteAllText(file, "contents2"); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.False(isClean); + } + } + + [Test] + public async Task StagedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.WriteAllText(file, "contents2"); + Commands.Stage(repo, path); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.False(isClean); + } + } + + // Files can sometimes show up as DeletedFromWorkdir but not appear on Team Explorer or when `git status` is executed. + // https://github.com/github/VisualStudio/issues/1691 + // + // Since there is no reason to block a checkout when a file is missing, IsWorkingDirectoryClean should return true + // when a file has been deleted. + [Test] + public async Task DeletedFromWorkdir_True() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Delete(file); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.True(isClean); + } + } + + [Test] + public async Task RemovedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Delete(file); + Commands.Stage(repo, path); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.False(isClean); + } + } + + [Test] + public async Task RenamedInIndexFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var renamedPath = "renamed.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + var renamedFile = Path.Combine(repo.Info.WorkingDirectory, renamedPath); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Move(file, renamedFile); + Commands.Stage(repo, path); + Commands.Stage(repo, renamedPath); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.False(isClean); + } + } + + [Test] + public async Task RenamedInWorkingDirFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var renamedPath = "renamed.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + var renamedFile = Path.Combine(repo.Info.WorkingDirectory, renamedPath); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Move(file, renamedFile); + + // NOTE: `RetrieveStatus(new StatusOptions { DetectRenamesInWorkDir = true })` would need to be used + // for renamed files to appear as `RenamedInWorkingDir` rather than `DeletedFromWorkdir` and `NewInWorkdir`. + // This isn't required in the current implementation. + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.True(isClean); + } + } + + [Test] // WorkDirModified + public async Task ChangedSubmodule_True_Async() + { + using (var subRepoDir = new TempDirectory()) + using (var subRepo = CreateRepository(subRepoDir)) + using (var repoDir = new TempDirectory()) + using (var repo = CreateRepository(repoDir)) + { + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content", Author); + RepositoryHelpers.AddSubmodule(repo, "sub_name", "sub/path", subRepo); + repo.Commit("Add submodule", Author, Author); + RepositoryHelpers.UpdateSubmodules(repo); + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content2", Author); + RepositoryHelpers.AddSubmodule(repo, "sub_name", "sub/path", subRepo); + repo.Commit("Update submodule", Author, Author); + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var isClean = await service.IsWorkingDirectoryClean(repositoryModel).FirstAsync(); + + Assert.True(isClean); + } + } + } + + public class TheCountSubmodulesToSyncMethod + { + [Test] // WorkDirDeleted + public async Task CommittedSubmodule_True_Async() + { + using (var subRepoDir = new TempDirectory()) + using (var subRepo = CreateRepository(subRepoDir)) + using (var repoDir = new TempDirectory()) + using (var repo = CreateRepository(repoDir)) + { + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content", Author); + RepositoryHelpers.AddSubmodule(repo, "sub_name", "sub/path", subRepo); + repo.Commit($"Add submodule", Author, Author); + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(1, Is.EqualTo(count)); + } + } + + [Test] // WorkDirUninitialized + public async Task UninitializedSubmodule_True_Async() + { + using (var subRepoDir = new TempDirectory()) + using (var subRepo = CreateRepository(subRepoDir)) + using (var repoDir = new TempDirectory()) + using (var repo = CreateRepository(repoDir)) + { + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content", Author); + var subPath = "sub/path"; + RepositoryHelpers.AddSubmodule(repo, "sub_name", subPath, subRepo); + repo.Commit($"Add submodule", Author, Author); + var subDir = Path.Combine(repo.Info.WorkingDirectory, subPath); + Directory.CreateDirectory(subDir); + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(1, Is.EqualTo(count)); + } + } + + [Test] // WorkDirModified + public async Task ChangedSubmodule_True_Async() + { + using (var subRepoDir = new TempDirectory()) + using (var subRepo = CreateRepository(subRepoDir)) + using (var repoDir = new TempDirectory()) + using (var repo = CreateRepository(repoDir)) + { + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content", Author); + RepositoryHelpers.AddSubmodule(repo, "sub_name", "sub/path", subRepo); + repo.Commit("Add submodule", Author, Author); + RepositoryHelpers.UpdateSubmodules(repo); + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content2", Author); + RepositoryHelpers.AddSubmodule(repo, "sub_name", "sub/path", subRepo); + repo.Commit("Update submodule", Author, Author); + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(1, Is.EqualTo(count)); + } + } + + // TODO: Find out when `SubmoduleStatus.WorkDirAdded` is used. + + [Test] + public async Task UpdatedSubmodule_False_Async() + { + using (var subRepoDir = new TempDirectory()) + using (var subRepo = CreateRepository(subRepoDir)) + using (var repoDir = new TempDirectory()) + using (var repo = CreateRepository(repoDir)) + { + RepositoryHelpers.CommitFile(subRepo, "readme.txt", "content", Author); + RepositoryHelpers.AddSubmodule(repo, "sub_name", "sub/path", subRepo); + repo.Commit($"Add submodule", Author, Author); + RepositoryHelpers.UpdateSubmodules(repo); + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task NewRepo_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task UntrackedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var file = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); + File.WriteAllText(file, "contents"); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task CommitFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var file = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, file); + repo.Commit("foo", Author, Author); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task AddedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task ModifiedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.WriteAllText(file, "contents2"); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task StagedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.WriteAllText(file, "contents2"); + Commands.Stage(repo, path); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task MissingFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Delete(file); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task RemovedFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Delete(file); + Commands.Stage(repo, path); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task RenamedInIndexFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var renamedPath = "renamed.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + var renamedFile = Path.Combine(repo.Info.WorkingDirectory, renamedPath); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Move(file, renamedFile); + Commands.Stage(repo, path); + Commands.Stage(repo, renamedPath); + + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + + [Test] + public async Task RenamedInWorkingDirFile_False_Async() + { + using (var tempDir = new TempDirectory()) + using (var repo = CreateRepository(tempDir)) + { + var service = CreatePullRequestService(repo); + var repositoryModel = CreateLocalRepositoryModel(repo); + var path = "file.txt"; + var renamedPath = "renamed.txt"; + var file = Path.Combine(repo.Info.WorkingDirectory, path); + var renamedFile = Path.Combine(repo.Info.WorkingDirectory, renamedPath); + File.WriteAllText(file, "contents"); + Commands.Stage(repo, path); + repo.Commit("foo", Author, Author); + File.Move(file, renamedFile); + + // NOTE: `RetrieveStatus(new StatusOptions { DetectRenamesInWorkDir = true })` would need to be used + // for renamed files to appear as `RenamedInWorkingDir` rather than `Missing` and `Untracked`. + // This isn't required in the current implementation. + var count = await service.CountSubmodulesToSync(repositoryModel).FirstAsync(); + + Assert.That(0, Is.EqualTo(count)); + } + } + } + + protected static Repository CreateRepository(TempDirectory tempDirectory) + { + var repoDir = tempDirectory.Directory.FullName; + return new Repository(Repository.Init(repoDir)); + } + + static PullRequestService CreatePullRequestService(Repository repo) + { + var repoDir = repo.Info.WorkingDirectory; + var serviceProvider = Substitutes.ServiceProvider; + var gitService = serviceProvider.GetGitService(); + gitService.GetRepository(repoDir).Returns(repo); + var service = new PullRequestService( + Substitute.For<IGitClient>(), + gitService, + Substitute.For<IVSGitExt>(), + Substitute.For<IGraphQLClientFactory>(), + serviceProvider.GetOperatingSystem(), + Substitute.For<IUsageTracker>()); + return service; + } + + static ILocalRepositoryModel CreateLocalRepositoryModel(Repository repo) + { + var repoDir = repo.Info.WorkingDirectory; + var repositoryModel = Substitute.For<ILocalRepositoryModel>(); + repositoryModel.LocalPath.Returns(repoDir); + return repositoryModel; + } + + static Signature Author => new Signature("foo", "foo@bar.com", DateTimeOffset.Now); + + public class TheExtractToTempFileMethod + { + [Test] + public async Task ExtractsExistingFile_Async() + { + var gitClient = MockGitClient(); + var target = CreateTarget(gitClient); + var repository = Substitute.For<ILocalRepositoryModel>(); + var fileContent = "file content"; + var pr = CreatePullRequest(); + + gitClient.ExtractFile(Arg.Any<IRepository>(), "123", "filename").Returns(GetFileTaskAsync(fileContent)); + var file = await target.ExtractToTempFile(repository, pr, "filename", "123", Encoding.UTF8); + + try + { + Assert.That(File.ReadAllText(file), Is.EqualTo(fileContent)); + } + finally + { + File.Delete(file); + } + } + + [Test] + public async Task CreatesEmptyFileForNonExistentFileAsync() + { + var gitClient = MockGitClient(); + var target = CreateTarget(gitClient); + var repository = Substitute.For<ILocalRepositoryModel>(); + var pr = CreatePullRequest(); + + gitClient.ExtractFile(Arg.Any<IRepository>(), "123", "filename").Returns(GetFileTaskAsync(null)); + var file = await target.ExtractToTempFile(repository, pr, "filename", "123", Encoding.UTF8); + + try + { + Assert.That(File.ReadAllText(file), Is.EqualTo(string.Empty)); + } + finally + { + File.Delete(file); + } + } + + // https://github.com/github/VisualStudio/issues/1010 + [TestCase("utf-8")] // Unicode (UTF-8) + [TestCase("Windows-1252")] // Western European (Windows) + public async Task CanChangeEncodingAsync(string encodingName) + { + var encoding = Encoding.GetEncoding(encodingName); + var repoDir = Path.GetTempPath(); + var fileName = "fileName.txt"; + var fileContent = "file content"; + var gitClient = MockGitClient(); + var target = CreateTarget(gitClient); + var repository = Substitute.For<ILocalRepositoryModel>(); + var pr = CreatePullRequest(); + + var expectedPath = Path.Combine(repoDir, fileName); + var expectedContent = fileContent; + File.WriteAllText(expectedPath, expectedContent, encoding); + + gitClient.ExtractFile(Arg.Any<IRepository>(), "123", "filename").Returns(GetFileTaskAsync(fileContent)); + var file = await target.ExtractToTempFile(repository, pr, "filename", "123", encoding); + + try + { + Assert.That(File.ReadAllText(expectedPath), Is.EqualTo(File.ReadAllText(file))); + Assert.That(File.ReadAllBytes(expectedPath), Is.EqualTo(File.ReadAllBytes(file))); + } + finally + { + File.Delete(file); + } + } + + static PullRequestDetailModel CreatePullRequest() + { + var result = new PullRequestDetailModel(); + return result; + } + } + + [Test] + public void CreatePullRequestAllArgsMandatory() + { + var serviceProvider = Substitutes.ServiceProvider; + var gitService = serviceProvider.GetGitService(); + var service = new PullRequestService( + Substitute.For<IGitClient>(), + serviceProvider.GetGitService(), + Substitute.For<IVSGitExt>(), + Substitute.For<IGraphQLClientFactory>(), + serviceProvider.GetOperatingSystem(), + Substitute.For<IUsageTracker>()); + + IModelService ms = null; + ILocalRepositoryModel sourceRepo = null; + ILocalRepositoryModel targetRepo = null; + string title = null; + string body = null; + IBranch source = null; + IBranch target = null; + + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + ms = Substitute.For<IModelService>(); + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + sourceRepo = new LocalRepositoryModel("name", new GitHub.Primitives.UriString("http://github.com/github/stuff"), "c:\\path", gitService); + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + targetRepo = new LocalRepositoryModel("name", new GitHub.Primitives.UriString("http://github.com/github/stuff"), "c:\\path", gitService); + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + title = "a title"; + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + body = "a body"; + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + source = new BranchModel("source", sourceRepo); + Assert.Throws<ArgumentNullException>(() => service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body)); + + target = new BranchModel("target", targetRepo); + var pr = service.CreatePullRequest(ms, sourceRepo, targetRepo, source, target, title, body); + + Assert.NotNull(pr); + } + + public class TheCheckoutMethod + { + [Test] + public async Task ShouldCheckoutExistingBranchAsync() + { + var gitClient = MockGitClient(); + var service = CreateTarget(gitClient, MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + + var pr = new PullRequestDetailModel + { + Number = 4, + BaseRefName = "master", + BaseRefSha = "123", + BaseRepositoryOwner = "owner", + }; + + await service.Checkout(localRepo, pr, "pr/123-foo1"); + + gitClient.Received().Checkout(Arg.Any<IRepository>(), "pr/123-foo1").Forget(); + gitClient.Received().SetConfig(Arg.Any<IRepository>(), "branch.pr/123-foo1.ghfvs-pr-owner-number", "owner#4").Forget(); + + Assert.That(2, Is.EqualTo(gitClient.ReceivedCalls().Count())); + } + + [Test] + public async Task ShouldCheckoutLocalBranchAsync() + { + var gitClient = MockGitClient(); + var service = CreateTarget(gitClient, MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://foo.bar/owner/repo")); + + var pr = new PullRequestDetailModel + { + Number = 5, + BaseRefName = "master", + BaseRefSha = "123", + BaseRepositoryOwner = "owner", + HeadRefName = "prbranch", + HeadRefSha = "123", + HeadRepositoryOwner = "owner", + }; + + await service.Checkout(localRepo, pr, "prbranch"); + + gitClient.Received().Fetch(Arg.Any<IRepository>(), "origin").Forget(); + gitClient.Received().Checkout(Arg.Any<IRepository>(), "prbranch").Forget(); + gitClient.Received().SetConfig(Arg.Any<IRepository>(), "branch.prbranch.ghfvs-pr-owner-number", "owner#5").Forget(); + + Assert.That(4, Is.EqualTo(gitClient.ReceivedCalls().Count())); + } + + [Test] + public async Task ShouldCheckoutLocalBranchOwnerCaseMismatchAsync() + { + var gitClient = MockGitClient(); + var service = CreateTarget(gitClient, MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://foo.bar/Owner/repo")); + + var pr = new PullRequestDetailModel + { + Number = 5, + BaseRefName = "master", + BaseRefSha = "123", + BaseRepositoryOwner = "owner", + HeadRefName = "prbranch", + HeadRefSha = "123", + HeadRepositoryOwner = "owner", + }; + + await service.Checkout(localRepo, pr, "prbranch"); + + gitClient.Received().Fetch(Arg.Any<IRepository>(), "origin").Forget(); + gitClient.Received().Checkout(Arg.Any<IRepository>(), "prbranch").Forget(); + gitClient.Received().SetConfig(Arg.Any<IRepository>(), "branch.prbranch.ghfvs-pr-owner-number", "owner#5").Forget(); + + Assert.That(4, Is.EqualTo(gitClient.ReceivedCalls().Count())); + } + + [Test] + public async Task ShouldCheckoutBranchFromForkAsync() + { + var gitClient = MockGitClient(); + var service = CreateTarget(gitClient, MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://foo.bar/owner/repo")); + + var pr = new PullRequestDetailModel + { + Number = 5, + BaseRefName = "master", + BaseRefSha = "123", + BaseRepositoryOwner = "owner", + HeadRefName = "prbranch", + HeadRefSha = "123", + HeadRepositoryOwner = "fork", + }; + + await service.Checkout(localRepo, pr, "pr/5-fork-branch"); + + gitClient.Received().SetRemote(Arg.Any<IRepository>(), "fork", new Uri("https://foo.bar/fork/repo")).Forget(); + gitClient.Received().SetConfig(Arg.Any<IRepository>(), "remote.fork.created-by-ghfvs", "true").Forget(); + gitClient.Received().Fetch(Arg.Any<IRepository>(), "fork").Forget(); + gitClient.Received().Fetch(Arg.Any<IRepository>(), "fork", "prbranch:pr/5-fork-branch").Forget(); + gitClient.Received().Checkout(Arg.Any<IRepository>(), "pr/5-fork-branch").Forget(); + gitClient.Received().SetTrackingBranch(Arg.Any<IRepository>(), "pr/5-fork-branch", "refs/remotes/fork/prbranch").Forget(); + gitClient.Received().SetConfig(Arg.Any<IRepository>(), "branch.pr/5-fork-branch.ghfvs-pr-owner-number", "owner#5").Forget(); + Assert.That(7, Is.EqualTo(gitClient.ReceivedCalls().Count())); + } + + [Test] + public async Task ShouldUseUniquelyNamedRemoteForForkAsync() + { + var gitClient = MockGitClient(); + var gitService = MockGitService(); + var service = CreateTarget(gitClient, gitService); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://foo.bar/owner/repo")); + + using (var repo = gitService.GetRepository(localRepo.CloneUrl)) + { + var remote = Substitute.For<Remote>(); + var remoteCollection = Substitute.For<RemoteCollection>(); + remoteCollection["fork"].Returns(remote); + repo.Network.Remotes.Returns(remoteCollection); + + var pr = new PullRequestDetailModel + { + Number = 5, + BaseRefName = "master", + BaseRefSha = "123", + BaseRepositoryOwner = "owner", + HeadRefName = "prbranch", + HeadRefSha = "123", + HeadRepositoryOwner = "fork", + }; + + await service.Checkout(localRepo, pr, "pr/5-fork-branch"); + + gitClient.Received().SetRemote(Arg.Any<IRepository>(), "fork1", new Uri("https://foo.bar/fork/repo")).Forget(); + gitClient.Received().SetConfig(Arg.Any<IRepository>(), "remote.fork1.created-by-ghfvs", "true").Forget(); + } + } + } + + public class TheGetDefaultLocalBranchNameMethod + { + [Test] + public async Task ShouldReturnCorrectDefaultLocalBranchNameAsync() + { + var service = CreateTarget(MockGitClient(), MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + var result = await service.GetDefaultLocalBranchName(localRepo, 123, "Pull requests can be \"named\" all sorts of thing's (sic)"); + Assert.That("pr/123-pull-requests-can-be-named-all-sorts-of-thing-s-sic", Is.EqualTo(result)); + } + + [Test] + public async Task ShouldReturnCorrectDefaultLocalBranchNameForPullRequestsWithNonLatinCharsAsync() + { + var service = new PullRequestService( + MockGitClient(), + MockGitService(), + Substitute.For<IVSGitExt>(), + Substitute.For<IGraphQLClientFactory>(), + Substitute.For<IOperatingSystem>(), + Substitute.For<IUsageTracker>()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + var result = await service.GetDefaultLocalBranchName(localRepo, 123, "コードをレビューする準備ができたこと"); + Assert.That("pr/123", Is.EqualTo(result)); + } + + [Test] + public async Task DefaultLocalBranchNameShouldNotClashWithExistingBranchNamesAsync() + { + var service = CreateTarget(MockGitClient(), MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + var result = await service.GetDefaultLocalBranchName(localRepo, 123, "foo1"); + Assert.That("pr/123-foo1-3", Is.EqualTo(result)); + } + } + + public class TheGetLocalBranchesMethod + { + [Test] + public async Task ShouldReturnPullRequestBranchForPullRequestFromSameRepositoryAsync() + { + var service = CreateTarget(MockGitClient(), MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://github.com/foo/bar")); + + var result = await service.GetLocalBranches(localRepo, CreatePullRequest(fromFork: false)); + + Assert.That("source", Is.EqualTo(result.Name)); + } + + [Test] + public async Task ShouldReturnPullRequestBranchForPullRequestFromSameRepositoryOwnerCaseMismatchAsync() + { + var service = CreateTarget(MockGitClient(), MockGitService()); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://github.com/Foo/bar")); + + var result = await service.GetLocalBranches(localRepo, CreatePullRequest(fromFork: false)); + + Assert.That("source", Is.EqualTo(result.Name)); + } + + [Test] + public async Task ShouldReturnMarkedBranchForPullRequestFromForkAsync() + { + var repo = Substitute.For<IRepository>(); + var config = Substitute.For<Configuration>(); + + var configEntry1 = Substitute.For<ConfigurationEntry<string>>(); + configEntry1.Key.Returns("branch.pr/1-foo.ghfvs-pr"); + configEntry1.Value.Returns("foo#1"); + var configEntry2 = Substitute.For<ConfigurationEntry<string>>(); + configEntry2.Key.Returns("branch.pr/2-bar.ghfvs-pr"); + configEntry2.Value.Returns("foo#2"); + + config.GetEnumerator().Returns(new List<ConfigurationEntry<string>> + { + configEntry1, + configEntry2, + }.GetEnumerator()); + + repo.Config.Returns(config); + + var service = CreateTarget(MockGitClient(), MockGitService(repo)); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://github.com/foo/bar.git")); + + var result = await service.GetLocalBranches(localRepo, CreatePullRequest(true)); + + Assert.That("pr/1-foo", Is.EqualTo(result.Name)); + } + + static PullRequestDetailModel CreatePullRequest(bool fromFork) + { + return new PullRequestDetailModel + { + Number = 1, + Title = "PR 1", + HeadRefName = "source", + HeadRefSha = "HEAD_SHA", + HeadRepositoryOwner = fromFork ? "fork" : "foo", + BaseRefName = "dest", + BaseRefSha = "BASE_SHA", + BaseRepositoryOwner = "foo", + }; + } + + static IGitService MockGitService(IRepository repository = null) + { + var result = Substitute.For<IGitService>(); + result.GetRepository(Arg.Any<string>()).Returns(repository ?? Substitute.For<IRepository>()); + return result; + } + } + + public class TheRemoteUnusedRemotesMethod + { + [Test] + public async Task ShouldRemoveUnusedRemoteAsync() + { + var gitClient = MockGitClient(); + var gitService = MockGitService(); + var service = CreateTarget(gitClient, gitService); + + var localRepo = Substitute.For<ILocalRepositoryModel>(); + localRepo.CloneUrl.Returns(new UriString("https://github.com/foo/bar")); + + using (var repo = gitService.GetRepository(localRepo.CloneUrl)) + { + var remote1 = Substitute.For<Remote>(); + var remote2 = Substitute.For<Remote>(); + var remote3 = Substitute.For<Remote>(); + var remotes = new List<Remote> { remote1, remote2, remote3 }; + var remoteCollection = Substitute.For<RemoteCollection>(); + remote1.Name.Returns("remote1"); + remote2.Name.Returns("remote2"); + remote3.Name.Returns("remote3"); + remoteCollection.GetEnumerator().Returns(_ => remotes.GetEnumerator()); + repo.Network.Remotes.Returns(remoteCollection); + + var branch1 = Substitute.For<LibGit2Sharp.Branch>(); + var branch2 = Substitute.For<LibGit2Sharp.Branch>(); + var branches = new List<LibGit2Sharp.Branch> { branch1, branch2 }; + var branchCollection = Substitute.For<BranchCollection>(); + branch1.RemoteName.Returns("remote1"); + branch2.RemoteName.Returns("remote1"); + branchCollection.GetEnumerator().Returns(_ => branches.GetEnumerator()); + repo.Branches.Returns(branchCollection); + + gitClient.GetConfig<bool>(Arg.Any<IRepository>(), "remote.remote1.created-by-ghfvs").Returns(Task.FromResult(true)); + gitClient.GetConfig<bool>(Arg.Any<IRepository>(), "remote.remote2.created-by-ghfvs").Returns(Task.FromResult(true)); + + await service.RemoveUnusedRemotes(localRepo); + + remoteCollection.DidNotReceive().Remove("remote1"); + remoteCollection.Received().Remove("remote2"); + remoteCollection.DidNotReceive().Remove("remote3"); + } + } + } + + static PullRequestService CreateTarget( + IGitClient gitClient = null, + IGitService gitService = null, + IVSGitExt gitExt = null, + IGraphQLClientFactory graphqlFactory = null, + IOperatingSystem os = null, + IUsageTracker usageTracker = null) + { + gitClient = gitClient ?? Substitute.For<IGitClient>(); + gitService = gitService ?? Substitute.For<IGitService>(); + gitExt = gitExt ?? Substitute.For<IVSGitExt>(); + graphqlFactory = graphqlFactory ?? Substitute.For<IGraphQLClientFactory>(); + os = os ?? Substitute.For<IOperatingSystem>(); + usageTracker = usageTracker ?? Substitute.For<IUsageTracker>(); + + return new PullRequestService( + gitClient, + gitService, + gitExt, + graphqlFactory, + os, + usageTracker); + } + + static BranchCollection MockBranches(params string[] names) + { + var result = Substitute.For<BranchCollection>(); + + foreach (var name in names) + { + var branch = Substitute.For<LibGit2Sharp.Branch>(); + branch.CanonicalName.Returns("refs/heads/" + name); + result[name].Returns(branch); + } + + return result; + } + + static IGitClient MockGitClient() + { + var result = Substitute.For<IGitClient>(); + var remote = Substitute.For<Remote>(); + remote.Name.Returns("origin"); + result.GetHttpRemote(Arg.Any<IRepository>(), Arg.Any<string>()).Returns(Task.FromResult(remote)); + return result; + } + + static IGitService MockGitService() + { + var repository = Substitute.For<IRepository>(); + var branches = MockBranches("pr/123-foo1", "pr/123-foo1-2"); + repository.Branches.Returns(branches); + + var result = Substitute.For<IGitService>(); + result.GetRepository(Arg.Any<string>()).Returns(repository); + return result; + } + + static Task<string> GetFileTaskAsync(object content) + { + if (content is string) + { + return Task.FromResult((string)content); + } + + if (content is Exception) + { + return Task.FromException<string>((Exception)content); + } + + if (content == null) + { + return Task.FromResult<string>(null); + } + + throw new ArgumentException("Unsupported content type: " + content); + } +} diff --git a/test/GitHub.App.UnitTests/Services/RepositoryCloneServiceTests.cs b/test/GitHub.App.UnitTests/Services/RepositoryCloneServiceTests.cs new file mode 100644 index 0000000000..7cb4822a86 --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/RepositoryCloneServiceTests.cs @@ -0,0 +1,46 @@ +using System.Reactive.Linq; +using System.Threading.Tasks; +using NSubstitute; +using NUnit.Framework; +using UnitTests; +using GitHub.Services; +using System.Linq.Expressions; +using System; +using GitHub.Models; + +public class RepositoryCloneServiceTests +{ + public class TheCloneRepositoryMethod : TestBaseClass + { + [Test] + public async Task ClonesToRepositoryPathAsync() + { + var serviceProvider = Substitutes.ServiceProvider; + var operatingSystem = serviceProvider.GetOperatingSystem(); + var vsGitServices = serviceProvider.GetVSGitServices(); + var cloneService = serviceProvider.GetRepositoryCloneService(); + + await cloneService.CloneRepository("https://github.com/foo/bar", "bar", @"c:\dev"); + + operatingSystem.Directory.Received().CreateDirectory(@"c:\dev\bar"); + await vsGitServices.Received().Clone("https://github.com/foo/bar", @"c:\dev\bar", true); + } + + [Test] + public async Task UpdatesMetricsWhenRepositoryClonedAsync() + { + var serviceProvider = Substitutes.ServiceProvider; + var operatingSystem = serviceProvider.GetOperatingSystem(); + var vsGitServices = serviceProvider.GetVSGitServices(); + var usageTracker = Substitute.For<IUsageTracker>(); + var cloneService = new RepositoryCloneService(operatingSystem, vsGitServices, usageTracker); + + await cloneService.CloneRepository("https://github.com/foo/bar", "bar", @"c:\dev"); + var model = UsageModel.Create(Guid.NewGuid()); + + await usageTracker.Received().IncrementCounter( + Arg.Is<Expression<Func<UsageModel.MeasuresModel, int>>>(x => + ((MemberExpression)x.Body).Member.Name == nameof(model.Measures.NumberOfClones))); + } + } +} diff --git a/src/UnitTests/GitHub.App/Services/RepositoryCreationServiceTests.cs b/test/GitHub.App.UnitTests/Services/RepositoryCreationServiceTests.cs similarity index 81% rename from src/UnitTests/GitHub.App/Services/RepositoryCreationServiceTests.cs rename to test/GitHub.App.UnitTests/Services/RepositoryCreationServiceTests.cs index 0089363c9e..df2cbded9f 100644 --- a/src/UnitTests/GitHub.App/Services/RepositoryCreationServiceTests.cs +++ b/test/GitHub.App.UnitTests/Services/RepositoryCreationServiceTests.cs @@ -6,17 +6,19 @@ using GitHub.Services; using NSubstitute; using Octokit; -using Xunit; +using NUnit.Framework; using UnitTests; public class RepositoryCreationServiceTests { public class TheCreateRepositoryMethod : TestBaseClass { - [Fact] + [Test] public void CreatesRepositoryOnlineViaApiAndThenClonesIt() { - var provider = Substitutes.ServiceProvider; + var cloneService = Substitutes.RepositoryCloneService; + var provider = Substitutes.GetServiceProvider(cloneService); + var newRepository = new NewRepository("octokit.net"); var repository = new TestRepository("octokit.net", "https://github.com/octokit/octokit.net"); var account = Substitute.For<IAccount>(); @@ -25,9 +27,6 @@ public void CreatesRepositoryOnlineViaApiAndThenClonesIt() var apiClient = Substitute.For<IApiClient>(); apiClient.CreateRepository(newRepository, "octokit", false) .Returns(Observable.Return(repository)); - var cloneService = provider.GetRepositoryCloneService(); - cloneService.CloneRepository("https://github.com/octokit/octokit.net", "octokit.net", @"c:\dev") - .Returns(Observable.Return(Unit.Default)); var creator = provider.GetRepositoryCreationService(); creator.CreateRepository(newRepository, account, @"c:\dev", apiClient).Subscribe(); diff --git a/test/GitHub.App.UnitTests/Services/TeamExplorerContextTests.cs b/test/GitHub.App.UnitTests/Services/TeamExplorerContextTests.cs new file mode 100644 index 0000000000..68b8aa63c2 --- /dev/null +++ b/test/GitHub.App.UnitTests/Services/TeamExplorerContextTests.cs @@ -0,0 +1,253 @@ +using System; +using System.IO; +using GitHub.Services; +using NUnit.Framework; +using NSubstitute; +using EnvDTE; +using GitHub.Models; +using Microsoft.VisualStudio.Threading; +using System.Threading.Tasks; + +namespace GitHub.App.UnitTests.Services +{ + public class TeamExplorerContextTests + { + public class TheActiveRepositoryProperty + { + [Test] + public void NoActiveRepository() + { + var gitExt = CreateGitExt(); + var target = CreateTeamExplorerContext(gitExt); + + var repo = target.ActiveRepository; + + Assert.That(repo, Is.Null); + } + + [Test] + public async Task SetActiveRepository_CheckWasSet() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + SetActiveRepository(gitExt, repoInfo); + var target = CreateTeamExplorerContext(gitExt); + + await target.JoinableTaskCollection.JoinTillEmptyAsync(); + + var repo = target.ActiveRepository; + Assert.That(repo, Is.EqualTo(repoInfo)); + } + } + + public class ThePropertyChangedEvent + { + [Test] + public void SetActiveRepository_CheckEventWasRaised() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + var target = CreateTeamExplorerContext(gitExt); + var eventWasRaised = false; + target.PropertyChanged += (s, e) => eventWasRaised = e.PropertyName == nameof(target.ActiveRepository); + + SetActiveRepository(gitExt, repoInfo); + + Assert.That(eventWasRaised, Is.True); + } + + [Test] + public void SetTwicePropertyChangedFiresOnce() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + var target = CreateTeamExplorerContext(gitExt); + var eventWasRaisedCount = 0; + target.PropertyChanged += (s, e) => eventWasRaisedCount++; + + SetActiveRepository(gitExt, repoInfo); + SetActiveRepository(gitExt, repoInfo); + + Assert.That(1, Is.EqualTo(1)); + } + + [Test] + public void ChangeActiveRepository_NoSolutionChange() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + var repositoryPath2 = Path.GetTempPath(); + var repoInfo2 = CreateRepositoryModel(repositoryPath2); + var target = CreateTeamExplorerContext(gitExt); + SetActiveRepository(gitExt, repoInfo); + var eventWasRaised = false; + target.PropertyChanged += (s, e) => eventWasRaised = e.PropertyName == nameof(target.ActiveRepository); + + SetActiveRepository(gitExt, repoInfo2); + + Assert.That(eventWasRaised, Is.True); + } + + [Test] + public void ClearActiveRepository_NoEventWhenNoSolutionChange() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + var target = CreateTeamExplorerContext(gitExt); + SetActiveRepository(gitExt, repoInfo); + var eventWasRaised = false; + target.PropertyChanged += (s, e) => eventWasRaised = e.PropertyName == nameof(target.ActiveRepository); + + SetActiveRepository(gitExt, null); + + Assert.That(eventWasRaised, Is.False); + Assert.That(target.ActiveRepository, Is.EqualTo(repoInfo)); + } + + [Test] + public void ClearActiveRepository_FireWhenSolutionChanged() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + var dte = Substitute.For<DTE>(); + var target = CreateTeamExplorerContext(gitExt, dte); + dte.Solution.FullName.Returns("Solution1"); + SetActiveRepository(gitExt, repoInfo); + var eventWasRaised = false; + target.PropertyChanged += (s, e) => eventWasRaised = e.PropertyName == nameof(target.ActiveRepository); + + dte.Solution.FullName.Returns("Solution2"); + SetActiveRepository(gitExt, null); + + Assert.That(eventWasRaised, Is.True); + Assert.That(target.ActiveRepository, Is.Null); + } + + [Test] + public void NoActiveRepositoryChange_SolutionChanges() + { + var gitExt = CreateGitExt(); + var repositoryPath = Directory.GetCurrentDirectory(); + var repoInfo = CreateRepositoryModel(repositoryPath); + var dte = Substitute.For<DTE>(); + var target = CreateTeamExplorerContext(gitExt, dte); + dte.Solution.FullName.Returns(""); + SetActiveRepository(gitExt, repoInfo); + var eventWasRaised = false; + target.PropertyChanged += (s, e) => eventWasRaised = e.PropertyName == nameof(target.ActiveRepository); + + dte.Solution.FullName.Returns("Solution"); + SetActiveRepository(gitExt, repoInfo); + + Assert.That(eventWasRaised, Is.False); + } + } + + public class TheStatusChangedEvent + { + [TestCase(false, "name1", "sha1", "name1", "sha1", false)] + [TestCase(false, "name1", "sha1", "name2", "sha1", true)] + [TestCase(false, "name1", "sha1", "name1", "sha2", true)] + [TestCase(false, "name1", "sha1", "name2", "sha2", true)] + [TestCase(true, "name1", "sha1", "name1", "sha1", false)] + [TestCase(true, "name1", "sha1", "name2", "sha2", false)] + public void SameActiveRepository_ExpectWasRaised(bool changePath, string name1, string sha1, string name2, string sha2, bool expectWasRaised) + { + var gitExt = CreateGitExt(); + var repositoryPaths = new[] { Directory.GetCurrentDirectory(), Path.GetTempPath() }; + var path1 = Directory.GetCurrentDirectory(); + var path2 = changePath ? Path.GetTempPath() : path1; + var repoInfo1 = CreateRepositoryModel(path1, name1, sha1); + var repoInfo2 = CreateRepositoryModel(path2, name2, sha2); + + var target = CreateTeamExplorerContext(gitExt); + var eventWasRaised = false; + target.StatusChanged += (s, e) => eventWasRaised = true; + + SetActiveRepository(gitExt, repoInfo1); + SetActiveRepository(gitExt, repoInfo2); + + Assert.That(eventWasRaised, Is.EqualTo(expectWasRaised)); + } + + [TestCase("trackedSha", "trackedSha", false)] + [TestCase("trackedSha1", "trackedSha2", true)] + public void TrackedShaChanges_CheckWasRaised(string trackedSha1, string trackedSha2, bool expectWasRaised) + { + var gitExt = CreateGitExt(); + var repositoryPaths = new[] { Directory.GetCurrentDirectory(), Path.GetTempPath() }; + var repoPath = Directory.GetCurrentDirectory(); + var repoInfo1 = CreateRepositoryModel(repoPath, "name", "sha", trackedSha1); + var repoInfo2 = CreateRepositoryModel(repoPath, "name", "sha", trackedSha2); + var target = CreateTeamExplorerContext(gitExt); + SetActiveRepository(gitExt, repoInfo1); + var eventWasRaised = false; + target.StatusChanged += (s, e) => eventWasRaised = true; + + SetActiveRepository(gitExt, repoInfo2); + + Assert.That(eventWasRaised, Is.EqualTo(expectWasRaised)); + } + + [Test] + public void SolutionUnloadedAndReloaded_DontFireStatusChanged() + { + var gitExt = CreateGitExt(); + var path = Directory.GetCurrentDirectory(); + var repoInfo1 = CreateRepositoryModel(path, "name", "sha"); + var repoInfo2 = CreateRepositoryModel(null); + var target = CreateTeamExplorerContext(gitExt); + SetActiveRepository(gitExt, repoInfo1); + SetActiveRepository(gitExt, repoInfo2); + + var eventWasRaised = false; + target.StatusChanged += (s, e) => eventWasRaised = true; + SetActiveRepository(gitExt, repoInfo1); + + Assert.That(eventWasRaised, Is.False); + } + } + + static TeamExplorerContext CreateTeamExplorerContext( + IVSGitExt gitExt, + DTE dte = null, + IPullRequestService pullRequestService = null, + JoinableTaskContext joinableTaskContext = null) + { + dte = dte ?? Substitute.For<DTE>(); + pullRequestService = pullRequestService ?? Substitute.For<IPullRequestService>(); + joinableTaskContext = joinableTaskContext ?? new JoinableTaskContext(); + return new TeamExplorerContext(gitExt, new AsyncLazy<DTE>(() => Task.FromResult(dte)), pullRequestService, joinableTaskContext); + } + + static ILocalRepositoryModel CreateRepositoryModel(string path, string branchName = null, string headSha = null, string trackedSha = null) + { + var repo = Substitute.For<ILocalRepositoryModel>(); + repo.LocalPath.Returns(path); + var currentBranch = Substitute.For<IBranch>(); + currentBranch.Name.Returns(branchName); + currentBranch.Sha.Returns(headSha); + currentBranch.TrackedSha.Returns(trackedSha); + repo.CurrentBranch.Returns(currentBranch); + return repo; + } + + static IVSGitExt CreateGitExt() + { + return Substitute.For<IVSGitExt>(); + } + + static void SetActiveRepository(IVSGitExt gitExt, ILocalRepositoryModel repo) + { + var repos = repo != null ? new[] { repo } : new ILocalRepositoryModel[0]; + gitExt.ActiveRepositories.Returns(repos); + gitExt.ActiveRepositoriesChanged += Raise.Event<Action>(); + } + } +} diff --git a/test/GitHub.App.UnitTests/Substitutes.cs b/test/GitHub.App.UnitTests/Substitutes.cs new file mode 100644 index 0000000000..9792829b8c --- /dev/null +++ b/test/GitHub.App.UnitTests/Substitutes.cs @@ -0,0 +1,203 @@ +using GitHub.Authentication; +using GitHub.Models; +using GitHub.Services; +using GitHub.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using NSubstitute; +using Rothko; +using System; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using GitHub.Factories; + +namespace UnitTests +{ + internal static class Substitutes + { + public static T1 For<T1, T2, T3, T4>(params object[] constructorArguments) + where T1 : class + where T2 : class + where T3 : class + where T4 : class + { + return (T1)Substitute.For(new Type[4] + { + typeof (T1), + typeof (T2), + typeof (T3), + typeof (T4) + }, constructorArguments); + } + + + // public static IGitRepositoriesExt IGitRepositoriesExt { get { return Substitute.For<IGitRepositoriesExt>(); } } + public static IGitService IGitService { get { return Substitute.For<IGitService>(); } } + + public static IVSGitServices IVSGitServices + { + get + { + var ret = Substitute.For<IVSGitServices>(); + ret.GetLocalClonePathFromGitProvider().Returns(@"c:\foo\bar"); + return ret; + } + } + + public static IOperatingSystem OperatingSystem + { + get + { + var ret = Substitute.For<IOperatingSystem>(); + // this expansion happens when the GetLocalClonePathFromGitProvider call is setup by default + // see IVSServices property above + ret.Environment.ExpandEnvironmentVariables(Args.String).Returns(x => x[0]); + return ret; + } + } + + public static IViewViewModelFactory ViewViewModelFactory { get { return Substitute.For<IViewViewModelFactory>(); } } + + public static IRepositoryCreationService RepositoryCreationService { get { return Substitute.For<IRepositoryCreationService>(); } } + public static IRepositoryCloneService RepositoryCloneService { get { return Substitute.For<IRepositoryCloneService>(); } } + + public static IConnection Connection { get { return Substitute.For<IConnection>(); } } + public static IConnectionManager ConnectionManager { get { return Substitute.For<IConnectionManager>(); } } + public static IDelegatingTwoFactorChallengeHandler TwoFactorChallengeHandler { get { return Substitute.For<IDelegatingTwoFactorChallengeHandler>(); } } + public static IGistPublishService GistPublishService { get { return Substitute.For<IGistPublishService>(); } } + public static IPullRequestService PullRequestService { get { return Substitute.For<IPullRequestService>(); } } + + /// <summary> + /// This returns a service provider with everything mocked except for + /// RepositoryCloneService and RepositoryCreationService, which are real + /// instances. + /// </summary> + public static IGitHubServiceProvider ServiceProvider { get { return GetServiceProvider(); } } + + /// <summary> + /// This returns a service provider with mocked IRepositoryCreationService and + /// IRepositoryCloneService as well as all other services mocked. The regular + /// GetServiceProvider method (and ServiceProvider property return a IServiceProvider + /// with real RepositoryCloneService and RepositoryCreationService instances. + /// </summary> + /// <returns></returns> + public static IServiceProvider GetFullyMockedServiceProvider() + { + return GetServiceProvider(RepositoryCloneService, RepositoryCreationService); + } + + /// <summary> + /// This returns a service provider with everything mocked except for + /// RepositoryCloneService and RepositoryCreationService, which are real + /// instances. + /// </summary> + /// <param name="cloneService"></param> + /// <param name="creationService"></param> + /// <returns></returns> + public static IGitHubServiceProvider GetServiceProvider( + IRepositoryCloneService cloneService = null, + IRepositoryCreationService creationService = null, + IAvatarProvider avatarProvider = null) + { + var ret = Substitute.For<IGitHubServiceProvider, IServiceProvider>(); + + var gitservice = IGitService; + var cm = Substitute.For<SComponentModel, IComponentModel>(); + var cc = new CompositionContainer(CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); + cc.ComposeExportedValue(gitservice); + ((IComponentModel)cm).DefaultExportProvider.Returns(cc); + ret.GetService(typeof(SComponentModel)).Returns(cm); + Services.UnitTestServiceProvider = ret; + + var os = OperatingSystem; + var vsgit = IVSGitServices; + var clone = cloneService ?? new RepositoryCloneService(os, vsgit, Substitute.For<IUsageTracker>()); + var create = creationService ?? new RepositoryCreationService(clone); + avatarProvider = avatarProvider ?? Substitute.For<IAvatarProvider>(); + //ret.GetService(typeof(IGitRepositoriesExt)).Returns(IGitRepositoriesExt); + ret.GetService(typeof(IGitService)).Returns(gitservice); + ret.GetService(typeof(IVSServices)).Returns(Substitute.For<IVSServices>()); + ret.GetService(typeof(IVSGitServices)).Returns(vsgit); + ret.GetService(typeof(IOperatingSystem)).Returns(os); + ret.GetService(typeof(IRepositoryCloneService)).Returns(clone); + ret.GetService(typeof(IRepositoryCreationService)).Returns(create); + ret.GetService(typeof(IViewViewModelFactory)).Returns(ViewViewModelFactory); + ret.GetService(typeof(IConnection)).Returns(Connection); + ret.GetService(typeof(IConnectionManager)).Returns(ConnectionManager); + ret.GetService(typeof(IAvatarProvider)).Returns(avatarProvider); + ret.GetService(typeof(IDelegatingTwoFactorChallengeHandler)).Returns(TwoFactorChallengeHandler); + ret.GetService(typeof(IGistPublishService)).Returns(GistPublishService); + ret.GetService(typeof(IPullRequestService)).Returns(PullRequestService); + return ret; + } + + //public static IGitRepositoriesExt GetGitExt(this IServiceProvider provider) + //{ + // return provider.GetService(typeof(IGitRepositoriesExt)) as IGitRepositoriesExt; + //} + + public static IVSServices GetVSServices(this IServiceProvider provider) + { + return provider.GetService(typeof(IVSServices)) as IVSServices; + } + + public static IVSGitServices GetVSGitServices(this IServiceProvider provider) + { + return provider.GetService(typeof(IVSGitServices)) as IVSGitServices; + } + + public static IGitService GetGitService(this IServiceProvider provider) + { + return provider.GetService(typeof(IGitService)) as IGitService; + } + + public static IOperatingSystem GetOperatingSystem(this IServiceProvider provider) + { + return provider.GetService(typeof(IOperatingSystem)) as IOperatingSystem; + } + + public static IRepositoryCloneService GetRepositoryCloneService(this IServiceProvider provider) + { + return provider.GetService(typeof(IRepositoryCloneService)) as IRepositoryCloneService; + } + + public static IRepositoryCreationService GetRepositoryCreationService(this IServiceProvider provider) + { + return provider.GetService(typeof(IRepositoryCreationService)) as IRepositoryCreationService; + } + + public static IViewViewModelFactory GetExportFactoryProvider(this IServiceProvider provider) + { + return provider.GetService(typeof(IViewViewModelFactory)) as IViewViewModelFactory; + } + + public static IConnection GetConnection(this IServiceProvider provider) + { + return provider.GetService(typeof(IConnection)) as IConnection; + } + + public static IConnectionManager GetConnectionManager(this IServiceProvider provider) + { + return provider.GetService(typeof(IConnectionManager)) as IConnectionManager; + } + + public static IAvatarProvider GetAvatarProvider(this IServiceProvider provider) + { + return provider.GetService(typeof(IAvatarProvider)) as IAvatarProvider; + } + + public static IDelegatingTwoFactorChallengeHandler GetTwoFactorChallengeHandler(this IServiceProvider provider) + { + return provider.GetService(typeof(IDelegatingTwoFactorChallengeHandler)) as IDelegatingTwoFactorChallengeHandler; + } + + public static IGistPublishService GetGistPublishService(this IServiceProvider provider) + { + return provider.GetService(typeof(IGistPublishService)) as IGistPublishService; + } + + public static IPullRequestService GetPullRequestsService(this IServiceProvider provider) + { + return provider.GetService(typeof(IPullRequestService)) as IPullRequestService; + } + } +} diff --git a/test/GitHub.App.UnitTests/TestDoubles/FakeCommitLog.cs b/test/GitHub.App.UnitTests/TestDoubles/FakeCommitLog.cs new file mode 100644 index 0000000000..7262bdcb7e --- /dev/null +++ b/test/GitHub.App.UnitTests/TestDoubles/FakeCommitLog.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using LibGit2Sharp; + +public class FakeCommitLog : List<Commit>, IQueryableCommitLog +{ + public CommitSortStrategies SortedBy + { + get + { + return CommitSortStrategies.Topological; + } + } + + public Commit FindMergeBase(IEnumerable<Commit> commits, MergeBaseFindingStrategy strategy) + { + throw new NotImplementedException(); + } + + public Commit FindMergeBase(Commit first, Commit second) + { + throw new NotImplementedException(); + } + + public IEnumerable<LogEntry> QueryBy(string path) + { + throw new NotImplementedException(); + } + + public ICommitLog QueryBy(CommitFilter filter) + { + throw new NotImplementedException(); + } + +#pragma warning disable 618 // Type or member is obsolete + public IEnumerable<LogEntry> QueryBy(string path, FollowFilter filter) + { + throw new NotImplementedException(); + } +#pragma warning restore 618 // Type or member is obsolete + + public IEnumerable<LogEntry> QueryBy(string path, CommitFilter filter) + { + throw new NotImplementedException(); + } +} diff --git a/src/UnitTests/TestDoubles/FakeMenuCommandService.cs b/test/GitHub.App.UnitTests/TestDoubles/FakeMenuCommandService.cs similarity index 85% rename from src/UnitTests/TestDoubles/FakeMenuCommandService.cs rename to test/GitHub.App.UnitTests/TestDoubles/FakeMenuCommandService.cs index 9f2efaee79..dd02937bd5 100644 --- a/src/UnitTests/TestDoubles/FakeMenuCommandService.cs +++ b/test/GitHub.App.UnitTests/TestDoubles/FakeMenuCommandService.cs @@ -17,6 +17,15 @@ public void AddCommand(MenuCommand command) addedCommands.Add(command); } + public void ExecuteCommand(int commandId) + { + var command = addedCommands.Find(_ => _.CommandID.ID == commandId); + if (command != null) + { + command.Invoke(); + } + } + public void AddVerb(DesignerVerb verb) { throw new NotImplementedException(); diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/GistCreationViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/GistCreationViewModelTests.cs new file mode 100644 index 0000000000..edf234c307 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/GistCreationViewModelTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using GitHub.Api; +using GitHub.Factories; +using GitHub.Models; +using GitHub.SampleData; +using GitHub.Services; +using GitHub.ViewModels.Dialog; +using NSubstitute; +using Octokit; +using ReactiveUI; +using UnitTests; +using NUnit.Framework; + +public class GistCreationViewModelTests +{ + static IGistCreationViewModel CreateViewModel(IServiceProvider provider, string selectedText = "", string fileName = "", bool isPrivate = false) + { + var selectedTextProvider = Substitute.For<ISelectedTextProvider>(); + selectedTextProvider.GetSelectedText().Returns(selectedText); + + var accounts = new ReactiveList<IAccount>() { Substitute.For<IAccount>(), Substitute.For<IAccount>() }; + var modelService = Substitute.For<IModelService>(); + modelService.GetAccounts().Returns(Observable.Return(accounts)); + + var modelServiceFactory = Substitute.For<IModelServiceFactory>(); + modelServiceFactory.CreateAsync(null).ReturnsForAnyArgs(modelService); + modelServiceFactory.CreateBlocking(null).ReturnsForAnyArgs(modelService); + + var gistPublishService = provider.GetGistPublishService(); + var notificationService = Substitute.For<INotificationService>(); + + return new GistCreationViewModel(modelServiceFactory, selectedTextProvider, gistPublishService, notificationService, Substitute.For<IUsageTracker>()) + { + FileName = fileName, + IsPrivate = isPrivate + }; + } + + public class TheCreateGistCommand : TestBaseClass + { + [TestCase("Console.WriteLine", "Gist.cs", true)] + [TestCase("Console.WriteLine", "Gist.cs", false)] + public void CreatesAGistUsingTheApiClient(string selectedText, string fileName, bool isPrivate) + { + var provider = Substitutes.ServiceProvider; + var vm = CreateViewModel(provider, selectedText, fileName, isPrivate); + var gistPublishService = provider.GetGistPublishService(); + vm.CreateGist.Execute(null); + + gistPublishService + .Received() + .PublishGist( + Arg.Any<IApiClient>(), + Arg.Is<NewGist>(g => g.Public == !isPrivate + && g.Files.First().Key == fileName + && g.Files.First().Value == selectedText)); + } + + [TestCase(null, false)] + [TestCase("", false)] + [TestCase("Gist.cs", true)] + public void CannotCreateGistIfFileNameIsMissing(string fileName, bool expected) + { + var provider = Substitutes.ServiceProvider; + var vm = CreateViewModel(provider, fileName: fileName); + + var actual = vm.CreateGist.CanExecute(null); + Assert.That(expected, Is.EqualTo(actual)); + } + + [Test] + public void Foo() + { + var x = new PullRequestDetailViewModelDesigner(); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/GitHubDialogWindowViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/GitHubDialogWindowViewModelTests.cs new file mode 100644 index 0000000000..628103147f --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/GitHubDialogWindowViewModelTests.cs @@ -0,0 +1,142 @@ +using System; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels; +using GitHub.ViewModels.Dialog; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.Dialog +{ + public class GitHubDialogWindowViewModelTests + { + public class TheStartMethod : TestBaseClass + { + [Test] + public void SetsContent() + { + var target = CreateTarget(); + var content = Substitute.For<IDialogContentViewModel>(); + + target.Start(content); + + Assert.That(content, Is.SameAs(target.Content)); + } + + [Test] + public void SignalsCloseWhenContentRaisesClosed() + { + var target = CreateTarget(); + var content = Substitute.For<IDialogContentViewModel>(); + var closed = new Subject<object>(); + var signalled = false; + + content.Done.Returns(closed); + target.Done.Subscribe(_ => signalled = true); + target.Start(content); + closed.OnNext(null); + + Assert.True(signalled); + } + } + + public class TheStartWithConnectionMethod + { + [Test] + public async Task ShowsLoginDialogWhenNoConnectionsAvailableAsync() + { + var target = CreateTarget(); + var content = Substitute.For<ITestViewModel>(); + + await target.StartWithConnection(content); + + Assert.That(target.Content, Is.InstanceOf<ILoginViewModel>()); + } + + [Test] + public async Task ShowsContentWhenConnectionAvailableAsync() + { + var connectionManager = CreateConnectionManager(1); + var target = CreateTarget(connectionManager); + var content = Substitute.For<ITestViewModel>(); + + await target.StartWithConnection(content); + + Assert.That(content, Is.SameAs(target.Content)); + await content.Received(1).InitializeAsync(connectionManager.Connections[0]); + } + + [Test] + public async Task ShowsContentWhenLoggedInAsync() + { + var target = CreateTarget(); + var content = Substitute.For<ITestViewModel>(); + + await target.StartWithConnection(content); + + var login = (ILoginViewModel)target.Content; + var connection = Substitute.For<IConnection>(); + ((ISubject<object>)login.Done).OnNext(connection); + + Assert.That(content, Is.SameAs(target.Content)); + await content.Received(1).InitializeAsync(connection); + } + + [Test] + public async Task ClosesDialogWhenLoginReturnsNullConnectionAsync() + { + var target = CreateTarget(); + var content = Substitute.For<ITestViewModel>(); + var closed = false; + + target.Done.Subscribe(_ => closed = true); + await target.StartWithConnection(content); + + var login = (ILoginViewModel)target.Content; + ((ISubject<object>)login.Done).OnNext(null); + + Assert.True(closed); + } + } + + static IConnectionManager CreateConnectionManager(int numberOfConnections) + { + var connections = new ObservableCollectionEx<IConnection>(); + + for (var i = 0; i < numberOfConnections; ++i) + { + var connection = Substitute.For<IConnection>(); + connection.IsLoggedIn.Returns(true); + connections.Add(connection); + } + + var result = Substitute.For<IConnectionManager>(); + result.Connections.Returns(connections); + result.GetLoadedConnections().Returns(connections); + return result; + } + + static GitHubDialogWindowViewModel CreateTarget(IConnectionManager connectionManager = null) + { + var login = Substitute.For<ILoginViewModel>(); + login.Done.Returns(new Subject<object>()); + + var factory = Substitute.For<IViewViewModelFactory>(); + factory.CreateViewModel<ILoginViewModel>().Returns(login); + + connectionManager = connectionManager ?? Substitute.For<IConnectionManager>(); + + return new GitHubDialogWindowViewModel( + factory, + new Lazy<IConnectionManager>(() => connectionManager)); + } + + public interface ITestViewModel : IDialogContentViewModel, IConnectionInitializedViewModel + { + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/Login2FaViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/Login2FaViewModelTests.cs new file mode 100644 index 0000000000..228de1cde0 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/Login2FaViewModelTests.cs @@ -0,0 +1,145 @@ +using System; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using GitHub.Authentication; +using GitHub.Services; +using GitHub.ViewModels.Dialog; +using NSubstitute; +using Octokit; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.Dialog +{ + public class Login2FaViewModelTests + { + public class TheShowMethod + { + [Test] + public void ClearsIsBusy() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + + target.OkCommand.ExecuteAsync(); + target.Show(new TwoFactorRequiredUserError(exception)); + + Assert.False(target.IsBusy); + } + + [Test] + public void InvalidAuthenticationCodeIsSetWhenRetryFailed() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + + target.Show(new TwoFactorRequiredUserError(exception)); + + Assert.True(target.InvalidAuthenticationCode); + } + + [Test] + public async Task OkCommandCompletesAndReturnsNullWithNoAuthorizationCodeAsync() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + var userError = new TwoFactorRequiredUserError(exception); + var task = target.Show(userError).ToTask(); + + target.OkCommand.Execute(null); + var result = await task; + + Assert.That(result, Is.Null); + } + + [Test] + public async Task OkCommandCompletesAndReturnsAuthorizationCodeAsync() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + var userError = new TwoFactorRequiredUserError(exception); + var task = target.Show(userError).ToTask(); + + target.AuthenticationCode = "123456"; + target.OkCommand.Execute(null); + + var result = await task; + Assert.That("123456", Is.EqualTo(result.AuthenticationCode)); + } + + [Test] + public async Task ResendCodeCommandCompletesAndReturnsRequestResendCodeAsync() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + var userError = new TwoFactorRequiredUserError(exception); + var task = target.Show(userError).ToTask(); + + target.AuthenticationCode = "123456"; + target.ResendCodeCommand.Execute(null); + var result = await task; + + Assert.False(target.IsBusy); + Assert.That(TwoFactorChallengeResult.RequestResendCode, Is.EqualTo(result)); + } + + [Test] + public async Task ShowErrorMessageIsClearedWhenAuthenticationCodeSentAsync() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + var userError = new TwoFactorRequiredUserError(exception); + var task = target.Show(userError).ToTask(); + + Assert.True(target.ShowErrorMessage); + target.ResendCodeCommand.Execute(null); + + var result = await task; + Assert.False(target.ShowErrorMessage); + } + } + + public class TheCancelMethod + { + [Test] + public async Task CancelCommandCompletesAndReturnsNullAsync() + { + var target = CreateTarget(); + var exception = new TwoFactorChallengeFailedException(); + var userError = new TwoFactorRequiredUserError(exception, TwoFactorType.AuthenticatorApp); + var task = target.Show(userError).ToTask(); + + target.AuthenticationCode = "123456"; + target.Cancel(); + var result = await task; + + Assert.False(target.IsBusy); + Assert.That(result, Is.Null); + } + + [Test] + public async Task Cancel_Resets_TwoFactorType_Async() + { + var target = CreateTarget(); + var exception = new TwoFactorRequiredException(TwoFactorType.Sms); + var userError = new TwoFactorRequiredUserError(exception); + var task = target.Show(userError).ToTask(); + + Assert.That(TwoFactorType.Sms, Is.EqualTo(target.TwoFactorType)); + + target.Cancel(); + await task; + + // TwoFactorType must be cleared here as the UIController uses it as a trigger + // to show the 2FA dialog view. + Assert.That(TwoFactorType.None, Is.EqualTo(target.TwoFactorType)); + } + } + + static Login2FaViewModel CreateTarget() + { + var browser = Substitute.For<IVisualStudioBrowser>(); + var twoFactorChallengeHandler = Substitute.For<IDelegatingTwoFactorChallengeHandler>(); + return new Login2FaViewModel(browser); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginCredentialsViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginCredentialsViewModelTests.cs new file mode 100644 index 0000000000..e91b2b0a2e --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginCredentialsViewModelTests.cs @@ -0,0 +1,121 @@ +using System; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.ViewModels.Dialog; +using NSubstitute; +using ReactiveUI; +using NUnit.Framework; + +public class LoginCredentialsViewModelTests +{ + public class TheDoneSignal : TestBaseClass + { + [Test] + public async Task SucessfulGitHubLoginSignalsDoneAsync() + { + var connectionManager = Substitute.For<IConnectionManager>(); + var connection = Substitute.For<IConnection>(); + + var gitHubLogin = Substitute.For<ILoginToGitHubViewModel>(); + var gitHubLoginCommand = ReactiveCommand.CreateAsyncObservable(_ => + Observable.Return(connection)); + gitHubLogin.Login.Returns(gitHubLoginCommand); + var enterpriseLogin = Substitute.For<ILoginToGitHubForEnterpriseViewModel>(); + + var loginViewModel = new LoginCredentialsViewModel(connectionManager, gitHubLogin, enterpriseLogin); + var signalled = false; + + loginViewModel.Done.Subscribe(_ => signalled = true); + await gitHubLoginCommand.ExecuteAsync(); + + Assert.True(signalled); + } + + [Test] + public async Task FailedGitHubLoginDoesNotSignalDoneAsync() + { + var connectionManager = Substitute.For<IConnectionManager>(); + + var gitHubLogin = Substitute.For<ILoginToGitHubViewModel>(); + var gitHubLoginCommand = ReactiveCommand.CreateAsyncObservable(_ => + Observable.Return<IConnection>(null)); + gitHubLogin.Login.Returns(gitHubLoginCommand); + var enterpriseLogin = Substitute.For<ILoginToGitHubForEnterpriseViewModel>(); + + var loginViewModel = new LoginCredentialsViewModel(connectionManager, gitHubLogin, enterpriseLogin); + var signalled = false; + + loginViewModel.Done.Subscribe(_ => signalled = true); + await gitHubLoginCommand.ExecuteAsync(); + + Assert.False(signalled); + } + + [Test] + public async Task AllowsLoginFromEnterpriseAfterGitHubLoginHasFailedAsync() + { + var connectionManager = Substitute.For<IConnectionManager>(); + var connection = Substitute.For<IConnection>(); + + var gitHubLogin = Substitute.For<ILoginToGitHubViewModel>(); + var gitHubLoginCommand = ReactiveCommand.CreateAsyncObservable(_ => + Observable.Return<IConnection>(null)); + gitHubLogin.Login.Returns(gitHubLoginCommand); + + var enterpriseLogin = Substitute.For<ILoginToGitHubForEnterpriseViewModel>(); + var enterpriseLoginCommand = ReactiveCommand.CreateAsyncObservable(_ => + Observable.Return(connection)); + enterpriseLogin.Login.Returns(enterpriseLoginCommand); + + var loginViewModel = new LoginCredentialsViewModel(connectionManager, gitHubLogin, enterpriseLogin); + var success = false; + + loginViewModel.Done + .OfType<IConnection>() + .Where(x => x != null) + .Subscribe(_ => success = true); + + await gitHubLoginCommand.ExecuteAsync(); + await enterpriseLoginCommand.ExecuteAsync(); + + Assert.True(success); + } + } + + public class TheLoginModeProperty : TestBaseClass + { + [Test] + public void LoginModeTracksAvailableConnections() + { + var connectionManager = Substitute.For<IConnectionManager>(); + var connections = new ObservableCollectionEx<IConnection>(); + var gitHubLogin = Substitute.For<ILoginToGitHubViewModel>(); + var enterpriseLogin = Substitute.For<ILoginToGitHubForEnterpriseViewModel>(); + var gitHubConnection = Substitute.For<IConnection>(); + var enterpriseConnection = Substitute.For<IConnection>(); + + connectionManager.Connections.Returns(connections); + gitHubConnection.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); + enterpriseConnection.HostAddress.Returns(HostAddress.Create("https://enterprise.url")); + gitHubConnection.IsLoggedIn.Returns(true); + enterpriseConnection.IsLoggedIn.Returns(true); + + var loginViewModel = new LoginCredentialsViewModel(connectionManager, gitHubLogin, enterpriseLogin); + + Assert.That(LoginMode.DotComOrEnterprise, Is.EqualTo(loginViewModel.LoginMode)); + + connections.Add(enterpriseConnection); + Assert.That(LoginMode.DotComOnly, Is.EqualTo(loginViewModel.LoginMode)); + + connections.Add(gitHubConnection); + Assert.That(LoginMode.None, Is.EqualTo(loginViewModel.LoginMode)); + + connections.RemoveAt(0); + Assert.That(LoginMode.EnterpriseOnly, Is.EqualTo(loginViewModel.LoginMode)); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModelTests.cs new file mode 100644 index 0000000000..53468244a4 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginToGitHubForEnterpriseViewModelTests.cs @@ -0,0 +1,236 @@ +using System; +using System.Reactive.Concurrency; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Services; +using GitHub.ViewModels.Dialog; +using Microsoft.Reactive.Testing; +using NSubstitute; +using Octokit; +using NUnit.Framework; + +public class LoginToGitHubForEnterpriseViewModelTests +{ + public class TheProbeStatusProperty : TestBaseClass + { + [Test] + public void InvalidUrlReturnsNone() + { + var scheduler = new TestScheduler(); + var target = CreateTarget(scheduler); + + target.EnterpriseUrl = "badurl"; + + Assert.That(EnterpriseProbeStatus.None, Is.EqualTo(target.ProbeStatus)); + } + + [Test] + public async Task ReturnsCheckingWhenProbeNotFinished() + { + var scheduler = new TestScheduler(); + var caps = Substitute.For<IEnterpriseCapabilitiesService>(); + var task = new TaskCompletionSource<EnterpriseProbeResult>(); + caps.Probe(null).ReturnsForAnyArgs(task.Task); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + + Assert.That(EnterpriseProbeStatus.Checking, Is.EqualTo(target.ProbeStatus)); + + try + { + task.SetCanceled(); + await task.Task; + } + catch (TaskCanceledException) { } + } + + [Test] + public async Task ReturnsValidWhenProbeReturnsOk() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseProbeResult.Ok); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + scheduler.Stop(); + await target.UpdatingProbeStatus; + + Assert.That(EnterpriseProbeStatus.Valid, Is.EqualTo(target.ProbeStatus)); + } + + [Test] + public async Task ReturnsInvalidWhenProbeReturnsFailed() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseProbeResult.Failed); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + scheduler.Stop(); + await target.UpdatingProbeStatus; + + Assert.That(EnterpriseProbeStatus.Invalid, Is.EqualTo(target.ProbeStatus)); + } + + [Test] + public async Task ReturnsInvalidWhenProbeReturnsNotFound() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseProbeResult.NotFound); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + scheduler.Stop(); + await target.UpdatingProbeStatus; + + Assert.That(EnterpriseProbeStatus.Invalid, Is.EqualTo(target.ProbeStatus)); + } + } + + public class TheSupportedLoginMethodsProperty : TestBaseClass + { + [Test] + public void InvalidUrlReturnsNull() + { + var scheduler = new TestScheduler(); + var target = CreateTarget(scheduler); + + target.EnterpriseUrl = "badurl"; + + Assert.That(target.SupportedLoginMethods, Is.Null); + } + + [Test] + public void ReturnsToken() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.Token); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + + Assert.That(EnterpriseLoginMethods.Token, Is.EqualTo(target.SupportedLoginMethods)); + } + + [Test] + public void ReturnsUsernameAndPassword() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.UsernameAndPassword); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + + Assert.That(EnterpriseLoginMethods.UsernameAndPassword, Is.EqualTo(target.SupportedLoginMethods)); + } + + [Test] + public void GivesPrecedenceToUsernameAndPasswordOverToken() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.Token | + EnterpriseLoginMethods.UsernameAndPassword | + EnterpriseLoginMethods.OAuth); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + + Assert.That( + EnterpriseLoginMethods.UsernameAndPassword | EnterpriseLoginMethods.OAuth, + Is.EqualTo(target.SupportedLoginMethods)); + } + } + + public class TheLoginCommand : TestBaseClass + { + [Test] + public void DisabledWhenUserNameEmpty() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.UsernameAndPassword); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + target.Password = "pass"; + + Assert.False(target.Login.CanExecute(null)); + } + + [Test] + public void DisabledWhenPasswordEmpty() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.UsernameAndPassword); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + target.UsernameOrEmail = "user"; + + Assert.False(target.Login.CanExecute(null)); + } + + [Test] + public void EnabledWhenUsernameAndPasswordSet() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.UsernameAndPassword); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + target.UsernameOrEmail = "user"; + target.Password = "pass"; + + Assert.True(target.Login.CanExecute(null)); + } + + [Test] + public void EnabledWhenOnlyPasswordSetWhenUsingTokenLogin() + { + var scheduler = new TestScheduler(); + var caps = CreateCapabilties(EnterpriseLoginMethods.Token); + var target = CreateTarget(scheduler, caps); + + target.EnterpriseUrl = "https://foo.bar"; + scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks); + target.Password = "pass"; + + Assert.True(target.Login.CanExecute(null)); + } + } + + static IEnterpriseCapabilitiesService CreateCapabilties(EnterpriseProbeResult probeResult) + { + var result = Substitute.For<IEnterpriseCapabilitiesService>(); + result.Probe(null).ReturnsForAnyArgs(probeResult); + return result; + } + + static IEnterpriseCapabilitiesService CreateCapabilties(EnterpriseLoginMethods methods) + { + var result = CreateCapabilties(EnterpriseProbeResult.Ok); + result.ProbeLoginMethods(null).ReturnsForAnyArgs(methods); + return result; + } + + static LoginToGitHubForEnterpriseViewModel CreateTarget( + IScheduler scheduler = null, + IEnterpriseCapabilitiesService capabilitiesService = null) + { + return new LoginToGitHubForEnterpriseViewModel( + Substitute.For<IConnectionManager>(), + capabilitiesService ?? Substitute.For<IEnterpriseCapabilitiesService>(), + Substitute.For<IVisualStudioBrowser>(), + scheduler ?? Scheduler.Immediate); + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginToGitHubViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginToGitHubViewModelTests.cs new file mode 100644 index 0000000000..e3551fb400 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/LoginToGitHubViewModelTests.cs @@ -0,0 +1,73 @@ +using System; +using System.Net; +using System.Reactive.Linq; +using GitHub.Authentication; +using GitHub.Info; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.ViewModels; +using NSubstitute; +using Octokit; +using ReactiveUI; +using NUnit.Framework; + +public class LoginToGitHubViewModelTests +{ + //public class TheLoginCommand : TestBaseClass + //{ + // [Test] + // public void ShowsHelpfulTooltipWhenForbiddenResponseReceived() + // { + // var response = Substitute.For<IResponse>(); + // response.StatusCode.Returns(HttpStatusCode.Forbidden); + // var repositoryHosts = Substitute.For<IRepositoryHosts>(); + // repositoryHosts.LogIn(HostAddress.GitHubDotComHostAddress, Args.String, Args.String) + // .Returns(_ => Observable.Throw<AuthenticationResult>(new ForbiddenException(response))); + // var browser = Substitute.For<IVisualStudioBrowser>(); + // var loginViewModel = new LoginToGitHubViewModel(repositoryHosts, browser); + + // loginViewModel.Login.Execute(null); + + // Assert.Equal("Make sure to use your password and not a Personal Access token to sign in.", + // loginViewModel.Error.ErrorMessage); + // } + //} + + //public class TheSignupCommand : TestBaseClass + //{ + // [Test] + // public void LaunchesBrowserToSignUpPage() + // { + // var repositoryHosts = Substitute.For<IRepositoryHosts>(); + // var gitHubHost = Substitute.For<IRepositoryHost>(); + // gitHubHost.Address.Returns(HostAddress.GitHubDotComHostAddress); + // repositoryHosts.GitHubHost.Returns(gitHubHost); + // var browser = Substitute.For<IVisualStudioBrowser>(); + // var loginViewModel = new LoginToGitHubViewModel(repositoryHosts, browser); + + // loginViewModel.SignUp.Execute(null); + + // browser.Received().OpenUrl(GitHubUrls.Plans); + // } + //} + + //public class TheForgotPasswordCommand : TestBaseClass + //{ + // [Test] + // public void LaunchesBrowserToForgotPasswordPage() + // { + // var repositoryHosts = Substitute.For<IRepositoryHosts>(); + // var gitHubHost = Substitute.For<IRepositoryHost>(); + // gitHubHost.Address.Returns(HostAddress.GitHubDotComHostAddress); + // repositoryHosts.GitHubHost.Returns(gitHubHost); + // var browser = Substitute.For<IVisualStudioBrowser>(); + // var loginViewModel = new LoginToGitHubViewModel(repositoryHosts, browser); + + // loginViewModel.NavigateForgotPassword.Execute(null); + + // browser.Received().OpenUrl(new Uri(HostAddress.GitHubDotComHostAddress.WebUri, GitHubUrls.ForgotPasswordPath)); + // } + //} +} + \ No newline at end of file diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/PullRequestCreationViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/PullRequestCreationViewModelTests.cs new file mode 100644 index 0000000000..c15cf47e99 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/PullRequestCreationViewModelTests.cs @@ -0,0 +1,212 @@ +using System; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using Octokit; +using UnitTests; +using NUnit.Framework; +using IConnection = GitHub.Models.IConnection; + +/// <summary> +/// All the tests in this class are split in subclasses so that when they run +/// in parallel the temp dir is set up uniquely for each test +/// </summary> + +public class PullRequestCreationViewModelTests : TestBaseClass +{ + static LibGit2Sharp.IRepository SetupLocalRepoMock(IGitClient gitClient, IGitService gitService, string remote, string head, bool isTracking) + { + var l2remote = Substitute.For<LibGit2Sharp.Remote>(); + l2remote.Name.Returns(remote); + gitClient.GetHttpRemote(Args.LibGit2Repo, Args.String).Returns(Task.FromResult(l2remote)); + + var l2repo = Substitute.For<LibGit2Sharp.IRepository>(); + var l2branchcol = Substitute.For<LibGit2Sharp.BranchCollection>(); + var l2branch = Substitute.For<LibGit2Sharp.Branch>(); + l2branch.FriendlyName.Returns(head); + l2branch.IsTracking.Returns(isTracking); + l2branchcol[Args.String].Returns(l2branch); + l2repo.Branches.Returns(l2branchcol); + l2repo.Head.Returns(l2branch); + gitService.GetRepository(Args.String).Returns(l2repo); + return l2repo; + } + + struct TestData + { + public IServiceProvider ServiceProvider; + public ILocalRepositoryModel ActiveRepo; + public LibGit2Sharp.IRepository L2Repo; + public IRepositoryModel SourceRepo; + public IRepositoryModel TargetRepo; + public IBranch SourceBranch; + public IBranch TargetBranch; + public IGitClient GitClient; + public IGitService GitService; + public INotificationService NotificationService; + public IConnection Connection; + public IApiClient ApiClient; + public IModelService ModelService; + + public IModelServiceFactory GetModelServiceFactory() + { + var result = Substitute.For<IModelServiceFactory>(); + result.CreateAsync(Connection).Returns(ModelService); + result.CreateBlocking(Connection).Returns(ModelService); + return result; + } + } + + static TestData PrepareTestData( + string repoName, string sourceRepoOwner, string sourceBranchName, + string targetRepoOwner, string targetBranchName, + string remote, + bool repoIsFork, bool sourceBranchIsTracking) + { + var serviceProvider = Substitutes.ServiceProvider; + var gitService = serviceProvider.GetGitService(); + var gitClient = Substitute.For<IGitClient>(); + var notifications = Substitute.For<INotificationService>(); + var connection = Substitute.For<IConnection>(); + var api = Substitute.For<IApiClient>(); + var ms = Substitute.For<IModelService>(); + + connection.HostAddress.Returns(HostAddress.Create("https://github.com")); + + // this is the local repo instance that is available via TeamExplorerServiceHolder and friends + var activeRepo = Substitute.For<ILocalRepositoryModel>(); + activeRepo.LocalPath.Returns(""); + activeRepo.Name.Returns(repoName); + activeRepo.CloneUrl.Returns(new UriString("http://github.com/" + sourceRepoOwner + "/" + repoName)); + activeRepo.Owner.Returns(sourceRepoOwner); + + Repository githubRepoParent = null; + if (repoIsFork) + githubRepoParent = CreateRepository(targetRepoOwner, repoName, id: 1); + var githubRepo = CreateRepository(sourceRepoOwner, repoName, id: 2, parent: githubRepoParent); + var sourceBranch = new BranchModel(sourceBranchName, activeRepo); + var sourceRepo = new RemoteRepositoryModel(githubRepo); + var targetRepo = targetRepoOwner == sourceRepoOwner ? sourceRepo : sourceRepo.Parent; + var targetBranch = targetBranchName != targetRepo.DefaultBranch.Name ? new BranchModel(targetBranchName, targetRepo) : targetRepo.DefaultBranch; + + activeRepo.CurrentBranch.Returns(sourceBranch); + api.GetRepository(Args.String, Args.String).Returns(Observable.Return(githubRepo)); + ms.ApiClient.Returns(api); + + // sets up the libgit2sharp repo and branch objects + var l2repo = SetupLocalRepoMock(gitClient, gitService, remote, sourceBranchName, sourceBranchIsTracking); + + return new TestData + { + ServiceProvider = serviceProvider, + ActiveRepo = activeRepo, + L2Repo = l2repo, + SourceRepo = sourceRepo, + SourceBranch = sourceBranch, + TargetRepo = targetRepo, + TargetBranch = targetBranch, + GitClient = gitClient, + GitService = gitService, + NotificationService = notifications, + Connection = connection, + ApiClient = api, + ModelService = ms + }; + } + + [Test] + public async Task TargetBranchDisplayNameIncludesRepoOwnerWhenForkAsync() + { + var data = PrepareTestData("octokit.net", "shana", "master", "octokit", "master", "origin", true, true); + var prservice = new PullRequestService(data.GitClient, data.GitService, Substitute.For<IVSGitExt>(), Substitute.For<IGraphQLClientFactory>(), data.ServiceProvider.GetOperatingSystem(), Substitute.For<IUsageTracker>()); + prservice.GetPullRequestTemplate(data.ActiveRepo).Returns(Observable.Empty<string>()); + var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService); + await vm.InitializeAsync(data.ActiveRepo, data.Connection); + Assert.That("octokit/master", Is.EqualTo(vm.TargetBranch.DisplayName)); + } + + [TestCase("repo-name-1", "source-repo-owner", "source-branch", true, true, "target-repo-owner", "target-branch", "title", null)] + [TestCase("repo-name-2", "source-repo-owner", "source-branch", true, true, "target-repo-owner", "master", "title", "description")] + [TestCase("repo-name-3", "source-repo-owner", "master", true, true, "target-repo-owner", "master", "title", "description")] + [TestCase("repo-name-4", "source-repo-owner", "source-branch", false, true, "source-repo-owner", "target-branch", "title", null)] + [TestCase("repo-name-5", "source-repo-owner", "source-branch", false, true, "source-repo-owner", "master", "title", "description")] + [TestCase("repo-name-6", "source-repo-owner", "source-branch", true, false, "target-repo-owner", "target-branch", "title", null)] + [TestCase("repo-name-7", "source-repo-owner", "source-branch", true, false, "target-repo-owner", "master", "title", "description")] + [TestCase("repo-name-8", "source-repo-owner", "master", true, false, "target-repo-owner", "master", "title", "description")] + [TestCase("repo-name-9", "source-repo-owner", "source-branch", false, false, "source-repo-owner", "target-branch", "title", null)] + [TestCase("repo-name-10", "source-repo-owner", "source-branch", false, false, "source-repo-owner", "master", "title", "description")] + [TestCase("repo-name-11", "source-repo-owner", "source-branch", false, false, "source-repo-owner", "master", null, null)] + public async Task CreatingPRsAsync( + string repoName, string sourceRepoOwner, string sourceBranchName, + bool repoIsFork, bool sourceBranchIsTracking, + string targetRepoOwner, string targetBranchName, + string title, string body) + { + var remote = "origin"; + var data = PrepareTestData(repoName, sourceRepoOwner, sourceBranchName, targetRepoOwner, targetBranchName, "origin", + repoIsFork, sourceBranchIsTracking); + + var targetRepo = data.TargetRepo; + var gitClient = data.GitClient; + var l2repo = data.L2Repo; + var activeRepo = data.ActiveRepo; + var sourceBranch = data.SourceBranch; + var targetBranch = data.TargetBranch; + var ms = data.ModelService; + + var prservice = new PullRequestService(data.GitClient, data.GitService, Substitute.For<IVSGitExt>(), Substitute.For<IGraphQLClientFactory>(), data.ServiceProvider.GetOperatingSystem(), Substitute.For<IUsageTracker>()); + var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService); + await vm.InitializeAsync(data.ActiveRepo, data.Connection); + + // the TargetBranch property gets set to whatever the repo default is (we assume master here), + // so we only set it manually to emulate the user selecting a different target branch + if (targetBranchName != "master") + vm.TargetBranch = new BranchModel(targetBranchName, targetRepo); + + if (title != null) + vm.PRTitle = title; + + // this is optional + if (body != null) + vm.Description = body; + + ms.CreatePullRequest(activeRepo, targetRepo, sourceBranch, targetBranch, Arg.Any<string>(), Arg.Any<string>()) + .Returns(x => + { + var pr = Substitute.For<IPullRequestModel>(); + pr.Base.Returns(new GitReferenceModel("ref", "label", "sha", "https://clone.url")); + return Observable.Return(pr); + }); + + await vm.CreatePullRequest.ExecuteAsync(); + + var unused2 = gitClient.Received().Push(l2repo, sourceBranchName, remote); + if (!sourceBranchIsTracking) + unused2 = gitClient.Received().SetTrackingBranch(l2repo, sourceBranchName, remote); + else + unused2 = gitClient.DidNotReceiveWithAnyArgs().SetTrackingBranch(Args.LibGit2Repo, Args.String, Args.String); + var unused = ms.Received().CreatePullRequest(activeRepo, targetRepo, sourceBranch, targetBranch, title ?? "Source branch", body ?? String.Empty); + } + + [Test] + public async Task TemplateIsUsedIfPresentAsync() + { + var data = PrepareTestData("stuff", "owner", "master", "owner", "master", + "origin", false, true); + + var prservice = Substitute.For<IPullRequestService>(); + prservice.GetPullRequestTemplate(data.ActiveRepo).Returns(Observable.Return("Test PR template")); + + var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService); + await vm.InitializeAsync(data.ActiveRepo, data.Connection); + + Assert.That("Test PR template", Is.EqualTo(vm.Description)); + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/RepositoryCloneViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/RepositoryCloneViewModelTests.cs new file mode 100644 index 0000000000..795b899027 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/RepositoryCloneViewModelTests.cs @@ -0,0 +1,400 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.Validation; +using NSubstitute; +using Rothko; +using NUnit.Framework; +using GitHub.Collections; +using NSubstitute.Core; +using GitHub.Factories; +using GitHub.Primitives; +using GitHub.ViewModels.Dialog; +using System.Diagnostics; + +public class RepositoryCloneViewModelTests +{ + const int Timeout = 2000; + + static RepositoryCloneViewModel GetVM(IModelService modelService, IRepositoryCloneService cloneService, IOperatingSystem os) + { + var connection = Substitute.For<IConnection>(); + connection.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); + var modelServiceFactory = Substitute.For<IModelServiceFactory>(); + modelServiceFactory.CreateAsync(connection).Returns(modelService); + + var vm = new RepositoryCloneViewModel( + modelServiceFactory, + cloneService, + os); + vm.InitializeAsync(connection).Wait(); + return vm; + } + + static ITrackingCollection<IRemoteRepositoryModel> SetupRepositories( + CallInfo callInfo, + IObservable<IRemoteRepositoryModel> repositories) + { + var collection = callInfo.Arg<ITrackingCollection<IRemoteRepositoryModel>>(); + collection.Listen(repositories); + return collection; + } + + static IRemoteRepositoryModel CreateMockRepositoryModel() + { + var result = Substitute.For<IRemoteRepositoryModel>(); + result.Equals(result).Returns(true); + return result; + } + + public class TheLoadRepositoriesCommand : TestBaseClass + { + [Test] + public async Task LoadsRepositoriesAsync() + { + var repos = new IRemoteRepositoryModel[] + { + CreateMockRepositoryModel(), + CreateMockRepositoryModel(), + CreateMockRepositoryModel(), + }; + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repos.ToObservable())); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + var col = (ITrackingCollection<IRemoteRepositoryModel>)vm.Repositories; + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout)); + Assert.That(3, Is.EqualTo(vm.Repositories.Count)); + } + } + + public class TheIsBusyProperty : TestBaseClass + { + [Test] + public async Task StartsTrueBecomesFalseWhenCompletedAsync() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + var col = (ITrackingCollection<IRemoteRepositoryModel>)vm.Repositories; + + Assert.True(vm.IsBusy); + + var done = new ReplaySubject<Unit>(); + done.OnNext(Unit.Default); + done.Subscribe(); + col.Subscribe(t => done?.OnCompleted(), () => { }); + + repoSubject.OnNext(Substitute.For<IRemoteRepositoryModel>()); + repoSubject.OnNext(Substitute.For<IRemoteRepositoryModel>()); + + await done.Timeout(TimeSpan.FromMilliseconds(Timeout)); + done = null; + + Assert.True(vm.IsBusy); + + repoSubject.OnCompleted(); + + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout)); + + // we need to wait slightly because the subscription OnComplete in the model + // runs right after the above await finishes, which means the assert + // gets checked before the flag is set + await Task.Delay(100); + Assert.False(vm.IsBusy); + } + + [Test] + public void IsFalseWhenLoadingReposFailsImmediately() + { + var repoSubject = Observable.Throw<IRemoteRepositoryModel>(new InvalidOperationException("Doh!")); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + Assert.True(vm.LoadingFailed); + Assert.False(vm.IsBusy); + } + } + + public class TheNoRepositoriesFoundProperty : TestBaseClass + { + [Test] + public void IsTrueInitially() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + + var connection = Substitute.For<IConnection>(); + connection.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); + + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + + var modelServiceFactory = Substitute.For<IModelServiceFactory>(); + modelServiceFactory.CreateAsync(connection).Returns(modelService); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + + var vm = new RepositoryCloneViewModel( + modelServiceFactory, + cloneService, + Substitute.For<IOperatingSystem>()); + + Assert.False(vm.LoadingFailed); + Assert.True(vm.NoRepositoriesFound); + } + + [Test] + public async Task IsFalseWhenLoadingAndCompletedWithRepositoryAsync() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + repoSubject.OnNext(Substitute.For<IRemoteRepositoryModel>()); + + Assert.False(vm.NoRepositoriesFound); + + repoSubject.OnCompleted(); + + var col = (ITrackingCollection<IRemoteRepositoryModel>)vm.Repositories; + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout)); + //Assert.Single(vm.Repositories); + Assert.False(vm.NoRepositoriesFound); + } + + [Test] + public void IsFalseWhenFailed() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + repoSubject.OnError(new InvalidOperationException()); + + Assert.False(vm.NoRepositoriesFound); + } + + [Test] + public async Task IsTrueWhenLoadingCompleteNotFailedAndNoRepositoriesAsync() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + repoSubject.OnCompleted(); + + // we need to delay slightly because the subscribers listening for OnComplete + // need to run before the assert is checked + await Task.Delay(100); + Assert.True(vm.NoRepositoriesFound); + } + } + + public class TheFilterTextEnabledProperty : TestBaseClass + { + [Test] + public void IsTrueInitially() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + var cloneService = Substitute.For<IRepositoryCloneService>(); + + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + Assert.False(vm.LoadingFailed); + Assert.True(vm.FilterTextIsEnabled); + } + + [Test] + public void IsFalseIfLoadingReposFails() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + Assert.False(vm.LoadingFailed); + + repoSubject.OnError(new InvalidOperationException("Doh!")); + + Assert.True(vm.LoadingFailed); + Assert.False(vm.FilterTextIsEnabled); + repoSubject.OnCompleted(); + } + + [Test] + public async Task IsFalseWhenLoadingCompleteNotFailedAndNoRepositoriesAsync() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + repoSubject.OnCompleted(); + + // we need to delay slightly because the subscribers listening for OnComplete + // need to run before the assert is checked + await Task.Delay(100); + Assert.False(vm.FilterTextIsEnabled); + } + } + + public class TheLoadingFailedProperty : TestBaseClass + { + [Test] + public void IsTrueIfLoadingReposFails() + { + var repoSubject = new Subject<IRemoteRepositoryModel>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, repoSubject)); + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + + Assert.False(vm.LoadingFailed); + + repoSubject.OnError(new InvalidOperationException("Doh!")); + + Assert.True(vm.LoadingFailed); + Assert.False(vm.IsBusy); + repoSubject.OnCompleted(); + } + } + + public class TheBaseRepositoryPathValidator + { + [Test] + public void IsInvalidWhenDestinationRepositoryExists() + { + var repo = Substitute.For<IRemoteRepositoryModel>(); + repo.Id.Returns(1); + repo.Name.Returns("bar"); + var data = new[] { repo }.ToObservable(); + + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, data)); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var os = Substitute.For<IOperatingSystem>(); + var directories = Substitute.For<IDirectoryFacade>(); + os.Directory.Returns(directories); + directories.Exists(@"c:\foo\bar").Returns(true); + var vm = GetVM( + modelService, + cloneService, + os); + + vm.BaseRepositoryPath = @"c:\foo"; + vm.SelectedRepository = repo; + + Assert.That(ValidationStatus.Invalid, Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Status)); + } + } + + public class TheCloneCommand : TestBaseClass + { + [Test] + public void IsEnabledWhenRepositorySelectedAndPathValid() + { + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, Observable.Empty<IRemoteRepositoryModel>())); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + Assert.False(vm.CloneCommand.CanExecute(null)); + + vm.BaseRepositoryPath = @"c:\fake\path"; + vm.SelectedRepository = Substitute.For<IRemoteRepositoryModel>(); + + Assert.True(vm.CloneCommand.CanExecute(null)); + } + + [Test] + public void IsNotEnabledWhenPathIsNotValid() + { + var modelService = Substitute.For<IModelService>(); + modelService.GetRepositories(Arg.Any<ITrackingCollection<IRemoteRepositoryModel>>()) + .Returns(x => SetupRepositories(x, Observable.Empty<IRemoteRepositoryModel>())); + + var cloneService = Substitute.For<IRepositoryCloneService>(); + var vm = GetVM( + modelService, + cloneService, + Substitute.For<IOperatingSystem>()); + vm.BaseRepositoryPath = @"c:|fake\path"; + Assert.False(vm.CloneCommand.CanExecute(null)); + + vm.SelectedRepository = Substitute.For<IRemoteRepositoryModel>(); + + Assert.False(vm.CloneCommand.CanExecute(null)); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/Dialog/RepositoryCreationViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/Dialog/RepositoryCreationViewModelTests.cs new file mode 100644 index 0000000000..6c33d30f40 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/Dialog/RepositoryCreationViewModelTests.cs @@ -0,0 +1,638 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.SampleData; +using GitHub.Services; +using GitHub.ViewModels.Dialog; +using NSubstitute; +using Octokit; +using Rothko; +using UnitTests; +using NUnit.Framework; +using IConnection = GitHub.Models.IConnection; + +public class RepositoryCreationViewModelTests +{ + static object DefaultInstance = new object(); + + static IRepositoryCreationViewModel GetMeAViewModel( + IServiceProvider provider = null, + IRepositoryCreationService creationService = null, + IModelService modelService = null) + { + if (provider == null) + provider = Substitutes.ServiceProvider; + var os = provider.GetOperatingSystem(); + creationService = creationService ?? provider.GetRepositoryCreationService(); + var avatarProvider = provider.GetAvatarProvider(); + var connection = provider.GetConnection(); + connection.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); + var usageTracker = Substitute.For<IUsageTracker>(); + modelService = modelService ?? Substitute.For<IModelService>(); + var factory = GetMeAFactory(modelService); + + var vm = new RepositoryCreationViewModel(factory, os, creationService, usageTracker); + vm.InitializeAsync(connection).Wait(); + return vm; + } + + static IModelServiceFactory GetMeAFactory(IModelService ms) + { + var result = Substitute.For<IModelServiceFactory>(); + result.CreateAsync(null).ReturnsForAnyArgs(ms); + result.CreateBlocking(null).ReturnsForAnyArgs(ms); + return result; + } + + public class TheSafeRepositoryNameProperty : TestBaseClass + { + [Test] + public void IsTheSameAsTheRepositoryNameWhenTheInputIsSafe() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake\"; + vm.RepositoryName = "this-is-bad"; + + Assert.That(vm.RepositoryName, Is.EqualTo(vm.SafeRepositoryName)); + } + + [Test] + public void IsConvertedWhenTheRepositoryNameIsNotSafe() + { + var vm = GetMeAViewModel(); + + vm.RepositoryName = "this is bad"; + + Assert.That("this-is-bad", Is.EqualTo(vm.SafeRepositoryName)); + } + + [Test] + public void IsNullWhenRepositoryNameIsNull() + { + var vm = GetMeAViewModel(); + Assert.Null(vm.SafeRepositoryName); + vm.RepositoryName = "not-null"; + vm.RepositoryName = null; + + Assert.That(vm.SafeRepositoryName, Is.Null); + } + } + + public class TheBrowseForDirectoryCommand : TestBaseClass + { + [Test] + public async Task SetsTheBaseRepositoryPathWhenUserChoosesADirectoryAsync() + { + var provider = Substitutes.ServiceProvider; + var windows = provider.GetOperatingSystem(); + windows.Dialog.BrowseForDirectory(@"c:\fake\dev", Args.String) + .Returns(new BrowseDirectoryResult(@"c:\fake\foo")); + var vm = GetMeAViewModel(provider); + + vm.BaseRepositoryPath = @"c:\fake\dev"; + + await vm.BrowseForDirectory.ExecuteAsync(); + + Assert.That(@"c:\fake\foo", Is.EqualTo(vm.BaseRepositoryPath)); + } + + [Test] + public async Task DoesNotChangeTheBaseRepositoryPathWhenUserDoesNotChooseResultAsync() + { + var provider = Substitutes.ServiceProvider; + var windows = provider.GetOperatingSystem(); + windows.Dialog.BrowseForDirectory(@"c:\fake\dev", Args.String) + .Returns(BrowseDirectoryResult.Failed); + var vm = GetMeAViewModel(provider); + vm.BaseRepositoryPath = @"c:\fake\dev"; + + await vm.BrowseForDirectory.ExecuteAsync(); + + Assert.That(@"c:\fake\dev", Is.EqualTo(vm.BaseRepositoryPath)); + } + } + + public class TheBaseRepositoryPathProperty : TestBaseClass + { + [Test] + public void IsSetFromTheRepositoryCreationService() + { + var repositoryCreationService = Substitute.For<IRepositoryCreationService>(); + repositoryCreationService.DefaultClonePath.Returns(@"c:\fake\default"); + + var vm = GetMeAViewModel(creationService: repositoryCreationService); + + Assert.That(@"c:\fake\default", Is.EqualTo(vm.BaseRepositoryPath)); + } + } + + public class TheBaseRepositoryPathValidatorProperty : TestBaseClass + { + [Test] + public void IsFalseWhenPathEmpty() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = ""; + vm.RepositoryName = "foo"; + + Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That("Please enter a repository path", Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Message)); + } + + [Test] + public void IsFalseWhenPathHasInvalidCharacters() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake!!>\"; + vm.RepositoryName = "foo"; + + Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That("Path contains invalid characters", + Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Message)); + } + + [Test] + public void IsFalseWhenLotsofInvalidCharactersInPath() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake???\sajoisfaoia\afsofsafs::::\"; + vm.RepositoryName = "foo"; + + Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That("Path contains invalid characters", + Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Message)); + } + + [Test] + public void IsValidWhenUserAccidentallyUsesForwardSlashes() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake\sajoisfaoia/afsofsafs/"; + vm.RepositoryName = "foo"; + + Assert.True(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + } + + [Test] + public void IsFalseWhenPathIsNotRooted() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = "fake"; + vm.RepositoryName = "foo"; + + Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That("Please enter a valid path", Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Message)); + } + + [Test] + public void IsFalseWhenAfterBeingTrue() + { + var vm = GetMeAViewModel(); + vm.BaseRepositoryPath = @"c:\fake\"; + vm.RepositoryName = "repo"; + + Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That(vm.RepositoryNameValidator.ValidationResult.Message, Is.Empty); + + vm.BaseRepositoryPath = ""; + + Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That("Please enter a repository path", Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Message)); + } + + [Test] + public void IsTrueWhenRepositoryNameAndPathIsValid() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake\"; + vm.RepositoryName = "thisisfine"; + + Assert.True(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That(vm.BaseRepositoryPathValidator.ValidationResult.Message, Is.Empty); + } + + [Test] + public void IsTrueWhenSetToValidQuotedPath() + { + var vm = GetMeAViewModel(); + + vm.RepositoryName = "thisisfine"; + vm.BaseRepositoryPath = @"""c:\fake"""; + + Assert.True(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That(@"c:\fake", Is.EqualTo(vm.BaseRepositoryPath)); + } + + [Test] + public void ReturnsCorrectMessageWhenPathTooLong() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"C:\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"; + + Assert.False(vm.BaseRepositoryPathValidator.ValidationResult.IsValid); + Assert.That("Path too long", Is.EqualTo(vm.BaseRepositoryPathValidator.ValidationResult.Message)); + } + } + + public class TheRepositoryNameValidatorProperty : TestBaseClass + { + [Test] + public void IsFalseWhenRepoNameEmpty() + { + var vm = GetMeAViewModel(); + vm.BaseRepositoryPath = @"c:\fake\"; + + vm.RepositoryName = ""; + + Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That("Please enter a repository name", Is.EqualTo(vm.RepositoryNameValidator.ValidationResult.Message)); + } + + [Test] + public void IsFalseWhenAfterBeingTrue() + { + var vm = GetMeAViewModel(); + vm.BaseRepositoryPath = @"c:\fake\"; + vm.RepositoryName = "repo"; + + Assert.True(vm.CreateRepository.CanExecute(null)); + Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That(vm.RepositoryNameValidator.ValidationResult.Message, Is.Empty); + + vm.RepositoryName = ""; + + Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That("Please enter a repository name", Is.EqualTo(vm.RepositoryNameValidator.ValidationResult.Message)); + } + + [Test] + public void IsTrueWhenRepositoryNameAndPathIsValid() + { + var vm = GetMeAViewModel(); + + vm.RepositoryName = "thisisfine"; + + Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That(vm.RepositoryNameValidator.ValidationResult.Message, Is.Empty); + } + + [TestCase(true, false)] + [TestCase(false, true)] + public void IsFalseWhenRepositoryAlreadyExists(bool exists, bool expected) + { + var provider = Substitutes.ServiceProvider; + var operatingSystem = provider.GetOperatingSystem(); + operatingSystem.Directory.Exists(@"c:\fake\foo").Returns(exists); + var vm = GetMeAViewModel(provider); + vm.BaseRepositoryPath = @"c:\fake\"; + + vm.RepositoryName = "foo"; + + Assert.That(expected, Is.EqualTo(vm.RepositoryNameValidator.ValidationResult.IsValid)); + if (!expected) + Assert.That("Repository with same name already exists at this location", + Is.EqualTo(vm.RepositoryNameValidator.ValidationResult.Message)); + } + } + + public class TheSafeRepositoryNameWarningValidatorProperty : TestBaseClass + { + [Test] + public void IsTrueWhenRepoNameIsSafe() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake\"; + vm.RepositoryName = "this-is-bad"; + + Assert.True(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + } + + [Test] + public void IsFalseWhenRepoNameIsNotSafe() + { + var vm = GetMeAViewModel(); + + vm.BaseRepositoryPath = @"c:\fake\"; + vm.RepositoryName = "this is bad"; + + Assert.False(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + Assert.That("Will be created as this-is-bad", Is.EqualTo(vm.SafeRepositoryNameWarningValidator.ValidationResult.Message)); + } + } + + public class TheAccountsProperty : TestBaseClass + { + [Test] + public async Task IsPopulatedByTheRepositoryHosAsynct() + { + var accounts = new List<IAccount> { new AccountDesigner(), new AccountDesigner() }; + var connection = Substitute.For<IConnection>(); + var modelService = Substitute.For<IModelService>(); + connection.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); + modelService.GetAccounts().Returns(Observable.Return(accounts)); + var vm = new RepositoryCreationViewModel( + GetMeAFactory(modelService), + Substitute.For<IOperatingSystem>(), + Substitute.For<IRepositoryCreationService>(), + Substitute.For<IUsageTracker>()); + await vm.InitializeAsync(connection); + + Assert.That(vm.Accounts[0], Is.EqualTo(vm.SelectedAccount)); + Assert.That(2, Is.EqualTo(vm.Accounts.Count)); + } + } + + public class TheGitIgnoreTemplatesProperty : TestBaseClass + { + [Test] + public async Task IsPopulatedByTheApiAndSortedWithRecommendedFirstAsync() + { + var gitIgnoreTemplates = new[] + { + "VisualStudio", + "Node", + "Waf", + "WordPress" + }.Select(GitIgnoreItem.Create); + var provider = Substitutes.ServiceProvider; + var modelService = Substitute.For<IModelService>(); + modelService + .GetGitIgnoreTemplates() + .Returns(gitIgnoreTemplates.ToObservable()); + var vm = GetMeAViewModel(provider, modelService: modelService); + + // this is how long the default collection waits to process about 5 things with the default UI settings + await Task.Delay(100); + + var result = vm.GitIgnoreTemplates; + + Assert.That(5, Is.EqualTo(result.Count)); + Assert.That("None", Is.EqualTo(result[0].Name)); + Assert.True(result[0].Recommended); + Assert.That("VisualStudio", Is.EqualTo(result[1].Name)); + Assert.True(result[1].Recommended); + Assert.That("Node", Is.EqualTo(result[2].Name)); + Assert.True(result[2].Recommended); + Assert.That("Waf", Is.EqualTo(result[3].Name)); + Assert.False(result[3].Recommended); + Assert.That("WordPress", Is.EqualTo(result[4].Name)); + Assert.False(result[4].Recommended); + } + } + + public class TheLicensesProperty : TestBaseClass + { + [Test] + public async Task IsPopulatedByTheModelServiceAsync() + { + var licenses = new[] + { + new LicenseItem("apache-2.0", "Apache License 2.0"), + new LicenseItem("mit", "MIT License"), + new LicenseItem("agpl-3.0", "GNU Affero GPL v3.0"), + new LicenseItem("artistic-2.0", "Artistic License 2.0") + }; + var provider = Substitutes.ServiceProvider; + var modelService = Substitute.For<IModelService>(); + modelService + .GetLicenses() + .Returns(licenses.ToObservable()); + var vm = GetMeAViewModel(provider, modelService: modelService); + + // this is how long the default collection waits to process about 5 things with the default UI settings + await Task.Delay(100); + + var result = vm.Licenses; + + Assert.That(5, Is.EqualTo(result.Count)); + Assert.That("", Is.EqualTo(result[0].Key)); + Assert.That("None", Is.EqualTo(result[0].Name)); + Assert.True(result[0].Recommended); + Assert.That("apache-2.0", Is.EqualTo(result[1].Key)); + Assert.True(result[1].Recommended); + Assert.That("mit", Is.EqualTo(result[2].Key)); + Assert.True(result[2].Recommended); + Assert.That("agpl-3.0", Is.EqualTo(result[3].Key)); + Assert.False(result[3].Recommended); + Assert.That("artistic-2.0", Is.EqualTo(result[4].Key)); + Assert.False(result[4].Recommended); + Assert.That(result[0], Is.EqualTo(vm.SelectedLicense)); + } + } + + public class TheSelectedGitIgnoreProperty : TestBaseClass + { + [Test] + public async Task DefaultsToVisualStudioAsync() + { + var gitignores = new[] + { + GitIgnoreItem.Create("C++"), + GitIgnoreItem.Create("Node"), + GitIgnoreItem.Create("VisualStudio"), + }; + var provider = Substitutes.ServiceProvider; + var modelService = Substitute.For<IModelService>(); + modelService + .GetGitIgnoreTemplates() + .Returns(gitignores.ToObservable()); + var vm = GetMeAViewModel(provider, modelService: modelService); + + // this is how long the default collection waits to process about 5 things with the default UI settings + await Task.Delay(100); + + Assert.That("VisualStudio", Is.EqualTo(vm.SelectedGitIgnoreTemplate.Name)); + } + + [Test] + public void DefaultsToNoneIfVisualStudioIsMissingSomehow() + { + var gitignores = new[] + { + GitIgnoreItem.None, + GitIgnoreItem.Create("C++"), + GitIgnoreItem.Create("Node"), + }; + var provider = Substitutes.ServiceProvider; + var modelService = Substitute.For<IModelService>(); + modelService + .GetGitIgnoreTemplates() + .Returns(gitignores.ToObservable()); + var vm = GetMeAViewModel(provider, modelService: modelService); + + Assert.That("None", Is.EqualTo(vm.SelectedGitIgnoreTemplate.Name)); + } + } + + public class TheCreateRepositoryCommand : TestBaseClass + { + [Test] + public async Task DisplaysUserErrorWhenCreationFailsAsync() + { + var creationService = Substitutes.RepositoryCreationService; + var provider = Substitutes.GetServiceProvider(creationService: creationService); + + creationService.CreateRepository(Args.NewRepository, Args.Account, Args.String, Args.ApiClient) + .Returns(Observable.Throw<Unit>(new InvalidOperationException("Could not create a repository on GitHub"))); + var vm = GetMeAViewModel(provider); + + vm.RepositoryName = "my-repo"; + + using (var handlers = ReactiveTestHelper.OverrideHandlersForTesting()) + { + await vm.CreateRepository.ExecuteAsync().Catch(Observable.Return(Unit.Default)); + + Assert.That("Could not create a repository on GitHub", Is.EqualTo(handlers.LastError.ErrorMessage)); + } + } + + [Test] + public void CreatesARepositoryUsingTheCreationService() + { + var creationService = Substitutes.RepositoryCreationService; + var provider = Substitutes.GetServiceProvider(creationService: creationService); + + var account = Substitute.For<IAccount>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetAccounts().Returns(Observable.Return(new List<IAccount> { account })); + var vm = GetMeAViewModel(provider, modelService: modelService); + vm.RepositoryName = "Krieger"; + vm.BaseRepositoryPath = @"c:\dev"; + vm.SelectedAccount = account; + vm.KeepPrivate = true; + + vm.CreateRepository.Execute(null); + + creationService + .Received() + .CreateRepository( + Arg.Is<NewRepository>(r => r.Name == "Krieger" + && r.Private == true + && r.AutoInit == null + && r.LicenseTemplate == null + && r.GitignoreTemplate == null), + account, + @"c:\dev", + Args.ApiClient); + } + + [Test] + public void SetsAutoInitToTrueWhenLicenseSelected() + { + var creationService = Substitutes.RepositoryCreationService; + var provider = Substitutes.GetServiceProvider(creationService: creationService); + var account = Substitute.For<IAccount>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetAccounts().Returns(Observable.Return(new List<IAccount> { account })); + var vm = GetMeAViewModel(provider, modelService: modelService); + vm.RepositoryName = "Krieger"; + vm.BaseRepositoryPath = @"c:\dev"; + vm.SelectedAccount = account; + vm.KeepPrivate = false; + vm.SelectedLicense = new LicenseItem("mit", "MIT"); + + vm.CreateRepository.Execute(null); + + creationService + .Received() + .CreateRepository( + Arg.Is<NewRepository>(r => r.Name == "Krieger" + && r.Private == false + && r.AutoInit == true + && r.LicenseTemplate == "mit" + && r.GitignoreTemplate == null), + account, + @"c:\dev", + Args.ApiClient); + } + + [Test] + public void SetsAutoInitToTrueWhenGitIgnore() + { + var creationService = Substitutes.RepositoryCreationService; + var provider = Substitutes.GetServiceProvider(creationService: creationService); + var account = Substitute.For<IAccount>(); + var modelService = Substitute.For<IModelService>(); + modelService.GetAccounts().Returns(Observable.Return(new List<IAccount> { account })); + var vm = GetMeAViewModel(provider, modelService: modelService); + vm.RepositoryName = "Krieger"; + vm.BaseRepositoryPath = @"c:\dev"; + vm.SelectedAccount = account; + vm.KeepPrivate = false; + vm.SelectedGitIgnoreTemplate = GitIgnoreItem.Create("VisualStudio"); + + vm.CreateRepository.Execute(null); + + creationService + .Received() + .CreateRepository( + Arg.Is<NewRepository>(r => r.Name == "Krieger" + && r.Private == false + && r.AutoInit == true + && r.LicenseTemplate == null + && r.GitignoreTemplate == "VisualStudio"), + account, + @"c:\dev", + Args.ApiClient); + } + + [TestCase("", "", false)] + [TestCase("", @"c:\dev", false)] + [TestCase("blah", @"c:\|dev", false)] + [TestCase("blah", @"c:\dev", true)] + public void CannotCreateWhenRepositoryNameOrBasePathIsInvalid( + string repositoryName, + string baseRepositoryPath, + bool expected) + { + var vm = GetMeAViewModel(); + vm.RepositoryName = repositoryName; + vm.BaseRepositoryPath = baseRepositoryPath; + var reactiveCommand = vm.CreateRepository as ReactiveUI.ReactiveCommand<Unit>; + + bool result = reactiveCommand.CanExecute(null); + + Assert.That(expected, Is.EqualTo(result)); + } + } + + public class TheCanKeepPrivateProperty : TestBaseClass + { + [TestCase(true, false, false, false)] + [TestCase(true, false, true, false)] + [TestCase(false, false, true, false)] + [TestCase(true, true, true, true)] + [TestCase(false, false, false, true)] + public void IsOnlyTrueWhenUserIsEntepriseOrNotOnFreeAccountThatIsNotMaxedOut( + bool isFreeAccount, + bool isEnterprise, + bool isMaxedOut, + bool expected) + { + var selectedAccount = Substitute.For<IAccount>(); + selectedAccount.IsOnFreePlan.Returns(isFreeAccount); + selectedAccount.IsEnterprise.Returns(isEnterprise); + selectedAccount.HasMaximumPrivateRepositories.Returns(isMaxedOut); + var vm = GetMeAViewModel(); + vm.SelectedAccount = selectedAccount; + + Assert.That(expected, Is.EqualTo(vm.CanKeepPrivate)); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/GitHubPaneViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/GitHubPaneViewModelTests.cs new file mode 100644 index 0000000000..0293d94bd0 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/GitHubPaneViewModelTests.cs @@ -0,0 +1,299 @@ +using System; +using System.ComponentModel.Design; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.ViewModels; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using ReactiveUI; +using NUnit.Framework; + +public class GitHubPaneViewModelTests : TestBaseClass +{ + const string ValidGitHubRepo = "https://github.com/owner/repo"; + const string ValidEnterpriseRepo = "https://enterprise.com/owner/repo"; + + public class TheInitializeMethod + { + [Test] + public async Task NotAGitRepositoryShownWhenNoRepositoryAsync() + { + var te = Substitute.For<ITeamExplorerContext>(); + te.ActiveRepository.Returns(null as ILocalRepositoryModel); + var target = CreateTarget(teamExplorerContext: te); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<INotAGitRepositoryViewModel>()); + } + + [Test] + public async Task NotAGitHubRepositoryShownWhenRepositoryCloneUrlIsNullAsync() + { + var te = CreateTeamExplorerContext(null); + var target = CreateTarget(teamExplorerContext: te); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<INotAGitHubRepositoryViewModel>()); + } + + [Test] + public async Task NotAGitHubRepositoryShownWhenRepositoryIsNotAGitHubInstanceAsync() + { + var te = CreateTeamExplorerContext("https://some.site/foo/bar"); + var target = CreateTarget(teamExplorerContext: te); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<INotAGitHubRepositoryViewModel>()); + } + + [Test] + public async Task NotAGitHubRepositoryShownWhenRepositoryIsADeletedGitHubRepoAsync() + { + var te = CreateTeamExplorerContext("https://github.com/invalid/repo"); + var cm = CreateConnectionManager("https://github.com"); + var target = CreateTarget(teamExplorerContext: te, connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<INotAGitHubRepositoryViewModel>()); + } + + [Test] + public async Task LoggedOutShownWhenNotLoggedInToGitHubAsync() + { + var te = CreateTeamExplorerContext(ValidGitHubRepo); + var cm = CreateConnectionManager("https://enterprise.com"); + var target = CreateTarget(teamExplorerContext: te, connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<ILoggedOutViewModel>()); + } + + [Test] + public async Task LoginFailedShownWhenConnectionHasError() + { + var te = CreateTeamExplorerContext(ValidGitHubRepo); + var exception = new Exception(); + var cm = CreateConnectionManager(exception, "https://github.com"); + var target = CreateTarget(teamExplorerContext: te, connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<ILoginFailedViewModel>()); + } + + [Test] + public async Task LoggedOutShownWhenNotLoggedInToEnterpriseAsync() + { + var te = CreateTeamExplorerContext(ValidEnterpriseRepo); + var cm = CreateConnectionManager("https://github.com"); + var target = CreateTarget(teamExplorerContext: te, connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<ILoggedOutViewModel>()); + } + + [Test] + public async Task NavigatorShownWhenRepositoryIsAGitHubRepoAsync() + { + var cm = CreateConnectionManager("https://github.com"); + var target = CreateTarget(connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<INavigationViewModel>()); + } + + [Test] + public async Task NavigatorShownWhenRepositoryIsAnEnterpriseRepoAsync() + { + var te = CreateTeamExplorerContext(ValidEnterpriseRepo); + var cm = CreateConnectionManager("https://enterprise.com"); + var target = CreateTarget(teamExplorerContext: te, connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<INavigationViewModel>()); + } + + [Test] + public async Task NavigatorShownWhenUserLogsInAsync() + { + var cm = CreateConnectionManager(); + var target = CreateTarget(connectionManager: cm); + + await InitializeAsync(target); + + Assert.That(target.Content, Is.InstanceOf<ILoggedOutViewModel>()); + + AddConnection(cm, "https://github.com"); + + Assert.That(target.Content, Is.InstanceOf<INavigationViewModel>()); + } + } + + public class TheShowPullRequestsMethod + { + [Test] + public async Task HasNoEffectWhenUserLoggedOutAsync() + { + var viewModelFactory = Substitute.For<IViewViewModelFactory>(); + var target = CreateTarget( + viewModelFactory: viewModelFactory, + connectionManager: CreateConnectionManager()); + + await InitializeAsync(target); + Assert.That(target.Content, Is.InstanceOf<ILoggedOutViewModel>()); + + await target.ShowPullRequests(); + + viewModelFactory.DidNotReceive().CreateViewModel<IPullRequestListViewModel>(); + } + + [Test] + public async Task HasNoEffectWhenAlreadyCurrentPageAsync() + { + var cm = CreateConnectionManager(ValidGitHubRepo); + var nav = new NavigationViewModel(); + var target = CreateTarget( + connectionManager: cm, + navigator: nav); + + await InitializeAsync(target); + Assert.That(nav, Is.SameAs(target.Content)); + Assert.That(nav.Content, Is.InstanceOf<IPullRequestListViewModel>()); + + await target.ShowPullRequests(); + + Assert.That(1, Is.EqualTo(nav.History.Count)); + } + } + + static GitHubPaneViewModel CreateTarget( + IViewViewModelFactory viewModelFactory = null, + ISimpleApiClientFactory apiClientFactory = null, + IConnectionManager connectionManager = null, + ITeamExplorerContext teamExplorerContext = null, + IVisualStudioBrowser browser = null, + IUsageTracker usageTracker = null, + INavigationViewModel navigator = null, + ILoggedOutViewModel loggedOut = null, + INotAGitHubRepositoryViewModel notAGitHubRepository = null, + INotAGitRepositoryViewModel notAGitRepository = null, + ILoginFailedViewModel loginFailed = null) + { + viewModelFactory = viewModelFactory ?? Substitute.For<IViewViewModelFactory>(); + connectionManager = connectionManager ?? Substitute.For<IConnectionManager>(); + teamExplorerContext = teamExplorerContext ?? CreateTeamExplorerContext(ValidGitHubRepo); + browser = browser ?? Substitute.For<IVisualStudioBrowser>(); + usageTracker = usageTracker ?? Substitute.For<IUsageTracker>(); + loggedOut = loggedOut ?? Substitute.For<ILoggedOutViewModel>(); + notAGitHubRepository = notAGitHubRepository ?? Substitute.For<INotAGitHubRepositoryViewModel>(); + notAGitRepository = notAGitRepository ?? Substitute.For<INotAGitRepositoryViewModel>(); + loginFailed = loginFailed ?? Substitute.For<ILoginFailedViewModel>(); + + if (navigator == null) + { + navigator = CreateNavigator(); + navigator.Content.Returns((IPanePageViewModel)null); + } + + if (apiClientFactory == null) + { + var validGitHubRepoClient = Substitute.For<ISimpleApiClient>(); + var validEnterpriseRepoClient = Substitute.For<ISimpleApiClient>(); + var invalidRepoClient = Substitute.For<ISimpleApiClient>(); + + validGitHubRepoClient.GetRepository().Returns(new Octokit.Repository(1)); + validEnterpriseRepoClient.GetRepository().Returns(new Octokit.Repository(1)); + validEnterpriseRepoClient.IsEnterprise().Returns(true); + + apiClientFactory = Substitute.For<ISimpleApiClientFactory>(); + apiClientFactory.Create(null).ReturnsForAnyArgs(invalidRepoClient); + apiClientFactory.Create(ValidGitHubRepo).Returns(validGitHubRepoClient); + apiClientFactory.Create(ValidEnterpriseRepo).Returns(validEnterpriseRepoClient); + } + + return new GitHubPaneViewModel( + viewModelFactory, + apiClientFactory, + connectionManager, + teamExplorerContext, + browser, + usageTracker, + navigator, + loggedOut, + notAGitHubRepository, + notAGitRepository, + loginFailed); + } + + static IConnectionManager CreateConnectionManager(params string[] addresses) + { + return CreateConnectionManager(null, addresses); + } + + static IConnectionManager CreateConnectionManager(Exception loginError, params string[] addresses) + { + var result = Substitute.For<IConnectionManager>(); + var connections = new ObservableCollectionEx<IConnection>(); + + result.Connections.Returns(connections); + result.GetLoadedConnections().Returns(connections); + result.GetConnection(null).ReturnsForAnyArgs(default(IConnection)); + + foreach (var address in addresses) + { + AddConnection(result, address, loginError); + } + + return result; + } + + static void AddConnection(IConnectionManager connectionManager, string address, Exception loginError = null) + { + var connection = Substitute.For<IConnection>(); + var hostAddress = HostAddress.Create(address); + var connections = (ObservableCollectionEx<IConnection>)connectionManager.Connections; + connection.HostAddress.Returns(hostAddress); + connection.IsLoggedIn.Returns(loginError == null); + connection.ConnectionError.Returns(loginError); + connectionManager.GetConnection(hostAddress).Returns(connection); + connections.Add(connection); + } + + static INavigationViewModel CreateNavigator() + { + var result = Substitute.For<INavigationViewModel>(); + result.NavigateBack.Returns(ReactiveCommand.Create()); + result.NavigateForward.Returns(ReactiveCommand.Create()); + return result; + } + + static ITeamExplorerContext CreateTeamExplorerContext(string repositoryCloneUrl) + { + var repository = Substitute.For<ILocalRepositoryModel>(); + repository.CloneUrl.Returns(new UriString(repositoryCloneUrl)); + var result = Substitute.For<ITeamExplorerContext>(); + result.ActiveRepository.Returns(repository); + return result; + } + + static async Task InitializeAsync(GitHubPaneViewModel target) + { + var paneServiceProvider = Substitute.For<IServiceProvider>(); + var menuCommandService = Substitute.For<IMenuCommandService>(); + paneServiceProvider.GetService(typeof(IMenuCommandService)).Returns(menuCommandService); + await target.InitializeAsync(paneServiceProvider); + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/IssueListViewModelBaseTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/IssueListViewModelBaseTests.cs new file mode 100644 index 0000000000..6963c29fdd --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/IssueListViewModelBaseTests.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Collections; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class IssueListViewModelBaseTests : TestBaseClass + { + [Test] + public async Task First_State_Should_Be_Selected() + { + var target = await CreateTargetAndInitialize(); + + Assert.That(target.SelectedState, Is.EqualTo("Open")); + } + + [Test] + public async Task Forks_Should_Be_Empty_If_No_Parent_Repository() + { + var target = await CreateTargetAndInitialize(); + + Assert.That(target.Forks, Is.Null); + } + + [Test] + public async Task Forks_Should_Not_Be_Empty_If_Has_Parent_Repository() + { + var repositoryService = CreateRepositoryService("parent"); + var target = await CreateTargetAndInitialize(repositoryService: repositoryService); + + Assert.That(target.Forks, Is.Not.Null); + Assert.That(target.Forks.Count, Is.EqualTo(2)); + } + + [Test] + public async Task Initializing_Loads_First_Page_Of_Items() + { + var target = await CreateTargetAndInitialize(); + + await target.ItemSource.Received().GetPage(0); + } + + [Test] + public async Task With_Items_Returns_Message_None() + { + var target = await CreateTargetAndInitialize(); + + Assert.That(target.Message, Is.EqualTo(IssueListMessage.None)); + } + + [Test] + public async Task No_Items_No_Filter_Returns_Message_NoOpenItems() + { + var target = await CreateTargetAndInitialize(itemCount: 0); + + Assert.That(target.Message, Is.EqualTo(IssueListMessage.NoOpenItems)); + } + + [Test] + public async Task No_Items_With_SearchQuery_Returns_Message_NoOpenItems() + { + var target = await CreateTargetAndInitialize(itemCount: 0); + target.SearchQuery = "foo"; + + Assert.That(target.Message, Is.EqualTo(IssueListMessage.NoItemsMatchCriteria)); + } + + [Test] + public async Task No_Items_With_Closed_State_Returns_Message_NoOpenItems() + { + var target = await CreateTargetAndInitialize(itemCount: 0); + target.SelectedState = "Closed"; + + Assert.That(target.Message, Is.EqualTo(IssueListMessage.NoItemsMatchCriteria)); + } + + [Test] + public async Task No_Items_With_Author_Filter_Returns_Message_NoOpenItems() + { + var target = await CreateTargetAndInitialize(itemCount: 0); + target.AuthorFilter.Selected = target.AuthorFilter.Users[0]; + + Assert.That(target.Message, Is.EqualTo(IssueListMessage.NoItemsMatchCriteria)); + } + + protected static ILocalRepositoryModel CreateLocalRepository( + string owner = "owner", + string name = "name") + { + var result = Substitute.For<ILocalRepositoryModel>(); + result.CloneUrl.Returns(new UriString($"https://giuthub.com/{owner}/{name}")); + result.Owner.Returns(owner); + result.Name.Returns(name); + return result; + } + + protected static IPullRequestSessionManager CreateSessionManager(PullRequestDetailModel pullRequest = null) + { + pullRequest = pullRequest ?? new PullRequestDetailModel(); + + var session = Substitute.For<IPullRequestSession>(); + session.PullRequest.Returns(pullRequest); + + var result = Substitute.For<IPullRequestSessionManager>(); + result.CurrentSession.Returns(session); + return result; + } + + protected static IPullRequestService CreatePullRequestService(int itemCount = 10) + { + var result = Substitute.For<IPullRequestService>(); + result.ReadPullRequests(null, null, null, null, null).ReturnsForAnyArgs( + new Page<PullRequestListItemModel> + { + Items = Enumerable.Range(0, itemCount).Select(x => new PullRequestListItemModel + { + Id = "pr" + x, + Number = x + 1, + }).ToList() + }); + return result; + } + + protected static IRepositoryService CreateRepositoryService(string parentOwnerLogin = null) + { + var result = Substitute.For<IRepositoryService>(); + var parent = parentOwnerLogin != null ? (parentOwnerLogin, "name") : ((string, string)?)null; + result.FindParent(null, null, null).ReturnsForAnyArgs(parent); + return result; + } + + static Target CreateTarget(IRepositoryService repositoryService = null, int itemCount = 1000) + { + repositoryService = repositoryService ?? CreateRepositoryService(); + return new Target(repositoryService, itemCount); + } + + static async Task<Target> CreateTargetAndInitialize( + IRepositoryService repositoryService = null, + ILocalRepositoryModel repository = null, + IConnection connection = null, + int itemCount = 1000) + { + repository = repository ?? CreateLocalRepository(); + connection = connection ?? Substitute.For<IConnection>(); + + var target = CreateTarget(repositoryService, itemCount); + await target.InitializeAsync(repository, connection); + return target; + } + + class Target : IssueListViewModelBase + { + public Target(IRepositoryService repositoryService, int itemCount) + : base(repositoryService) + { + ItemSource = Substitute.For<IVirtualizingListSource<IIssueListItemViewModelBase>>(); + ItemSource.GetCount().Returns(itemCount); + ItemSource.PageSize.Returns(100); + } + + public IVirtualizingListSource<IIssueListItemViewModelBase> ItemSource { get; } + + public override IReadOnlyList<string> States { get; } = new[] { "Open", "Closed" }; + + protected override IVirtualizingListSource<IIssueListItemViewModelBase> CreateItemSource() => ItemSource; + + protected override Task DoOpenItem(IIssueListItemViewModelBase item) + { + throw new NotImplementedException(); + } + + protected override Task<Page<ActorModel>> LoadAuthors(string after) + { + return Task.FromResult(new Page<ActorModel> + { + Items = new[] + { + new ActorModel { Login = "grokys" }, + new ActorModel { Login = "jcansdale" }, + }, + }); + } + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/NavigationViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/NavigationViewModelTests.cs new file mode 100644 index 0000000000..7db3f5f844 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/NavigationViewModelTests.cs @@ -0,0 +1,451 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +public class NavigationViewModelTests +{ + public class TheContentProperty + { + [Test] + public void ContentShouldInitiallyBeNull() + { + var target = new NavigationViewModel(); + + Assert.That(target.Content, Is.Null); + } + + [Test] + public void ContentShouldBeSetOnNavigatingToPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + Assert.That(first, Is.EqualTo(target.Content)); + + target.NavigateTo(second); + Assert.That(second, Is.SameAs(target.Content)); + } + + [Test] + public void ContentShouldBeSetOnNavigatingBack() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + + Assert.That(first, Is.SameAs(target.Content)); + } + + [Test] + public void ContentShouldBeSetOnNavigatingForward() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + target.Forward(); + + Assert.That(second, Is.SameAs(target.Content)); + } + + [Test] + public void ContentShouldBeSetWhenReplacingFuture() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + var third = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + target.NavigateTo(third); + + Assert.That(third, Is.EqualTo(target.Content)); + + target.Back(); + + Assert.That(first, Is.EqualTo(target.Content)); + } + } + + public class TheForwardAndBackCommands + { + [Test] + public void ForwardAndBackCommandsShouldInitiallyBeDisabled() + { + var target = new NavigationViewModel(); + + Assert.False(target.NavigateBack.CanExecute(null)); + Assert.False(target.NavigateForward.CanExecute(null)); + } + + [Test] + public void ForwardAndBackCommandsShouldBeDisabledOnNavigatingToFirstPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + + target.NavigateTo(first); + + Assert.False(target.NavigateBack.CanExecute(null)); + Assert.False(target.NavigateForward.CanExecute(null)); + } + + [Test] + public void BackCommandShouldBeEnabledOnNavigatingToSecondPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + + Assert.True(target.NavigateBack.CanExecute(null)); + Assert.False(target.NavigateForward.CanExecute(null)); + } + + [Test] + public void ForwardCommandShouldBeEnabledOnNavigatingBack() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + + Assert.False(target.NavigateBack.CanExecute(null)); + Assert.True(target.NavigateForward.CanExecute(null)); + } + + [Test] + public void BackShouldCallActivatedOnNewPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + + first.ClearReceivedCalls(); + target.Back(); + + first.Received(1).Activated(); + } + + [Test] + public void BackShouldCallDeactivatedOnOldPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + + second.ClearReceivedCalls(); + target.Back(); + + second.Received(1).Deactivated(); + } + + [Test] + public void ForwardShouldCallActivatedOnNewPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + + second.ClearReceivedCalls(); + target.Forward(); + + second.Received(1).Activated(); + } + + [Test] + public void ForwardShouldCallDeactivatedOnOldPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + + first.ClearReceivedCalls(); + target.Forward(); + + first.Received(1).Deactivated(); + } + } + + public class TheNavigateToMethod + { + [Test] + public void ShouldCallActivatedOnNewPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + + target.NavigateTo(first); + + first.Received(1).Activated(); + } + + [Test] + public void ShouldCallDeactivatedOnOldPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + first.ClearReceivedCalls(); + target.NavigateTo(second); + + first.Received(1).Deactivated(); + } + + [Test] + public void CloseRequestedShouldRemovePage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + var close = new Subject<Unit>(); + second.CloseRequested.Returns(close); + + target.NavigateTo(first); + target.NavigateTo(second); + close.OnNext(Unit.Default); + + //Assert.Single(target.History); + Assert.That(first, Is.SameAs(target.History[0])); + } + + [Test] + public void NavigatingToExistingPageInForwardHistoryShouldNotDisposePage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + target.NavigateTo(second); + + second.DidNotReceive().Dispose(); + } + } + + public class TheClearMethod + { + [Test] + public void ClearsTheContentAndHistory() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Clear(); + + Assert.That(target.Content, Is.Null); + Assert.False(target.NavigateBack.CanExecute(null)); + Assert.False(target.NavigateForward.CanExecute(null)); + } + + [Test] + public void DisposesPages() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var disposed = false; + + first.When(x => x.Dispose()).Do(_ => disposed = true); + + target.NavigateTo(first); + target.Clear(); + + Assert.True(disposed); + } + + [Test] + public void CallsDeactivatedAndThenDisposedOnPages() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + + target.NavigateTo(first); + target.Clear(); + + Received.InOrder(() => + { + first.Deactivated(); + first.Dispose(); + }); + } + + [Test] + public void DoesntThrowWhenHistoryHasMoreThan10Items() + { + var target = new NavigationViewModel(); + var pages = new List<IPanePageViewModel>(); + + for (var i = 0; i < 11; ++i) + { + var page = CreatePage(); + pages.Add(page); + target.NavigateTo(page); + } + + foreach (var page in pages) + { + page.ClearReceivedCalls(); + } + + target.Clear(); + + pages.Last().Received().Deactivated(); + pages.Last().Received().Dispose(); + + foreach (var page in pages.Take(pages.Count - 1)) + { + page.DidNotReceive().Deactivated(); + page.Received().Dispose(); + } + } + } + + public class TheRemoveMethod + { + [Test] + public void RemovesAllInstancesOfPage() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.NavigateTo(second); + target.NavigateTo(second); + target.RemoveAll(second); + + //Assert.Single(target.History); + } + + [Test] + public void RemovingItemAfterCurrentWorks() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.Back(); + target.RemoveAll(second); + + Assert.That(first, Is.SameAs(target.Content)); + Assert.That(first, Is.SameAs(target.History[0])); + Assert.That(0, Is.EqualTo(target.Index)); + //Assert.Single(target.History); + } + + [Test] + public void RemovingCurrentItemSetsContentToPrevious() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var second = CreatePage(); + + target.NavigateTo(first); + target.NavigateTo(second); + target.RemoveAll(second); + + Assert.That(first, Is.SameAs(target.Content)); + Assert.That(first, Is.SameAs(target.History[0])); + Assert.That(0, Is.EqualTo(target.Index)); + //Assert.Single(target.History); + } + + [Test] + public void RemovingOnlyItemWorks() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + + target.NavigateTo(first); + target.RemoveAll(first); + + Assert.That(target.Content, Is.Null); + Assert.That(target.History, Is.Empty); + Assert.That(-1, Is.EqualTo(target.Index)); + } + + [Test] + public void RemovingItemCallsDispose() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + var disposed = false; + + first.When(x => x.Dispose()).Do(_ => disposed = true); + + target.NavigateTo(first); + target.RemoveAll(first); + + Assert.True(disposed); + } + + [Test] + public void CallsDeactivatedAndThenDisposedOnPages() + { + var target = new NavigationViewModel(); + var first = CreatePage(); + + target.NavigateTo(first); + target.RemoveAll(first); + + Received.InOrder(() => + { + first.Deactivated(); + first.Dispose(); + }); + } + } + + static IPanePageViewModel CreatePage() + { + var result = Substitute.For<IPanePageViewModel>(); + result.CloseRequested.Returns(Observable.Never<Unit>()); + return result; + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs new file mode 100644 index 0000000000..50df1cba03 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestDetailViewModelTests.cs @@ -0,0 +1,646 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Commands; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using LibGit2Sharp; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestDetailViewModelTests + { + static readonly Uri Uri = new Uri("http://foo"); + + public class TheBodyProperty + { + [Test] + public async Task ShouldUsePlaceholderBodyIfNoneExistsAsync() + { + var target = CreateTarget(); + + await target.Load(CreatePullRequestModel(body: string.Empty)); + + Assert.That("*No description provided.*", Is.EqualTo(target.Body)); + } + } + + public class TheSourceBranchDisplayNameProperty : TestBaseClass + { + [Test] + public async Task ShouldAcceptNullHeadAsync() + { + var target = CreateTarget(); + var model = CreatePullRequestModel(); + + // PullRequest.HeadRepositoryOwner can be null if a user deletes the repository after creating the PR. + model.HeadRepositoryOwner = null; + + await target.Load(model); + + Assert.That("[invalid]", Is.EqualTo(target.SourceBranchDisplayName)); + } + } + + public class TheReviewsProperty : TestBaseClass + { + [Test] + public async Task ShouldShowLatestAcceptedOrChangesRequestedReviewAsync() + { + var dateTimeOffset = DateTimeOffset.Now; + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel("1", "grokys", PullRequestReviewState.ChangesRequested, dateTimeOffset.AddMinutes(1)), + CreatePullRequestReviewModel("2", "shana", PullRequestReviewState.ChangesRequested, dateTimeOffset.AddMinutes(2)), + CreatePullRequestReviewModel("3", "grokys", PullRequestReviewState.Approved, dateTimeOffset.AddMinutes(3)), + CreatePullRequestReviewModel("4", "grokys", PullRequestReviewState.Commented, dateTimeOffset.AddMinutes(4))); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(3)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[1].User.Login, Is.EqualTo("shana")); + Assert.That(target.Reviews[2].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo("3")); + Assert.That(target.Reviews[1].Id, Is.EqualTo("2")); + Assert.That(target.Reviews[2].Id, Is.Null); + } + + [Test] + public async Task ShouldShowLatestCommentedReviewIfNothingElsePresentAsync() + { + var dateTimeOffset = DateTimeOffset.Now; + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel("1", "shana", PullRequestReviewState.Commented, dateTimeOffset.AddMinutes(1)), + CreatePullRequestReviewModel("2", "shana", PullRequestReviewState.Commented, dateTimeOffset.AddMinutes(2))); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(2)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("shana")); + Assert.That(target.Reviews[1].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo("2")); + } + + [Test] + public async Task ShouldNotShowStartNewReviewWhenHasPendingReviewAsync() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel("1", "grokys", PullRequestReviewState.Pending)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(1)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo("1")); + } + + [Test] + public async Task ShouldShowPendingReviewOverApprovedAsync() + { + var dateTimeOffset = DateTimeOffset.Now; + + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel("1", "grokys", PullRequestReviewState.Approved, dateTimeOffset.AddMinutes(1)), + CreatePullRequestReviewModel("2", "grokys", PullRequestReviewState.Pending)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(1)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.EqualTo("2")); + } + + [Test] + public async Task ShouldNotShowPendingReviewForOtherUserAsync() + { + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel("1", "shana", PullRequestReviewState.Pending)); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(1)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("grokys")); + Assert.That(target.Reviews[0].Id, Is.Null); + } + + [Test] + public async Task ShouldNotShowChangesRequestedAfterDismissed() + { + var dateTimeOffset = DateTimeOffset.Now; + + var target = CreateTarget(); + var model = CreatePullRequestModel( + CreatePullRequestReviewModel("1", "shana", PullRequestReviewState.ChangesRequested, dateTimeOffset.AddMinutes(1)), + CreatePullRequestReviewModel("2", "shana", PullRequestReviewState.Dismissed, dateTimeOffset.AddMinutes(2))); + + await target.Load(model); + + Assert.That(target.Reviews, Has.Count.EqualTo(2)); + Assert.That(target.Reviews[0].User.Login, Is.EqualTo("shana")); + Assert.That(target.Reviews[0].State, Is.EqualTo(PullRequestReviewState.Dismissed)); + Assert.That(target.Reviews[1].User.Login, Is.EqualTo("grokys")); + } + + static PullRequestDetailModel CreatePullRequestModel( + params PullRequestReviewModel[] reviews) + { + return PullRequestDetailViewModelTests.CreatePullRequestModel(reviews: reviews); + } + + static PullRequestReviewModel CreatePullRequestReviewModel(string id, + string login, + PullRequestReviewState state, + DateTimeOffset? submittedAt = null) + { + var account = new ActorModel + { + Login = login, + }; + + return new PullRequestReviewModel + { + Id = id, + Author = account, + State = state, + SubmittedAt = submittedAt + }; + } + } + + public class TheCheckoutCommand : TestBaseClass + { + [Test] + public async Task CheckedOutAndUpToDateAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Checkout.CanExecute(null)); + Assert.That(target.CheckoutState, Is.Null); + } + + [Test] + public async Task NotCheckedOutAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Checkout.CanExecute(null)); + Assert.True(target.CheckoutState.IsEnabled); + Assert.That("Checkout pr/123", Is.EqualTo(target.CheckoutState.ToolTip)); + } + + [Test] + public async Task NotCheckedOutWithWorkingDirectoryDirtyAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123", + dirty: true); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Checkout.CanExecute(null)); + Assert.That("Cannot checkout as your working directory has uncommitted changes.", Is.EqualTo(target.CheckoutState.ToolTip)); + } + + [Test] + public async Task CheckoutExistingLocalBranchAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel(number: 123)); + + Assert.True(target.Checkout.CanExecute(null)); + Assert.That("Checkout pr/123", Is.EqualTo(target.CheckoutState.Caption)); + } + + [Test] + public async Task CheckoutNonExistingLocalBranchAsync() + { + var target = CreateTarget( + currentBranch: "master"); + + await target.Load(CreatePullRequestModel(number: 123)); + + Assert.True(target.Checkout.CanExecute(null)); + Assert.That("Checkout to pr/123", Is.EqualTo(target.CheckoutState.Caption)); + } + + [Test] + public async Task UpdatesOperationErrorWithExceptionMessageAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + var pr = CreatePullRequestModel(); + + pr.HeadRepositoryOwner = null; + + await target.Load(pr); + + Assert.False(target.Checkout.CanExecute(null)); + Assert.That("The source repository is no longer available.", Is.EqualTo(target.CheckoutState.ToolTip)); + } + + [Test] + public async Task SetsOperationErrorOnCheckoutFailureAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Checkout.CanExecute(null)); + + Assert.ThrowsAsync<FileNotFoundException>(async () => await target.Checkout.ExecuteAsyncTask()); + + Assert.That("Switch threw", Is.EqualTo(target.OperationError)); + } + + [Test] + public async Task ClearsOperationErrorOnCheckoutSuccessAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Checkout.CanExecute(null)); + Assert.ThrowsAsync<FileNotFoundException>(async () => await target.Checkout.ExecuteAsyncTask()); + Assert.That("Switch threw", Is.EqualTo(target.OperationError)); + + await target.Checkout.ExecuteAsync(); + Assert.That(target.OperationError, Is.Null); + } + + [Test] + public async Task ClearsOperationErrorOnCheckoutRefreshAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Checkout.CanExecute(null)); + Assert.ThrowsAsync<FileNotFoundException>(async () => await target.Checkout.ExecuteAsyncTask()); + Assert.That("Switch threw", Is.EqualTo(target.OperationError)); + + await target.Refresh(); + Assert.That(target.OperationError, Is.Null); + } + } + + public class ThePullCommand : TestBaseClass + { + [Test] + public async Task NotCheckedOutAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Pull.CanExecute(null)); + Assert.That(target.UpdateState, Is.Null); + } + + [Test] + public async Task CheckedOutAndUpToDateAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Pull.CanExecute(null)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("No commits to pull", Is.EqualTo(target.UpdateState.PullToolTip)); + } + + [Test] + public async Task CheckedOutAndBehindAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + behindBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Pull.CanExecute(null)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("Pull from remote branch baz", Is.EqualTo(target.UpdateState.PullToolTip)); + } + + [Test] + public async Task CheckedOutAndAheadAndBehindAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + aheadBy: 3, + behindBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Pull.CanExecute(null)); + Assert.That(3, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("Pull from remote branch baz", Is.EqualTo(target.UpdateState.PullToolTip)); + } + + [Test] + public async Task CheckedOutAndBehindForkAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + prFromFork: true, + behindBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Pull.CanExecute(null)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("Pull from fork branch foo:baz", Is.EqualTo(target.UpdateState.PullToolTip)); + } + + [Test] + public async Task UpdatesOperationErrorWithExceptionMessageAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.ThrowsAsync<FileNotFoundException>(() => target.Pull.ExecuteAsyncTask(null)); + Assert.That("Pull threw", Is.EqualTo(target.OperationError)); + } + } + + public class ThePushCommand : TestBaseClass + { + [Test] + public async Task NotCheckedOutAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Push.CanExecute(null)); + Assert.That(target.UpdateState, Is.Null); + } + + [Test] + public async Task CheckedOutAndUpToDateAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Push.CanExecute(null)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("No commits to push", Is.EqualTo(target.UpdateState.PushToolTip)); + } + + [Test] + public async Task CheckedOutAndAheadAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + aheadBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Push.CanExecute(null)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("Push to remote branch baz", Is.EqualTo(target.UpdateState.PushToolTip)); + } + + [Test] + public async Task CheckedOutAndBehindAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + behindBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Push.CanExecute(null)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("No commits to push", Is.EqualTo(target.UpdateState.PushToolTip)); + } + + [Test] + public async Task CheckedOutAndAheadAndBehindAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + aheadBy: 3, + behindBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.False(target.Push.CanExecute(null)); + Assert.That(3, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("You must pull before you can push", Is.EqualTo(target.UpdateState.PushToolTip)); + } + + [Test] + public async Task CheckedOutAndAheadOfForkAsync() + { + var target = CreateTarget( + currentBranch: "pr/123", + existingPrBranch: "pr/123", + prFromFork: true, + aheadBy: 2); + + await target.Load(CreatePullRequestModel()); + + Assert.True(target.Push.CanExecute(null)); + Assert.That(2, Is.EqualTo(target.UpdateState.CommitsAhead)); + Assert.That(0, Is.EqualTo(target.UpdateState.CommitsBehind)); + Assert.That("Push to fork branch foo:baz", Is.EqualTo(target.UpdateState.PushToolTip)); + } + + [Test] + public async Task UpdatesOperationErrorWithExceptionMessageAsync() + { + var target = CreateTarget( + currentBranch: "master", + existingPrBranch: "pr/123"); + + await target.Load(CreatePullRequestModel()); + + Assert.ThrowsAsync<FileNotFoundException>(() => target.Push.ExecuteAsyncTask(null)); + Assert.That("Push threw", Is.EqualTo(target.OperationError)); + } + } + + static PullRequestDetailViewModel CreateTarget( + string currentBranch = "master", + string existingPrBranch = null, + bool prFromFork = false, + bool dirty = false, + int aheadBy = 0, + int behindBy = 0, + IPullRequestSessionManager sessionManager = null) + { + return CreateTargetAndService( + currentBranch: currentBranch, + existingPrBranch: existingPrBranch, + prFromFork: prFromFork, + dirty: dirty, + aheadBy: aheadBy, + behindBy: behindBy, + sessionManager: sessionManager).Item1; + } + + static Tuple<PullRequestDetailViewModel, IPullRequestService> CreateTargetAndService( + string currentBranch = "master", + string existingPrBranch = null, + bool prFromFork = false, + bool dirty = false, + int aheadBy = 0, + int behindBy = 0, + IPullRequestSessionManager sessionManager = null) + { + var repository = Substitute.For<ILocalRepositoryModel>(); + var currentBranchModel = new BranchModel(currentBranch, repository); + repository.CurrentBranch.Returns(currentBranchModel); + repository.CloneUrl.Returns(new UriString(Uri.ToString())); + repository.LocalPath.Returns(@"C:\projects\ThisRepo"); + repository.Name.Returns("repo"); + + var pullRequestService = Substitute.For<IPullRequestService>(); + + if (existingPrBranch != null) + { + var existingBranchModel = new BranchModel(existingPrBranch, repository); + pullRequestService.GetLocalBranches(repository, Arg.Any<PullRequestDetailModel>()) + .Returns(Observable.Return(existingBranchModel)); + } + else + { + pullRequestService.GetLocalBranches(repository, Arg.Any<PullRequestDetailModel>()) + .Returns(Observable.Empty<IBranch>()); + } + + pullRequestService.Checkout(repository, Arg.Any<PullRequestDetailModel>(), Arg.Any<string>()).Returns(x => Throws("Checkout threw")); + pullRequestService.GetDefaultLocalBranchName(repository, Arg.Any<int>(), Arg.Any<string>()).Returns(x => Observable.Return($"pr/{x[1]}")); + pullRequestService.IsPullRequestFromRepository(repository, Arg.Any<PullRequestDetailModel>()).Returns(!prFromFork); + pullRequestService.IsWorkingDirectoryClean(repository).Returns(Observable.Return(!dirty)); + pullRequestService.Pull(repository).Returns(x => Throws("Pull threw")); + pullRequestService.Push(repository).Returns(x => Throws("Push threw")); + pullRequestService.SwitchToBranch(repository, Arg.Any<PullRequestDetailModel>()) + .Returns( + x => Throws("Switch threw"), + _ => Observable.Return(Unit.Default)); + + var divergence = Substitute.For<BranchTrackingDetails>(); + divergence.AheadBy.Returns(aheadBy); + divergence.BehindBy.Returns(behindBy); + pullRequestService.CalculateHistoryDivergence(repository, Arg.Any<int>()) + .Returns(Observable.Return(divergence)); + + if (sessionManager == null) + { + var currentSession = Substitute.For<IPullRequestSession>(); + currentSession.PullRequest.Returns(CreatePullRequestModel()); + currentSession.User.Returns(new ActorModel { Login = "grokys" }); + + sessionManager = Substitute.For<IPullRequestSessionManager>(); + sessionManager.CurrentSession.Returns(currentSession); + sessionManager.GetSession("owner", "repo", 1).ReturnsForAnyArgs(currentSession); + } + + var vm = new PullRequestDetailViewModel( + pullRequestService, + sessionManager, + Substitute.For<IModelServiceFactory>(), + Substitute.For<IUsageTracker>(), + Substitute.For<ITeamExplorerContext>(), + Substitute.For<IPullRequestFilesViewModel>(), + Substitute.For<ISyncSubmodulesCommand>(), + Substitute.For<IViewViewModelFactory>()); + vm.InitializeAsync(repository, Substitute.For<IConnection>(), "owner", "repo", 1).Wait(); + + return Tuple.Create(vm, pullRequestService); + } + + static PullRequestDetailModel CreatePullRequestModel( + int number = 1, + string body = "PR Body", + IEnumerable<PullRequestReviewModel> reviews = null) + { + var author = Substitute.For<IAccount>(); + + reviews = reviews ?? new PullRequestReviewModel[0]; + + return new PullRequestDetailModel + { + Number = number, + Title = "PR 1", + Author = new ActorModel(), + State = PullRequestStateEnum.Open, + Body = string.Empty, + BaseRefName = "master", + BaseRefSha = "BASE_REF", + HeadRefName = "baz", + HeadRefSha = "HEAD_REF", + HeadRepositoryOwner = "foo", + UpdatedAt = DateTimeOffset.Now, + Reviews = reviews.ToList(), + }; + } + + static IObservable<Unit> Throws(string message) + { + Func<IObserver<Unit>, Action> f = _ => { throw new FileNotFoundException(message); }; + return Observable.Create(f); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs new file mode 100644 index 0000000000..e3a3eac1a0 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestFilesViewModelTests.cs @@ -0,0 +1,127 @@ +using System; +using System.ComponentModel; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestFilesViewModelTests + { + static readonly Uri Uri = new Uri("http://foo"); + + [Test] + public async Task ShouldCreateChangesTreeAsync() + { + var target = CreateTarget(); + var session = CreateSession(); + + session.PullRequest.ChangedFiles = new[] + { + new PullRequestFileModel { FileName = "readme.md", Sha = "abc", Status = PullRequestFileStatus.Modified }, + new PullRequestFileModel { FileName = "dir1/f1.cs", Sha = "abc", Status = PullRequestFileStatus.Modified }, + new PullRequestFileModel { FileName = "dir1/f2.cs", Sha = "abc", Status = PullRequestFileStatus.Modified }, + new PullRequestFileModel { FileName = "dir1/dir1a/f3.cs", Sha = "abc", Status = PullRequestFileStatus.Modified }, + new PullRequestFileModel { FileName = "dir2/f4.cs", Sha = "abc", Status = PullRequestFileStatus.Modified }, + }; + + await target.InitializeAsync(session); + + Assert.That(target.Items.Count, Is.EqualTo(3)); + + var dir1 = (PullRequestDirectoryNode)target.Items[0]; + Assert.That(dir1.DirectoryName, Is.EqualTo("dir1")); + Assert.That(dir1.Files, Has.Exactly(2).Items); + + Assert.That(dir1.Directories, Has.One.Items); + Assert.That(dir1.Files[0].FileName, Is.EqualTo("f1.cs")); + Assert.That(dir1.Files[1].FileName, Is.EqualTo("f2.cs")); + Assert.That(dir1.Files[0].RelativePath, Is.EqualTo("dir1\\f1.cs")); + Assert.That(dir1.Files[1].RelativePath, Is.EqualTo("dir1\\f2.cs")); + + var dir1a = (PullRequestDirectoryNode)dir1.Directories[0]; + Assert.That(dir1a.DirectoryName, Is.EqualTo("dir1a")); + Assert.That(dir1a.Files, Has.One.Items); + Assert.That(dir1a.Directories, Is.Empty); + + var dir2 = (PullRequestDirectoryNode)target.Items[1]; + Assert.That(dir2.DirectoryName, Is.EqualTo("dir2")); + Assert.That(dir2.Files, Has.One.Items); + Assert.That(dir2.Directories, Is.Empty); + + var readme = (PullRequestFileNode)target.Items[2]; + Assert.That(readme.FileName, Is.EqualTo("readme.md")); + } + + [Test] + public async Task FileCommentCountShouldTrackSessionInlineCommentsAsync() + { + var outdatedThread = CreateThread(-1); + var session = CreateSession(); + + session.PullRequest.ChangedFiles = new[] + { + new PullRequestFileModel { FileName = "readme.md", Sha = "abc", Status = PullRequestFileStatus.Modified, } + }; + + var file = Substitute.For<IPullRequestSessionFile>(); + var thread1 = CreateThread(5); + var thread2 = CreateThread(6); + file.InlineCommentThreads.Returns(new[] { thread1 }); + session.GetFile("readme.md").Returns(Task.FromResult(file)); + + var target = CreateTarget(); + + await target.InitializeAsync(session); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(1)); + + file.InlineCommentThreads.Returns(new[] { thread1, thread2 }); + RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(2)); + + // Outdated comment is not included in the count. + file.InlineCommentThreads.Returns(new[] { thread1, thread2, outdatedThread }); + RaisePropertyChanged(file, nameof(file.InlineCommentThreads)); + Assert.That(((IPullRequestFileNode)target.Items[0]).CommentCount, Is.EqualTo(2)); + + file.Received(1).PropertyChanged += Arg.Any<PropertyChangedEventHandler>(); + } + + static PullRequestFilesViewModel CreateTarget() + { + var pullRequestService = Substitute.For<IPullRequestService>(); + var editorService = Substitute.For<IPullRequestEditorService>(); + return new PullRequestFilesViewModel(pullRequestService, editorService); + } + + static IPullRequestSession CreateSession() + { + var author = Substitute.For<IAccount>(); + + var repository = Substitute.For<ILocalRepositoryModel>(); + repository.LocalPath.Returns(@"C:\Foo"); + + var result = Substitute.For<IPullRequestSession>(); + result.LocalRepository.Returns(repository); + result.PullRequest.Returns(new PullRequestDetailModel()); + return result; + } + + IInlineCommentThreadModel CreateThread(int lineNumber) + { + var result = Substitute.For<IInlineCommentThreadModel>(); + result.LineNumber.Returns(lineNumber); + return result; + } + + void RaisePropertyChanged<T>(T o, string propertyName) + where T : INotifyPropertyChanged + { + o.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestListViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestListViewModelTests.cs new file mode 100644 index 0000000000..54e026053f --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestListViewModelTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestListViewModelTests : IssueListViewModelBaseTests + { + [Test] + public async Task OpenItem_Navigates_To_Correct_Fork_Url() + { + var repository = CreateLocalRepository(); + var target = await CreateTargetAndInitialize( + repositoryService: CreateRepositoryService("owner"), + repository: CreateLocalRepository("fork", "name")); + + var uri = (Uri)null; + target.NavigationRequested.Subscribe(x => uri = x); + + target.OpenItem.Execute(target.Items[1]); + + Assert.That(uri, Is.EqualTo(new Uri("github://pane/owner/name/pull/2"))); + } + + static PullRequestListViewModel CreateTarget( + IPullRequestSessionManager sessionManager = null, + IRepositoryService repositoryService = null, + IPullRequestService service = null) + { + sessionManager = sessionManager ?? CreateSessionManager(); + repositoryService = repositoryService ?? CreateRepositoryService(); + service = service ?? CreatePullRequestService(); + + return new PullRequestListViewModel( + sessionManager, + repositoryService, + service); + } + + static async Task<PullRequestListViewModel> CreateTargetAndInitialize( + IPullRequestSessionManager sessionManager = null, + IRepositoryService repositoryService = null, + IPullRequestService service = null, + ILocalRepositoryModel repository = null, + IConnection connection = null) + { + var result = CreateTarget(sessionManager, repositoryService, service); + await result.InitializeAsync(repository, connection); + return result; + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs new file mode 100644 index 0000000000..18c708ead2 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModelTests.cs @@ -0,0 +1,558 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestReviewAuthoringViewModelTests + { + [Test] + public async Task Creates_New_Pending_Review_Model_Async() + { + var target = CreateTarget(); + + await InitializeAsync(target); + + Assert.That(target.Model.Id, Is.Null); + } + + [Test] + public async Task Uses_Existing_Pending_Review_Model_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest(reviews: review); + + var target = CreateTarget(model); + + await InitializeAsync(target); + + Assert.That(target.Model.Id, Is.EqualTo("12")); + } + + [Test] + public async Task Doesnt_Use_Non_Pending_Review_Model_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Approved); + var model = CreatePullRequest(reviews: review); + + var target = CreateTarget(model); + + await InitializeAsync(target); + + Assert.That(target.Model.Id, Is.Null); + } + + [Test] + public async Task Doesnt_Use_Other_Users_Pending_Review_Model_Async() + { + var review = CreateReview("12", "shana", state: PullRequestReviewState.Pending); + var model = CreatePullRequest(reviews: review); + + var target = CreateTarget(model); + + await InitializeAsync(target); + + Assert.That(target.Model.Id, Is.Null); + } + + [Test] + public async Task Body_Is_Set_Async() + { + var review = CreateReview(body: "Review body"); + var model = CreatePullRequest(reviews: review); + + var target = CreateTarget(model); + + await InitializeAsync(target); + + Assert.That(target.Body, Is.EqualTo("Review body")); + } + + [Test] + public async Task CanApproveRequestChanges_Is_False_When_Is_Own_PullRequest_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("grokys", review); + + var target = CreateTarget(model); + + await InitializeAsync(target); + + Assert.That(target.CanApproveRequestChanges, Is.False); + } + + [Test] + public async Task CanApproveRequestChanges_Is_True_When_Is_Someone_Elses_PullRequest_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + + var target = CreateTarget(model); + + await InitializeAsync(target); + + Assert.That(target.CanApproveRequestChanges, Is.True); + } + + [Test] + public async Task Initializes_Files_Async() + { + var session = CreateSession(); + var sessionManager = CreateSessionManager(session); + var target = CreateTarget(sessionManager: sessionManager); + + await InitializeAsync(target); + + await target.Files.Received(1).InitializeAsync(session, Arg.Any<Func<IInlineCommentThreadModel, bool>>()); + } + + [Test] + public async Task ReInitializes_Files_When_Session_PullRequestChanged_Async() + { + var session = CreateSession(); + var sessionManager = CreateSessionManager(session); + var target = CreateTarget(sessionManager: sessionManager); + + await InitializeAsync(target); + + await target.Files.Received(1).InitializeAsync(session, Arg.Any<Func<IInlineCommentThreadModel, bool>>()); + + RaisePullRequestChanged(session, CreatePullRequest()); + + await target.Files.Received(2).InitializeAsync(session, Arg.Any<Func<IInlineCommentThreadModel, bool>>()); + } + + [Test] + public async Task Popuplates_FileComments_Async() + { + var review = CreateReview(id: "12"); + var anotherReview = CreateReview(id: "11"); + var model = CreatePullRequest(reviews: review); + var session = CreateSession( + "grokys", + model, + CreateSessionFile( + CreateInlineCommentThread( + CreateReviewComment(anotherReview)), + CreateInlineCommentThread( + CreateReviewComment(review), + CreateReviewComment(review)))); + + var target = CreateTarget(model, session); + + await InitializeAsync(target); + + Assert.That(target.FileComments, Has.Count.EqualTo(2)); + } + + [Test] + public async Task Updates_FileComments_When_Session_PullRequestChanged_Async() + { + var review = CreateReview(id: "12"); + var anotherReview = CreateReview(id: "11"); + var model = CreatePullRequest(reviews: review); + var session = CreateSession( + "grokys", + model, + CreateSessionFile( + CreateInlineCommentThread( + CreateReviewComment(anotherReview)), + CreateInlineCommentThread( + CreateReviewComment(review), + CreateReviewComment(review)))); + + var target = CreateTarget(model, session); + + await InitializeAsync(target); + + Assert.That(target.FileComments, Has.Count.EqualTo(2)); + + var newSessionFile = CreateSessionFile( + CreateInlineCommentThread( + CreateReviewComment(anotherReview)), + CreateInlineCommentThread( + CreateReviewComment(review))); + session.GetAllFiles().Returns(new[] { newSessionFile }); + RaisePullRequestChanged(session, CreatePullRequest()); + + Assert.That(target.FileComments, Has.Count.EqualTo(1)); + } + + [Test] + public async Task Updates_Model_Id_From_PendingReviewId_When_Session_PullRequestChanged_Async() + { + var review = CreateReview(id: "12"); + var anotherReview = CreateReview(id: "11"); + var model = CreatePullRequest(); + var session = CreateSession( + "grokys", + model, + CreateSessionFile( + CreateInlineCommentThread( + CreateReviewComment(anotherReview)), + CreateInlineCommentThread( + CreateReviewComment(review), + CreateReviewComment(review)))); + + var target = CreateTarget(model, session); + + await InitializeAsync(target); + + Assert.That(target.Model.Id, Is.Null); + + session.PendingReviewId.Returns("123"); + RaisePullRequestChanged(session, model); + + Assert.That(target.Model.Id, Is.EqualTo("123")); + } + + [Test] + public async Task Approve_Calls_Session_PostReview_And_Closes_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(model: model); + var closed = false; + + var target = CreateTarget(model, session); + + await InitializeAsync(target); + target.Body = "Post review"; + target.CloseRequested.Subscribe(_ => closed = true); + target.Approve.Execute(null); + + await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.Approve); + Assert.True(closed); + } + + [Test] + public async Task Comment_Is_Disabled_When_Has_Empty_Body_And_No_File_Comments_Async() + { + var review = CreateReview("12", "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(model: model); + + var target = CreateTarget(model, session); + await InitializeAsync(target); + + Assert.IsFalse(target.Comment.CanExecute(null)); + } + + [Test] + public async Task Comment_Is_Enabled_When_Has_Body_Async() + { + var review = CreateReview("12", "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await InitializeAsync(target); + target.Body = "Review body"; + + Assert.IsTrue(target.Comment.CanExecute(null)); + } + + [Test] + public async Task Comment_Is_Enabled_When_Has_File_Comments_Async() + { + var review = CreateReview("12", "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession( + "grokys", + model, + CreateSessionFile( + CreateInlineCommentThread(CreateReviewComment(review)))); + + var target = CreateTarget(model, session); + await InitializeAsync(target); + + Assert.IsTrue(target.Comment.CanExecute(null)); + } + + [Test] + public async Task Comment_Calls_Session_PostReview_And_Closes_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + + await InitializeAsync(target); + target.Body = "Post review"; + target.CloseRequested.Subscribe(_ => closed = true); + target.Comment.Execute(null); + + await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.Comment); + Assert.True(closed); + } + + [Test] + public async Task RequestChanges_Is_Disabled_When_Has_Empty_Body_And_No_File_RequestChangess_Async() + { + var review = CreateReview("12", "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await InitializeAsync(target); + + Assert.IsFalse(target.RequestChanges.CanExecute(null)); + } + + [Test] + public async Task RequestChanges_Is_Enabled_When_Has_Body_Async() + { + var review = CreateReview("12", "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + + var target = CreateTarget(model, session); + await InitializeAsync(target); + target.Body = "Review body"; + + Assert.IsTrue(target.RequestChanges.CanExecute(null)); + } + + [Test] + public async Task RequestChanges_Is_Enabled_When_Has_File_Comments_Async() + { + var review = CreateReview("12", "grokys", body: "", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession( + "grokys", + model, + CreateSessionFile( + CreateInlineCommentThread(CreateReviewComment(review)))); + + var target = CreateTarget(model, session); + await InitializeAsync(target); + + Assert.IsTrue(target.RequestChanges.CanExecute(null)); + } + + [Test] + public async Task RequestChanges_Calls_Session_PostReview_And_Closes_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + + await InitializeAsync(target); + target.Body = "Post review"; + target.CloseRequested.Subscribe(_ => closed = true); + target.RequestChanges.Execute(null); + + await session.Received(1).PostReview("Post review", Octokit.PullRequestReviewEvent.RequestChanges); + Assert.True(closed); + } + + [Test] + public async Task Cancel_Calls_Session_CancelReview_And_Closes_When_Has_Pending_Review_Async() + { + var review = CreateReview("12", "grokys", state: PullRequestReviewState.Pending); + var model = CreatePullRequest("shana", review); + var session = CreateSession(model: model); + var closed = false; + + var pullRequestService = Substitute.For<IPullRequestService>(); + pullRequestService.ConfirmCancelPendingReview().Returns(true); + + var target = CreateTarget(model, session, pullRequestService); + await InitializeAsync(target); + + target.CloseRequested.Subscribe(_ => closed = true); + target.Cancel.Execute(null); + + await session.Received(1).CancelReview(); + Assert.True(closed); + } + + [Test] + public async Task Cancel_Just_Closes_When_Has_No_Pending_Review_Async() + { + var model = CreatePullRequest("shana"); + var session = CreateSession(); + var closed = false; + + var target = CreateTarget(model, session); + await InitializeAsync(target); + + target.CloseRequested.Subscribe(_ => closed = true); + target.Cancel.Execute(null); + + await session.Received(0).CancelReview(); + Assert.True(closed); + } + + static PullRequestReviewAuthoringViewModel CreateTarget( + PullRequestDetailModel model, + IPullRequestSession session = null, + IPullRequestService pullRequestService = null) + { + session = session ?? CreateSession(model: model); + + return CreateTarget( + pullRequestService: pullRequestService, + sessionManager: CreateSessionManager(session)); + } + + static PullRequestReviewAuthoringViewModel CreateTarget( + IPullRequestService pullRequestService = null, + IPullRequestEditorService editorService = null, + IPullRequestSessionManager sessionManager = null, + IPullRequestFilesViewModel files = null) + { + editorService = editorService ?? Substitute.For<IPullRequestEditorService>(); + sessionManager = sessionManager ?? CreateSessionManager(); + files = files ?? Substitute.For<IPullRequestFilesViewModel>(); + + return new PullRequestReviewAuthoringViewModel( + pullRequestService, + editorService, + sessionManager, + files); + } + + static PullRequestReviewModel CreateReview( + string id = "5", + string login = "grokys", + string body = "Review body", + PullRequestReviewState state = PullRequestReviewState.Pending) + { + return new PullRequestReviewModel + { + Id = id, + State = state, + Author = new ActorModel + { + Login = login, + }, + Body = body, + }; + } + + static InlineCommentModel CreateReviewComment(PullRequestReviewModel review) + { + return new InlineCommentModel + { + Review = review, + Comment = new PullRequestReviewCommentModel(), + }; + } + + static PullRequestDetailModel CreatePullRequest( + string authorLogin = "grokys", + params PullRequestReviewModel[] reviews) + { + return new PullRequestDetailModel + { + Number = 5, + Title = "Pull Request", + Author = new ActorModel + { + Login = authorLogin, + }, + Reviews = reviews.ToList(), + }; + } + + static PullRequestDetailModel CreatePullRequest( + string authorLogin = "grokys", + IEnumerable<PullRequestReviewModel> reviews = null) + { + return new PullRequestDetailModel + { + Number = 5, + Title = "Pull Request", + Author = new ActorModel + { + Login = authorLogin, + }, + Reviews = (reviews ?? new PullRequestReviewModel[0]).ToList() + }; + } + + static IPullRequestSession CreateSession( + string userLogin = "grokys", + PullRequestDetailModel model = null, + params IPullRequestSessionFile[] files) + { + model = model ?? CreatePullRequest(); + + var result = Substitute.For<IPullRequestSession>(); + result.PendingReviewId.Returns((string)null); + result.PullRequest.Returns(model); + result.User.Returns(new ActorModel { Login = userLogin }); + result.GetAllFiles().Returns(files); + result.PullRequestChanged.Returns(new Subject<PullRequestDetailModel>()); + return result; + } + + static IPullRequestSessionFile CreateSessionFile( + params IInlineCommentThreadModel[] threads) + { + var result = Substitute.For<IPullRequestSessionFile>(); + result.InlineCommentThreads.Returns(threads); + return result; + } + + static IInlineCommentThreadModel CreateInlineCommentThread( + params InlineCommentModel[] comments) + { + var result = Substitute.For<IInlineCommentThreadModel>(); + result.Comments.Returns(comments); + return result; + } + + static IPullRequestSessionManager CreateSessionManager( + IPullRequestSession session = null) + { + session = session ?? CreateSession(); + + var result = Substitute.For<IPullRequestSessionManager>(); + result.GetSession(null, null, 0).ReturnsForAnyArgs(session); + return result; + } + + static ILocalRepositoryModel CreateLocalRepositoryModel() + { + var result = Substitute.For<ILocalRepositoryModel>(); + result.Owner.Returns("owner"); + result.Name.Returns("repo"); + return result; + } + + static async Task InitializeAsync( + IPullRequestReviewAuthoringViewModel target, + ILocalRepositoryModel localRepository = null) + { + localRepository = localRepository ?? CreateLocalRepositoryModel(); + + await target.InitializeAsync( + localRepository, + Substitute.For<IConnection>(), + "owner", + "repo", + 5); + } + + static void RaisePullRequestChanged(IPullRequestSession session, PullRequestDetailModel newPullRequest) + { + ((ISubject<PullRequestDetailModel>)session.PullRequestChanged).OnNext(newPullRequest); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs new file mode 100644 index 0000000000..8acbafe6d8 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestReviewViewModelTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Linq; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestReviewViewModelTests + { + [Test] + public void Empty_Body_Is_Exposed_As_Null() + { + var pr = CreatePullRequest(); + pr.Reviews[0].Body = string.Empty; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.Body, Is.Null); + } + + [Test] + public void Creates_FileComments_And_OutdatedComments() + { + var pr = CreatePullRequest(); + pr.Reviews[0].Body = string.Empty; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.FileComments, Has.Count.EqualTo(2)); + Assert.That(target.OutdatedFileComments, Has.Count.EqualTo(1)); + } + + [Test] + public void HasDetails_True_When_Has_Body() + { + var pr = CreatePullRequest(); + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.HasDetails, Is.True); + } + + [Test] + public void HasDetails_True_When_Has_Comments() + { + var pr = CreatePullRequest(); + pr.Reviews[0].Body = string.Empty; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.HasDetails, Is.True); + } + + [Test] + public void HasDetails_False_When_Has_No_Body_Or_Comments() + { + var pr = CreatePullRequest(); + var review = pr.Reviews[0]; + + review.Body = string.Empty; + review.Comments = new PullRequestReviewCommentModel[0]; + + var target = CreateTarget(pullRequest: pr); + + Assert.That(target.HasDetails, Is.False); + } + + PullRequestReviewViewModel CreateTarget( + IPullRequestEditorService editorService = null, + IPullRequestSession session = null, + PullRequestDetailModel pullRequest = null, + PullRequestReviewModel model = null) + { + editorService = editorService ?? Substitute.For<IPullRequestEditorService>(); + session = session ?? Substitute.For<IPullRequestSession>(); + pullRequest = pullRequest ?? CreatePullRequest(); + model = model ?? pullRequest.Reviews[0]; + + return new PullRequestReviewViewModel( + editorService, + session, + model); + } + + private PullRequestDetailModel CreatePullRequest( + int number = 5, + string title = "Pull Request Title", + string body = "Pull Request Body", + ActorModel author = null) + { + var thread1 = new PullRequestReviewThreadModel + { + Position = 10 + }; + + return new PullRequestDetailModel + { + Number = number, + Title = title, + Author = author ?? new ActorModel(), + Body = body, + Reviews = new[] + { + new PullRequestReviewModel + { + Id = "1", + Body = "Looks good to me!", + State = PullRequestReviewState.Approved, + Comments = new[] + { + new PullRequestReviewCommentModel + { + Body = "I like this.", + Thread = new PullRequestReviewThreadModel { Position = 10 }, + }, + new PullRequestReviewCommentModel + { + Body = "This is good.", + Thread = new PullRequestReviewThreadModel { Position = 11 }, + }, + new PullRequestReviewCommentModel + { + Body = "Fine, but outdated.", + Thread = new PullRequestReviewThreadModel { Position = null }, + }, + }, + }, + new PullRequestReviewModel + { + Id = "2", + Body = "Changes please.", + State = PullRequestReviewState.ChangesRequested, + Comments = new[] + { + new PullRequestReviewCommentModel + { + Body = "Not great.", + Thread = new PullRequestReviewThreadModel { Position = 20 }, + }, + new PullRequestReviewCommentModel + { + Body = "This sucks.", + Thread = new PullRequestReviewThreadModel { Position = 21 }, + }, + new PullRequestReviewCommentModel + { + Body = "Bad and old.", + Thread = new PullRequestReviewThreadModel { Position = null }, + }, + }, + }, + }, + }; + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs new file mode 100644 index 0000000000..835da92d9c --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestUserReviewsViewModelTests.cs @@ -0,0 +1,225 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using NSubstitute; +using NUnit.Framework; + +namespace UnitTests.GitHub.App.ViewModels.GitHubPane +{ + public class PullRequestUserReviewsViewModelTests + { + const string AuthorLogin = "grokys"; + + [Test] + public async Task InitializeAsync_Loads_User_Async() + { + var modelSerivce = Substitute.For<IModelService>(); + + var target = CreateTarget(); + + await InitializeAsync(target); + + Assert.That(target.User.Login, Is.EqualTo(AuthorLogin)); + } + + [Test] + public async Task InitializeAsync_Creates_Reviews_Async() + { + var author = new ActorModel { Login = AuthorLogin }; + var anotherAuthor = new ActorModel { Login = "SomeoneElse" }; + + var pullRequest = new PullRequestDetailModel + { + Number = 5, + Author = author, + Reviews = new[] + { + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.ChangesRequested, + }, + new PullRequestReviewModel + { + Author = anotherAuthor, + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Dismissed, + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Pending, + }, + }, + }; + + var user = Substitute.For<IAccount>(); + var target = CreateTarget( + sessionManager: CreateSessionManager(pullRequest)); + + await InitializeAsync(target); + + // Should load reviews by the correct author which are not Pending. + Assert.That(target.Reviews, Has.Count.EqualTo(3)); + } + + [Test] + public async Task Orders_Reviews_Descending_Async() + { + var author = new ActorModel { Login = AuthorLogin }; + + var pullRequest = new PullRequestDetailModel + { + Number = 5, + Reviews = new[] + { + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Approved, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(2), + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.ChangesRequested, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(3), + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Dismissed, + SubmittedAt = DateTimeOffset.Now - TimeSpan.FromDays(1), + }, + }, + }; + + var user = Substitute.For<IAccount>(); + var target = CreateTarget( + sessionManager: CreateSessionManager(pullRequest)); + + await InitializeAsync(target); + + Assert.That(target.Reviews, Is.Not.Empty); + Assert.That( + target.Reviews.Select(x => x.Model.SubmittedAt), + Is.EqualTo(target.Reviews.Select(x => x.Model.SubmittedAt).OrderByDescending(x => x))); + } + + [Test] + public async Task First_Review_Is_Expanded_Async() + { + var author = new ActorModel { Login = AuthorLogin }; + + var pullRequest = new PullRequestDetailModel + { + Number = 5, + Reviews = new[] + { + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Approved, + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.ChangesRequested, + }, + new PullRequestReviewModel + { + Author = author, + State = PullRequestReviewState.Dismissed, + }, + }, + }; + + var user = Substitute.For<IAccount>(); + var target = CreateTarget( + sessionManager: CreateSessionManager(pullRequest)); + + await InitializeAsync(target); + + Assert.That(target.Reviews[0].IsExpanded, Is.True); + Assert.That(target.Reviews[1].IsExpanded, Is.False); + Assert.That(target.Reviews[2].IsExpanded, Is.False); + } + + async Task InitializeAsync( + PullRequestUserReviewsViewModel target, + ILocalRepositoryModel localRepository = null, + IConnection connection = null, + int pullRequestNumber = 5, + string login = AuthorLogin) + { + localRepository = localRepository ?? CreateRepository(); + connection = connection ?? Substitute.For<IConnection>(); + + await target.InitializeAsync( + localRepository, + connection, + localRepository.Owner, + localRepository.Name, + pullRequestNumber, + login); + } + + IPullRequestSessionManager CreateSessionManager(PullRequestDetailModel pullRequest = null) + { + pullRequest = pullRequest ?? new PullRequestDetailModel + { + Reviews = new PullRequestReviewModel[0], + }; + + var session = Substitute.For<IPullRequestSession>(); + session.User.Returns(new ActorModel { Login = AuthorLogin }); + session.PullRequest.Returns(pullRequest); + + var result = Substitute.For<IPullRequestSessionManager>(); + result.GetSession(null, null, 0).ReturnsForAnyArgs(session); + + return result; + } + + PullRequestUserReviewsViewModel CreateTarget( + IPullRequestEditorService editorService = null, + IPullRequestSessionManager sessionManager = null) + { + editorService = editorService ?? Substitute.For<IPullRequestEditorService>(); + sessionManager = sessionManager ?? CreateSessionManager(); + + return new PullRequestUserReviewsViewModel( + editorService, + sessionManager); + } + + IModelServiceFactory CreateFactory(IModelService modelService) + { + var result = Substitute.For<IModelServiceFactory>(); + result.CreateAsync(null).ReturnsForAnyArgs(modelService); + return result; + } + + ILocalRepositoryModel CreateRepository(string owner = "owner", string name = "repo") + { + var result = Substitute.For<ILocalRepositoryModel>(); + result.Owner.Returns(owner); + result.Name.Returns(name); + return result; + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/TeamExplorer/RepositoryPublishViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/TeamExplorer/RepositoryPublishViewModelTests.cs new file mode 100644 index 0000000000..261d889465 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/TeamExplorer/RepositoryPublishViewModelTests.cs @@ -0,0 +1,375 @@ +using System.Reactive.Linq; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels.TeamExplorer; +using NSubstitute; +using NUnit.Framework; +using UnitTests; +using System.Threading.Tasks; +using System; +using GitHub.Primitives; +using GitHub.Info; +using System.Collections.Generic; +using System.Linq; +using GitHub.Extensions; +using GitHub.Factories; + +public class RepositoryPublishViewModelTests +{ + public static class Helpers + { + public static IRepositoryPublishViewModel GetViewModel(IRepositoryPublishService service = null) + { + return GetViewModel(service); + } + + public static IRepositoryPublishViewModel GetViewModel( + IRepositoryPublishService service = null, + INotificationService notificationService = null, + IConnectionManager connectionManager = null, + IModelServiceFactory factory = null) + { + service = service ?? Substitute.For<IRepositoryPublishService>(); + notificationService = notificationService ?? Substitute.For<INotificationService>(); + connectionManager = connectionManager ?? Substitutes.ConnectionManager; + factory = factory ?? Substitute.For<IModelServiceFactory>(); + + return new RepositoryPublishViewModel(service, notificationService, connectionManager, + factory, Substitute.For<IUsageTracker>()); + } + + public static void SetupConnections(List<HostAddress> adds, List<IConnection> conns, string uri) + { + var add = HostAddress.Create(new Uri(uri)); + var conn = Substitute.For<IConnection>(); + conn.HostAddress.Returns(add); + adds.Add(add); + conns.Add(conn); + } + + public static IRepositoryPublishViewModel SetupConnectionsAndViewModel( + IRepositoryPublishService service = null, + INotificationService notificationService = null, + IConnectionManager cm = null, + string uri = GitHubUrls.GitHub) + { + cm = cm ?? Substitutes.ConnectionManager; + var adds = new List<HostAddress>(); + var conns = new List<IConnection>(); + SetupConnections(adds, conns, uri); + //hsts[0].ModelService.GetAccounts().Returns(Observable.Return(new List<IAccount>())); + cm.Connections.Returns(new ObservableCollectionEx<IConnection>(conns)); + return GetViewModel(service, notificationService, cm); + } + + public static string[] GetArgs(params string[] args) + { + var ret = new List<string>(); + foreach (var arg in args) + if (arg != null) + ret.Add(arg); + return ret.ToArray(); + } + + } + + public class TheConnectionsProperty : TestBaseClass + { + [TestCase(GitHubUrls.GitHub, "https://github.enterprise")] + [TestCase("https://github.enterprise", null)] + [TestCase(GitHubUrls.GitHub, null)] + public void ConnectionsMatchConnectionManager(string arg1, string arg2) + { + var args = Helpers.GetArgs(arg1, arg2); + + var cm = Substitutes.ConnectionManager; + var adds = new List<HostAddress>(); + var conns = new List<IConnection>(); + foreach (var uri in args) + Helpers.SetupConnections(adds, conns, uri); + + cm.Connections.Returns(new ObservableCollectionEx<IConnection>(conns)); + + var vm = Helpers.GetViewModel(connectionManager: cm); + + Assert.That(conns, Is.EqualTo(vm.Connections)); + } + } + + public class TheSelectedConnectionProperty : TestBaseClass + { + [TestCase(GitHubUrls.GitHub, "https://github.enterprise")] + [TestCase("https://github.enterprise", GitHubUrls.GitHub)] + public void DefaultsToGitHub(string arg1, string arg2) + { + var args = Helpers.GetArgs(arg1, arg2); + + var cm = Substitutes.ConnectionManager; + var adds = new List<HostAddress>(); + var conns = new List<IConnection>(); + foreach (var uri in args) + Helpers.SetupConnections(adds, conns, uri); + + cm.Connections.Returns(new ObservableCollectionEx<IConnection>(conns)); + var vm = Helpers.GetViewModel(connectionManager: cm); + + Assert.That(adds.First(x => x.IsGitHubDotCom()), Is.SameAs(vm.SelectedConnection.HostAddress)); + Assert.That(conns.First(x => x.HostAddress.IsGitHubDotCom()), Is.SameAs(vm.SelectedConnection)); + } + } + + public class TheAccountsProperty : TestBaseClass + { + [Test] + public void IsPopulatedByTheAccountsForTheSelectedHost() + { + var cm = Substitutes.ConnectionManager; + var adds = new List<HostAddress>(); + var conns = new List<IConnection>(); + Helpers.SetupConnections(adds, conns, GitHubUrls.GitHub); + Helpers.SetupConnections(adds, conns, "https://github.enterprise"); + + var gitHubAccounts = new List<IAccount> { Substitute.For<IAccount>(), Substitute.For<IAccount>() }; + var enterpriseAccounts = new List<IAccount> { Substitute.For<IAccount>() }; + + var gitHubModelService = Substitute.For<IModelService>(); + var enterpriseModelService = Substitute.For<IModelService>(); + gitHubModelService.GetAccounts().Returns(Observable.Return(gitHubAccounts)); + enterpriseModelService.GetAccounts().Returns(Observable.Return(enterpriseAccounts)); + + var factory = Substitute.For<IModelServiceFactory>(); + factory.CreateAsync(conns[0]).Returns(gitHubModelService); + factory.CreateAsync(conns[1]).Returns(enterpriseModelService); + + cm.Connections.Returns(new ObservableCollectionEx<IConnection>(conns)); + var vm = Helpers.GetViewModel(connectionManager: cm, factory: factory); + + Assert.That(2, Is.EqualTo(vm.Accounts.Count)); + Assert.That(gitHubAccounts[0], Is.SameAs(vm.SelectedAccount)); + + vm.SelectedConnection = conns.First(x => !x.HostAddress.IsGitHubDotCom()); + + Assert.AreEqual(1, vm.Accounts.Count); + Assert.That(enterpriseAccounts[0], Is.SameAs(vm.SelectedAccount)); + } + } + + public class TheSafeRepositoryNameProperty : TestBaseClass + { + [Test] + public void IsTheSameAsTheRepositoryNameWhenTheInputIsSafe() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "this-is-bad"; + + Assert.That(vm.RepositoryName, Is.EqualTo(vm.SafeRepositoryName)); + } + + [Test] + public void IsConvertedWhenTheRepositoryNameIsNotSafe() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "this is bad"; + + Assert.That("this-is-bad", Is.EqualTo(vm.SafeRepositoryName)); + } + + [Test] + public void IsNullWhenRepositoryNameIsNull() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + Assert.Null(vm.SafeRepositoryName); + vm.RepositoryName = "not-null"; + vm.RepositoryName = null; + + Assert.That(vm.SafeRepositoryName, Is.Null); + } + } + + public class TheRepositoryNameValidatorProperty : TestBaseClass + { + [Test] + public void IsFalseWhenRepoNameEmpty() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = ""; + + Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That("Please enter a repository name", Is.EqualTo(vm.RepositoryNameValidator.ValidationResult.Message)); + } + + [Test] + public void IsFalseWhenAfterBeingTrue() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "repo"; + + Assert.True(vm.PublishRepository.CanExecute(null)); + Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That(vm.RepositoryNameValidator.ValidationResult.Message, Is.Empty); + + vm.RepositoryName = ""; + + Assert.False(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That("Please enter a repository name", Is.EqualTo(vm.RepositoryNameValidator.ValidationResult.Message)); + } + + [Test] + public void IsTrueWhenRepositoryNameIsValid() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "thisisfine"; + + Assert.True(vm.RepositoryNameValidator.ValidationResult.IsValid); + Assert.That(vm.RepositoryNameValidator.ValidationResult.Message, Is.Empty); + } + } + + public class TheSafeRepositoryNameWarningValidatorProperty : TestBaseClass + { + [Test] + public void IsTrueWhenRepoNameIsSafe() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "this-is-bad"; + + Assert.True(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + } + + [Test] + public void IsFalseWhenRepoNameIsNotSafe() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "this is bad"; + + Assert.False(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + Assert.That("Will be created as this-is-bad", Is.EqualTo(vm.SafeRepositoryNameWarningValidator.ValidationResult.Message)); + } + + [Test] + public void ResetsSafeNameValidator() + { + var cm = Substitutes.ConnectionManager; + var vm = Helpers.SetupConnectionsAndViewModel(cm: cm); + + vm.RepositoryName = "this"; + Assert.True(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + + vm.RepositoryName = "this is bad"; + Assert.That("this-is-bad", Is.EqualTo(vm.SafeRepositoryName)); + Assert.False(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + + vm.RepositoryName = "this"; + + Assert.True(vm.SafeRepositoryNameWarningValidator.ValidationResult.IsValid); + } + } + + public class ThePublishRepositoryCommand : TestBaseClass + { + [Test] + public async Task RepositoryExistsCallsNotificationServiceWithErrorAsync() + { + var cm = Substitutes.ConnectionManager; + var notificationService = Substitute.For<INotificationService>(); + + var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); + repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) + .Returns(Observable.Throw<Octokit.Repository>(new Octokit.RepositoryExistsException("repo-name", new Octokit.ApiValidationException()))); + var vm = Helpers.SetupConnectionsAndViewModel(repositoryPublishService, notificationService, cm); + vm.RepositoryName = "repo-name"; + + await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Fail)); + + Assert.That(vm.SafeRepositoryNameWarningValidator.ValidationResult.Message, Is.Not.Null); + notificationService.DidNotReceive().ShowMessage(Args.String); + notificationService.Received().ShowError("There is already a repository named 'repo-name' for the current account."); + } + + [Test] + public async Task ResetsWhenSwitchingHostsAsync() + { + var args = Helpers.GetArgs(GitHubUrls.GitHub, "https://github.enterprise"); + + var cm = Substitutes.ConnectionManager; + var adds = new List<HostAddress>(); + var conns = new List<IConnection>(); + foreach (var uri in args) + Helpers.SetupConnections(adds, conns, uri); + + cm.Connections.Returns(new ObservableCollectionEx<IConnection>(conns)); + + var notificationService = Substitute.For<INotificationService>(); + + var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); + repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) + .Returns(Observable.Throw<Octokit.Repository>(new Octokit.RepositoryExistsException("repo-name", new Octokit.ApiValidationException()))); + var vm = Helpers.GetViewModel(repositoryPublishService, notificationService, cm); + + vm.RepositoryName = "repo-name"; + + await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Fail)); + + Assert.That("repo-name", Is.EqualTo(vm.RepositoryName)); + notificationService.Received().ShowError("There is already a repository named 'repo-name' for the current account."); + + var wasCalled = false; + vm.SafeRepositoryNameWarningValidator.PropertyChanged += (s, e) => wasCalled = true; + + vm.SelectedConnection = conns.First(x => x != vm.SelectedConnection); + + Assert.True(wasCalled); + } + + [Test] + public async Task ResetsWhenSwitchingAccountsAsync() + { + var cm = Substitutes.ConnectionManager; + var adds = new List<HostAddress>(); + var conns = new List<IConnection>(); + Helpers.SetupConnections(adds, conns, GitHubUrls.GitHub); + + var accounts = new List<IAccount> { Substitute.For<IAccount>(), Substitute.For<IAccount>() }; + + cm.Connections.Returns(new ObservableCollectionEx<IConnection>(conns)); + + var notificationService = Substitute.For<INotificationService>(); + + var repositoryPublishService = Substitute.For<IRepositoryPublishService>(); + repositoryPublishService.PublishRepository(Args.NewRepository, Args.Account, Args.ApiClient) + .Returns(Observable.Throw<Octokit.Repository>(new Octokit.RepositoryExistsException("repo-name", new Octokit.ApiValidationException()))); + var vm = Helpers.GetViewModel(repositoryPublishService, notificationService, cm); + + vm.RepositoryName = "repo-name"; + + await vm.PublishRepository.ExecuteAsync().Catch(Observable.Return(ProgressState.Fail)); + + Assert.That("repo-name", Is.EqualTo(vm.RepositoryName)) + ; + notificationService.Received().ShowError("There is already a repository named 'repo-name' for the current account."); + + var wasCalled = false; + vm.SafeRepositoryNameWarningValidator.PropertyChanged += (s, e) => wasCalled = true; + + vm.SelectedAccount = accounts[1]; + + Assert.True(wasCalled); + } + } +} diff --git a/test/GitHub.App.UnitTests/ViewModels/UserFilterViewModelTests.cs b/test/GitHub.App.UnitTests/ViewModels/UserFilterViewModelTests.cs new file mode 100644 index 0000000000..7bfe391019 --- /dev/null +++ b/test/GitHub.App.UnitTests/ViewModels/UserFilterViewModelTests.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Data; +using GitHub.Models; +using GitHub.ViewModels; +using NUnit.Framework; + +namespace GitHub.App.UnitTests.ViewModels +{ + public class UserFilterViewModelTests + { + [Test] + public void Accessing_Users_Load_Users() + { + var target = CreateTarget(); + + Assert.That(target.Users.Count, Is.EqualTo(5)); + } + + [Test] + public void Setting_Filter_Adds_Ersatz_User() + { + var target = CreateTarget(); + var view = (ListCollectionView)target.UsersView; + + target.Filter = "grok"; + + Assert.That(target.Users.Count, Is.EqualTo(6)); + Assert.That(target.Users.Last().Login, Is.EqualTo("grok")); + Assert.That(((IActorViewModel)view.GetItemAt(0)).Login, Is.EqualTo("grok")); + } + + [Test] + public void Changing_Filter_Updates_Ersatz_User() + { + var target = CreateTarget(); + var view = (ListCollectionView)target.UsersView; + + target.Filter = "grok"; + + Assert.That(target.Users.Count, Is.EqualTo(6)); + Assert.That(target.Users.Last().Login, Is.EqualTo("grok")); + + target.Filter = "shan"; + + Assert.That(target.Users.Count, Is.EqualTo(6)); + Assert.That(target.Users.Last().Login, Is.EqualTo("shan")); + } + + [Test] + public void Changing_Filter_To_Existing_User_Removes_Ersatz_User() + { + var target = CreateTarget(); + var view = (ListCollectionView)target.UsersView; + + target.Filter = "grok"; + + Assert.That(target.Users.Count, Is.EqualTo(6)); + Assert.That(target.Users.Last().Login, Is.EqualTo("grok")); + + target.Filter = "shana"; + + Assert.That(target.Users.Count, Is.EqualTo(5)); + } + + [Test] + public void Selecting_User_Clears_Filter() + { + var target = CreateTarget(); + + target.Filter = "grok"; + target.Selected = target.Users[1]; + + Assert.Null(target.Filter); + } + + static UserFilterViewModel CreateTarget(UserFilterViewModel.LoadPageDelegate load = null) + { + Task<Page<ActorModel>> LoadPage(string after) => Task.FromResult(new Page<ActorModel> + { + TotalCount = 5, + Items = new[] + { + new ActorModel { Login = "grokys" }, + new ActorModel { Login = "jcansdale" }, + new ActorModel { Login = "meaghanlewis" }, + new ActorModel { Login = "shana" }, + new ActorModel { Login = "StanleyGoldman" }, + }, + }); + + load = load ?? LoadPage; + + return new UserFilterViewModel(load); + } + } +} diff --git a/test/GitHub.App.UnitTests/packages.config b/test/GitHub.App.UnitTests/packages.config new file mode 100644 index 0000000000..84d11fa23a --- /dev/null +++ b/test/GitHub.App.UnitTests/packages.config @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="EnvDTE" version="8.0.0" targetFramework="net461" /> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="15.3.15" targetFramework="net461" /> + <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Octokit.GraphQL" version="0.1.1-beta" targetFramework="net461" /> + <package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Testing" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> + <package id="stdole" version="7.0.3300" targetFramework="net461" /> + <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.Exports.Reactive.UnitTests/Caches/AccountCacheItemTests.cs b/test/GitHub.Exports.Reactive.UnitTests/Caches/AccountCacheItemTests.cs new file mode 100644 index 0000000000..e667169de3 --- /dev/null +++ b/test/GitHub.Exports.Reactive.UnitTests/Caches/AccountCacheItemTests.cs @@ -0,0 +1,24 @@ + +using System; +using GitHub.Caches; +using Octokit; +using NUnit.Framework; + +public class AccountCacheItemTests +{ + public class TheConstructor : TestBaseClass + { + [TestCase("https://foo.com", true)] + [TestCase("https://notgithub.com", true)] + [TestCase("https://github.com", false)] + [TestCase("https://api.github.com", false)] + [TestCase("GARBAGE", false)] + public void SetsIsEnterpriseCorrectly(string htmlUrl, bool expected) + { + var apiAccount = CreateOctokitUser("foo", htmlUrl); + var cachedAccount = new AccountCacheItem(apiAccount); + + Assert.That(expected, Is.EqualTo(cachedAccount.IsEnterprise)); + } + } +} diff --git a/test/GitHub.Exports.Reactive.UnitTests/GitHub.Exports.Reactive.UnitTests.csproj b/test/GitHub.Exports.Reactive.UnitTests/GitHub.Exports.Reactive.UnitTests.csproj new file mode 100644 index 0000000000..2a2ee4a37c --- /dev/null +++ b/test/GitHub.Exports.Reactive.UnitTests/GitHub.Exports.Reactive.UnitTests.csproj @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{C59868FC-D8BC-4D47-B4F3-16908D2641C6}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.Exports.Reactive.UnitTests</RootNamespace> + <AssemblyName>GitHub.Exports.Reactive.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Testing.2.2.5-custom\lib\net45\Microsoft.Reactive.Testing.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Windows.Threading, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Xaml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="Caches\AccountCacheItemTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Services\PullRequestEditorServiceTests.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.Exports.Reactive.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.Exports.Reactive.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..4bc71b7809 --- /dev/null +++ b/test/GitHub.Exports.Reactive.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.Exports.Reactive.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.Exports.Reactive.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c59868fc-d8bc-4d47-b4f3-16908d2641c6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.Exports.Reactive.UnitTests/Services/PullRequestEditorServiceTests.cs b/test/GitHub.Exports.Reactive.UnitTests/Services/PullRequestEditorServiceTests.cs new file mode 100644 index 0000000000..2b14feeb96 --- /dev/null +++ b/test/GitHub.Exports.Reactive.UnitTests/Services/PullRequestEditorServiceTests.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using GitHub.Services; +using NUnit.Framework; +using NSubstitute; +using Microsoft.VisualStudio.Editor; +using GitHub.Commands; +using Microsoft.VisualStudio.Text.Editor; + +public class PullRequestEditorServiceTests +{ + public class TheFindNearestMatchingLineMethod + { + [TestCase(new[] { "line" }, new[] { "line" }, 0, 0, 1, Description = "Match same line")] + [TestCase(new[] { "line" }, new[] { "line_no_match" }, 0, -1, 0, Description = "No matching line")] + [TestCase(new[] { "line" }, new[] { "", "line" }, 0, 1, 1, Description = "Match line moved up")] + [TestCase(new[] { "", "line" }, new[] { "line" }, 1, 0, 1, Description = "Match line moved down")] + [TestCase(new[] { "line", "line" }, new[] { "line", "line" }, 0, 0, 2, Description = "Match nearest line")] + [TestCase(new[] { "line", "line" }, new[] { "line", "line" }, 1, 1, 2, Description = "Match nearest line")] + [TestCase(new[] { "line" }, new[] { "line" }, 1, 0, 1, Description = "Treat after last line the same as last line")] + public void FindNearestMatchingLine(IList<string> fromLines, IList<string> toLines, int line, + int expectNearestLine, int expectMatchingLines) + { + var target = CreateNavigationService(); + + int matchedLines; + var nearestLine = target.FindNearestMatchingLine(fromLines, toLines, line, out matchedLines); + + Assert.That(nearestLine, Is.EqualTo(expectNearestLine)); + Assert.That(matchedLines, Is.EqualTo(expectMatchingLines)); + } + } + + public class TheFindMatchingLineMethod + { + [TestCase(new[] { "void method()", "code" }, new[] { "void method()", "// code" }, 1, 1)] + [TestCase(new[] { "void method()", "code" }, new[] { "void method()" }, 1, 0, Description = "Keep within bounds")] + [TestCase(new[] { "code" }, new[] { "// code" }, 0, -1)] + [TestCase(new[] { "line", "line" }, new[] { "line", "line" }, 0, 0, Description = "Match nearest line")] + [TestCase(new[] { "line", "line" }, new[] { "line", "line" }, 1, 1, Description = "Match nearest line")] + public void FindNearestMatchingLine(IList<string> fromLines, IList<string> toLines, int line, + int matchingLine) + { + var target = CreateNavigationService(); + + var nearestLine = target.FindMatchingLine(fromLines, toLines, line, matchLinesAbove: 1); + + Assert.That(nearestLine, Is.EqualTo(matchingLine)); + } + } + + static PullRequestEditorService CreateNavigationService() + { + var sp = Substitute.For<IGitHubServiceProvider>(); + var pullRequestService = Substitute.For<IPullRequestService>(); + var vsEditorAdaptersFactory = Substitute.For<IVsEditorAdaptersFactoryService>(); + var statusBar = Substitute.For<IStatusBarNotificationService>(); + var openFileInSolutionCommand = Substitute.For<IGoToSolutionOrPullRequestFileCommand>(); + var editorOptionsFactoryService = Substitute.For<IEditorOptionsFactoryService>(); + var usageTracker = Substitute.For<IUsageTracker>(); + return new PullRequestEditorService( + sp, + pullRequestService, + vsEditorAdaptersFactory, + statusBar, + openFileInSolutionCommand, + editorOptionsFactoryService, + usageTracker); + } +} diff --git a/test/GitHub.Exports.Reactive.UnitTests/packages.config b/test/GitHub.Exports.Reactive.UnitTests/packages.config new file mode 100644 index 0000000000..58eae675b6 --- /dev/null +++ b/test/GitHub.Exports.Reactive.UnitTests/packages.config @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Testing" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.Exports.UnitTests/Args.cs b/test/GitHub.Exports.UnitTests/Args.cs new file mode 100644 index 0000000000..2a04705cde --- /dev/null +++ b/test/GitHub.Exports.UnitTests/Args.cs @@ -0,0 +1,35 @@ +using System; +using GitHub.Api; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using LibGit2Sharp; +using Microsoft.VisualStudio.Text; +using NSubstitute; +using Octokit; + +internal static class Args +{ + public static bool Boolean { get { return Arg.Any<bool>(); } } + public static int Int32 { get { return Arg.Any<int>(); } } + public static string String { get { return Arg.Any<string>(); } } + public static Span Span { get { return Arg.Any<Span>(); } } + public static SnapshotPoint SnapshotPoint { get { return Arg.Any<SnapshotPoint>(); } } + public static NewRepository NewRepository { get { return Arg.Any<NewRepository>(); } } + public static IAccount Account { get { return Arg.Any<IAccount>(); } } + public static IApiClient ApiClient { get { return Arg.Any<IApiClient>(); } } + public static IServiceProvider ServiceProvider { get { return Arg.Any<IServiceProvider>(); } } + public static IAvatarProvider AvatarProvider { get { return Arg.Any<IAvatarProvider>(); } } + public static HostAddress HostAddress { get { return Arg.Any<HostAddress>(); } } + public static Uri Uri { get { return Arg.Any<Uri>(); } } + public static LibGit2Sharp.IRepository LibGit2Repo { get { return Arg.Any<LibGit2Sharp.IRepository>(); } } + public static LibGit2Sharp.Branch LibGit2Branch { get { return Arg.Any<LibGit2Sharp.Branch>(); } } + public static Remote LibgGit2Remote { get { return Arg.Any<Remote>(); } } + public static ILocalRepositoryModel LocalRepositoryModel { get { return Arg.Any<ILocalRepositoryModel>(); } } + public static IRemoteRepositoryModel RemoteRepositoryModel { get { return Arg.Any<IRemoteRepositoryModel>(); } } + public static IBranch Branch { get { return Arg.Any<IBranch>(); } } + public static IGitService GitService { get { return Arg.Any<IGitService>(); } } + public static Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> + TwoFactorChallengCallback + { get { return Arg.Any<Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>> (); } } +} diff --git a/test/GitHub.Exports.UnitTests/GitHub.Exports.UnitTests.csproj b/test/GitHub.Exports.UnitTests/GitHub.Exports.UnitTests.csproj new file mode 100644 index 0000000000..bf2224795d --- /dev/null +++ b/test/GitHub.Exports.UnitTests/GitHub.Exports.UnitTests.csproj @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{94509FCB-6C97-4ED6-AED6-6E74AB3CA336}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.Exports.UnitTests</RootNamespace> + <AssemblyName>GitHub.Exports.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <EmbedInteropTypes>False</EmbedInteropTypes> + <HintPath>..\..\packages\EnvDTE.8.0.0\lib\net10\EnvDTE.dll</HintPath> + </Reference> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="rothko, Version=0.0.3.0, Culture=neutral, PublicKeyToken=9f664c41f503810a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rothko.0.0.3-ghfvs\lib\net45\rothko.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="Args.cs" /> + <Compile Include="GitServiceTests.cs" /> + <Compile Include="HostAddressTests.cs" /> + <Compile Include="LocalRepositoryModelTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Substitutes.cs" /> + <Compile Include="TestDoubles\FakeCommitLog.cs" /> + <Compile Include="VSServicesTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.Exports.UnitTests/GitServiceTests.cs b/test/GitHub.Exports.UnitTests/GitServiceTests.cs new file mode 100644 index 0000000000..8c38dcbb2a --- /dev/null +++ b/test/GitHub.Exports.UnitTests/GitServiceTests.cs @@ -0,0 +1,36 @@ +using System; +using GitHub.Services; +using LibGit2Sharp; +using NSubstitute; +using NUnit.Framework; +using System.Linq; +using System.Collections; +using System.Collections.Generic; + +public class GitServiceTests : TestBaseClass +{ + [TestCase("asdf", null)] + [TestCase("", null)] + [TestCase(null, null)] + [TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] + [TestCase("http://example.com/", "http://example.com/")] + [TestCase("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")] + [TestCase("https://github.com/github/Windows", "https://github.com/github/Windows")] + [TestCase("https://github.com/github/Windows.git", "https://github.com/github/Windows")] + [TestCase("https://haacked@github.com/github/Windows.git", "https://github.com/github/Windows")] + [TestCase("http://example.com:4000/github/Windows", "http://example.com:4000/github/Windows")] + [TestCase("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/github/Windows")] + [TestCase("git@example.com:org/repo.git", "https://example.com/org/repo")] + [TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")] + [TestCase("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")] + public void GetUriShouldNotThrow(string url, string expected) + { + var origin = Substitute.For<Remote>(); + origin.Url.Returns(url); + var repository = Substitute.For<IRepository>(); + repository.Network.Remotes["origin"].Returns(origin); + + var gitservice = new GitService(); + Assert.That(expected, Is.EqualTo(gitservice.GetUri(repository)?.ToString())); + } +} diff --git a/test/GitHub.Exports.UnitTests/HostAddressTests.cs b/test/GitHub.Exports.UnitTests/HostAddressTests.cs new file mode 100644 index 0000000000..d57d82037c --- /dev/null +++ b/test/GitHub.Exports.UnitTests/HostAddressTests.cs @@ -0,0 +1,39 @@ +using System; +using GitHub.Primitives; +using NUnit.Framework; + +namespace UnitTests.GitHub.Exports +{ + public class HostAddressTests + { + [Test] + public void ShouldBeEqualIfAddressesMatch() + { + var address1 = HostAddress.Create("foo.com"); + var address2 = HostAddress.Create("foo.com"); + var null1 = default(HostAddress); + var null2 = default(HostAddress); + + Assert.True(address1.Equals(address2)); + Assert.True(address1 == address2); + Assert.False(address1 != address2); + Assert.True(null1 == null2); + } + + [Test] + public void ShouldBeNotEqualIfAddressesDontMatch() + { + var address1 = HostAddress.Create("foo.com"); + var address2 = HostAddress.Create("bar.com"); + var null1 = default(HostAddress); + + Assert.False(address1.Equals(address2)); + Assert.False(address1 == address2); + Assert.True(address1 != address2); + Assert.False(null1 == address1); + Assert.False(address1 == null1); + Assert.True(null1 != address1); + Assert.True(address1 != null1); + } + } +} diff --git a/test/GitHub.Exports.UnitTests/LocalRepositoryModelTests.cs b/test/GitHub.Exports.UnitTests/LocalRepositoryModelTests.cs new file mode 100644 index 0000000000..cb2c163b61 --- /dev/null +++ b/test/GitHub.Exports.UnitTests/LocalRepositoryModelTests.cs @@ -0,0 +1,79 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using GitHub.Models; +using GitHub.Exports; +using GitHub.Services; +using GitHub.Primitives; +using NSubstitute; +using LibGit2Sharp; +using NUnit.Framework; + +public class LocalRepositoryModelTests : TestBaseClass +{ + [TestCase(1, LinkType.Blob, false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [TestCase(2, LinkType.Blob, false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1")] + [TestCase(3, LinkType.Blob, false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, 1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1")] + [TestCase(4, LinkType.Blob, false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1-L2")] + [TestCase(5, LinkType.Blob, false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 2, 1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1-L2")] + [TestCase(6, LinkType.Blob, false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [TestCase(7, LinkType.Blob, false, "https://github.com/foo/bar", "123123", "", 1, 2, "https://github.com/foo/bar/commit/123123")] + [TestCase(8, LinkType.Blob, false, "https://github.com/foo/bar", "", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar")] + [TestCase(9, LinkType.Blob, false, "https://github.com/foo/bar", null, null, -1, -1, "https://github.com/foo/bar")] + [TestCase(10, LinkType.Blob, false, null, "123123", @"src\dir\file1.cs", 1, 2, null)] + [TestCase(11, LinkType.Blob, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [TestCase(12, LinkType.Blob, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1")] + [TestCase(13, LinkType.Blob, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, 1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1")] + [TestCase(14, LinkType.Blob, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1-L2")] + [TestCase(15, LinkType.Blob, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 2, 1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1-L2")] + [TestCase(16, LinkType.Blob, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [TestCase(17, LinkType.Blob, true, "https://github.com/foo/bar", "", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar")] + [TestCase(18, LinkType.Blob, true, null, "123123", @"src\dir\file1.cs", 1, 2, null)] + [TestCase(19, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [TestCase(20, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\File1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/File1.cs")] + [TestCase(21, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\ThisIsFile1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/ThisIsFile1.cs")] + [TestCase(22, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\ThisIsFile1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/ThisIsFile1.cs")] + [TestCase(23, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\ThisIsFile1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/ThisIsFile1.cs")] + [TestCase(24, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\ThisIsFile1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/ThisIsFile1.cs")] + [TestCase(25, LinkType.Blob, false, "git@github.com/foo/bar", "123123", @"src\dir\ThisIsFile1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/ThisIsFile1.cs")] + [TestCase(22, LinkType.Blame, true, "git@github.com/foo/bar", "123123", @"src\dir\ThisIsFile1.cs", -1, -1, "https://github.com/foo/bar/blame/123123/src/dir/ThisIsFile1.cs")] + [TestCase(23, LinkType.Blame, true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blame/123123/src/dir/file1.cs")] + [TestCase(24, LinkType.Blame, false, "https://github.com/foo/bar", "123123", "", 1, 2, "https://github.com/foo/bar/commit/123123")] + public async Task GenerateUrl(int testid, LinkType linkType, bool createRootedPath, string baseUrl, string sha, string path, int startLine, int endLine, string expected) + { + using (var temp = new TempDirectory()) + { + var gitService = CreateGitService(sha); + + var basePath = temp.Directory.CreateSubdirectory("generate-url-test1-" + testid); + if (createRootedPath && path != null) + path = System.IO.Path.Combine(basePath.FullName, path); + ILocalRepositoryModel model = null; + if (!string.IsNullOrEmpty(baseUrl)) + model = new LocalRepositoryModel("bar", new UriString(baseUrl), basePath.FullName, gitService); + else + model = new LocalRepositoryModel(basePath.FullName, gitService); + var result = await model.GenerateUrl(linkType, path, startLine, endLine); + Assert.That(expected, Is.EqualTo(result?.ToString())); + } + } + + static IGitService CreateGitService(string sha) + { + var gitservice = Substitute.For<IGitService>(); + var repo = Substitute.For<IRepository>(); + gitservice.GetRepository(Args.String).Returns(repo); + gitservice.GetLatestPushedSha(Args.String).Returns(Task.FromResult(sha)); + if (!string.IsNullOrEmpty(sha)) + { + var refs = Substitute.For<ReferenceCollection>(); + var refrence = Substitute.For<Reference>(); + refs.ReachableFrom(Arg.Any<IEnumerable<Reference>>(), Arg.Any<IEnumerable<Commit>>()).Returns(new Reference[] { refrence }); + repo.Refs.Returns(refs); + var commit = Substitute.For<Commit>(); + commit.Sha.Returns(sha); + repo.Commits.Returns(new FakeCommitLog { commit }); + } + + return gitservice; + } +} diff --git a/test/GitHub.Exports.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.Exports.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..257eddfa25 --- /dev/null +++ b/test/GitHub.Exports.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.Exports.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.Exports.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("94509fcb-6c97-4ed6-aed6-6e74ab3ca336")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.Exports.UnitTests/Substitutes.cs b/test/GitHub.Exports.UnitTests/Substitutes.cs new file mode 100644 index 0000000000..9792829b8c --- /dev/null +++ b/test/GitHub.Exports.UnitTests/Substitutes.cs @@ -0,0 +1,203 @@ +using GitHub.Authentication; +using GitHub.Models; +using GitHub.Services; +using GitHub.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using NSubstitute; +using Rothko; +using System; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using GitHub.Factories; + +namespace UnitTests +{ + internal static class Substitutes + { + public static T1 For<T1, T2, T3, T4>(params object[] constructorArguments) + where T1 : class + where T2 : class + where T3 : class + where T4 : class + { + return (T1)Substitute.For(new Type[4] + { + typeof (T1), + typeof (T2), + typeof (T3), + typeof (T4) + }, constructorArguments); + } + + + // public static IGitRepositoriesExt IGitRepositoriesExt { get { return Substitute.For<IGitRepositoriesExt>(); } } + public static IGitService IGitService { get { return Substitute.For<IGitService>(); } } + + public static IVSGitServices IVSGitServices + { + get + { + var ret = Substitute.For<IVSGitServices>(); + ret.GetLocalClonePathFromGitProvider().Returns(@"c:\foo\bar"); + return ret; + } + } + + public static IOperatingSystem OperatingSystem + { + get + { + var ret = Substitute.For<IOperatingSystem>(); + // this expansion happens when the GetLocalClonePathFromGitProvider call is setup by default + // see IVSServices property above + ret.Environment.ExpandEnvironmentVariables(Args.String).Returns(x => x[0]); + return ret; + } + } + + public static IViewViewModelFactory ViewViewModelFactory { get { return Substitute.For<IViewViewModelFactory>(); } } + + public static IRepositoryCreationService RepositoryCreationService { get { return Substitute.For<IRepositoryCreationService>(); } } + public static IRepositoryCloneService RepositoryCloneService { get { return Substitute.For<IRepositoryCloneService>(); } } + + public static IConnection Connection { get { return Substitute.For<IConnection>(); } } + public static IConnectionManager ConnectionManager { get { return Substitute.For<IConnectionManager>(); } } + public static IDelegatingTwoFactorChallengeHandler TwoFactorChallengeHandler { get { return Substitute.For<IDelegatingTwoFactorChallengeHandler>(); } } + public static IGistPublishService GistPublishService { get { return Substitute.For<IGistPublishService>(); } } + public static IPullRequestService PullRequestService { get { return Substitute.For<IPullRequestService>(); } } + + /// <summary> + /// This returns a service provider with everything mocked except for + /// RepositoryCloneService and RepositoryCreationService, which are real + /// instances. + /// </summary> + public static IGitHubServiceProvider ServiceProvider { get { return GetServiceProvider(); } } + + /// <summary> + /// This returns a service provider with mocked IRepositoryCreationService and + /// IRepositoryCloneService as well as all other services mocked. The regular + /// GetServiceProvider method (and ServiceProvider property return a IServiceProvider + /// with real RepositoryCloneService and RepositoryCreationService instances. + /// </summary> + /// <returns></returns> + public static IServiceProvider GetFullyMockedServiceProvider() + { + return GetServiceProvider(RepositoryCloneService, RepositoryCreationService); + } + + /// <summary> + /// This returns a service provider with everything mocked except for + /// RepositoryCloneService and RepositoryCreationService, which are real + /// instances. + /// </summary> + /// <param name="cloneService"></param> + /// <param name="creationService"></param> + /// <returns></returns> + public static IGitHubServiceProvider GetServiceProvider( + IRepositoryCloneService cloneService = null, + IRepositoryCreationService creationService = null, + IAvatarProvider avatarProvider = null) + { + var ret = Substitute.For<IGitHubServiceProvider, IServiceProvider>(); + + var gitservice = IGitService; + var cm = Substitute.For<SComponentModel, IComponentModel>(); + var cc = new CompositionContainer(CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); + cc.ComposeExportedValue(gitservice); + ((IComponentModel)cm).DefaultExportProvider.Returns(cc); + ret.GetService(typeof(SComponentModel)).Returns(cm); + Services.UnitTestServiceProvider = ret; + + var os = OperatingSystem; + var vsgit = IVSGitServices; + var clone = cloneService ?? new RepositoryCloneService(os, vsgit, Substitute.For<IUsageTracker>()); + var create = creationService ?? new RepositoryCreationService(clone); + avatarProvider = avatarProvider ?? Substitute.For<IAvatarProvider>(); + //ret.GetService(typeof(IGitRepositoriesExt)).Returns(IGitRepositoriesExt); + ret.GetService(typeof(IGitService)).Returns(gitservice); + ret.GetService(typeof(IVSServices)).Returns(Substitute.For<IVSServices>()); + ret.GetService(typeof(IVSGitServices)).Returns(vsgit); + ret.GetService(typeof(IOperatingSystem)).Returns(os); + ret.GetService(typeof(IRepositoryCloneService)).Returns(clone); + ret.GetService(typeof(IRepositoryCreationService)).Returns(create); + ret.GetService(typeof(IViewViewModelFactory)).Returns(ViewViewModelFactory); + ret.GetService(typeof(IConnection)).Returns(Connection); + ret.GetService(typeof(IConnectionManager)).Returns(ConnectionManager); + ret.GetService(typeof(IAvatarProvider)).Returns(avatarProvider); + ret.GetService(typeof(IDelegatingTwoFactorChallengeHandler)).Returns(TwoFactorChallengeHandler); + ret.GetService(typeof(IGistPublishService)).Returns(GistPublishService); + ret.GetService(typeof(IPullRequestService)).Returns(PullRequestService); + return ret; + } + + //public static IGitRepositoriesExt GetGitExt(this IServiceProvider provider) + //{ + // return provider.GetService(typeof(IGitRepositoriesExt)) as IGitRepositoriesExt; + //} + + public static IVSServices GetVSServices(this IServiceProvider provider) + { + return provider.GetService(typeof(IVSServices)) as IVSServices; + } + + public static IVSGitServices GetVSGitServices(this IServiceProvider provider) + { + return provider.GetService(typeof(IVSGitServices)) as IVSGitServices; + } + + public static IGitService GetGitService(this IServiceProvider provider) + { + return provider.GetService(typeof(IGitService)) as IGitService; + } + + public static IOperatingSystem GetOperatingSystem(this IServiceProvider provider) + { + return provider.GetService(typeof(IOperatingSystem)) as IOperatingSystem; + } + + public static IRepositoryCloneService GetRepositoryCloneService(this IServiceProvider provider) + { + return provider.GetService(typeof(IRepositoryCloneService)) as IRepositoryCloneService; + } + + public static IRepositoryCreationService GetRepositoryCreationService(this IServiceProvider provider) + { + return provider.GetService(typeof(IRepositoryCreationService)) as IRepositoryCreationService; + } + + public static IViewViewModelFactory GetExportFactoryProvider(this IServiceProvider provider) + { + return provider.GetService(typeof(IViewViewModelFactory)) as IViewViewModelFactory; + } + + public static IConnection GetConnection(this IServiceProvider provider) + { + return provider.GetService(typeof(IConnection)) as IConnection; + } + + public static IConnectionManager GetConnectionManager(this IServiceProvider provider) + { + return provider.GetService(typeof(IConnectionManager)) as IConnectionManager; + } + + public static IAvatarProvider GetAvatarProvider(this IServiceProvider provider) + { + return provider.GetService(typeof(IAvatarProvider)) as IAvatarProvider; + } + + public static IDelegatingTwoFactorChallengeHandler GetTwoFactorChallengeHandler(this IServiceProvider provider) + { + return provider.GetService(typeof(IDelegatingTwoFactorChallengeHandler)) as IDelegatingTwoFactorChallengeHandler; + } + + public static IGistPublishService GetGistPublishService(this IServiceProvider provider) + { + return provider.GetService(typeof(IGistPublishService)) as IGistPublishService; + } + + public static IPullRequestService GetPullRequestsService(this IServiceProvider provider) + { + return provider.GetService(typeof(IPullRequestService)) as IPullRequestService; + } + } +} diff --git a/test/GitHub.Exports.UnitTests/TestDoubles/FakeCommitLog.cs b/test/GitHub.Exports.UnitTests/TestDoubles/FakeCommitLog.cs new file mode 100644 index 0000000000..7262bdcb7e --- /dev/null +++ b/test/GitHub.Exports.UnitTests/TestDoubles/FakeCommitLog.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using LibGit2Sharp; + +public class FakeCommitLog : List<Commit>, IQueryableCommitLog +{ + public CommitSortStrategies SortedBy + { + get + { + return CommitSortStrategies.Topological; + } + } + + public Commit FindMergeBase(IEnumerable<Commit> commits, MergeBaseFindingStrategy strategy) + { + throw new NotImplementedException(); + } + + public Commit FindMergeBase(Commit first, Commit second) + { + throw new NotImplementedException(); + } + + public IEnumerable<LogEntry> QueryBy(string path) + { + throw new NotImplementedException(); + } + + public ICommitLog QueryBy(CommitFilter filter) + { + throw new NotImplementedException(); + } + +#pragma warning disable 618 // Type or member is obsolete + public IEnumerable<LogEntry> QueryBy(string path, FollowFilter filter) + { + throw new NotImplementedException(); + } +#pragma warning restore 618 // Type or member is obsolete + + public IEnumerable<LogEntry> QueryBy(string path, CommitFilter filter) + { + throw new NotImplementedException(); + } +} diff --git a/test/GitHub.Exports.UnitTests/VSServicesTests.cs b/test/GitHub.Exports.UnitTests/VSServicesTests.cs new file mode 100644 index 0000000000..9094551dad --- /dev/null +++ b/test/GitHub.Exports.UnitTests/VSServicesTests.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; +using DTE = EnvDTE.DTE; +using Rothko; +using Serilog; + +public class VSServicesTests +{ + public class TheTryOpenRepositoryMethod : TestBaseClass + { + [Test] + public void NoExceptions_ReturnsTrue() + { + var repoDir = @"x:\repo"; + var target = CreateVSServices(repoDir); + + var success = target.TryOpenRepository(repoDir); + + Assert.True(success); + } + + [Test] + public void SolutionCreateThrows_ReturnsFalse() + { + var repoDir = @"x:\repo"; + var dte = Substitute.For<DTE>(); + var log = Substitute.For<ILogger>(); + var ex = new COMException(); + dte.Solution.When(s => s.Create(Arg.Any<string>(), Arg.Any<string>())).Do(ci => { throw ex; }); + var target = CreateVSServices(repoDir, dte: dte, log: log); + + var success = target.TryOpenRepository(repoDir); + + Assert.False(success); + log.Received(1).Error(ex, "Error opening repository"); + } + + [Test] + public void RepoDirExistsFalse_ReturnFalse() + { + var repoDir = @"x:\repo"; + var target = CreateVSServices(repoDir, repoDirExists: false); + + var success = target.TryOpenRepository(repoDir); + + Assert.False(success); + } + + [Test] + public void DeleteThrowsIOException_ReturnTrue() + { + var repoDir = @"x:\repo"; + var tempDir = Path.Combine(repoDir, ".vs", VSServices.TempSolutionName); + var os = Substitute.For<IOperatingSystem>(); + var directoryInfo = Substitute.For<IDirectoryInfo>(); + directoryInfo.Exists.Returns(true); + os.Directory.GetDirectory(tempDir).Returns(directoryInfo); + directoryInfo.When(di => di.Delete(true)).Do( + ci => { throw new IOException(); }); + var target = CreateVSServices(repoDir, os: os); + + var success = target.TryOpenRepository(repoDir); + + Assert.True(success); + } + + [Test] + public void SolutionCreate_DeleteVsSolutionSubdir() + { + var repoDir = @"x:\repo"; + var tempDir = Path.Combine(repoDir, ".vs", VSServices.TempSolutionName); + var os = Substitute.For<IOperatingSystem>(); + var directoryInfo = Substitute.For<IDirectoryInfo>(); + directoryInfo.Exists.Returns(true); + os.Directory.GetDirectory(tempDir).Returns(directoryInfo); + var target = CreateVSServices(repoDir, os: os); + + var success = target.TryOpenRepository(repoDir); + + directoryInfo.Received().Delete(true); + } + + VSServices CreateVSServices(string repoDir, IOperatingSystem os = null, DTE dte = null, bool repoDirExists = true, ILogger log = null) + { + os = os ?? Substitute.For<IOperatingSystem>(); + dte = dte ?? Substitute.For<DTE>(); + log = log ?? Substitute.For<ILogger>(); + + if (repoDir != null) + { + var directoryInfo = Substitute.For<IDirectoryInfo>(); + directoryInfo.Exists.Returns(repoDirExists); + os.Directory.GetDirectory(repoDir).Returns(directoryInfo); + } + + var provider = Substitute.For<IGitHubServiceProvider>(); + provider.TryGetService<DTE>().Returns(dte); + provider.TryGetService<IOperatingSystem>().Returns(os); + return new VSServices(provider, log); + } + } + + public class TheCloneMethod : TestBaseClass + { + /* + [Theory] + [InlineData(true, CloneOptions.RecurseSubmodule)] + [InlineData(false, CloneOptions.None)] + public void CallsCloneOnVsProvidedCloneService(bool recurseSubmodules, CloneOptions expectedCloneOptions) + { + var provider = Substitute.For<IUIProvider>(); + var gitRepositoriesExt = Substitute.For<IGitRepositoriesExt>(); + provider.GetService(typeof(IGitRepositoriesExt)).Returns(gitRepositoriesExt); + provider.TryGetService(typeof(IGitRepositoriesExt)).Returns(gitRepositoriesExt); + var vsServices = new VSServices(provider); + + vsServices.Clone("https://github.com/github/visualstudio", @"c:\fake\ghfvs", recurseSubmodules); + + gitRepositoriesExt.Received() + .Clone("https://github.com/github/visualstudio", @"c:\fake\ghfvs", expectedCloneOptions); + } + */ + } +} diff --git a/test/GitHub.Exports.UnitTests/packages.config b/test/GitHub.Exports.UnitTests/packages.config new file mode 100644 index 0000000000..6845f9e0c7 --- /dev/null +++ b/test/GitHub.Exports.UnitTests/packages.config @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" /> + <package id="Serilog" version="2.5.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.Extensions.UnitTests/GitHub.Extensions.UnitTests.csproj b/test/GitHub.Extensions.UnitTests/GitHub.Extensions.UnitTests.csproj new file mode 100644 index 0000000000..61ccc63438 --- /dev/null +++ b/test/GitHub.Extensions.UnitTests/GitHub.Extensions.UnitTests.csproj @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{DE704BBB-6EC6-4173-B695-D9EBF5AEB092}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.Extensions.UnitTests</RootNamespace> + <AssemblyName>GitHub.Extensions.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Testing.2.2.5-custom\lib\net45\Microsoft.Reactive.Testing.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Windows.Threading, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="GuardTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="UriExtensionTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <Choose> + <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + </ItemGroup> + </When> + </Choose> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.Extensions.UnitTests/GuardTests.cs b/test/GitHub.Extensions.UnitTests/GuardTests.cs new file mode 100644 index 0000000000..8690454754 --- /dev/null +++ b/test/GitHub.Extensions.UnitTests/GuardTests.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GitHub.Extensions; +using NUnit.Framework; + +namespace UnitTests.GitHub.Extensions +{ + public class GuardTests + { + public class TheArgumentNotNullMethod : TestBaseClass + { + [Test] + public void ShouldNotThrow() + { + Guard.ArgumentNotNull(new object(), "name"); + } + + [Test] + public void ShouldThrow() + { + Assert.Throws<ArgumentNullException>(() => Guard.ArgumentNotNull(null, "name")); + } + } + + public class TheArgumentNonNegativeMethod : TestBaseClass + { + [Test] + public void ShouldNotThrowFor0() + { + Guard.ArgumentNonNegative(0, "name"); + } + + [Test] + public void ShouldNotThrowFor1() + { + Guard.ArgumentNonNegative(1, "name"); + } + + [Test] + public void ShouldThrowForMinus1() + { + Assert.Throws<ArgumentException>(() => Guard.ArgumentNonNegative(-1, "name")); + } + } + + public class TheArgumentNotEmptyStringMethod : TestBaseClass + { + [Test] + public void ShouldNotThrowForString() + { + Guard.ArgumentNotEmptyString("string", "name"); + } + + [Test] + public void ShouldThrowForEmptyString() + { + Assert.Throws<ArgumentException>(() => Guard.ArgumentNotEmptyString("", "name")); + } + + [Test] + public void ShouldThrowForNull() + { + Assert.Throws<ArgumentException>(() => Guard.ArgumentNotEmptyString(null, "name")); + } + } + + public class TheArgumentInRangeMethod : TestBaseClass + { + [Test] + public void ShouldNotThrowForGreaterThanMinimum() + { + Guard.ArgumentInRange(12, 10, "name"); + } + + [Test] + public void ShouldNotThrowForEqualToMinimumNoMaximum() + { + Guard.ArgumentInRange(10, 10, "name"); + } + + [Test] + public void ShouldNotThrowForEqualToMinimumWithMaximum() + { + Guard.ArgumentInRange(10, 10, 20, "name"); + } + + [Test] + public void ShouldNotThrowForEqualToMaximum() + { + Guard.ArgumentInRange(20, 10, 20, "name"); + } + + [Test] + public void ShouldNotThrowForBetweenMinimumAndMaximum() + { + Guard.ArgumentInRange(12, 10, 20, "name"); + } + + [Test] + public void ShouldThrowForLessThanMinimumNoMaximum() + { + Assert.Throws<ArgumentOutOfRangeException>(() => Guard.ArgumentInRange(2, 10, "name")); + } + + [Test] + public void ShouldThrowForLessThanMinimumWithMaximum() + { + Assert.Throws<ArgumentOutOfRangeException>(() => Guard.ArgumentInRange(2, 10, 20, "name")); + } + + [Test] + public void ShouldThrowForGreaterThanMaximum() + { + Assert.Throws<ArgumentOutOfRangeException>(() => Guard.ArgumentInRange(22, 10, 20, "name")); + } + } + } +} \ No newline at end of file diff --git a/test/GitHub.Extensions.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.Extensions.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..687251f17c --- /dev/null +++ b/test/GitHub.Extensions.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.Extensions.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.Extensions.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("de704bbb-6ec6-4173-b695-d9ebf5aeb092")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.Extensions.UnitTests/UriExtensionTests.cs b/test/GitHub.Extensions.UnitTests/UriExtensionTests.cs new file mode 100644 index 0000000000..eb194fb18e --- /dev/null +++ b/test/GitHub.Extensions.UnitTests/UriExtensionTests.cs @@ -0,0 +1,24 @@ +using System; +using GitHub.Extensions; +using NUnit.Framework; + +public class UriExtensionTests +{ + public class TheAppendMethod : TestBaseClass + { + [TestCase("https://github.com/foo/bar", "graphs", "https://github.com/foo/bar/graphs")] + [TestCase("https://github.com/foo/bar/", "graphs", "https://github.com/foo/bar/graphs")] + [TestCase("https://github.com", "bippety/boppety", "https://github.com/bippety/boppety")] + [TestCase("https://github.com/", "bippety/boppety", "https://github.com/bippety/boppety")] + [TestCase("https://github.com/foo/bar", "bippety/boppety", "https://github.com/foo/bar/bippety/boppety")] + public void AppendsRelativePath(string url, string relativePath, string expected) + { + var uri = new Uri(url, UriKind.Absolute); + var expectedUri = new Uri(expected, UriKind.Absolute); + + var result = uri.Append(relativePath); + + Assert.That(expectedUri, Is.EqualTo(result)); + } + } +} diff --git a/test/GitHub.Extensions.UnitTests/packages.config b/test/GitHub.Extensions.UnitTests/packages.config new file mode 100644 index 0000000000..ab0809708f --- /dev/null +++ b/test/GitHub.Extensions.UnitTests/packages.config @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Testing" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj b/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj new file mode 100644 index 0000000000..a9205cdafd --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj @@ -0,0 +1,195 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{17EB676B-BB91-48B5-AA59-C67695C647C2}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.InlineReviews.UnitTests</RootNamespace> + <AssemblyName>GitHub.InlineReviews.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <TargetFrameworkProfile /> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.Win32.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Net.Http, Version=4.1.0.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Net.Http.4.1.1\lib\net46\System.Net.Http.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.Encoding, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="Models\DiffUtilitiesTests.cs" /> + <Compile Include="Services\PullRequestSessionManagerTests.cs" /> + <Compile Include="Services\PullRequestSessionServiceTests.cs" /> + <Compile Include="Tags\InlineCommentTaggerTests.cs" /> + <Compile Include="TestDoubles\FakeDiffService.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Services\DiffServiceTests.cs" /> + <Compile Include="Services\PullRequestSessionTests.cs" /> + <Compile Include="ViewModels\InlineCommentPeekViewModelTests.cs" /> + <Compile Include="ViewModels\InlineCommentThreadViewModelTests.cs" /> + <Compile Include="ViewModels\NewInlineCommentThreadViewModelTests.cs" /> + <Compile Include="ViewModels\PullRequestReviewCommentViewModelTests.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.App\GitHub.App.csproj"> + <Project>{1a1da411-8d1f-4578-80a6-04576bea2dc5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.InlineReviews\GitHub.InlineReviews.csproj"> + <Project>{7f5ed78b-74a3-4406-a299-70cfb5885b8b}</Project> + <Name>GitHub.InlineReviews</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1CE2D235-8072-4649-BA5A-CFB1AF8776E0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Content Include="Resources\pr-960-diff.txt" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.InlineReviews.UnitTests/Models/DiffUtilitiesTests.cs b/test/GitHub.InlineReviews.UnitTests/Models/DiffUtilitiesTests.cs new file mode 100644 index 0000000000..32be48765b --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Models/DiffUtilitiesTests.cs @@ -0,0 +1,357 @@ +using System; +using System.IO; +using System.Linq; +using GitHub.Models; +using NUnit.Framework; + +namespace GitHub.InlineReviews.UnitTests.Models +{ + public class DiffUtilitiesTests + { + public class TheParseFragmentMethod + { + [Test] + public void EmptyDiff_NoDiffChunks() + { + var chunks = DiffUtilities.ParseFragment(""); + + Assert.That(chunks, Is.Empty); + } + + [TestCase("@@ -1 +1 @@")] + [TestCase("@@ -1 +1,0 @@")] + [TestCase("@@ -1,0 +1 @@")] + [TestCase("@@ -1,0 +1,0 @@")] + [TestCase("@@ -1,0 +1,0 @@ THIS IS A COMMENT THAT WILL BE IGNORED")] + public void HeaderOnly_OneChunkNoLines(string header) + { + var chunks = DiffUtilities.ParseFragment(header); + + Assert.That(chunks, Has.One.Items); + var chunk = chunks.First(); + Assert.That(chunk.Lines, Is.Empty); + } + + [TestCase("@@ -1 +2 @@", 1, 2)] + [TestCase("@@ -1 +2,0 @@", 1, 2)] + [TestCase("@@ -1,0 +2 @@", 1, 2)] + [TestCase("@@ -1,0 +2,0 @@", 1, 2)] + [TestCase("@@ -1,0 +2,0 @@ THIS IS A COMMENT THAT WILL BE IGNORED", 1, 2)] + [TestCase( +@"diff --git a/src/Foo.cs b/src/Foo.cs +index b02decb..f7dadae 100644 +--- a/src/Foo.cs ++++ b/src/Foo.cs +@@ -1 +2 @@", 1, 2)] + + public void HeaderOnly_OldAndNewLineNumbers(string header, int expectOldLineNumber, int expectNewLineNumber) + { + var chunks = DiffUtilities.ParseFragment(header); + var chunk = chunks.First(); + + Assert.That(expectOldLineNumber, Is.EqualTo(chunk.OldLineNumber)); + Assert.That(expectNewLineNumber, Is.EqualTo(chunk.NewLineNumber)); + } + + [Test] + public void HeaderOnlyNoNewLineAtEnd_NoLines() + { + var header = +@"@@ -1 +1 @@ +\ No newline at end of file\n"; + + var chunks = DiffUtilities.ParseFragment(header); + + var chunk = chunks.First(); + Assert.That(chunk.Lines, Is.Empty); + } + + [Test] + public void NoNewLineNotAtEndOfChunk_CheckLineCount() + { + var header = +@"@@ -1 +1 @@ +-old +\ No newline at end of file ++new"; + + var chunk = DiffUtilities.ParseFragment(header).First(); + + Assert.That(2, Is.EqualTo(chunk.Lines.Count())); + } + + [Test] + public void NoNewLineNotAtEndOfChunk_CheckDiffLineNumber() + { + var header = +@"@@ -1 +1 @@ +-old +\ No newline at end of file ++new"; + + var chunk = DiffUtilities.ParseFragment(header).First(); + + var line = chunk.Lines.Last(); + Assert.That(3, Is.EqualTo(line.DiffLineNumber)); + } + + [TestCase("+foo\n+bar\n", "+foo", "+bar")] + [TestCase("+fo\ro\n+bar\n", "+fo\ro", "+bar")] + [TestCase("+foo\r\r\n+bar\n", "+foo\r", "+bar")] + [TestCase("+\\r\n+\r\n", "+\\r", "+")] + public void FirstChunk_CheckLineContent(string diffLines, string contentLine0, string contentLine1) + { + var header = "@@ -1 +1 @@"; + var diff = header + "\n" + diffLines; + + var chunk = DiffUtilities.ParseFragment(diff).First(); + + Assert.That(contentLine0, Is.EqualTo(chunk.Lines[0].Content)); + Assert.That(contentLine1, Is.EqualTo(chunk.Lines[1].Content)); + } + + [TestCase("+foo\n+bar\n", 1, 2)] + [TestCase("+fo\ro\n+bar\n", 1, 3)] + [TestCase("+foo\r\r\n+bar\n", 1, 3)] + public void FirstChunk_CheckNewLineNumber(string diffLines, int lineNumber0, int lineNumber1) + { + var header = "@@ -1 +1 @@"; + var diff = header + "\n" + diffLines; + + var chunk = DiffUtilities.ParseFragment(diff).First(); + + Assert.That(lineNumber0, Is.EqualTo(chunk.Lines[0].NewLineNumber)); + Assert.That(lineNumber1, Is.EqualTo(chunk.Lines[1].NewLineNumber)); + } + + [TestCase("-foo\n-bar\n", 1, 2)] + [TestCase("-fo\ro\n-bar\n", 1, 3)] + [TestCase("-foo\r\r\n-bar\n", 1, 3)] + public void FirstChunk_CheckOldLineNumber(string diffLines, int lineNumber0, int lineNumber1) + { + var header = "@@ -1 +1 @@"; + var diff = header + "\n" + diffLines; + + var chunk = DiffUtilities.ParseFragment(diff).First(); + + Assert.That(lineNumber0, Is.EqualTo(chunk.Lines[0].OldLineNumber)); + Assert.That(lineNumber1, Is.EqualTo(chunk.Lines[1].OldLineNumber)); + } + + [Test] + public void FirstChunk_CheckDiffLineZeroBased() + { + var expectDiffLine = 0; + var header = "@@ -1 +1 @@"; + + var chunk = DiffUtilities.ParseFragment(header).First(); + + Assert.That(expectDiffLine, Is.EqualTo(chunk.DiffLine)); + } + + [TestCase(1, 2)] + public void FirstChunk_CheckLineNumbers(int oldLineNumber, int newLineNumber) + { + var header = $"@@ -{oldLineNumber} +{newLineNumber} @@"; + + var chunk = DiffUtilities.ParseFragment(header).First(); + + Assert.That(oldLineNumber, Is.EqualTo(chunk.OldLineNumber)); + Assert.That(newLineNumber, Is.EqualTo(chunk.NewLineNumber)); + } + + [TestCase(1, 2, " 1", 1, 2)] + [TestCase(1, 2, "+1", -1, 2)] + [TestCase(1, 2, "-1", 1, -1)] + public void FirstLine_CheckLineNumbers(int oldLineNumber, int newLineNumber, string line, int expectOldLineNumber, int expectNewLineNumber) + { + var header = $"@@ -{oldLineNumber} +{newLineNumber} @@\n{line}"; + + var chunk = DiffUtilities.ParseFragment(header).First(); + var diffLine = chunk.Lines.First(); + + Assert.That(expectOldLineNumber, Is.EqualTo(diffLine.OldLineNumber)); + Assert.That(expectNewLineNumber, Is.EqualTo(diffLine.NewLineNumber)); + } + + [TestCase(" 1", 0, 1)] + [TestCase(" 1\n 2", 1, 2)] + [TestCase(" 1\n 2\n 3", 2, 3)] + public void SkipNLines_CheckDiffLineNumber(string lines, int skip, int expectDiffLineNumber) + { + var fragment = $"@@ -1 +1 @@\n{lines}"; + + var result = DiffUtilities.ParseFragment(fragment); + + var firstLine = result.First().Lines.Skip(skip).First(); + Assert.That(expectDiffLineNumber, Is.EqualTo(firstLine.DiffLineNumber)); + } + + [TestCase(" FIRST")] + [TestCase("+FIRST")] + [TestCase("-FIRST")] + public void FirstLine_CheckToString(string line) + { + var fragment = $"@@ -1 +1 @@\n{line}"; + var result = DiffUtilities.ParseFragment(fragment); + var firstLine = result.First().Lines.First(); + + var str = firstLine.ToString(); + + Assert.That(line, Is.EqualTo(str)); + } + + [TestCase(" FIRST")] + [TestCase("+FIRST")] + [TestCase("-FIRST")] + public void FirstLine_CheckContent(string line) + { + var fragment = $"@@ -1,4 +1,4 @@\n{line}"; + + var result = DiffUtilities.ParseFragment(fragment); + var firstLine = result.First().Lines.First(); + + Assert.That(line, Is.EqualTo(firstLine.Content)); + } + + [TestCase(" FIRST", DiffChangeType.None)] + [TestCase("+FIRST", DiffChangeType.Add)] + [TestCase("-FIRST", DiffChangeType.Delete)] + public void FirstLine_CheckDiffChangeTypes(string line, DiffChangeType expectType) + { + var fragment = $"@@ -1 +1 @@\n{line}"; + + var result = DiffUtilities.ParseFragment(fragment); + + var firstLine = result.First().Lines.First(); + Assert.That(expectType, Is.EqualTo(firstLine.Type)); + } + + [TestCase("?FIRST", "Invalid diff line change char: '?'.")] + public void InvalidDiffLineChangeChar(string line, string expectMessage) + { + var fragment = $"@@ -1,4 +1,4 @@\n{line}"; + + var result = DiffUtilities.ParseFragment(fragment); + var e = Assert.Throws<InvalidDataException>(() => result.First()); + + Assert.That(expectMessage, Is.EqualTo(e.Message)); + } + } + + public class TheMatchMethod + { + /// <param name="diffLines">Target diff chunk with header (with '.' as line separator)</param> + /// <param name="matchLines">Diff lines to match (with '.' as line separator)</param> + /// <param name="expectedDiffLineNumber">The DiffLineNumber that the last line of matchLines falls on</param> + [TestCase(" 1", " 1", 1)] + [TestCase(" 1. 2", " 2", 2)] + [TestCase(" 1. 1", " 1", 2)] // match the later line + [TestCase("+x", "-x", -1)] + [TestCase("", " x", -1)] + [TestCase(" x", "", -1)] + + [TestCase(" 1. 2.", " 1. 2.", 2)] // matched full context + [TestCase(" 1. 2.", " 3. 2.", -1)] // didn't match full context + [TestCase(" 2.", " 1. 2.", 1)] // match if we run out of context lines + + // Tests for https://github.com/github/VisualStudio/issues/1149 + // Matching algorithm got confused when there was a partial match. + [TestCase("+a.+x.+x.", "+a.+x.", 2)] + [TestCase("+a.+x.+x.", "+a.+x.+x.", 3)] + [TestCase("+a.+x.+x.+b.+x.+x.", "+a.+x.", 2)] + [TestCase("+a.+x.+x.+b.+x.+x.", "+b.+x.", 5)] + [TestCase("+a.+b.+x", "+a.+x.", -1)] // backtrack when there is a failed match + public void MatchLine(string diffLines, string matchLines, int expectedDiffLineNumber /* -1 for no match */) + { + var header = "@@ -1 +1 @@"; + diffLines = diffLines.Replace(".", "\r\n"); + matchLines = matchLines.Replace(".", "\r\n"); + var chunks1 = DiffUtilities.ParseFragment(header + "\n" + diffLines).ToList(); + var chunks2 = DiffUtilities.ParseFragment(header + "\n" + matchLines).ToList(); + var targetLines = chunks2.First().Lines.Reverse().ToList(); + + var line = DiffUtilities.Match(chunks1, targetLines); + + var diffLineNumber = (line != null) ? line.DiffLineNumber : -1; + Assert.That(expectedDiffLineNumber, Is.EqualTo(diffLineNumber)); + } + + [Test] + public void MatchSameLine() + { + var diff = "@@ -1 +1 @@\n 1"; + var chunks1 = DiffUtilities.ParseFragment(diff).ToList(); + var chunks2 = DiffUtilities.ParseFragment(diff).ToList(); + var expectLine = chunks1.First().Lines.First(); + var targetLine = chunks2.First().Lines.First(); + var targetLines = new[] { targetLine }; + + var line = DiffUtilities.Match(chunks1, targetLines); + + Assert.That(expectLine, Is.EqualTo(line)); + } + + [Test] + public void NoLineMatchesFromNoLines() + { + var chunks = new DiffChunk[0]; + var lines = new DiffLine[0]; + + var line = DiffUtilities.Match(chunks, lines); + + Assert.That(line, Is.Null); + } + } + + public class TheLineReaderClass + { + [TestCase("", new[] { "", null })] + [TestCase("\n", new[] { "", null })] + [TestCase("\r\n", new[] { "", null })] + [TestCase("1", new[] { "1", null })] + [TestCase("1\n2\n", new[] { "1", "2", null })] + [TestCase("1\n2", new[] { "1", "2", null })] + [TestCase("1\r\n2\n", new[] { "1", "2", null })] + [TestCase("1\r\n2", new[] { "1", "2", null })] + [TestCase("\r", new[] { "\r", null })] + [TestCase("\r\r", new[] { "\r\r", null })] + [TestCase("\r\r\n", new[] { "\r", null })] + [TestCase("\r_\n", new[] { "\r_", null })] + public void ReadLines(string text, string[] expectLines) + { + var lineReader = new DiffUtilities.LineReader(text); + + foreach (var expectLine in expectLines) + { + var line = lineReader.ReadLine(); + Assert.That(expectLine, Is.EqualTo(line)); + } + } + + [Test] + public void Constructor_NullText_ArgumentNullException() + { + Assert.Throws<ArgumentNullException>(() => new DiffUtilities.LineReader(null)); + } + + [TestCase("", 0)] + [TestCase("\r", 1)] + [TestCase("\r\n", 1)] + [TestCase("\r\r", 2)] + [TestCase("\r-\r", 2)] + public void CountCarriageReturns(string text, int expectCount) + { + var count = DiffUtilities.LineReader.CountCarriageReturns(text); + + Assert.That(expectCount, Is.EqualTo(count)); + } + + [Test] + public void CountCarriageReturns_NullText_ArgumentNullException() + { + Assert.Throws<ArgumentNullException>(() => DiffUtilities.LineReader.CountCarriageReturns(null)); + } + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.InlineReviews.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..5a8cdbf1b2 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using NUnit.Framework; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.InlineReviews.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.InlineReviews.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("17eb676b-bb91-48b5-aa59-c67695c647c2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: Timeout(2 /*minutes*/ * 60 * 1000)] diff --git a/test/GitHub.InlineReviews.UnitTests/Properties/Resources.Designer.cs b/test/GitHub.InlineReviews.UnitTests/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..40d6b2a39d --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Properties/Resources.Designer.cs @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace GitHub.InlineReviews.UnitTests.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GitHub.InlineReviews.UnitTests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to diff --git a/src/GitHub.VisualStudio/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs + ///index b02decb..f7dadae 100644 + ///--- a/src/GitHub.VisualStudio/Services/UsageTracker.cs + ///+++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs + ///@@ -11,21 +11,21 @@ + /// using GitHub.Extensions; + /// using System.Threading.Tasks; + /// using GitHub.Helpers; + ///+using System.Threading; + /// + /// namespace GitHub.Services + /// { + ///- public class UsageTracker : IUsageTracker + ///+ public sealed class UsageTracker : IU [rest of string was truncated]";. + /// </summary> + internal static string pr_960_diff { + get { + return ResourceManager.GetString("pr_960_diff", resourceCulture); + } + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/Properties/Resources.resx b/test/GitHub.InlineReviews.UnitTests/Properties/Resources.resx new file mode 100644 index 0000000000..539b905040 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Properties/Resources.resx @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> + <data name="pr_960_diff" type="System.Resources.ResXFileRef, System.Windows.Forms"> + <value>..\resources\pr-960-diff.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> + </data> +</root> \ No newline at end of file diff --git a/test/GitHub.InlineReviews.UnitTests/Resources/pr-960-diff.txt b/test/GitHub.InlineReviews.UnitTests/Resources/pr-960-diff.txt new file mode 100644 index 0000000000..01faa5cc78 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Resources/pr-960-diff.txt @@ -0,0 +1,83 @@ +diff --git a/src/GitHub.VisualStudio/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs +index b02decb..f7dadae 100644 +--- a/src/GitHub.VisualStudio/Services/UsageTracker.cs ++++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs +@@ -11,21 +11,21 @@ + using GitHub.Extensions; + using System.Threading.Tasks; + using GitHub.Helpers; ++using System.Threading; + + namespace GitHub.Services + { +- public class UsageTracker : IUsageTracker ++ public sealed class UsageTracker : IUsageTracker, IDisposable + { + const string StoreFileName = "ghfvs.usage"; + static readonly Calendar cal = CultureInfo.InvariantCulture.Calendar; + + readonly IGitHubServiceProvider gitHubServiceProvider; +- readonly DispatcherTimer timer; +- + IMetricsService client; + IConnectionManager connectionManager; + IPackageSettings userSettings; + IVSServices vsservices; ++ Timer timer; + string storePath; + bool firstRun = true; + +@@ -61,13 +61,16 @@ public UsageTracker(IGitHubServiceProvider gitHubServiceProvider) + }; + dirCreate = (path) => System.IO.Directory.CreateDirectory(path); + +- this.timer = new DispatcherTimer( +- TimeSpan.FromMinutes(3), +- DispatcherPriority.Background, ++ this.timer = new Timer( + TimerTick, +- ThreadingHelper.MainThreadDispatcher); ++ null, ++ TimeSpan.FromMinutes(3), ++ TimeSpan.FromHours(8)); ++ } + +- RunTimer(); ++ public void Dispose() ++ { ++ timer?.Dispose(); + } + + public async Task IncrementLaunchCount() +@@ -244,14 +247,7 @@ void SaveUsage(UsageStore store) + writeAllText(storePath, json, Encoding.UTF8); + } + +- void RunTimer() +- { +- // The timer first ticks after 3 minutes to allow things to settle down after startup. +- // This will be changed to 8 hours after the first tick by the TimerTick method. +- timer.Start(); +- } +- +- void TimerTick(object sender, EventArgs e) ++ void TimerTick(object state) + { + TimerTick() + .Catch(ex => +@@ -268,13 +264,13 @@ void TimerTick(object sender, EventArgs e) + if (firstRun) + { + await IncrementLaunchCount(); +- timer.Interval = TimeSpan.FromHours(8); + firstRun = false; + } + + if (client == null || !userSettings.CollectMetrics) + { +- timer.Stop(); ++ timer.Dispose(); ++ timer = null; + return; + } + \ No newline at end of file diff --git a/test/GitHub.InlineReviews.UnitTests/Services/DiffServiceTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/DiffServiceTests.cs new file mode 100644 index 0000000000..1752d24ae3 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Services/DiffServiceTests.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Linq; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.UnitTests.Properties; +using GitHub.Services; +using GitHub.Models; +using NSubstitute; +using NUnit.Framework; + +namespace GitHub.InlineReviews.UnitTests.Services +{ + public class DiffServiceTests + { + public class TheParseFragmentMethod + { + [Test] + public void ShouldParsePr960() + { + var target = new DiffService(Substitute.For<IGitClient>()); + var result = DiffUtilities.ParseFragment(Resources.pr_960_diff).ToList(); + + Assert.That(4, Is.EqualTo(result.Count)); + + Assert.That(11, Is.EqualTo(result[0].OldLineNumber)); + Assert.That(11, Is.EqualTo(result[0].NewLineNumber)); + Assert.That(24, Is.EqualTo(result[0].Lines.Count)); + + Assert.That(61, Is.EqualTo(result[1].OldLineNumber)); + Assert.That(61, Is.EqualTo(result[1].NewLineNumber)); + Assert.That(21, Is.EqualTo(result[1].Lines.Count)); + + Assert.That(244, Is.EqualTo(result[2].OldLineNumber)); + Assert.That(247, Is.EqualTo(result[2].NewLineNumber)); + Assert.That(15, Is.EqualTo(result[2].Lines.Count)); + + Assert.That(268, Is.EqualTo(result[3].OldLineNumber)); + Assert.That(264, Is.EqualTo(result[3].NewLineNumber)); + Assert.That(15, Is.EqualTo(result[3].Lines.Count)); + + // - public class UsageTracker : IUsageTracker + Assert.That(17, Is.EqualTo(result[0].Lines[7].OldLineNumber)); + Assert.That(-1, Is.EqualTo(result[0].Lines[7].NewLineNumber)); + Assert.That(8, Is.EqualTo(result[0].Lines[7].DiffLineNumber)); + + // + public sealed class UsageTracker : IUsageTracker, IDisposable + Assert.That(-1, Is.EqualTo(result[0].Lines[8].OldLineNumber)); + Assert.That(18, Is.EqualTo(result[0].Lines[8].NewLineNumber)); + Assert.That(9, Is.EqualTo(result[0].Lines[8].DiffLineNumber)); + + // IConnectionManager connectionManager; + Assert.That(26, Is.EqualTo(result[0].Lines[17].OldLineNumber)); + Assert.That(25, Is.EqualTo(result[0].Lines[17].NewLineNumber)); + Assert.That(18, Is.EqualTo(result[0].Lines[17].DiffLineNumber)); + } + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs new file mode 100644 index 0000000000..fd7f7bf039 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs @@ -0,0 +1,942 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Disposables; +using System.Text; +using System.Threading.Tasks; +using GitHub.Factories; +using GitHub.InlineReviews.Models; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.UnitTests.TestDoubles; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using NSubstitute; +using NUnit.Framework; +using System.ComponentModel; +using GitHub.Api; + +namespace GitHub.InlineReviews.UnitTests.Services +{ + public class PullRequestSessionManagerTests + { + const int CurrentBranchPullRequestNumber = 15; + const int NotCurrentBranchPullRequestNumber = 10; + const string OwnerCloneUrl = "https://github.com/owner/repo"; + static readonly ActorModel CurrentUser = new ActorModel { Login = "currentUser" }; + + public PullRequestSessionManagerTests() + { + Splat.ModeDetector.Current.SetInUnitTestRunner(true); + } + + public class TheConstructor : PullRequestSessionManagerTests + { + [Test] + public void ReadsPullRequestFromCorrectFork() + { + var service = CreatePullRequestService(); + var sessionService = CreateSessionService(); + + service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs( + Observable.Return(Tuple.Create("fork", CurrentBranchPullRequestNumber))); + + var connectionManager = CreateConnectionManager(); + var target = CreateTarget( + service: service, + sessionService: sessionService, + connectionManager: connectionManager); + + var address = HostAddress.Create(OwnerCloneUrl); + sessionService.Received(1).ReadPullRequestDetail(address, "fork", "repo", 15); + } + + [Test] + public void LocalRepositoryModelNull() + { + var repositoryModel = null as LocalRepositoryModel; + var teamExplorerContext = CreateTeamExplorerContext(repositoryModel); + + var target = CreateTarget(teamExplorerContext: teamExplorerContext); + + Assert.Null(target.CurrentSession); + } + } + + public class TheCurrentSessionProperty : PullRequestSessionManagerTests + { + [Test] + public void CreatesSessionForCurrentBranch() + { + var target = CreateTarget(); + + Assert.That(target.CurrentSession, Is.Not.Null); + Assert.That(target.CurrentSession.IsCheckedOut, Is.True); + } + + [Test] + public void CurrentSessionIsNullIfNoPullRequestForCurrentBranch() + { + var service = CreatePullRequestService(); + service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Empty<Tuple<string, int>>()); + + var target = CreateTarget(service: service); + + Assert.That(target.CurrentSession, Is.Null); + } + + [Test] + public void CurrentSessionChangesWhenBranchChanges() + { + var service = CreatePullRequestService(); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + var target = CreateTarget( + service: service, + teamExplorerContext: teamExplorerContext); + + var session = target.CurrentSession; + + service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Return(Tuple.Create("foo", 22))); + teamExplorerContext.StatusChanged += Raise.Event(); + + Assert.That(session, Is.Not.SameAs(target.CurrentSession)); + } + + [Test] + public void LocalRepositoryModelNull() + { + var repositoryModel = null as LocalRepositoryModel; + var target = CreateTarget( + teamExplorerContext: CreateTeamExplorerContext(null)); + + Assert.That(target.CurrentSession, Is.Null); + } + + [Test] + public void CurrentSessionChangesToNullIfNoPullRequestForCurrentBranch() + { + var service = CreatePullRequestService(); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + var target = CreateTarget( + service: service, + teamExplorerContext: teamExplorerContext); + Assert.That(target.CurrentSession, Is.Not.Null); + + Tuple<string, int> newPullRequest = null; + service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Return(newPullRequest)); + teamExplorerContext.StatusChanged += Raise.Event(); + + var session = target.CurrentSession; + + Assert.That(session, Is.Null); + } + + [Test] + public void CurrentSessionChangesToNullWhenRepoChangedToNull() + { + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + var target = CreateTarget(teamExplorerContext: teamExplorerContext); + + Assert.That(target.CurrentSession, Is.Not.Null); + + SetActiveRepository(teamExplorerContext, null); + var session = target.CurrentSession; + + Assert.That(session, Is.Null); + } + + [Test] + public void CurrentSessionChangesWhenRepoChanged() + { + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + var target = CreateTarget(teamExplorerContext: teamExplorerContext); + var session = target.CurrentSession; + + SetActiveRepository(teamExplorerContext, CreateRepositoryModel("https://github.com/owner/other")); + + Assert.That(session, Is.Not.SameAs(target.CurrentSession)); + } + + [Test] + public void RepoChangedDoesntCreateNewSessionIfNotNecessary() + { + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + var target = CreateTarget(teamExplorerContext: teamExplorerContext); + var session = target.CurrentSession; + + teamExplorerContext.StatusChanged += Raise.Event(); + + Assert.That(session, Is.SameAs(target.CurrentSession)); + } + + [Test] + public void RepoChangedHandlesNullRepository() + { + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + var target = CreateTarget(teamExplorerContext: teamExplorerContext); + + SetActiveRepository(teamExplorerContext, null); + + Assert.That(target.CurrentSession, Is.Null); + } + + [Test] + public void CreatesSessionWithCorrectRepositoryOwner() + { + var target = CreateTarget(service: CreatePullRequestService("this-owner")); + + Assert.That("this-owner", Is.EqualTo(target.CurrentSession.RepositoryOwner)); + } + } + + public class TheGetLiveFileMethod : PullRequestSessionManagerTests + { + const string FilePath = "test.cs"; + + [Test] + public async Task BaseShaIsSet() + { + var textView = CreateTextView(); + var target = CreateTarget(); + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That("BASESHA", Is.SameAs(file.BaseSha)); + } + + [Test] + public async Task CommitShaIsSet() + { + var textView = CreateTextView(); + var target = CreateTarget(); + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That("TIPSHA", Is.SameAs(file.CommitSha)); + } + + [Test] + public async Task CommitShaIsNullIfModified() + { + var textView = CreateTextView(); + + var target = CreateTarget(sessionService: CreateSessionService(isModified: true)); + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(file.CommitSha, Is.Null); + } + + [Test] + public async Task DiffIsSet() + { + var textView = CreateTextView(); + var contents = Encoding.UTF8.GetBytes("File contents"); + var diff = new List<DiffChunk>(); + var sessionService = CreateSessionService(); + + sessionService.GetContents(textView.TextBuffer).Returns(contents); + sessionService.GetPullRequestMergeBase(null, null).ReturnsForAnyArgs("MERGE_BASE"); + sessionService.Diff( + Arg.Any<ILocalRepositoryModel>(), + "MERGE_BASE", + "HEADSHA", + FilePath, + contents).Returns(diff); + + var target = CreateTarget(sessionService: sessionService); + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(diff, Is.SameAs(file.Diff)); + } + + [Test] + public async Task InlineCommentThreadsIsSet() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + var threads = new List<IInlineCommentThreadModel>(); + var target = CreateTarget(sessionService: sessionService); + + sessionService.BuildCommentThreads( + target.CurrentSession.PullRequest, + FilePath, + Arg.Any<IReadOnlyList<DiffChunk>>(), + Arg.Any<string>()) + .Returns(threads); + + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(threads, Is.SameAs(file.InlineCommentThreads)); + } + + [Test] + public async Task CreatesTrackingPointsForThreads() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + var threads = new List<IInlineCommentThreadModel> + { + CreateInlineCommentThreadModel(1), + CreateInlineCommentThreadModel(2), + }; + + var target = CreateTarget(sessionService: sessionService); + + sessionService.BuildCommentThreads( + target.CurrentSession.PullRequest, + FilePath, + Arg.Any<IReadOnlyList<DiffChunk>>(), + Arg.Any<string>()) + .Returns(threads); + + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(2, Is.EqualTo(file.TrackingPoints.Count)); + } + + [Test] + public async Task MovingToNoRepositoryShouldNullOutProperties() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + var threads = new List<IInlineCommentThreadModel>(); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); + + var target = CreateTarget( + sessionService: sessionService, + teamExplorerContext: teamExplorerContext); + + sessionService.BuildCommentThreads( + target.CurrentSession.PullRequest, + FilePath, + Arg.Any<IReadOnlyList<DiffChunk>>(), + Arg.Any<string>()) + .Returns(threads); + + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(file.BaseSha, Is.Not.Null); + Assert.That(file.CommitSha, Is.Not.Null); + Assert.That(file.Diff, Is.Not.Null); + Assert.That(file.InlineCommentThreads, Is.Not.Null); + Assert.That(file.TrackingPoints, Is.Not.Null); + + SetActiveRepository(teamExplorerContext, null); + + Assert.That(file.BaseSha, Is.Null); + Assert.That(file.CommitSha, Is.Null); + Assert.That(file.Diff, Is.Null); + Assert.That(file.InlineCommentThreads, Is.Null); + Assert.That(file.TrackingPoints, Is.Null); + } + + [Test] + public async Task ModifyingBufferMarksThreadsAsStaleAndSignalsRebuild() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + var rebuild = Substitute.For<ISubject<ITextSnapshot, ITextSnapshot>>(); + sessionService.CreateRebuildSignal().Returns(rebuild); + + var threads = new List<IInlineCommentThreadModel> + { + CreateInlineCommentThreadModel(1), + CreateInlineCommentThreadModel(2), + }; + + var target = CreateTarget(sessionService: sessionService); + + sessionService.BuildCommentThreads( + target.CurrentSession.PullRequest, + FilePath, + Arg.Any<IReadOnlyList<DiffChunk>>(), + Arg.Any<string>()) + .Returns(threads); + + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + var linesChangedReceived = false; + file.LinesChanged.Subscribe(x => linesChangedReceived = true); + + // Make the first tracking points return a different value so that the thread is marked as stale. + var snapshot = textView.TextSnapshot; + file.TrackingPoints[file.InlineCommentThreads[0]].GetPosition(snapshot).ReturnsForAnyArgs(5); + + SignalTextChanged(textView.TextBuffer); + + threads[0].Received().IsStale = true; + threads[1].DidNotReceive().IsStale = true; + + Assert.That(linesChangedReceived, Is.True); + file.Rebuild.Received().OnNext(Arg.Any<ITextSnapshot>()); + } + + [Test] + public async Task RebuildSignalUpdatesCommitSha() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + sessionService.CreateRebuildSignal().Returns(new Subject<ITextSnapshot>()); + + var threads = new List<IInlineCommentThreadModel> + { + CreateInlineCommentThreadModel(1), + CreateInlineCommentThreadModel(2), + }; + + var target = CreateTarget(sessionService: sessionService); + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That("TIPSHA", Is.SameAs(file.CommitSha)); + + sessionService.IsUnmodifiedAndPushed(null, null, null).ReturnsForAnyArgs(false); + file.Rebuild.OnNext(textView.TextBuffer.CurrentSnapshot); + + Assert.That(file.CommitSha, Is.Null); + } + + [Test] + public async Task ClosingTextViewDisposesFile() + { + var textView = CreateTextView(); + var target = CreateTarget(); + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + var compositeDisposable = file.ToDispose as CompositeDisposable; + Assert.That(compositeDisposable, Is.Not.Null); + Assert.That(compositeDisposable.IsDisposed, Is.False); + + textView.Closed += Raise.Event(); + + Assert.That(compositeDisposable.IsDisposed, Is.True); + } + + [Test] + public async Task InlineCommentThreadsAreLoadedFromCurrentSession() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var contents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + var thread = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService()) + { + var textView = CreateTextView(contents); + var pullRequest = CreatePullRequestModel( + CurrentBranchPullRequestNumber, + thread); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + + var target = CreateTarget(sessionService: CreateRealSessionService(diffService, pullRequest)); + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(file.InlineCommentThreads.Count, Is.EqualTo(1)); + Assert.That(file.InlineCommentThreads[0].LineNumber, Is.EqualTo(2)); + } + } + + [Test] + public async Task UpdatesInlineCommentThreadsFromEditorContent() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var contents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + var editorContents = @"New Line 1 +New Line 2 +Line 1 +Line 2 +Line 3 with comment +Line 4"; + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService()) + { + var textView = CreateTextView(contents); + var pullRequest = CreatePullRequestModel( + CurrentBranchPullRequestNumber, + comment); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + + var target = CreateTarget(sessionService: CreateRealSessionService(diffService, pullRequest)); + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(1, Is.EqualTo(file.InlineCommentThreads.Count)); + Assert.That(2, Is.EqualTo(file.InlineCommentThreads[0].LineNumber)); + + textView.TextSnapshot.GetText().Returns(editorContents); + SignalTextChanged(textView.TextBuffer); + + var linesChanged = await file.LinesChanged.Take(1); + + Assert.That(1, Is.EqualTo(file.InlineCommentThreads.Count)); + Assert.That(4, Is.EqualTo(file.InlineCommentThreads[0].LineNumber)); + Assert.That( + new[] + { + Tuple.Create(2, DiffSide.Right), + Tuple.Create(4, DiffSide.Right), + }, + Is.EqualTo(linesChanged.ToArray())); + } + } + + [Test] + public async Task UpdatesReviewCommentWithNewBody() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var contents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", "Original Comment"); + var updatedComment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", "Updated Comment"); + + using (var diffService = new FakeDiffService()) + { + var textView = CreateTextView(contents); + var pullRequest = CreatePullRequestModel( + CurrentBranchPullRequestNumber, + comment); + var sessionService = CreateRealSessionService(diffService, pullRequest); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + + var target = CreateTarget(sessionService: sessionService); + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(file.InlineCommentThreads[0].Comments[0].Comment.Body, Is.EqualTo("Original Comment")); + + pullRequest = CreatePullRequestModel( + CurrentBranchPullRequestNumber, + updatedComment); + sessionService.ReadPullRequestDetail( + Arg.Any<HostAddress>(), + Arg.Any<string>(), + Arg.Any<string>(), + Arg.Any<int>()).Returns(pullRequest); + await target.CurrentSession.Refresh(); + + await file.LinesChanged.Take(1); + + Assert.That("Updated Comment", Is.EqualTo(file.InlineCommentThreads[0].Comments[0].Comment.Body)); + } + } + + [Test, Ignore("Flaky test, see https://github.com/github/VisualStudio/issues/1795")] + public async Task AddsNewReviewCommentToThread() + { + var baseContents = @"Line 1 + Line 2 + Line 3 + Line 4"; + var contents = @"Line 1 + Line 2 + Line 3 with comment + Line 4"; + var comment1 = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 + -Line 3 + +Line 3 with comment", "Comment1"); + + var comment2 = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 + -Line 3 + +Line 3 with comment", "Comment2"); + + using (var diffService = new FakeDiffService()) + { + var textView = CreateTextView(contents); + var pullRequest = CreatePullRequestModel( + CurrentBranchPullRequestNumber, + comment1); + var sessionService = CreateRealSessionService(diffService, pullRequest); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + + var target = CreateTarget(sessionService: sessionService); + var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That(1, Is.EqualTo(file.InlineCommentThreads[0].Comments.Count)); + + pullRequest = CreatePullRequestModel( + CurrentBranchPullRequestNumber, + comment1, + comment2); + sessionService.ReadPullRequestDetail( + Arg.Any<HostAddress>(), + Arg.Any<string>(), + Arg.Any<string>(), + Arg.Any<int>()).Returns(pullRequest); + await target.CurrentSession.Refresh(); + + var linesChanged = await file.LinesChanged.Take(1); + + Assert.That(2, Is.EqualTo(file.InlineCommentThreads[0].Comments.Count)); + Assert.That("Comment1", Is.EqualTo(file.InlineCommentThreads[0].Comments[0].Comment.Body)); + Assert.That("Comment2", Is.EqualTo(file.InlineCommentThreads[0].Comments[1].Comment.Body)); + } + } + + [Test] + public async Task CommitShaIsUpdatedOnTextChange() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + + var target = CreateTarget(sessionService: sessionService); + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + + Assert.That("TIPSHA", Is.EqualTo(file.CommitSha)); + + sessionService.IsUnmodifiedAndPushed(null, null, null).ReturnsForAnyArgs(false); + SignalTextChanged(textView.TextBuffer); + + Assert.That(file.CommitSha, Is.Null); + } + + [Test] + public async Task RefreshingCurrentSessionPullRequestTriggersLinesChanged() + { + var textView = CreateTextView(); + var sessionService = CreateSessionService(); + var expectedLineNumber = 2; + var threads = new[] + { + CreateInlineCommentThreadModel(expectedLineNumber), + }; + + sessionService.BuildCommentThreads(null, null, null, null).ReturnsForAnyArgs(threads); + + var target = CreateTarget(sessionService: sessionService); + var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); + var raised = false; + var pullRequest = target.CurrentSession.PullRequest; + + file.LinesChanged.Subscribe(x => raised = x.Count == 1 && x[0].Item1 == expectedLineNumber); + + // LinesChanged should be raised even if the PullRequestDetailModel is the same. + await target.CurrentSession.Refresh(); + + Assert.That(raised, Is.True); + } + + static PullRequestReviewThreadModel CreateCommentThread( + string diffHunk, + string body = "Comment", + string filePath = FilePath) + { + var thread = new PullRequestReviewThreadModel + { + DiffHunk = diffHunk, + Path = filePath, + OriginalCommitSha = "ORIG", + OriginalPosition = 1, + }; + + thread.Comments = new[] + { + new PullRequestReviewCommentModel + { + Body = body, + Thread = thread, + }, + }; + + return thread; + } + + IPullRequestSessionService CreateRealSessionService( + IDiffService diff, + PullRequestDetailModel pullRequest) + { + var result = Substitute.ForPartsOf<PullRequestSessionService>( + Substitute.For<IGitService>(), + Substitute.For<IGitClient>(), + diff, + Substitute.For<IApiClientFactory>(), + Substitute.For<IGraphQLClientFactory>(), + Substitute.For<IUsageTracker>()); + result.CreateRebuildSignal().Returns(new Subject<ITextSnapshot>()); + result.GetPullRequestMergeBase( + Arg.Any<ILocalRepositoryModel>(), + Arg.Any<PullRequestDetailModel>()).Returns("MERGE_BASE"); + result.ReadPullRequestDetail( + Arg.Any<HostAddress>(), + Arg.Any<string>(), + Arg.Any<string>(), + Arg.Any<int>()).Returns(pullRequest); + result.ReadViewer(Arg.Any<HostAddress>()).Returns(new ActorModel()); + return result; + } + + ITextView CreateTextView(string content = "Default content") + { + var snapshot = Substitute.For<ITextSnapshot>(); + snapshot.GetText().Returns(content); + + // We map snapshot positions to line numbers in tracking points, so return 100 as + // the length so we can map lines 0-99. + snapshot.Length.Returns(100); + snapshot.GetLineFromLineNumber(0).ReturnsForAnyArgs(x => + { + var line = Substitute.For<ITextSnapshotLine>(); + var lineNumber = x.Arg<int>(); + var point = new SnapshotPoint(snapshot, lineNumber); + line.LineNumber.Returns(lineNumber); + line.Start.Returns(point); + return line; + }); + snapshot.GetLineNumberFromPosition(0).ReturnsForAnyArgs(x => x.Arg<int>()); + snapshot.CreateTrackingPoint(0, 0).ReturnsForAnyArgs(x => + { + var point = Substitute.For<ITrackingPoint>(); + point.GetPosition(snapshot).Returns(x.Arg<int>()); + return point; + }); + + var textBuffer = Substitute.For<ITextBuffer>(); + textBuffer.Properties.Returns(new PropertyCollection()); + textBuffer.CurrentSnapshot.Returns(snapshot); + + var result = Substitute.For<ITextView>(); + result.TextBuffer.Returns(textBuffer); + result.TextSnapshot.Returns(snapshot); + + return result; + } + + IInlineCommentThreadModel CreateInlineCommentThreadModel(int lineNumber) + { + var result = Substitute.For<IInlineCommentThreadModel>(); + result.LineNumber.Returns(lineNumber); + return result; + } + + static void SignalTextChanged(ITextBuffer buffer) + { + var snapshot = buffer.CurrentSnapshot; + var ev = new TextContentChangedEventArgs(snapshot, snapshot, EditOptions.None, null); + buffer.Changed += Raise.EventWith(buffer, ev); + } + } + + public class TheGetSessionMethod : PullRequestSessionManagerTests + { + [Test] + public async Task GetSessionReturnsSameSessionForSamePullRequest() + { + var target = CreateTarget(); + var newModel = CreatePullRequestModel(NotCurrentBranchPullRequestNumber); + var result1 = await target.GetSession("owner", "repo", 5); + var result2 = await target.GetSession("owner", "repo", 5); + var result3 = await target.GetSession("owner", "repo", 6); + + Assert.That(result1, Is.SameAs(result2)); + Assert.That(result1, Is.Not.SameAs(result3)); + } + + [Test] + public async Task GetSessionReturnsSameSessionForSamePullRequestOwnerCaseMismatch() + { + var target = CreateTarget(); + var newModel = CreatePullRequestModel(NotCurrentBranchPullRequestNumber); + var result1 = await target.GetSession("owner", "repo", 5); + var result2 = await target.GetSession("Owner", "repo", 5); + var result3 = await target.GetSession("owner", "repo", 6); + + Assert.That(result1, Is.SameAs(result2)); + Assert.That(result1, Is.Not.SameAs(result3)); + } + + [Test] + public async Task SessionCanBeCollected() + { + WeakReference<IPullRequestSession> weakSession = null; + + var target = CreateTarget(); + + Func<Task> run = async () => + { + var newModel = CreatePullRequestModel(NotCurrentBranchPullRequestNumber); + var session = await target.GetSession("owner", "repo", 5); + + Assert.That(session, Is.Not.Null); + + weakSession = new WeakReference<IPullRequestSession>(session); + }; + + await run(); + GC.Collect(); + + IPullRequestSession result; + weakSession.TryGetTarget(out result); + + Assert.That(result, Is.Null); + } + + [Test] + public async Task GetSessionUpdatesCurrentSessionIfCurrentBranchIsPullRequestButWasNotMarked() + { + var service = CreatePullRequestService(); + var model = CreatePullRequestModel(); + var sessionService = CreateSessionService(model); + + service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Empty<Tuple<string, int>>()); + + var target = CreateTarget(service: service, sessionService: sessionService); + + Assert.That(target.CurrentSession, Is.Null); + + service.EnsureLocalBranchesAreMarkedAsPullRequests(Arg.Any<ILocalRepositoryModel>(), model).Returns(Observable.Return(true)); + service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Return(Tuple.Create("owner", CurrentBranchPullRequestNumber))); + + var session = await target.GetSession("owner", "name", CurrentBranchPullRequestNumber); + + Assert.That(target.CurrentSession, Is.SameAs(session)); + } + } + + PullRequestSessionManager CreateTarget( + IPullRequestService service = null, + IPullRequestSessionService sessionService = null, + IConnectionManager connectionManager = null, + ITeamExplorerContext teamExplorerContext = null) + { + service = service ?? CreatePullRequestService(); + sessionService = sessionService ?? CreateSessionService(); + connectionManager = connectionManager ?? CreateConnectionManager(); + teamExplorerContext = teamExplorerContext ?? CreateTeamExplorerContext(CreateRepositoryModel()); + + return new PullRequestSessionManager( + service, + sessionService, + teamExplorerContext); + } + + PullRequestDetailModel CreatePullRequestModel( + int number = 5, + params PullRequestReviewThreadModel[] threads) + { + var result = new PullRequestDetailModel + { + Number = number, + BaseRefName = "BASEREF", + BaseRefSha = "BASESHA", + HeadRefName = "HEADREF", + HeadRefSha = "HEADSHA", + Threads = threads, + }; + + if (threads.Length > 0) + { + result.Reviews = new[] + { + new PullRequestReviewModel + { + Comments = threads.SelectMany(x => x.Comments).ToList(), + Author = CurrentUser, + }, + }; + } + else + { + result.Reviews = new PullRequestReviewModel[0]; + } + + return result; + } + + IPullRequestService CreatePullRequestService(string owner = "owner") + { + var result = Substitute.For<IPullRequestService>(); + result.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Return(Tuple.Create(owner, CurrentBranchPullRequestNumber))); + return result; + } + + IConnectionManager CreateConnectionManager() + { + var connection = Substitute.For<IConnection>(); + connection.HostAddress.Returns(HostAddress.Create("https://github.com")); + connection.IsLoggedIn.Returns(true); + + var result = Substitute.For<IConnectionManager>(); + result.GetConnection(connection.HostAddress).Returns(connection); + return result; + } + + IPullRequestSessionService CreateSessionService( + PullRequestDetailModel pullRequest = null, + bool isModified = false) + { + pullRequest = pullRequest ?? CreatePullRequestModel(); + + var sessionService = Substitute.For<IPullRequestSessionService>(); + sessionService.CreateRebuildSignal().Returns(new Subject<ITextSnapshot>()); + sessionService.IsUnmodifiedAndPushed(null, null, null).ReturnsForAnyArgs(!isModified); + sessionService.GetPullRequestMergeBase(null, null).ReturnsForAnyArgs("MERGE_BASE"); + sessionService.GetTipSha(null).ReturnsForAnyArgs("TIPSHA"); + sessionService.ReadPullRequestDetail(null, null, null, 0).ReturnsForAnyArgs(pullRequest); + sessionService.ReadViewer(null).ReturnsForAnyArgs(CurrentUser); + return sessionService; + } + + ILocalRepositoryModel CreateRepositoryModel(string cloneUrl = OwnerCloneUrl) + { + var result = Substitute.For<ILocalRepositoryModel>(); + var uriString = new UriString(cloneUrl); + result.CloneUrl.Returns(uriString); + result.Name.Returns(uriString.RepositoryName); + result.Owner.Returns(uriString.Owner); + return result; + } + + static ITeamExplorerContext CreateTeamExplorerContext(ILocalRepositoryModel repo) + { + var teamExplorerContext = Substitute.For<ITeamExplorerContext>(); + teamExplorerContext.ActiveRepository.Returns(repo); + return teamExplorerContext; + } + + static void SetActiveRepository(ITeamExplorerContext teamExplorerContext, ILocalRepositoryModel localRepositoryModel) + { + teamExplorerContext.ActiveRepository.Returns(localRepositoryModel); + var eventArgs = new PropertyChangedEventArgs(nameof(teamExplorerContext.ActiveRepository)); + teamExplorerContext.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(teamExplorerContext, eventArgs); + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs new file mode 100644 index 0000000000..0ab2b674f1 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionServiceTests.cs @@ -0,0 +1,354 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Factories; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.UnitTests.TestDoubles; +using GitHub.Models; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; + +namespace GitHub.InlineReviews.UnitTests.Services +{ + public class PullRequestSessionServiceTests + { + const int PullRequestNumber = 5; + const string RepoUrl = "https://foo.bar/owner/repo"; + const string FilePath = "test.cs"; + + public class TheBuildCommentThreadsMethod + { + [Test] + public async Task MatchesReviewCommentOnOriginalLine() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService(FilePath, baseContents)) + { + var diff = await diffService.Diff(FilePath, headContents); + var pullRequest = CreatePullRequest(FilePath, comment); + var target = CreateTarget(diffService); + + var result = target.BuildCommentThreads( + pullRequest, + FilePath, + diff, + "HEAD_SHA"); + + var thread = result.Single(); + Assert.That(thread.LineNumber, Is.EqualTo(2)); + } + } + + [Test] + public async Task IgnoreCommentsWithNoDiffLineContext() + { + var baseContents = "Line 1"; + var headContents = "Line 1"; + + var comment = CreateCommentThread(@"@@ -10,7 +10,6 @@ class Program"); + + using (var diffService = new FakeDiffService(FilePath, baseContents)) + { + var diff = await diffService.Diff(FilePath, headContents); + var pullRequest = CreatePullRequest(FilePath, comment); + var target = CreateTarget(diffService); + + var result = target.BuildCommentThreads( + pullRequest, + FilePath, + diff, + "HEAD_SHA"); + + Assert.That(result, Is.Empty); + } + } + + [Test] + public async Task MatchesReviewCommentOnDifferentLine() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"New Line 1 +New Line 2 +Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService(FilePath, baseContents)) + { + var diff = await diffService.Diff(FilePath, headContents); + var pullRequest = CreatePullRequest(FilePath, comment); + var target = CreateTarget(diffService); + + var result = target.BuildCommentThreads( + pullRequest, + FilePath, + diff, + "HEAD_SHA"); + + var thread = result.Single(); + Assert.That(thread.LineNumber, Is.EqualTo(4)); + } + } + + [Test] + public async Task ReturnsLineNumberMinus1ForNonMatchingComment() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment1 = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", position: 1); + + var comment2 = CreateCommentThread(@"@@ -1,4 +1,4 @@ +-Line 1 + Line 2 +-Line 3 ++Line 3 with comment", position: 2); + + using (var diffService = new FakeDiffService(FilePath, baseContents)) + { + var diff = await diffService.Diff(FilePath, headContents); + var pullRequest = CreatePullRequest(FilePath, comment1, comment2); + var target = CreateTarget(diffService); + + var result = target.BuildCommentThreads( + pullRequest, + FilePath, + diff, + "HEAD_SHA"); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result[1].LineNumber, Is.EqualTo(-1)); + } + } + + [Test] + public async Task HandlesDifferingPathSeparators() + { + var winFilePath = @"foo\test.cs"; + var gitHubFilePath = "foo/test.cs"; + + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"New Line 1 +New Line 2 +Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", gitHubFilePath); + + using (var diffService = new FakeDiffService(winFilePath, baseContents)) + { + var diff = await diffService.Diff(winFilePath, headContents); + var pullRequest = CreatePullRequest(gitHubFilePath, comment); + var target = CreateTarget(diffService); + + var result = target.BuildCommentThreads( + pullRequest, + winFilePath, + diff, + "HEAD_SHA"); + + var thread = result.First(); + Assert.That(thread.LineNumber, Is.EqualTo(4)); + } + } + } + + public class TheUpdateCommentThreadsMethod + { + [Test] + public async Task UpdatesWithNewLineNumber() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + var newHeadContents = @"Inserted Line +Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService(FilePath, baseContents)) + { + var diff = await diffService.Diff(FilePath, headContents); + var pullRequest = CreatePullRequest(FilePath, comment); + var target = CreateTarget(diffService); + + var threads = target.BuildCommentThreads( + pullRequest, + FilePath, + diff, + "HEAD_SHA"); + + Assert.That(2, Is.EqualTo(threads[0].LineNumber)); + + diff = await diffService.Diff(FilePath, newHeadContents); + var changedLines = target.UpdateCommentThreads(threads, diff); + + Assert.That(threads[0].LineNumber, Is.EqualTo(3)); + Assert.That(changedLines.ToArray(), Is.EqualTo(new[] + { + Tuple.Create(2, DiffSide.Right), + Tuple.Create(3, DiffSide.Right) + })); + } + } + + [Test] + public async Task UnmarksStaleThreads() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment = CreateCommentThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService(FilePath, baseContents)) + { + var diff = await diffService.Diff(FilePath, headContents); + var pullRequest = CreatePullRequest(FilePath, comment); + var target = CreateTarget(diffService); + + var threads = target.BuildCommentThreads( + pullRequest, + FilePath, + diff, + "HEAD_SHA"); + + threads[0].IsStale = true; + var changedLines = target.UpdateCommentThreads(threads, diff); + + Assert.That(threads[0].IsStale, Is.False); + Assert.That(changedLines.ToArray(), Is.EqualTo(new[] { Tuple.Create(2, DiffSide.Right) })); + } + } + } + + static PullRequestSessionService CreateTarget(IDiffService diffService) + { + return new PullRequestSessionService( + Substitute.For<IGitService>(), + Substitute.For<IGitClient>(), + diffService, + Substitute.For<IApiClientFactory>(), + Substitute.For<IGraphQLClientFactory>(), + Substitute.For<IUsageTracker>()); + } + + static PullRequestReviewThreadModel CreateCommentThread( + string diffHunk, + string filePath = FilePath, + string body = "Comment", + int position = 1) + { + return new PullRequestReviewThreadModel + { + DiffHunk = diffHunk, + Path = filePath, + OriginalCommitSha = "ORIG", + OriginalPosition = position, + Comments = new[] + { + new PullRequestReviewCommentModel + { + Body = body, + Author = new ActorModel { Login = "Author" }, + } + }, + }; + } + + static PullRequestDetailModel CreatePullRequest( + string filePath, + params PullRequestReviewThreadModel[] threads) + { + return new PullRequestDetailModel + { + Number = PullRequestNumber, + BaseRefName = "BASE", + BaseRefSha = "BASE_SHA", + BaseRepositoryOwner = "owner", + HeadRefName = "HEAD", + HeadRefSha = "HEAD_SHA", + HeadRepositoryOwner = "owner", + ChangedFiles = new [] + { + new PullRequestFileModel { FileName = filePath }, + new PullRequestFileModel { FileName = "other.cs" }, + }, + Threads = threads, + Reviews = new[] + { + new PullRequestReviewModel + { + Comments = threads.SelectMany(x => x.Comments).ToList(), + }, + }, + }; + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs new file mode 100644 index 0000000000..81c5f463e1 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionTests.cs @@ -0,0 +1,822 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.UnitTests.TestDoubles; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; + +namespace GitHub.InlineReviews.UnitTests.Services +{ + public class PullRequestSessionTests + { + const int PullRequestNumber = 5; + const string PullRequestNodeId = "pull_request_id"; + const string RepoUrl = "https://foo.bar/owner/repo"; + const string FilePath = "test.cs"; + + public class TheHasPendingReviewProperty + { + [Test] + public void IsFalseWithNoPendingReview() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.False); + } + + [Test] + public void IsFalseWithPendingReviewForOtherUser() + { + var currentUser = CreateActor("grokys"); + var otherUser = CreateActor("shana"); + var review = CreateReview(author: otherUser, state: PullRequestReviewState.Pending); + var pr = CreatePullRequest(review); + + var target = new PullRequestSession( + CreateRealSessionService(), + currentUser, + pr, + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.False); + } + + [Test] + public void IsFalseWithNonPendingReviewForCurrentUser() + { + var currentUser = CreateActor("grokys"); + var review = CreateReview(author: currentUser, state: PullRequestReviewState.Approved); + var pr = CreatePullRequest(review); + + var target = new PullRequestSession( + CreateRealSessionService(), + currentUser, + pr, + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.False); + } + + [Test] + public void IsTrueWithPendingReviewForCurrentUser() + { + var currentUser = CreateActor(); + var review = CreateReview(author: currentUser, state: PullRequestReviewState.Pending); + var pr = CreatePullRequest(review); + + var target = new PullRequestSession( + CreateRealSessionService(), + currentUser, + pr, + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.True); + } + + [Test] + public async Task IsTrueWhenRefreshedWithPendingReview() + { + var sessionService = CreateMockSessionService(); + var currentUser = CreateActor("grokys"); + var target = new PullRequestSession( + sessionService, + currentUser, + CreatePullRequest(), + CreateLocalRepository(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.False); + + var review = CreateReview(author: currentUser, state: PullRequestReviewState.Pending); + UpdateReadPullRequest(sessionService, CreatePullRequest(review)); + await target.Refresh(); + + Assert.That(target.HasPendingReview, Is.True); + } + + [Test] + public async Task IsTrueWhenStartReviewCalled() + { + var currentUser = CreateActor(); + var service = Substitute.For<IPullRequestSessionService>(); + var review = CreateReview(author: currentUser, state: PullRequestReviewState.Pending); + service.CreatePendingReview(null, null).ReturnsForAnyArgs(CreatePullRequest(review)); + + var target = new PullRequestSession( + service, + currentUser, + CreatePullRequest(), + CreateLocalRepository(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.False); + + await target.StartReview(); + + Assert.That(target.HasPendingReview, Is.True); + } + + [Test] + public async Task IsFalseWhenReviewCancelled() + { + var currentUser = CreateActor(); + var review = CreateReview(author: currentUser, state: PullRequestReviewState.Pending); + var service = Substitute.For<IPullRequestSessionService>(); + var pr = CreatePullRequest(review); + + var target = new PullRequestSession( + service, + currentUser, + pr, + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + Assert.That(target.HasPendingReview, Is.True); + + service.CancelPendingReview(null, null).ReturnsForAnyArgs(CreatePullRequest()); + await target.CancelReview(); + + Assert.That(target.HasPendingReview, Is.False); + } + } + + public class TheGetFileMethod + { + [Test] + public async Task BaseShaIsSet() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + var file = await target.GetFile(FilePath); + + Assert.That("BASE_SHA", Is.SameAs(file.BaseSha)); + } + + [Test] + public async Task HeadCommitShaIsSet() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + var file = await target.GetFile(FilePath); + + Assert.That("HEAD_SHA", Is.SameAs(file.CommitSha)); + Assert.That(file.IsTrackingHead, Is.True); + } + + [Test] + public async Task PinnedCommitShaIsSet() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + var file = await target.GetFile(FilePath, "123"); + + Assert.That("123", Is.SameAs(file.CommitSha)); + Assert.That(file.IsTrackingHead, Is.False); + } + + [Test] + public async Task DiffShaIsSet() + { + var diff = new List<DiffChunk>(); + var sessionService = CreateRealSessionService(); + + sessionService.Diff( + Arg.Any<ILocalRepositoryModel>(), + "MERGE_BASE", + "HEAD_SHA", + FilePath).Returns(diff); + + var target = new PullRequestSession( + sessionService, + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + var file = await target.GetFile(FilePath); + + Assert.That(diff, Is.SameAs(file.Diff)); + } + + [Test] + public async Task InlineCommentThreadsIsSet() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var thread = CreateThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService()) + { + var pullRequest = CreatePullRequest(thread); + var service = CreateRealSessionService(diffService); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + diffService.AddFile(FilePath, headContents, "HEAD_SHA"); + + var target = new PullRequestSession( + service, + CreateActor(), + pullRequest, + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + var file = await target.GetFile(FilePath); + var inlineThread = file.InlineCommentThreads.First(); + Assert.That(2, Is.EqualTo(inlineThread.LineNumber)); + } + } + + [Test] + public async Task SameNonHeadCommitShasReturnSameFiles() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + var file1 = await target.GetFile(FilePath, "123"); + var file2 = await target.GetFile(FilePath, "123"); + + Assert.That(file1, Is.SameAs(file2)); + } + + [Test] + public async Task DifferentCommitShasReturnDifferentFiles() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + var file1 = await target.GetFile(FilePath, "123"); + var file2 = await target.GetFile(FilePath, "456"); + + Assert.That(file1, Is.Not.SameAs(file2)); + } + } + + public class TheCancelReviewMethod + { + [Test] + public void ThrowsWithNoPendingReview() + { + var target = new PullRequestSession( + CreateRealSessionService(), + CreateActor(), + CreatePullRequest(), + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + + Assert.ThrowsAsync<InvalidOperationException>(async () => await target.CancelReview()); + } + + [Test] + public async Task CallsServiceWithNodeId() + { + var service = Substitute.For<IPullRequestSessionService>(); + var target = CreateTargetWithPendingReview(service); + + service.CancelPendingReview(null, null).ReturnsForAnyArgs(CreatePullRequest()); + + await target.CancelReview(); + + await service.Received(1).CancelPendingReview( + Arg.Any<ILocalRepositoryModel>(), + "review1"); + } + + [Test] + public async Task RemovesReviewFromModel() + { + var service = Substitute.For<IPullRequestSessionService>(); + var target = CreateTargetWithPendingReview(service); + + service.CancelPendingReview(null, null).ReturnsForAnyArgs(CreatePullRequest()); + + await target.CancelReview(); + + Assert.IsEmpty(target.PullRequest.Reviews); + } + + public static PullRequestSession CreateTargetWithPendingReview( + IPullRequestSessionService service) + { + var currentUser = CreateActor(); + var review = CreateReview( + author: currentUser, + state: PullRequestReviewState.Pending, + comments: CreateComment()); + var pr = CreatePullRequest(review); + + return new PullRequestSession( + service, + currentUser, + pr, + Substitute.For<ILocalRepositoryModel>(), + "owner", + true); + } + } + + public class ThePostReviewMethod + { + [Test] + public async Task PostsToCorrectForkWithNoPendingReview() + { + var service = CreateMockSessionService(); + var target = CreateTarget(service, "fork", "owner", false); + + service.PostReview(null, null, null, null, 0).ReturnsForAnyArgs(CreatePullRequest()); + await target.PostReview("New Review", Octokit.PullRequestReviewEvent.Approve); + + await service.Received(1).PostReview( + target.LocalRepository, + "pr1", + "HEAD_SHA", + "New Review", + Octokit.PullRequestReviewEvent.Approve); + } + + [Test] + public async Task PostsToCorrectForkWithPendingReview() + { + var service = CreateMockSessionService(); + var target = CreateTarget(service, "fork", "owner", true); + + service.SubmitPendingReview(null, null, null, 0).ReturnsForAnyArgs(CreatePullRequest()); + await target.PostReview("New Review", Octokit.PullRequestReviewEvent.RequestChanges); + + await service.Received(1).SubmitPendingReview( + target.LocalRepository, + "pendingReviewId", + "New Review", + Octokit.PullRequestReviewEvent.RequestChanges); + } + } + + public class ThePostReviewCommentMethod + { + [Test] + public async Task PostsToCorrectForkWithNoPendingReview() + { + var service = CreateMockSessionService(); + var target = CreateTarget(service, "fork", "owner", false); + + service.PostStandaloneReviewComment(null, null, null, null, null, 0).ReturnsForAnyArgs(CreatePullRequest()); + await target.PostReviewComment("New Comment", "COMMIT_ID", "file.cs", new DiffChunk[0], 1); + + await service.Received(1).PostStandaloneReviewComment( + target.LocalRepository, + "pr1", + "New Comment", + "COMMIT_ID", + "file.cs", + 1); + } + + [Test] + public async Task PostsReplyToCorrectForkWithNoPendingReview() + { + var service = CreateMockSessionService(); + var target = CreateTarget(service, "fork", "owner", false); + + service.PostStandaloneReviewCommentReply(null, null, null, null).ReturnsForAnyArgs(CreatePullRequest()); + await target.PostReviewComment("New Comment", "node1"); + + await service.Received(1).PostStandaloneReviewCommentReply( + target.LocalRepository, + "pr1", + "New Comment", + "node1"); + } + + [Test] + public async Task PostsToCorrectForkWithPendingReview() + { + var service = CreateMockSessionService(); + var target = CreateTarget(service, "fork", "owner", true); + + service.PostPendingReviewComment(null, null, null, null, null, 0).ReturnsForAnyArgs(CreatePullRequest()); + await target.PostReviewComment("New Comment", "COMMIT_ID", "file.cs", new DiffChunk[0], 1); + + await service.Received(1).PostPendingReviewComment( + target.LocalRepository, + "pendingReviewId", + "New Comment", + "COMMIT_ID", + "file.cs", + 1); + } + + [Test] + public async Task PostsReplyToCorrectForkWithPendingReview() + { + var service = CreateMockSessionService(); + var target = CreateTarget(service, "fork", "owner", true); + + service.PostPendingReviewCommentReply(null, null, null, null).ReturnsForAnyArgs(CreatePullRequest()); + await target.PostReviewComment("New Comment", "node1"); + + await service.Received(1).PostPendingReviewCommentReply( + target.LocalRepository, + "pendingReviewId", + "New Comment", + "node1"); + } + } + + public class TheRefreshMethod + { + [Test] + public async Task UpdatesThePullRequestModel() + { + var sessionService = CreateMockSessionService(); + var target = new PullRequestSession( + sessionService, + CreateActor(), + CreatePullRequest(), + CreateLocalRepository(), + "owner", + true); + + var newPullRequest = CreatePullRequest(); + UpdateReadPullRequest(sessionService, newPullRequest); + await target.Refresh(); + + Assert.That(newPullRequest, Is.SameAs(target.PullRequest)); + } + + [Test] + public async Task AddsNewReviewCommentToThreadOnHeadFile() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + var thread1 = CreateThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", "Comment1"); + var thread2 = CreateThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", "Comment2"); + + using (var diffService = new FakeDiffService()) + { + var pullRequest = CreatePullRequest(thread1); + var service = CreateRealSessionService(diffService); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + diffService.AddFile(FilePath, headContents, "HEAD_SHA"); + + var target = new PullRequestSession( + service, + CreateActor(), + pullRequest, + CreateLocalRepository(), + "owner", + true); + + var file = await target.GetFile(FilePath, "HEAD"); + + Assert.That(file.InlineCommentThreads[0].Comments, Has.Count.EqualTo(1)); + Assert.That(file.InlineCommentThreads[0].LineNumber, Is.EqualTo(2)); + + pullRequest = CreatePullRequest(thread1, thread2); + UpdateReadPullRequest(service, pullRequest); + await target.Refresh(); + + Assert.That(file.InlineCommentThreads[0].Comments, Has.Count.EqualTo(2)); + Assert.That(file.InlineCommentThreads[0].LineNumber, Is.EqualTo(2)); + } + } + + [Test] + public async Task AddsNewReviewCommentToThreadNonHeadFile() + { + var baseContents = @"Line 1 +Line 2 +Line 3 +Line 4"; + var headContents = @"Line 1 +Line 2 +Line 3 with comment +Line 4"; + + var comment1 = CreateThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", "Comment1"); + var comment2 = CreateThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment", "Comment2"); + + using (var diffService = new FakeDiffService()) + { + var pullRequest = CreatePullRequest(comment1); + var service = CreateRealSessionService(diffService); + + diffService.AddFile(FilePath, baseContents, "MERGE_BASE"); + diffService.AddFile(FilePath, headContents, "123"); + + var target = new PullRequestSession( + service, + CreateActor(), + pullRequest, + CreateLocalRepository(), + "owner", + true); + + var file = await target.GetFile(FilePath, "123"); + + Assert.That(file.InlineCommentThreads[0].Comments, Has.Count.EqualTo(1)); + Assert.That(file.InlineCommentThreads[0].LineNumber, Is.EqualTo(2)); + + pullRequest = CreatePullRequest(comment1, comment2); + UpdateReadPullRequest(service, pullRequest); + await target.Refresh(); + + Assert.That(file.InlineCommentThreads[0].Comments, Has.Count.EqualTo(2)); + Assert.That(file.InlineCommentThreads[0].LineNumber, Is.EqualTo(2)); + } + } + + [Test] + public async Task DoesntThrowIfGetFileCalledDuringUpdate() + { + var thread = CreateThread(@"@@ -1,4 +1,4 @@ + Line 1 + Line 2 +-Line 3 ++Line 3 with comment"); + + using (var diffService = new FakeDiffService()) + { + var pullRequest = CreatePullRequest(thread); + var service = CreateRealSessionService(diffService); + + var target = new PullRequestSession( + service, + CreateActor(), + pullRequest, + CreateLocalRepository(), + string.Empty, + true); + + await target.GetFile("test.cs"); + + // Simulate calling GetFile with a file that's not yet been initialized + // while doing the Update. + service.WhenForAnyArgs(x => x.Diff(null, null, null, null)) + .Do(_ => target.GetFile("other.cs").Forget()); + UpdateReadPullRequest(service, pullRequest); + + await target.Refresh(); + } + } + } + + static ActorModel CreateActor(string login = null) + { + return new ActorModel { Login = login ?? "Viewer" }; + } + + static PullRequestReviewCommentModel CreateComment( + string id = "comment1", + string body = "body") + { + return new PullRequestReviewCommentModel + { + Id = "1", + Body = body, + }; + } + + static PullRequestReviewModel CreateReview( + string id = "review1", + ActorModel author = null, + PullRequestReviewState state = PullRequestReviewState.Approved, + params PullRequestReviewCommentModel[] comments) + { + return new PullRequestReviewModel + { + Id = id, + Author = author ?? CreateActor(), + Comments = comments, + State = state, + }; + } + + static PullRequestReviewThreadModel CreateThread(string diffHunk, string body = "Comment") + { + return new PullRequestReviewThreadModel + { + DiffHunk = diffHunk, + Path = FilePath, + OriginalCommitSha = "ORIG", + OriginalPosition = 1, + Comments = new[] + { + CreateComment(body), + } + }; + } + + static PullRequestDetailModel CreatePullRequest() + { + return CreatePullRequest(new PullRequestReviewModel[0]); + } + + static PullRequestDetailModel CreatePullRequest(params PullRequestReviewModel[] reviews) + { + return new PullRequestDetailModel + { + Id = "pr1", + Number = PullRequestNumber, + BaseRefName = "BASE", + BaseRefSha = "BASE_SHA", + BaseRepositoryOwner = "owner", + HeadRefName = "HEAD", + HeadRefSha = "HEAD_SHA", + HeadRepositoryOwner = "owner", + ChangedFiles = new[] + { + new PullRequestFileModel { FileName = FilePath }, + new PullRequestFileModel { FileName = "other.cs" }, + }, + Threads = new[] + { + new PullRequestReviewThreadModel + { + Comments = reviews.SelectMany(x => x.Comments).ToList(), + }, + }, + Reviews = reviews, + }; + } + + static PullRequestDetailModel CreatePullRequest(params PullRequestReviewThreadModel[] threads) + { + return new PullRequestDetailModel + { + Id = "pr1", + Number = PullRequestNumber, + BaseRefName = "BASE", + BaseRefSha = "BASE_SHA", + BaseRepositoryOwner = "owner", + HeadRefName = "HEAD", + HeadRefSha = "HEAD_SHA", + HeadRepositoryOwner = "owner", + ChangedFiles = new[] + { + new PullRequestFileModel { FileName = FilePath }, + new PullRequestFileModel { FileName = "other.cs" }, + }, + Threads = threads, + Reviews = new[] + { + new PullRequestReviewModel + { + Author = CreateActor(), + Comments = threads.SelectMany(x => x.Comments).ToList(), + }, + } + }; + } + + static ILocalRepositoryModel CreateLocalRepository() + { + var result = Substitute.For<ILocalRepositoryModel>(); + result.CloneUrl.Returns(new UriString("https://github.com/owner/repo")); + return result; + } + + static IPullRequestSessionService CreateMockSessionService() + { + var result = Substitute.For<IPullRequestSessionService>(); + return result; + } + + static IPullRequestSessionService CreateRealSessionService(IDiffService diffService = null) + { + var result = Substitute.ForPartsOf<PullRequestSessionService>( + Substitute.For<IGitService>(), + Substitute.For<IGitClient>(), + diffService ?? Substitute.For<IDiffService>(), + Substitute.For<IApiClientFactory>(), + Substitute.For<IGraphQLClientFactory>(), + Substitute.For<IUsageTracker>()); + + result.GetTipSha(Arg.Any<ILocalRepositoryModel>()).Returns("BRANCH_TIP"); + result.GetPullRequestMergeBase(Arg.Any<ILocalRepositoryModel>(), Arg.Any<PullRequestDetailModel>()) + .Returns("MERGE_BASE"); + return result; + } + + static PullRequestSession CreateTarget( + IPullRequestSessionService service, + string localRepositoryOwner, + string remoteRepositoryOwner, + bool hasPendingReview) + { + var repository = Substitute.For<ILocalRepositoryModel>(); + + repository.CloneUrl.Returns(new UriString($"https://github.com/{localRepositoryOwner}/reop")); + repository.Owner.Returns(localRepositoryOwner); + repository.Name.Returns("repo"); + + var pr = CreatePullRequest(); + var user = CreateActor(); + + if (hasPendingReview) + { + pr.Reviews = new[] + { + CreateReview(id: "pendingReviewId", author: user, state: PullRequestReviewState.Pending), + }; + } + + return new PullRequestSession( + service, + user, + pr, + repository, + remoteRepositoryOwner, + true); + } + + static void UpdateReadPullRequest(IPullRequestSessionService service, PullRequestDetailModel pullRequest) + { + service.ReadPullRequestDetail( + Arg.Any<HostAddress>(), + Arg.Any<string>(), + Arg.Any<string>(), + Arg.Any<int>()).Returns(pullRequest); + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/Tags/InlineCommentTaggerTests.cs b/test/GitHub.InlineReviews.UnitTests/Tags/InlineCommentTaggerTests.cs new file mode 100644 index 0000000000..01f71a7626 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/Tags/InlineCommentTaggerTests.cs @@ -0,0 +1,446 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.InlineReviews.Tags; +using GitHub.Models; +using GitHub.Services; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using NSubstitute; +using NUnit.Framework; +using GitHub.InlineReviews.Margins; + +namespace GitHub.InlineReviews.UnitTests.Tags +{ + public class InlineCommentTaggerTests + { + public class WithTextBufferInfo + { + [Test] + public void FirstPassShouldReturnEmptyTags() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager(DiffSide.Right)); + + var result = target.GetTags(CreateSpan(10)); + + Assert.That(result, Is.Empty); + } + + [Test] + public void ShouldReturnShowCommentTagForRhs() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager(DiffSide.Right)); + + // Line 10 has an existing RHS comment. + var span = CreateSpan(10); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Has.One.Items); + Assert.That(result[0].Tag, Is.InstanceOf<ShowInlineCommentTag>()); + } + + [Test] + public void ShouldReturnAddNewCommentTagForAddedLineOnRhs() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager(DiffSide.Right)); + + // Line 11 has an add diff entry. + var span = CreateSpan(11); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Has.One.Items); + Assert.That(result[0].Tag, Is.InstanceOf<AddInlineCommentTag>()); + } + + [Test] + public void ShouldNotReturnAddNewCommentTagForDeletedLineOnRhs() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager(DiffSide.Right)); + + // Line 13 has an delete diff entry. + var span = CreateSpan(13); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Is.Empty); + } + + [Test] + public void ShouldReturnShowCommentTagForLhs() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager(DiffSide.Left)); + + // Line 12 has an existing LHS comment. + var span = CreateSpan(12); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Has.One.Items); + Assert.That(result[0].Tag, Is.InstanceOf<ShowInlineCommentTag>()); + } + + [Test] + public void ShouldReturnAddCommentTagForLhs() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager(DiffSide.Left)); + + // Line 13 has an delete diff entry. + var span = CreateSpan(13); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Has.One.Items); + Assert.That(result[0].Tag, Is.InstanceOf<AddInlineCommentTag>()); + } + + [Test] + public void ShouldRaiseTagsChangedOnFileLinesChanged() + { + var file = CreateSessionFile(); + var manager = CreateSessionManager(file, DiffSide.Right); + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + CreateBuffer(), + manager); + + var session = manager.GetTextBufferInfo(null).Session; + var span = CreateSpan(14); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + var raised = false; + + target.TagsChanged += (s, e) => raised = e.Span.Start == 140; + ((ISubject<IReadOnlyList<Tuple<int, DiffSide>>>)file.LinesChanged).OnNext(new[] + { + Tuple.Create(14, DiffSide.Right), + }); + + Assert.True(raised); + } + + [Test] + public void ShouldCallSessionGetFileWithCorrectCommitSha() + { + var sessionManager = CreateSessionManager( + CreateSessionFile(), + DiffSide.Right, + "123"); + var session = sessionManager.CurrentSession; + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + sessionManager); + + // Line 11 has an add diff entry. + var span = CreateSpan(11); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + session.Received(1).GetFile("file.cs", "123"); + } + + [Test] + public void ShouldAlwaysCallSessionGetFileWithHeadCommitShaForLeftHandSide() + { + var sessionManager = CreateSessionManager( + CreateSessionFile(), + DiffSide.Left, + "123"); + var session = sessionManager.CurrentSession; + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + sessionManager); + + // Line 11 has an add diff entry. + var span = CreateSpan(11); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + session.Received(1).GetFile("file.cs", "HEAD"); + } + + static IPullRequestSessionFile CreateSessionFile() + { + var diffChunk = new DiffChunk + { + Lines = + { + // Line numbers here are 1-based. There is an add diff entry on line 11 + // and a delete entry on line 13. + new DiffLine { Type = DiffChangeType.Add, NewLineNumber = 11 + 1 }, + new DiffLine { Type = DiffChangeType.Delete, OldLineNumber = 13 + 1 }, + } + }; + var diff = new List<DiffChunk> { diffChunk }; + + var rhsThread = Substitute.For<IInlineCommentThreadModel>(); + rhsThread.DiffLineType.Returns(DiffChangeType.Add); + rhsThread.LineNumber.Returns(10); + + var lhsThread = Substitute.For<IInlineCommentThreadModel>(); + lhsThread.DiffLineType.Returns(DiffChangeType.Delete); + lhsThread.LineNumber.Returns(12); + + // We have a comment to display on the right-hand-side of the diff view on line + // 11 and a comment to display on line 13 on the left-hand-side. + var threads = new List<IInlineCommentThreadModel> { rhsThread, lhsThread }; + + var file = Substitute.For<IPullRequestSessionFile>(); + file.Diff.Returns(diff); + file.InlineCommentThreads.Returns(threads); + file.LinesChanged.Returns(new Subject<IReadOnlyList<Tuple<int, DiffSide>>>()); + + return file; + } + + static IPullRequestSessionManager CreateSessionManager(DiffSide side) + { + var file = CreateSessionFile(); + return CreateSessionManager(file, side); + } + + static IPullRequestSessionManager CreateSessionManager( + IPullRequestSessionFile file, + DiffSide side, + string bufferInfoCommitSha = "HEAD") + { + var session = Substitute.For<IPullRequestSession>(); + session.GetFile("file.cs", bufferInfoCommitSha).Returns(file); + + var info = new PullRequestTextBufferInfo(session, "file.cs", bufferInfoCommitSha, side); + var result = Substitute.For<IPullRequestSessionManager>(); + result.CurrentSession.Returns(session); + result.GetTextBufferInfo(null).ReturnsForAnyArgs(info); + return result; + } + } + + public class WithoutTextBufferInfo + { + [Test] + public void FirstPassShouldReturnEmptyTags() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager()); + + var result = target.GetTags(CreateSpan(10)); + Assert.That(result, Is.Empty); + } + + [Test] + public void ShouldReturnShowCommentTag() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager()); + + // Line 10 has an existing RHS comment. + var span = CreateSpan(10); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Has.One.Items); + Assert.That(result[0].Tag, Is.InstanceOf<ShowInlineCommentTag>()); + } + + [Test] + public void ShouldReturnAddNewCommentTagForAddedLine() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager()); + + // Line 11 has an add diff entry. + var span = CreateSpan(11); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + + Assert.That(result, Has.One.Items); + Assert.That(result[0].Tag, Is.InstanceOf<AddInlineCommentTag>()); + } + + [Test] + public void ShouldNotReturnAddNewCommentTagForDeletedLineOnRhs() + { + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + Substitute.For<ITextBuffer>(), + CreateSessionManager()); + + // Line 13 has an delete diff entry. + var span = CreateSpan(13); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + Assert.That(result, Is.Empty); + } + + [TestCase(true, true)] + [TestCase(false, false)] + public void ShouldRaiseTagsChangedOnFileLinesChanged(bool inlineCommentMarginVisible, bool expectRaised) + { + var file = CreateSessionFile(); + var manager = CreateSessionManager(file); + var target = new InlineCommentTagger( + CreateTextView(inlineCommentMarginVisible), + CreateBuffer(), + manager); + + var span = CreateSpan(14); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + var raised = false; + + target.TagsChanged += (s, e) => raised = e.Span.Start == 140; + ((ISubject<IReadOnlyList<Tuple<int, DiffSide>>>)file.LinesChanged).OnNext(new[] + { + Tuple.Create(14, DiffSide.Right), + }); + + Assert.That(raised, Is.EqualTo(expectRaised)); + } + + [Test] + public void ShouldNotRaiseTagsChangedOnLeftHandSideLinesChanged() + { + var file = CreateSessionFile(); + var manager = CreateSessionManager(file); + var target = new InlineCommentTagger( + Substitute.For<ITextView>(), + CreateBuffer(), + manager); + + var span = CreateSpan(14); + var firstPass = target.GetTags(span); + var result = target.GetTags(span).ToList(); + var raised = false; + + target.TagsChanged += (s, e) => raised = true; + ((ISubject<IReadOnlyList<Tuple<int, DiffSide>>>)file.LinesChanged).OnNext(new[] + { + Tuple.Create(14, DiffSide.Left), + }); + + Assert.False(raised); + } + + static ITextView CreateTextView(bool inlineCommentMarginVisible = true) + { + var textView = Substitute.For<ITextView>(); + textView.Options.GetOptionValue(InlineCommentTextViewOptions.MarginVisibleId).Returns(inlineCommentMarginVisible); + return textView; + } + + static IPullRequestSessionFile CreateSessionFile() + { + var diffChunk = new DiffChunk + { + Lines = + { + // Line numbers here are 1-based. There is an add diff entry on line 11 + // and a delete entry on line 13. + new DiffLine { Type = DiffChangeType.Add, NewLineNumber = 11 + 1 }, + new DiffLine { Type = DiffChangeType.Delete, OldLineNumber = 13 + 1 }, + } + }; + var diff = new List<DiffChunk> { diffChunk }; + + var rhsThread = Substitute.For<IInlineCommentThreadModel>(); + rhsThread.DiffLineType.Returns(DiffChangeType.Add); + rhsThread.LineNumber.Returns(10); + + var lhsThread = Substitute.For<IInlineCommentThreadModel>(); + lhsThread.DiffLineType.Returns(DiffChangeType.Delete); + lhsThread.LineNumber.Returns(12); + + // We have a comment to display on the right-hand-side of the diff view on line + // 11 and a comment to display on line 13 on the left-hand-side. + var threads = new List<IInlineCommentThreadModel> { rhsThread, lhsThread }; + + var file = Substitute.For<IPullRequestSessionFile>(); + file.Diff.Returns(diff); + file.InlineCommentThreads.Returns(threads); + file.LinesChanged.Returns(new Subject<IReadOnlyList<Tuple<int, DiffSide>>>()); + + return file; + } + + static IPullRequestSessionManager CreateSessionManager() + { + var file = CreateSessionFile(); + return CreateSessionManager(file); + } + + static IPullRequestSessionManager CreateSessionManager(IPullRequestSessionFile file) + { + var result = Substitute.For<IPullRequestSessionManager>(); + result.GetLiveFile("file.cs", Arg.Any<ITextView>(), Arg.Any<ITextBuffer>()) + .Returns(Task.FromResult(file)); + result.GetRelativePath(null).ReturnsForAnyArgs("file.cs"); + return result; + } + } + + static ITextSnapshot CreateSnapshot() + { + // We pretend that each line has 10 chars and there are 20 lines. + var result = Substitute.For<ITextSnapshot>(); + result.Length.Returns(200); + result.GetLineFromPosition(0).ReturnsForAnyArgs(x => CreateLine(result, x.Arg<int>() / 10)); + result.GetLineFromLineNumber(0).ReturnsForAnyArgs(x => CreateLine(result, x.Arg<int>())); + return result; + } + + static NormalizedSnapshotSpanCollection CreateSpan(int lineNumber) + { + var snapshot = CreateSnapshot(); + var span = new Span(lineNumber * 10, 9); + return new NormalizedSnapshotSpanCollection(snapshot, span); + } + + static ITextBuffer CreateBuffer() + { + var snapshot = CreateSnapshot(); + var result = Substitute.For<ITextBuffer>(); + result.CurrentSnapshot.Returns(snapshot); + return result; + } + + static ITextSnapshotLine CreateLine(ITextSnapshot snapshot, int lineNumber) + { + var result = Substitute.For<ITextSnapshotLine>(); + var start = new SnapshotPoint(snapshot, lineNumber * 10); + var end = new SnapshotPoint(snapshot, (lineNumber * 10) + 9); + result.LineNumber.Returns(lineNumber); + result.Start.Returns(start); + result.End.Returns(end); + return result; + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/TestDoubles/FakeDiffService.cs b/test/GitHub.InlineReviews.UnitTests/TestDoubles/FakeDiffService.cs new file mode 100644 index 0000000000..112c1bc83c --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/TestDoubles/FakeDiffService.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GitHub.InlineReviews.Services; +using GitHub.Models; +using GitHub.Services; +using LibGit2Sharp; +using NSubstitute; + +namespace GitHub.InlineReviews.UnitTests.TestDoubles +{ + sealed class FakeDiffService : IDiffService, IDisposable + { + readonly IRepository repository; + readonly IDiffService inner; + readonly Dictionary<string, string> commitAliases = new Dictionary<string, string>(); + + public FakeDiffService() + { + this.repository = CreateRepository(); + this.inner = new DiffService(Substitute.For<IGitClient>()); + } + + public FakeDiffService(string path, string contents) + { + this.repository = CreateRepository(); + this.inner = new DiffService(Substitute.For<IGitClient>()); + AddFile(path, contents); + } + + public string AddFile(string path, string contents) + { + var signature = new Signature("user", "user@user", DateTimeOffset.Now); + var fullPath = Path.Combine(repository.Info.WorkingDirectory, path); + var directory = Path.GetDirectoryName(fullPath); + Directory.CreateDirectory(directory); + File.WriteAllText(fullPath, contents); +#pragma warning disable 618 // Type or member is obsolete + repository.Stage(path); +#pragma warning restore 618 // Type or member is obsolete + repository.Commit("Added " + path, signature, signature); + return repository.Head.Tip.Sha; + } + + public string AddFile(string path, string contents, string commitAlias) + { + var sha = AddFile(path, contents); + commitAliases.Add(commitAlias, sha); + return sha; + } + + public void Dispose() + { + var path = repository.Info.WorkingDirectory; + + // The .git folder has some files marked as readonly, meaning that a simple + // Directory.Delete doesn't work here. + DeleteDirectory(path); + } + + public Task<IReadOnlyList<DiffChunk>> Diff(IRepository repo, string baseSha, string headSha, string path) + { + var blob1 = GetBlob(path, baseSha); + var blob2 = GetBlob(path, headSha); + var patch = repository.Diff.Compare(blob1, blob2).Patch; + return Task.FromResult<IReadOnlyList<DiffChunk>>(DiffUtilities.ParseFragment(patch).ToList()); + } + + public Task<IReadOnlyList<DiffChunk>> Diff(string path, string baseSha, byte[] contents) + { + var tip = repository.Head.Tip.Sha; + var stream = contents != null ? new MemoryStream(contents) : new MemoryStream(); + var blob1 = GetBlob(path, baseSha); + var blob2 = repository.ObjectDatabase.CreateBlob(stream, path); + var patch = repository.Diff.Compare(blob1, blob2).Patch; + return Task.FromResult<IReadOnlyList<DiffChunk>>(DiffUtilities.ParseFragment(patch).ToList()); + } + + public Task<IReadOnlyList<DiffChunk>> Diff(string path, string contents) + { + return Diff(path, repository.Head.Tip.Sha, Encoding.UTF8.GetBytes(contents)); + } + + public Task<IReadOnlyList<DiffChunk>> Diff(IRepository repo, string baseSha, string headSha, string path, byte[] contents) + { + return Diff(path, baseSha, contents); + } + + Blob GetBlob(string path, string id) + { + string sha; + if (!commitAliases.TryGetValue(id, out sha)) sha = id; + var commit = repository.Lookup<Commit>(sha); + return commit?[path]?.Target as Blob; + } + + static IRepository CreateRepository() + { + var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempPath); + Repository.Init(tempPath); + + var result = new Repository(tempPath); + var signature = new Signature("user", "user@user", DateTimeOffset.Now); + + File.WriteAllText(Path.Combine(tempPath, ".gitattributes"), "* text=auto"); +#pragma warning disable 618 // Type or member is obsolete + result.Stage("*"); +#pragma warning restore 618 // Type or member is obsolete + result.Commit("Initial commit", signature, signature); + + return result; + } + + static void DeleteDirectory(string path) + { + foreach (var d in Directory.EnumerateDirectories(path)) + { + DeleteDirectory(d); + } + + foreach (var f in Directory.EnumerateFiles(path)) + { + var fileInfo = new FileInfo(f); + fileInfo.Attributes = FileAttributes.Normal; + fileInfo.Delete(); + } + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs b/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs new file mode 100644 index 0000000000..53debb849f --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentPeekViewModelTests.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Factories; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.ViewModels; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using NSubstitute; +using Octokit; +using NUnit.Framework; +using GitHub.Commands; + +namespace GitHub.InlineReviews.UnitTests.ViewModels +{ + public class InlineCommentPeekViewModelTests + { + const string FullPath = "c:\\repo\\test.cs"; + const string RelativePath = "test.cs"; + + public InlineCommentPeekViewModelTests() + { + Splat.ModeDetector.Current.SetInUnitTestRunner(true); + } + + [Test] + public async Task ThreadIsCreatedForExistingComments() + { + // There is an existing comment thread at line 10. + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + CreatePeekSession(), + CreateSessionManager(), + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + // There should be an existing comment and a reply placeholder. + Assert.That(target.Thread, Is.InstanceOf(typeof(InlineCommentThreadViewModel))); + Assert.That(target.Thread.Comments.Count, Is.EqualTo(2)); + Assert.That(target.Thread.Comments[0].Body, Is.EqualTo("Existing comment")); + Assert.That(target.Thread.Comments[1].Body, Is.EqualTo(string.Empty)); + Assert.That(target.Thread.Comments[1].EditState, Is.EqualTo(CommentEditState.Placeholder)); + } + + [Test] + public async Task ThreadIsCreatedForNewComment() + { + // There is no existing comment thread at line 9, but there is a + diff entry. + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 9), + CreatePeekSession(), + CreateSessionManager(), + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + Assert.That(target.Thread, Is.InstanceOf(typeof(NewInlineCommentThreadViewModel))); + Assert.That(target.Thread.Comments[0].Body, Is.EqualTo(string.Empty)); + Assert.That(target.Thread.Comments[0].EditState, Is.EqualTo(CommentEditState.Editing)); + } + + [Test] + public async Task ShouldGetRelativePathFromTextBufferInfoIfPresent() + { + var session = CreateSession(); + var bufferInfo = new PullRequestTextBufferInfo(session, RelativePath, "123", DiffSide.Right); + var sessionManager = CreateSessionManager( + relativePath: "ShouldNotUseThis", + session: session, + textBufferInfo: bufferInfo); + + // There is an existing comment thread at line 10. + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + CreatePeekSession(), + sessionManager, + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + // There should be an existing comment and a reply placeholder. + Assert.That(target.Thread, Is.InstanceOf(typeof(InlineCommentThreadViewModel))); + Assert.That(target.Thread.Comments.Count, Is.EqualTo(2)); + Assert.That(target.Thread.Comments[0].Body, Is.EqualTo("Existing comment")); + Assert.That(target.Thread.Comments[1].Body, Is.EqualTo(string.Empty)); + Assert.That(target.Thread.Comments[1].EditState, Is.EqualTo(CommentEditState.Placeholder)); + } + + [Test] + public async Task SwitchesFromNewThreadToExistingThreadWhenCommentPosted() + { + var sessionManager = CreateSessionManager(); + var peekSession = CreatePeekSession(); + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 8), + peekSession, + sessionManager, + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + Assert.That(target.Thread, Is.InstanceOf(typeof(NewInlineCommentThreadViewModel))); + + target.Thread.Comments[0].Body = "New Comment"; + + sessionManager.CurrentSession + .When(x => x.PostReviewComment( + Arg.Any<string>(), + Arg.Any<string>(), + Arg.Any<string>(), + Arg.Any<IReadOnlyList<DiffChunk>>(), + Arg.Any<int>())) + .Do(async x => + { + // Simulate the thread being added to the session. + var file = await sessionManager.GetLiveFile( + RelativePath, + peekSession.TextView, + peekSession.TextView.TextBuffer); + var newThread = CreateThread(8, "New Comment"); + file.InlineCommentThreads.Returns(new[] { newThread }); + RaiseLinesChanged(file, Tuple.Create(8, DiffSide.Right)); + }); + + await target.Thread.Comments[0].CommitEdit.ExecuteAsyncTask(null); + + Assert.That(target.Thread, Is.InstanceOf(typeof(InlineCommentThreadViewModel))); + } + + [Test] + public async Task RefreshesWhenSessionInlineCommentThreadsChanges() + { + var sessionManager = CreateSessionManager(); + var peekSession = CreatePeekSession(); + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + peekSession, + sessionManager, + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + Assert.That(target.Thread, Is.InstanceOf(typeof(InlineCommentThreadViewModel))); + Assert.That(target.Thread.Comments.Count, Is.EqualTo(2)); + + var file = await sessionManager.GetLiveFile( + RelativePath, + peekSession.TextView, + peekSession.TextView.TextBuffer); + AddCommentToExistingThread(file); + + Assert.That(target.Thread.Comments.Count, Is.EqualTo(3)); + } + + [Test] + public async Task RetainsCommentBeingEditedWhenSessionRefreshed() + { + var sessionManager = CreateSessionManager(); + var peekSession = CreatePeekSession(); + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + CreatePeekSession(), + sessionManager, + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + Assert.That(target.Thread.Comments.Count, Is.EqualTo(2)); + + var placeholder = target.Thread.Comments.Last(); + placeholder.BeginEdit.Execute(null); + placeholder.Body = "Comment being edited"; + + var file = await sessionManager.GetLiveFile( + RelativePath, + peekSession.TextView, + peekSession.TextView.TextBuffer); + AddCommentToExistingThread(file); + + placeholder = target.Thread.Comments.Last(); + Assert.That(target.Thread.Comments.Count, Is.EqualTo(3)); + Assert.That(placeholder.EditState, Is.EqualTo(CommentEditState.Editing)); + Assert.That(placeholder.Body, Is.EqualTo("Comment being edited")); + } + + [Test] + public async Task CommittingEditDoesntRetainSubmittedCommentInPlaceholderAfterPost() + { + var sessionManager = CreateSessionManager(); + var peekSession = CreatePeekSession(); + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + peekSession, + sessionManager, + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + Assert.That(target.Thread.Comments.Count, Is.EqualTo(2)); + + sessionManager.CurrentSession.PostReviewComment(null, null) + .ReturnsForAnyArgs(async x => + { + var file = await sessionManager.GetLiveFile( + RelativePath, + peekSession.TextView, + peekSession.TextView.TextBuffer); + AddCommentToExistingThread(file); + }); + + var placeholder = target.Thread.Comments.Last(); + placeholder.BeginEdit.Execute(null); + placeholder.Body = "Comment being edited"; + placeholder.CommitEdit.Execute(null); + + placeholder = target.Thread.Comments.Last(); + Assert.That(placeholder.EditState, Is.EqualTo(CommentEditState.Placeholder)); + Assert.That(placeholder.Body, Is.EqualTo(string.Empty)); + } + + [Test] + public async Task StartingReviewDoesntRetainSubmittedCommentInPlaceholderAfterPost() + { + var sessionManager = CreateSessionManager(); + var peekSession = CreatePeekSession(); + var target = new InlineCommentPeekViewModel( + CreatePeekService(lineNumber: 10), + peekSession, + sessionManager, + Substitute.For<INextInlineCommentCommand>(), + Substitute.For<IPreviousInlineCommentCommand>(), + Substitute.For<ICommentService>()); + + await target.Initialize(); + + Assert.That(target.Thread.Comments.Count, Is.EqualTo(2)); + + sessionManager.CurrentSession.StartReview() + .ReturnsForAnyArgs(async x => + { + var file = await sessionManager.GetLiveFile( + RelativePath, + peekSession.TextView, + peekSession.TextView.TextBuffer); + RaiseLinesChanged(file, Tuple.Create(10, DiffSide.Right)); + }); + + var placeholder = (IPullRequestReviewCommentViewModel)target.Thread.Comments.Last(); + placeholder.BeginEdit.Execute(null); + placeholder.Body = "Comment being edited"; + placeholder.StartReview.Execute(null); + + placeholder = (IPullRequestReviewCommentViewModel)target.Thread.Comments.Last(); + Assert.That(placeholder.EditState, Is.EqualTo(CommentEditState.Placeholder)); + Assert.That(placeholder.Body, Is.EqualTo(string.Empty)); + } + + void AddCommentToExistingThread(IPullRequestSessionFile file) + { + var newThreads = file.InlineCommentThreads.ToList(); + var thread = file.InlineCommentThreads.Single(); + var newComment = CreateComment("New Comment"); + var newComments = thread.Comments.Concat(new[] { newComment }).ToList(); + thread.Comments.Returns(newComments); + file.InlineCommentThreads.Returns(newThreads); + RaiseLinesChanged(file, Tuple.Create(thread.LineNumber, DiffSide.Right)); + } + + IApiClientFactory CreateApiClientFactory() + { + var apiClient = Substitute.For<IApiClient>(); + apiClient.CreatePullRequestReviewComment(null, null, 0, null, 0) + .ReturnsForAnyArgs(_ => Observable.Return(new PullRequestReviewComment())); + apiClient.CreatePullRequestReviewComment(null, null, 0, null, null, null, 0) + .ReturnsForAnyArgs(_ => Observable.Return(new PullRequestReviewComment())); + + var result = Substitute.For<IApiClientFactory>(); + result.Create(null).ReturnsForAnyArgs(apiClient); + return result; + } + + InlineCommentModel CreateComment(string body) + { + return new InlineCommentModel + { + Comment = new PullRequestReviewCommentModel + { + Body = body, + }, + Review = new PullRequestReviewModel(), + }; + } + + IInlineCommentThreadModel CreateThread(int lineNumber, params string[] comments) + { + var result = Substitute.For<IInlineCommentThreadModel>(); + var commentList = comments.Select(x => CreateComment(x)).ToList(); + result.Comments.Returns(commentList); + result.LineNumber.Returns(lineNumber); + return result; + } + + IInlineCommentPeekService CreatePeekService(int lineNumber) + { + var result = Substitute.For<IInlineCommentPeekService>(); + result.GetLineNumber(null, null).ReturnsForAnyArgs(Tuple.Create(lineNumber, false)); + return result; + } + + IPeekSession CreatePeekSession() + { + var document = Substitute.For<ITextDocument>(); + document.FilePath.Returns(FullPath); + + var propertyCollection = new PropertyCollection(); + propertyCollection.AddProperty(typeof(ITextDocument), document); + + var result = Substitute.For<IPeekSession>(); + result.TextView.TextBuffer.Properties.Returns(propertyCollection); + + return result; + } + + IPullRequestSession CreateSession() + { + var result = Substitute.For<IPullRequestSession>(); + result.User.Returns(new ActorModel { Login = "CurrentUser" }); + result.LocalRepository.CloneUrl.Returns(new UriString("https://foo.bar")); + return result; + } + + IPullRequestSessionManager CreateSessionManager( + string commitSha = "COMMIT", + string relativePath = RelativePath, + IPullRequestSession session = null, + PullRequestTextBufferInfo textBufferInfo = null) + { + var thread = CreateThread(10, "Existing comment"); + + var diff = new DiffChunk + { + DiffLine = 10, + OldLineNumber = 1, + NewLineNumber = 1, + }; + + for (var i = 0; i < 10; ++i) + { + diff.Lines.Add(new DiffLine + { + NewLineNumber = i, + DiffLineNumber = i + 10, + Type = i < 5 ? DiffChangeType.Delete : DiffChangeType.Add, + }); + } + + var file = Substitute.For<IPullRequestSessionFile>(); + file.CommitSha.Returns(commitSha); + file.Diff.Returns(new[] { diff }); + file.InlineCommentThreads.Returns(new[] { thread }); + file.LinesChanged.Returns(new Subject<IReadOnlyList<Tuple<int, DiffSide>>>()); + + session = session ?? CreateSession(); + + if (textBufferInfo != null) + { + session.GetFile(textBufferInfo.RelativePath, textBufferInfo.CommitSha).Returns(file); + } + + var result = Substitute.For<IPullRequestSessionManager>(); + result.CurrentSession.Returns(session); + result.GetLiveFile(relativePath, Arg.Any<ITextView>(), Arg.Any<ITextBuffer>()).Returns(file); + result.GetRelativePath(Arg.Any<ITextBuffer>()).Returns(relativePath); + result.GetTextBufferInfo(Arg.Any<ITextBuffer>()).Returns(textBufferInfo); + + return result; + } + + void RaiseLinesChanged(IPullRequestSessionFile file, params Tuple<int, DiffSide>[] lineNumbers) + { + var subject = (Subject<IReadOnlyList<Tuple<int, DiffSide>>>)file.LinesChanged; + subject.OnNext(lineNumbers); + } + + void RaisePropertyChanged<T>(T o, string propertyName) + where T : INotifyPropertyChanged + { + o.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentThreadViewModelTests.cs b/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentThreadViewModelTests.cs new file mode 100644 index 0000000000..7704bde7cf --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/ViewModels/InlineCommentThreadViewModelTests.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.ViewModels; +using GitHub.Models; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; + +namespace GitHub.InlineReviews.UnitTests.ViewModels +{ + public class InlineCommentThreadViewModelTests + { + [Test] + public void CreatesComments() + { + var target = new InlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + CreateSession(), + CreateComments("Comment 1", "Comment 2")); + + Assert.That(3, Is.EqualTo(target.Comments.Count)); + Assert.That( + new[] + { + "Comment 1", + "Comment 2", + string.Empty + }, + Is.EqualTo(target.Comments.Select(x => x.Body))); + + Assert.That( + new[] + { + CommentEditState.None, + CommentEditState.None, + CommentEditState.Placeholder, + }, + Is.EqualTo(target.Comments.Select(x => x.EditState))); + } + + [Test] + public void PlaceholderCommitEnabledWhenCommentHasBody() + { + var target = new InlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + CreateSession(), + CreateComments("Comment 1")); + + Assert.That(target.Comments[1].CommitEdit.CanExecute(null), Is.False); + + target.Comments[1].Body = "Foo"; + Assert.That(target.Comments[1].CommitEdit.CanExecute(null), Is.True); + } + + [Test] + public void PostsCommentInReplyToCorrectComment() + { + var session = CreateSession(); + var target = new InlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + session, + CreateComments("Comment 1", "Comment 2")); + + target.Comments[2].Body = "New Comment"; + target.Comments[2].CommitEdit.Execute(null); + + session.Received(1).PostReviewComment("New Comment", "1"); + } + + InlineCommentModel CreateComment(string id, string body) + { + return new InlineCommentModel + { + Comment = new PullRequestReviewCommentModel + { + Id = id, + Body = body, + }, + Review = new PullRequestReviewModel(), + }; + } + + IEnumerable<InlineCommentModel> CreateComments(params string[] bodies) + { + var id = 1; + + foreach (var body in bodies) + { + yield return CreateComment((id++).ToString(), body); + } + } + + IPullRequestSession CreateSession() + { + var result = Substitute.For<IPullRequestSession>(); + result.User.Returns(new ActorModel { Login = "Viewer" }); + result.RepositoryOwner.Returns("owner"); + result.LocalRepository.Name.Returns("repo"); + result.LocalRepository.Owner.Returns("shouldnt-be-used"); + result.PullRequest.Returns(new PullRequestDetailModel + { + Number = 47, + }); + return result; + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/ViewModels/NewInlineCommentThreadViewModelTests.cs b/test/GitHub.InlineReviews.UnitTests/ViewModels/NewInlineCommentThreadViewModelTests.cs new file mode 100644 index 0000000000..c1e8383801 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/ViewModels/NewInlineCommentThreadViewModelTests.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using System.ComponentModel; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.ViewModels; +using GitHub.Models; +using GitHub.Services; +using NSubstitute; +using NUnit.Framework; + +namespace GitHub.InlineReviews.UnitTests.ViewModels +{ + public class NewInlineCommentThreadViewModelTests + { + public NewInlineCommentThreadViewModelTests() + { + Splat.ModeDetector.Current.SetInUnitTestRunner(true); + } + + [Test] + public void CreatesReplyPlaceholder() + { + var target = new NewInlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + CreateSession(), + Substitute.For<IPullRequestSessionFile>(), + 10, + false); + + Assert.That(target.Comments, Has.One.Items); + Assert.That(target.Comments[0].Body, Is.EqualTo(string.Empty)); + Assert.That(target.Comments[0].EditState, Is.EqualTo(CommentEditState.Editing)); + } + + [Test] + public void NeedsPushTracksFileCommitSha() + { + var file = CreateFile(); + var target = new NewInlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + CreateSession(), + file, + 10, + false); + + Assert.That(target.NeedsPush, Is.False); + Assert.That(target.PostComment.CanExecute(false), Is.True); + + file.CommitSha.Returns((string)null); + RaisePropertyChanged(file, nameof(file.CommitSha)); + Assert.That(target.NeedsPush, Is.True); + Assert.That(target.PostComment.CanExecute(false), Is.False); + + file.CommitSha.Returns("COMMIT_SHA"); + RaisePropertyChanged(file, nameof(file.CommitSha)); + Assert.That(target.NeedsPush, Is.False); + Assert.That(target.PostComment.CanExecute(false), Is.True); + } + + [Test] + public void PlaceholderCommitEnabledWhenCommentHasBodyAndPostCommentIsEnabled() + { + var file = CreateFile(); + var target = new NewInlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + CreateSession(), + file, + 10, + false); + + file.CommitSha.Returns((string)null); + RaisePropertyChanged(file, nameof(file.CommitSha)); + Assert.That(target.Comments[0].CommitEdit.CanExecute(null), Is.False); + + target.Comments[0].Body = "Foo"; + Assert.That(target.Comments[0].CommitEdit.CanExecute(null), Is.False); + + file.CommitSha.Returns("COMMIT_SHA"); + RaisePropertyChanged(file, nameof(file.CommitSha)); + Assert.That(target.Comments[0].CommitEdit.CanExecute(null), Is.True); + } + + [Test] + public void PostsCommentToCorrectAddedLine() + { + var session = CreateSession(); + var file = CreateFile(); + var target = new NewInlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + session, file, 10, false); + + target.Comments[0].Body = "New Comment"; + target.Comments[0].CommitEdit.Execute(null); + + session.Received(1).PostReviewComment( + "New Comment", + "COMMIT_SHA", + "file.cs", + Arg.Any<IReadOnlyList<DiffChunk>>(), + 5); + } + + [Test] + public void AddsCommentToCorrectDeletedLine() + { + var session = CreateSession(); + var file = CreateFile(); + + file.Diff.Returns(new[] + { + new DiffChunk + { + Lines = + { + new DiffLine { OldLineNumber = 17, DiffLineNumber = 7 } + } + } + }); + + var target = new NewInlineCommentThreadViewModel( + Substitute.For<ICommentService>(), + session, file, 16, true); + + target.Comments[0].Body = "New Comment"; + target.Comments[0].CommitEdit.Execute(null); + + session.Received(1).PostReviewComment( + "New Comment", + "COMMIT_SHA", + "file.cs", + Arg.Any<IReadOnlyList<DiffChunk>>(), + 7); + } + + IPullRequestSessionFile CreateFile() + { + var result = Substitute.For<IPullRequestSessionFile>(); + result.CommitSha.Returns("COMMIT_SHA"); + result.Diff.Returns(new[] + { + new DiffChunk + { + Lines = + { + new DiffLine { NewLineNumber = 11, DiffLineNumber = 5 } + } + } + }); + result.RelativePath.Returns("file.cs"); + return result; + } + + IPullRequestSession CreateSession() + { + var result = Substitute.For<IPullRequestSession>(); + result.RepositoryOwner.Returns("owner"); + result.LocalRepository.Name.Returns("repo"); + result.LocalRepository.Owner.Returns("shouldnt-be-used"); + result.PullRequest.Returns(new PullRequestDetailModel { Number = 47 }); + result.User.Returns(new ActorModel()); + return result; + } + + void RaisePropertyChanged<T>(T o, string propertyName) + where T : INotifyPropertyChanged + { + o.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/ViewModels/PullRequestReviewCommentViewModelTests.cs b/test/GitHub.InlineReviews.UnitTests/ViewModels/PullRequestReviewCommentViewModelTests.cs new file mode 100644 index 0000000000..03bcaab457 --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/ViewModels/PullRequestReviewCommentViewModelTests.cs @@ -0,0 +1,259 @@ +using System; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.InlineReviews.Services; +using GitHub.InlineReviews.ViewModels; +using GitHub.Models; +using GitHub.Services; +using GitHub.ViewModels; +using NSubstitute; +using NUnit.Framework; +using ReactiveUI; + +namespace GitHub.InlineReviews.UnitTests.ViewModels +{ + public class PullRequestReviewCommentViewModelTests + { + public class TheCanStartReviewProperty + { + [Test] + public void IsFalseWhenSessionHasPendingReview() + { + var session = CreateSession(true); + var target = CreateTarget(session); + + Assert.That(target.CanStartReview, Is.False); + } + + [Test] + public void IsTrueWhenSessionHasNoPendingReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + Assert.That(target.CanStartReview, Is.True); + } + + [Test] + public void IsFalseWhenEditingExistingComment() + { + var session = CreateSession(false); + var pullRequestReviewCommentModel = new PullRequestReviewCommentModel { Id = "1" }; + var target = CreateTarget(session, comment: pullRequestReviewCommentModel); + + Assert.That(target.CanStartReview, Is.False); + } + } + + public class TheBeginEditProperty + { + [Test] + public void CanBeExecutedForPlaceholders() + { + var session = CreateSession(); + var thread = CreateThread(); + var currentUser = Substitute.For<IActorViewModel>(); + var commentService = Substitute.For<ICommentService>(); + var target = PullRequestReviewCommentViewModel.CreatePlaceholder(session, commentService, thread, currentUser); + Assert.That(target.BeginEdit.CanExecute(new object()), Is.True); + } + + [Test] + public void CanBeExecutedForCommentsByTheSameAuthor() + { + var session = CreateSession(); + var thread = CreateThread(); + + var currentUser = new ActorModel { Login = "CurrentUser" }; + var comment = new PullRequestReviewCommentModel { Author = currentUser }; + + var target = CreateTarget(session, null, thread, currentUser, null, comment); + Assert.That(target.BeginEdit.CanExecute(new object()), Is.True); + } + + [Test] + public void CannotBeExecutedForCommentsByAnotherAuthor() + { + var session = CreateSession(); + var thread = CreateThread(); + + var currentUser = new ActorModel { Login = "CurrentUser" }; + var otherUser = new ActorModel { Login = "OtherUser" }; + var comment = new PullRequestReviewCommentModel { Author = otherUser }; + + var target = CreateTarget(session, null, thread, currentUser, null, comment); + Assert.That(target.BeginEdit.CanExecute(new object()), Is.False); + } + } + + public class TheDeleteProperty + { + [Test] + public void CannotBeExecutedForPlaceholders() + { + var session = CreateSession(); + var thread = CreateThread(); + var currentUser = Substitute.For<IActorViewModel>(); + var commentService = Substitute.For<ICommentService>(); + var target = PullRequestReviewCommentViewModel.CreatePlaceholder(session, commentService, thread, currentUser); + Assert.That(target.Delete.CanExecute(new object()), Is.False); + } + + [Test] + public void CanBeExecutedForCommentsByTheSameAuthor() + { + var session = CreateSession(); + var thread = CreateThread(); + + var currentUser = new ActorModel { Login = "CurrentUser" }; + var comment = new PullRequestReviewCommentModel { Author = currentUser }; + + var target = CreateTarget(session, null, thread, currentUser, null, comment); + Assert.That(target.Delete.CanExecute(new object()), Is.True); + } + + [Test] + public void CannotBeExecutedForCommentsByAnotherAuthor() + { + var session = CreateSession(); + var thread = CreateThread(); + + var currentUser = new ActorModel { Login = "CurrentUser" }; + var otherUser = new ActorModel { Login = "OtherUser" }; + var comment = new PullRequestReviewCommentModel { Author = otherUser }; + + var target = CreateTarget(session, null, thread, currentUser, null, comment); + Assert.That(target.Delete.CanExecute(new object()), Is.False); + } + } + + public class TheCommitCaptionProperty + { + [Test] + public void IsAddReviewCommentWhenSessionHasPendingReview() + { + var session = CreateSession(true); + var target = CreateTarget(session); + + Assert.That(target.CommitCaption, Is.EqualTo("Add review comment")); + } + + [Test] + public void IsAddSingleCommentWhenSessionHasNoPendingReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + Assert.That(target.CommitCaption, Is.EqualTo("Add a single comment")); + } + + [Test] + public void IsUpdateCommentWhenEditingExistingComment() + { + var session = CreateSession(false); + var pullRequestReviewCommentModel = new PullRequestReviewCommentModel { Id = "1" }; + var target = CreateTarget(session, comment: pullRequestReviewCommentModel); + + Assert.That(target.CommitCaption, Is.EqualTo("Update comment")); + } + } + + public class TheStartReviewCommand + { + public TheStartReviewCommand() + { + Splat.ModeDetector.Current.SetInUnitTestRunner(true); + } + + [Test] + public void IsDisabledWhenSessionHasPendingReview() + { + var session = CreateSession(true); + var target = CreateTarget(session); + + Assert.That(target.StartReview.CanExecute(null), Is.False); + } + + [Test] + public void IsDisabledWhenSessionHasNoPendingReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + Assert.That(target.StartReview.CanExecute(null), Is.False); + } + + [Test] + public void IsEnabledWhenSessionHasNoPendingReviewAndBodyNotEmpty() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + target.Body = "body"; + + Assert.That(target.StartReview.CanExecute(null), Is.True); + } + + [Test] + public void CallsSessionStartReview() + { + var session = CreateSession(false); + var target = CreateTarget(session); + + target.Body = "body"; + target.StartReview.Execute(null); + + session.Received(1).StartReview(); + } + } + + static PullRequestReviewCommentViewModel CreateTarget( + IPullRequestSession session = null, + ICommentService commentService = null, + ICommentThreadViewModel thread = null, + ActorModel currentUser = null, + PullRequestReviewModel review = null, + PullRequestReviewCommentModel comment = null) + { + session = session ?? CreateSession(); + commentService = commentService ?? Substitute.For<ICommentService>(); + thread = thread ?? CreateThread(); + currentUser = currentUser ?? new ActorModel { Login = "CurrentUser" }; + comment = comment ?? new PullRequestReviewCommentModel(); + review = review ?? CreateReview(comment); + + return new PullRequestReviewCommentViewModel( + session, + commentService, + thread, + new ActorViewModel(currentUser), + review, + comment); + } + + static IPullRequestSession CreateSession( + bool hasPendingReview = false) + { + var result = Substitute.For<IPullRequestSession>(); + result.HasPendingReview.Returns(hasPendingReview); + result.User.Returns(new ActorModel()); + return result; + } + + static PullRequestReviewModel CreateReview(params PullRequestReviewCommentModel[] comments) + { + return new PullRequestReviewModel + { + Comments = comments, + }; + } + + static ICommentThreadViewModel CreateThread( + bool canPost = true) + { + var result = Substitute.For<ICommentThreadViewModel>(); + result.PostComment.Returns(ReactiveCommand.CreateAsyncTask(_ => Task.CompletedTask)); + return result; + } + } +} diff --git a/test/GitHub.InlineReviews.UnitTests/packages.config b/test/GitHub.InlineReviews.UnitTests/packages.config new file mode 100644 index 0000000000..d294d83d7e --- /dev/null +++ b/test/GitHub.InlineReviews.UnitTests/packages.config @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net461" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net461" /> + <package id="System.Diagnostics.DiagnosticSource" version="4.0.0" targetFramework="net461" /> + <package id="System.Net.Http" version="4.1.1" targetFramework="net461" /> + <package id="System.Security.Cryptography.Algorithms" version="4.2.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.Encoding" version="4.0.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.Primitives" version="4.0.0" targetFramework="net461" /> + <package id="System.Security.Cryptography.X509Certificates" version="4.1.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.Primitives.UnitTests/GitHub.Primitives.UnitTests.csproj b/test/GitHub.Primitives.UnitTests/GitHub.Primitives.UnitTests.csproj new file mode 100644 index 0000000000..9fdcc1a6b6 --- /dev/null +++ b/test/GitHub.Primitives.UnitTests/GitHub.Primitives.UnitTests.csproj @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{E687457A-BEDC-422D-8D9D-2DA58099EBBA}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.Primitives.UnitTests</RootNamespace> + <AssemblyName>GitHub.Primitives.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Xaml" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="UriStringTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.Primitives.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.Primitives.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..84857e0848 --- /dev/null +++ b/test/GitHub.Primitives.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.Primitives.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.Primitives.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e687457a-bedc-422d-8d9d-2da58099ebba")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.Primitives.UnitTests/UriStringTests.cs b/test/GitHub.Primitives.UnitTests/UriStringTests.cs new file mode 100644 index 0000000000..6258fab7bf --- /dev/null +++ b/test/GitHub.Primitives.UnitTests/UriStringTests.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using GitHub.Primitives; +using NUnit.Framework; + +public class UriStringTests +{ + public class TheConstructor : TestBaseClass + { + [TestCase("http://192.168.1.3/foo/bar.git", "192.168.1.3", "foo", "bar")] + [TestCase("http://haacked@example.com/foo/bar", "example.com", "foo", "bar")] + [TestCase("http://haacked:password@example.com/foo/bar", "example.com", "foo", "bar")] + [TestCase("http://example.com/foo/bar.git", "example.com", "foo", "bar")] + [TestCase("http://example.com/foo/bar", "example.com", "foo", "bar")] + [TestCase("http://example.com:1234/foo/bar.git", "example.com", "foo", "bar")] + [TestCase("https://github.com/github/Windows.git", "github.com", "github", "Windows")] + [TestCase("https://github.com/libgit2/libgit2.github.com", "github.com", "libgit2", "libgit2.github.com")] + [TestCase("https://github.com/github/Windows", "github.com", "github", "Windows")] + [TestCase("git@192.168.1.2:github/Windows.git", "192.168.1.2", "github", "Windows")] + [TestCase("git@github.com:github/Windows.git", "github.com", "github", "Windows")] + [TestCase("git@github.com:github/Windows", "github.com", "github", "Windows")] + [TestCase("git@example.com:org/repo.git", "example.com", "org", "repo")] + [TestCase("ssh://git@github.com:443/benstraub/libgit2", "github.com", "benstraub", "libgit2")] + [TestCase("git://git@github.com:443/benstraub/libgit2", "github.com", "benstraub", "libgit2")] + [TestCase("jane;fingerprint=9e:1a:5e:27:16:4d:2a:13:90:2c:64:41:bd:25:fd:35@foo.com:github/Windows.git", + "foo.com", "github", "Windows")] + [TestCase("https://haacked@bitbucket.org/haacked/test-mytest.git", "bitbucket.org", "haacked", "test-mytest")] + [TestCase("https://git01.codeplex.com/nuget", "git01.codeplex.com", "nuget", null, + Description = "We assume the first component is the owner")] + [TestCase("https://github.com/github/Windows.git?pr=24&branch=pr/23&filepath=relative/to/the/path.md", + "github.com", "github", "Windows")] + [TestCase("https://github.com/github/VisualStudio/blob/master/src/code.cs", "github.com", "github", "VisualStudio")] + [TestCase("https://github.com/github", "github.com", "github", null)] + [TestCase("https://github.com", "github.com", null, null)] + public void ParsesWellFormedUrlComponents(string url, string expectedHost, string owner, string repositoryName) + { + var cloneUrl = new UriString(url); + + Assert.That(cloneUrl.Host, Is.EqualTo(expectedHost)); + Assert.That(cloneUrl.Owner, Is.EqualTo(owner)); + Assert.That(cloneUrl.RepositoryName, Is.EqualTo(repositoryName)); + Assert.False(cloneUrl.IsFileUri); + } + + [TestCase(@"..\bar\foo")] + [TestCase(@"..\..\foo")] + [TestCase(@"../..\foo")] + [TestCase(@"../../foo")] + [TestCase(@"..\..\foo.git")] + [TestCase(@"c:\dev\exp\foo")] + [TestCase(@"c:\dev\bar\..\exp\foo")] + [TestCase("c:/dev/exp/foo")] + [TestCase(@"c:\dev\exp\foo.git")] + [TestCase("c:/dev/exp/foo.git")] + [TestCase("c:/dev/exp/bar/../foo.git")] + [TestCase("file:///C:/dev/exp/foo")] + [TestCase("file://C:/dev/exp/foo")] + [TestCase("file://C:/dev/exp/foo.git")] + public void ParsesLocalFileUris(string path) + { + var cloneUrl = new UriString(path); + + Assert.That("", Is.EqualTo(cloneUrl.Host)); + Assert.That("", Is.EqualTo(cloneUrl.Owner)); + Assert.That("foo", Is.EqualTo(cloneUrl.RepositoryName)); + Assert.That(cloneUrl.ToString(), Is.EqualTo(path.Replace('\\', '/'))); + Assert.True(cloneUrl.IsFileUri); + } + + [TestCase("complete garbage", "", "", null)] + [TestCase(@"..\other_folder", "", "", "other_folder")] + [TestCase("http://example.com", "example.com", null, null)] + [TestCase("http://example.com?bar", "example.com", null, null)] + [TestCase("https://example.com?bar", "example.com", null, null)] + [TestCase("ssh://git@example.com/Windows.git", "example.com", "Windows.git", null, + Description = "We assume the first component is the owner even if it ends with .git")] + [TestCase("blah@bar.com:/", "bar.com", null, null)] + [TestCase("blah@bar.com/", "bar.com", null, null)] + [TestCase("blah@bar.com", "bar.com", null, null)] + [TestCase("blah@bar.com:/Windows.git", "bar.com", null, "Windows")] + [TestCase("blah@baz.com/Windows.git", "baz.com", null, "Windows")] + [TestCase("ssh://git@github.com:github/Windows.git", "github.com", "github", "Windows")] + + // NOTE: Used by LocalRepositoryModelTests.GenerateUrl but I don't think it's a legal URL + [TestCase("git@github.com/foo/bar", "github.com", null, "foo/bar")] + public void ParsesWeirdUrlsAsWellAsPossible(string url, string expectedHost, string owner, string repositoryName) + { + var cloneUrl = new UriString(url); + + Assert.That(cloneUrl.Host, Is.EqualTo(expectedHost)); + Assert.That(cloneUrl.Owner, Is.EqualTo(owner)); + Assert.That(cloneUrl.RepositoryName, Is.EqualTo(repositoryName)); + } + + [TestCase(@"http:\\example.com/bar\baz")] + [TestCase(@"http://example.com/bar/baz")] + public void NormalizesSeparator(string uriString) + { + var path = new UriString(uriString); + new UriString("http://example.com/bar/baz").Equals(path); + } + + [Test] + public void AcceptsNullConversion() + { + Assert.That(new UriString(null), Is.Not.Null); + } + } + + public class TheNameWithOwnerProperty : TestBaseClass + { + [TestCase("http://192.168.1.3/foo/bar.git", "foo/bar")] + [TestCase("http://192.168.1.3/foo/bar", "foo/bar")] + [TestCase("http://192.168.1.3/foo/bar/baz/qux", "foo/bar")] + [TestCase("https://github.com/github/Windows.git", "github/Windows")] + [TestCase("https://github.com/github/", "github")] + [TestCase("blah@bar.com:/Windows.git", "Windows")] + [TestCase("git@github.com:github/Windows.git", "github/Windows")] + [TestCase("https://github.com/github/VisualStudio/blob/master/src/code.cs", "github/VisualStudio")] + public void DependsOnOwnerAndRepoNameNotBeingNull(string url, string expectedNameWithOwner) + { + var cloneUrl = new UriString(url); + + Assert.That(cloneUrl.NameWithOwner, Is.EqualTo(expectedNameWithOwner)); + } + } + + public class TheCombineMethod : TestBaseClass + { + [TestCase("http://example.com", "foo/bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", "foo/bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", "/foo/bar/", @"http://example.com/foo/bar/")] + [TestCase("http://example.com/foo", "bar/", @"http://example.com/foo/bar/")] + [TestCase("http://example.com", @"foo\bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", @"foo\bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", @"/foo\bar/", @"http://example.com/foo/bar/")] + [TestCase("http://example.com/foo", @"bar\", @"http://example.com/foo/bar/")] + [TestCase("http://example.com/foo?bar", @"baz", @"http://example.com/foo?bar&baz")] + [TestCase("http://example.com/foo?bar", @"&baz", @"http://example.com/foo?bar&baz")] + [TestCase("http://example.com/foo?", @"bar", @"http://example.com/foo?bar")] + [TestCase("http://example.com/foo?", @"&bar", @"http://example.com/foo?&bar")] + public void ComparesHostInsensitively(string uriString, string path, string expected) + { + Assert.That(new UriString(uriString).Combine(path), Is.EqualTo((UriString)expected)); + } + } + + public class TheIsValidUriProperty : TestBaseClass + { + [TestCase("http://example.com/", true)] + [TestCase("file:///C:/dev/exp/foo", true)] + [TestCase("garbage", false)] + [TestCase("git@192.168.1.2:github/Windows.git", false)] + public void ReturnWhetherTheUriIsParseableByUri(string uriString, bool expected) + { + Assert.That(new UriString(uriString).IsValidUri, Is.EqualTo(expected)); + } + } + + public class TheToRepositoryUrlMethod : TestBaseClass + { + [TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] + [TestCase("http://example.com/", "http://example.com/")] + [TestCase("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")] + [TestCase("https://github.com/github/Windows", "https://github.com/github/Windows")] + [TestCase("https://github.com/github/Windows.git", "https://github.com/github/Windows")] + [TestCase("https://haacked@github.com/github/Windows.git", "https://github.com/github/Windows")] + [TestCase("http://example.com:4000/github/Windows", "http://example.com:4000/github/Windows")] + [TestCase("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/github/Windows")] + [TestCase("git@example.com:org/repo.git", "https://example.com/org/repo")] + [TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")] + [TestCase("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")] + [TestCase("https://github.com/github/VisualStudio/blob/master/src/code.cs", "https://github.com/github/VisualStudio")] + public void ConvertsToWebUrl(string uriString, string expected) + { + Assert.That(new UriString(uriString).ToRepositoryUrl(), Is.EqualTo(new Uri(expected))); + } + + [TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] + [TestCase("http://example.com/", "http://example.com/")] + [TestCase("http://haacked@example.com/foo/bar", "http://example.com/baz/bar")] + [TestCase("https://github.com/github/Windows", "https://github.com/baz/Windows")] + [TestCase("https://github.com/github/Windows.git", "https://github.com/baz/Windows")] + [TestCase("https://haacked@github.com/github/Windows.git", "https://github.com/baz/Windows")] + [TestCase("http://example.com:4000/github/Windows", "http://example.com:4000/baz/Windows")] + [TestCase("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/baz/Windows")] + [TestCase("git@example.com:org/repo.git", "https://example.com/baz/repo")] + [TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/baz/cef")] + [TestCase("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/baz/encourage")] + [TestCase("https://github.com/github", "https://github.com/github")] + [TestCase("https://github.com/github/Windows", "https://github.com/github/Windows", null)] + public void ConvertsWithNewOwner(string uriString, string expected, string owner = "baz") + { + Assert.That(new UriString(uriString).ToRepositoryUrl(owner), Is.EqualTo(new Uri(expected))); + } + + [TestCase("asdf", null)] + [TestCase("", null)] + [TestCase("file:///C:/dev/exp/foo", "file:///C:/dev/exp/foo")] + [TestCase("http://example.com/", "http://example.com/")] + [TestCase("http://haacked@example.com/foo/bar", "http://example.com/foo/bar")] + [TestCase("https://github.com/github/Windows", "https://github.com/github/Windows")] + [TestCase("https://github.com/github/Windows.git", "https://github.com/github/Windows")] + [TestCase("https://haacked@github.com/github/Windows.git", "https://github.com/github/Windows")] + [TestCase("http://example.com:4000/github/Windows", "http://example.com:4000/github/Windows")] + [TestCase("git@192.168.1.2:github/Windows.git", "https://192.168.1.2/github/Windows")] + [TestCase("git@example.com:org/repo.git", "https://example.com/org/repo")] + [TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef")] + [TestCase("ssh://git@example.com:23/haacked/encourage", "https://example.com:23/haacked/encourage")] + public void ShouldNeverThrow(string url, string expected) + { + Uri uri; + Uri.TryCreate(expected, UriKind.Absolute, out uri); + Assert.That(uri, Is.EqualTo(new UriString(url).ToRepositoryUrl())); + } + } + + public class TheAdditionOperator : TestBaseClass + { + [TestCase("http://example.com", "foo/bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", "foo/bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", "/foo/bar/", @"http://example.com/foo/bar/")] + [TestCase("http://example.com/foo", "bar/", @"http://example.com/foo/bar/")] + [TestCase("http://example.com", @"foo\bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", @"foo\bar", @"http://example.com/foo/bar")] + [TestCase("http://example.com/", @"/foo\bar/", @"http://example.com/foo/bar/")] + [TestCase("http://example.com/foo", @"bar\", @"http://example.com/foo/bar/")] + [TestCase("http://example.com/foo?bar", @"baz", @"http://example.com/foo?bar&baz")] + [TestCase("http://example.com/foo?bar", @"&baz", @"http://example.com/foo?bar&baz")] + [TestCase("http://example.com/foo?", @"bar", @"http://example.com/foo?bar")] + [TestCase("http://example.com/foo?", @"&bar", @"http://example.com/foo?&bar")] + public void CombinesPaths(string uriString, string addition, string expected) + { + UriString path = uriString; + var newPath = path + addition; + Assert.That(newPath, Is.EqualTo((UriString)expected)); + } + } + + public class ImplicitConversionToString : TestBaseClass + { + [Test] + public void ConvertsBackToString() + { + var uri = new UriString("http://github.com/foo/bar/"); + string cloneUri = uri; + Assert.That("http://github.com/foo/bar/", Is.EqualTo(cloneUri)); + } + + [Test] + public void ConvertsNullToNull() + { + UriString uri = null; + Assert.That(uri, Is.Null); + string cloneUri = uri; + Assert.That(cloneUri, Is.Null); + } + } + + public class ImplicitConversionFromString : TestBaseClass + { + [Test] + public void ConvertsToCloneUri() + { + UriString cloneUri = "http://github.com/foo/bar/"; + Assert.That("github.com", Is.EqualTo(cloneUri.Host)); + } + + [Test] + public void ConvertsNullToNull() + { + UriString cloneUri = (string)null; + Assert.That(cloneUri, Is.Null); + } + } + + public class TheIsHypertextTransferProtocolProperty : TestBaseClass + { + [TestCase("http://example.com", true)] + [TestCase("HTTP://example.com", true)] + [TestCase("https://example.com", true)] + [TestCase("HTTPs://example.com", true)] + [TestCase("ftp://example.com", false)] + [TestCase("c:/example.com", false)] + [TestCase("git@github.com:github/Windows", false)] + public void IsTrueOnlyForHttpAndHttps(string url, bool expected) + { + var uri = new UriString(url); + Assert.That(uri.IsHypertextTransferProtocol, Is.EqualTo(expected)); + } + } + + public class TheEqualsMethod : TestBaseClass + { + [TestCase("https://github.com/foo/bar", "https://github.com/foo/bar", true)] + [TestCase("https://github.com/foo/bar", "https://github.com/foo/BAR", false)] + [TestCase("https://github.com/foo/bar", "https://github.com/foo/bar/", false)] + [TestCase("https://github.com/foo/bar", null, false)] + public void ReturnsTrueForCaseSensitiveEquality(string source, string compare, bool expected) + { + Assert.That(expected, Is.EqualTo(source.Equals(compare))); + Assert.That(expected, Is.EqualTo(EqualityComparer<UriString>.Default.Equals(source, compare))); + } + + [Test] + public void MakesUriStringSuitableForDictionaryKey() + { + var dictionary = new Dictionary<UriString, string> + { + { new UriString("https://github.com/foo/bar"), "whatever" } + }; + + Assert.False(dictionary.ContainsKey("https://github.com/foo/not-bar")); + Assert.True(dictionary.ContainsKey("https://github.com/foo/bar")); + Assert.True(dictionary.ContainsKey(new UriString("https://github.com/foo/bar"))); + } + } + + public class TheRepositoryUrlsAreEqualMethod + { + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo", true)] + [TestCase("https://github.com/owner/repo", "HTTPS://GITHUB.COM/OWNER/REPO", true)] + [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo", true)] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo.git", true)] + [TestCase("https://github.com/owner/repo", "https://github.com/different_owner/repo", false)] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/different_repo", false)] + [TestCase("ssh://git@github.com:443/shana/cef", "https://github.com/shana/cef", false)] + [TestCase("file://github.com/github/visualstudio", "https://github.com/github/visualstudio", false)] + [TestCase("http://github.com/github/visualstudio", "https://github.com/github/visualstudio", false, + Description = "http is different to https")] + [TestCase("ssh://git@github.com:443/shana/cef", "ssh://git@github.com:443/shana/cef", false, + Description = "The same but not a repository URL")] + public void RepositoryUrlsAreEqual(string url1, string url2, bool expectEqual) + { + var uriString1 = new UriString(url1); + var uriString2 = new UriString(url2); + + var equal = UriString.RepositoryUrlsAreEqual(uriString1, uriString2); + + Assert.That(equal, Is.EqualTo(expectEqual)); + } + } +} diff --git a/test/GitHub.Primitives.UnitTests/packages.config b/test/GitHub.Primitives.UnitTests/packages.config new file mode 100644 index 0000000000..bba46a2bca --- /dev/null +++ b/test/GitHub.Primitives.UnitTests/packages.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.TeamFoundation.UnitTests/GitHub.TeamFoundation.UnitTests.csproj b/test/GitHub.TeamFoundation.UnitTests/GitHub.TeamFoundation.UnitTests.csproj new file mode 100644 index 0000000000..2dd74a96d8 --- /dev/null +++ b/test/GitHub.TeamFoundation.UnitTests/GitHub.TeamFoundation.UnitTests.csproj @@ -0,0 +1,219 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{93778A89-3E58-4853-B772-948EBB3F17BE}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.TeamFoundation.UnitTests</RootNamespace> + <AssemblyName>GitHub.TeamFoundation.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Git.Provider, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Git.Provider.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Testing.2.2.5-custom\lib\net45\Microsoft.Reactive.Testing.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Windows.Threading, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="VSGitExtTests.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.TeamFoundation.14\GitHub.TeamFoundation.14.csproj"> + <Project>{161DBF01-1DBF-4B00-8551-C5C00F26720D}</Project> + <Name>GitHub.TeamFoundation.14</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.TeamFoundation.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.TeamFoundation.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ad7f7e7ef1 --- /dev/null +++ b/test/GitHub.TeamFoundation.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.TeamFoundation")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.TeamFoundation")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("93778a89-3e58-4853-b772-948ebb3f17be")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.TeamFoundation.UnitTests/VSGitExtTests.cs b/test/GitHub.TeamFoundation.UnitTests/VSGitExtTests.cs new file mode 100644 index 0000000000..8a2ebb4971 --- /dev/null +++ b/test/GitHub.TeamFoundation.UnitTests/VSGitExtTests.cs @@ -0,0 +1,310 @@ +using System; +using System.Linq; +using System.Threading; +using System.ComponentModel; +using System.Collections.Generic; +using GitHub.Models; +using GitHub.Services; +using GitHub.VisualStudio; +using GitHub.VisualStudio.Base; +using NUnit.Framework; +using NSubstitute; +using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Task = System.Threading.Tasks.Task; +using static Microsoft.VisualStudio.VSConstants; + +public class VSGitExtTests +{ + public class TheConstructor : TestBaseClass + { + [TestCase(true, 1)] + [TestCase(false, 0)] + public void GetServiceIGitExt_WhenRepositoryOpenIsActive(bool isActive, int expectCalls) + { + var context = CreateVSUIContext(isActive); + var sp = Substitute.For<IAsyncServiceProvider>(); + + var target = CreateVSGitExt(context, sp: sp); + + sp.Received(expectCalls).GetServiceAsync(typeof(IGitExt)); + } + + [TestCase(true, 1)] + [TestCase(false, 0)] + public void GetServiceIGitExt_WhenUIContextChanged(bool activated, int expectCalls) + { + var context = CreateVSUIContext(false); + var sp = Substitute.For<IAsyncServiceProvider>(); + var target = CreateVSGitExt(context, sp: sp); + + context.IsActive = activated; + target.JoinTillEmpty(); + + sp.Received(expectCalls).GetServiceAsync(typeof(IGitExt)); + } + + [Test] + public void ActiveRepositories_ReadUsingThreadPoolThread() + { + var gitExt = Substitute.For<IGitExt>(); + bool? threadPool = null; + gitExt.ActiveRepositories.Returns(x => + { + threadPool = Thread.CurrentThread.IsThreadPoolThread; + return new IGitRepositoryInfo[0]; + }); + + var target = CreateVSGitExt(gitExt: gitExt); + target.JoinTillEmpty(); + + Assert.That(threadPool, Is.True); + } + } + + public class TheActiveRepositoriesChangedEvent : TestBaseClass + { + [Test] + public void GitExtPropertyChangedEvent_ActiveRepositoriesChangedIsFired() + { + var context = CreateVSUIContext(true); + var gitExt = CreateGitExt(); + + var target = CreateVSGitExt(context, gitExt); + + bool wasFired = false; + target.ActiveRepositoriesChanged += () => wasFired = true; + var eventArgs = new PropertyChangedEventArgs(nameof(gitExt.ActiveRepositories)); + gitExt.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(gitExt, eventArgs); + target.JoinTillEmpty(); + + Assert.That(wasFired, Is.True); + } + + [Test] + public void ExceptionReadingActiveRepositories_StillEmptySoNoEvent() + { + var context = CreateVSUIContext(true); + var gitExt = CreateGitExt(new[] { "repoPath" }); + gitExt.ActiveRepositories.Returns(x => { throw new Exception("Boom!"); }); + + var target = CreateVSGitExt(context, gitExt); + + bool wasFired = false; + target.ActiveRepositoriesChanged += () => wasFired = true; + var eventArgs = new PropertyChangedEventArgs(nameof(gitExt.ActiveRepositories)); + gitExt.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(gitExt, eventArgs); + + Assert.That(target.ActiveRepositories, Is.Empty); + Assert.That(wasFired, Is.False); + } + + [Test] + public void WhenUIContextChanged_ActiveRepositoriesChangedIsFired() + { + var context = CreateVSUIContext(false); + var gitExt = CreateGitExt(); + var target = CreateVSGitExt(context, gitExt); + + bool wasFired = false; + target.ActiveRepositoriesChanged += () => wasFired = true; + + context.IsActive = true; + target.JoinTillEmpty(); + + Assert.That(wasFired, Is.True); + } + + [Test] + public void WhenUIContextChanged_FiredUsingThreadPoolThread() + { + var context = CreateVSUIContext(false); + var gitExt = CreateGitExt(); + var target = CreateVSGitExt(context, gitExt); + + bool? threadPool = null; + target.ActiveRepositoriesChanged += () => threadPool = Thread.CurrentThread.IsThreadPoolThread; + + context.IsActive = true; + target.JoinTillEmpty(); + + Assert.That(threadPool, Is.True); + } + } + + public class TheActiveRepositoriesProperty : TestBaseClass + { + [Test] + public void RepositoryOpenContextNotActive_IsEmpty() + { + var context = CreateVSUIContext(false); + var target = CreateVSGitExt(context); + + Assert.That(target.ActiveRepositories, Is.Empty); + } + + [Test] + public void RepositoryOpenIsActive_InitializeWithActiveRepositories() + { + var repoPath = "repoPath"; + var repoFactory = Substitute.For<ILocalRepositoryModelFactory>(); + var context = CreateVSUIContext(true); + var gitExt = CreateGitExt(new[] { repoPath }); + var target = CreateVSGitExt(context, gitExt, repoFactory: repoFactory); + target.JoinTillEmpty(); + + var activeRepositories = target.ActiveRepositories; + + Assert.That(activeRepositories.Count, Is.EqualTo(1)); + repoFactory.Received(1).Create(repoPath); + } + + [Test] + public void ExceptionRefreshingRepositories_ReturnsEmptyList() + { + var repoPath = "repoPath"; + var repoFactory = Substitute.For<ILocalRepositoryModelFactory>(); + repoFactory.Create(repoPath).ReturnsForAnyArgs(x => { throw new Exception("Boom!"); }); + var context = CreateVSUIContext(true); + var gitExt = CreateGitExt(new[] { repoPath }); + var target = CreateVSGitExt(context, gitExt, repoFactory: repoFactory); + target.JoinTillEmpty(); + + var activeRepositories = target.ActiveRepositories; + + repoFactory.Received(1).Create(repoPath); + Assert.That(activeRepositories.Count, Is.EqualTo(0)); + } + + [Test] + public async Task ActiveRepositoriesChangedOrderingShouldBeCorrectAcrossThreads() + { + var gitExt = new MockGitExt(); + var repoFactory = new MockRepositoryFactory(); + var target = CreateVSGitExt(gitExt: gitExt, repoFactory: repoFactory); + var activeRepositories1 = CreateActiveRepositories("repo1"); + var activeRepositories2 = CreateActiveRepositories("repo2"); + var task1 = Task.Run(() => gitExt.ActiveRepositories = activeRepositories1); + await Task.Delay(1); + var task2 = Task.Run(() => gitExt.ActiveRepositories = activeRepositories2); + + await Task.WhenAll(task1, task2); + target.JoinTillEmpty(); + + Assert.That(target.ActiveRepositories.Single().LocalPath, Is.EqualTo("repo2")); + } + } + + static IReadOnlyList<IGitRepositoryInfo> CreateActiveRepositories(params string[] repositoryPaths) + { + var repositories = new List<IGitRepositoryInfo>(); + foreach (var repositoryPath in repositoryPaths) + { + var repoInfo = Substitute.For<IGitRepositoryInfo>(); + repoInfo.RepositoryPath.Returns(repositoryPath); + repositories.Add(repoInfo); + } + + return repositories.AsReadOnly(); + } + + static VSGitExt CreateVSGitExt(IVSUIContext context = null, IGitExt gitExt = null, IAsyncServiceProvider sp = null, + ILocalRepositoryModelFactory repoFactory = null, JoinableTaskContext joinableTaskContext = null) + { + context = context ?? CreateVSUIContext(true); + gitExt = gitExt ?? CreateGitExt(); + sp = sp ?? Substitute.For<IAsyncServiceProvider>(); + repoFactory = repoFactory ?? Substitute.For<ILocalRepositoryModelFactory>(); + joinableTaskContext = joinableTaskContext ?? new JoinableTaskContext(); + var factory = Substitute.For<IVSUIContextFactory>(); + factory.GetUIContext(UICONTEXT.RepositoryOpen_guid).Returns(context); + sp.GetServiceAsync(typeof(IGitExt)).Returns(gitExt); + var vsGitExt = new VSGitExt(sp, factory, repoFactory, joinableTaskContext); + vsGitExt.JoinTillEmpty(); + return vsGitExt; + } + + static IGitExt CreateGitExt(params string[] repositoryPaths) + { + var gitExt = Substitute.For<IGitExt>(); + var repoList = CreateActiveRepositories(repositoryPaths); + gitExt.ActiveRepositories.Returns(repoList); + return gitExt; + } + + static MockVSUIContext CreateVSUIContext(bool isActive) + { + return new MockVSUIContext { IsActive = isActive }; + } + + class MockVSUIContext : IVSUIContext + { + bool isActive; + Action action; + + public bool IsActive + { + get { return isActive; } + set + { + isActive = value; + if (isActive && action != null) + { + action.Invoke(); + action = null; + } + } + } + + public void WhenActivated(Action action) + { + if (isActive) + { + action.Invoke(); + return; + } + + this.action = action; + } + } + + class MockGitExt : IGitExt + { + IReadOnlyList<IGitRepositoryInfo> activeRepositories = new IGitRepositoryInfo[0]; + + public IReadOnlyList<IGitRepositoryInfo> ActiveRepositories + { + get { return activeRepositories; } + set + { + if (activeRepositories != value) + { + activeRepositories = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ActiveRepositories))); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + } + + class MockRepositoryFactory : ILocalRepositoryModelFactory + { + public ILocalRepositoryModel Create(string localPath) + { + var result = Substitute.For<ILocalRepositoryModel>(); + result.LocalPath.Returns(localPath); + + if (localPath == "repo1") + { + // Trying to force #1493 here by introducing a a delay on the first + // ActiveRepositories changed notification so that the second completes + // first. + Thread.Sleep(10); + } + + return result; + } + } +} diff --git a/test/GitHub.TeamFoundation.UnitTests/packages.config b/test/GitHub.TeamFoundation.UnitTests/packages.config new file mode 100644 index 0000000000..997587c79f --- /dev/null +++ b/test/GitHub.TeamFoundation.UnitTests/packages.config @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Testing" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.UI.UnitTests/Converters.cs b/test/GitHub.UI.UnitTests/Converters.cs new file mode 100644 index 0000000000..d01f17543d --- /dev/null +++ b/test/GitHub.UI.UnitTests/Converters.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GitHub.UI; +using NUnit.Framework; +using System.Globalization; + +public class Converters +{ + [TestCase(0, 0, -23, 0, "just now")] + [TestCase(-2, 0, 0, 0, "just now")] + [TestCase(-1, 0, 0, 0, "just now")] + [TestCase(0, 0, 0, 0, "just now")] + [TestCase(1, 0, 0, 0, "1 second ago")] + [TestCase(2, 0, 0, 0, "2 seconds ago")] + [TestCase(59, 0, 0, 0, "59 seconds ago")] + [TestCase(0, 1, 0, 0, "1 minute ago")] + [TestCase(0, 2, 0, 0, "2 minutes ago")] + [TestCase(0, 59, 0, 0, "59 minutes ago")] + [TestCase(0, 60, 0, 0, "1 hour ago")] + [TestCase(0, 0, 1, 0, "1 hour ago")] + [TestCase(0, 0, 2, 0, "2 hours ago")] + [TestCase(0, 0, 23, 0, "23 hours ago")] + [TestCase(0, 0, 24, 0, "1 day ago")] + [TestCase(0, 0, 0, 1, "1 day ago")] + [TestCase(0, 0, 0, 2, "2 days ago")] + [TestCase(0, 0, 0, 29, "29 days ago")] + [TestCase(0, 0, 0, 30, "1 month ago")] + [TestCase(0, 0, 0, 59, "1 month ago")] + [TestCase(0, 0, 0, 60, "2 months ago")] + [TestCase(0, 0, 0, 364, "11 months ago")] + [TestCase(0, 0, 0, 365, "1 year ago")] + [TestCase(0, 0, 0, 365*2-1, "1 year ago")] + [TestCase(0, 0, 0, 365*2, "2 years ago")] + public void DurationToStringConversion(int sec, int min, int hou, int day, string expected) + { + var ts = new TimeSpan(day, hou, min, sec); + var conv = new DurationToStringConverter(); + var ret = (string)conv.Convert(ts, typeof(string), null, CultureInfo.CurrentCulture); + Assert.That(ret, Is.EqualTo(expected)); + } +} diff --git a/test/GitHub.UI.UnitTests/GitHub.UI.UnitTests.csproj b/test/GitHub.UI.UnitTests/GitHub.UI.UnitTests.csproj new file mode 100644 index 0000000000..e31a1fb3cc --- /dev/null +++ b/test/GitHub.UI.UnitTests/GitHub.UI.UnitTests.csproj @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{110B206F-8554-4B51-BF86-94DAA32F5E26}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.UI.UnitTests</RootNamespace> + <AssemblyName>GitHub.UI.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <TargetFrameworkProfile /> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Xaml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\AppDomainContext.cs" /> + <Compile Include="..\Helpers\ResourceDictionaryUtilities.cs" /> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="..\Helpers\Urls.cs" /> + <Compile Include="Converters.cs" /> + <Compile Include="SharedDictionaryManagerTests.cs" /> + <Compile Include="SharedDictionaryManagerIntegrationTests.cs" /> + <Compile Include="LoadingResourceDictionaryIntegrationTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="TestAutomation\ResourceValueTests.cs" /> + <Compile Include="TwoFactorInputTests.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj"> + <Project>{d1dfbb0c-b570-4302-8f1e-2e3a19c41961}</Project> + <Name>GitHub.VisualStudio.UI</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj"> + <Project>{158B05E8-FDBC-4D71-B871-C96E28D5ADF5}</Project> + <Name>GitHub.UI.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.UI\GitHub.UI.csproj"> + <Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project> + <Name>GitHub.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Page Include="Helpers\SharedDictionary.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.UI.UnitTests/Helpers/SharedDictionary.xaml b/test/GitHub.UI.UnitTests/Helpers/SharedDictionary.xaml new file mode 100644 index 0000000000..0a72b749fd --- /dev/null +++ b/test/GitHub.UI.UnitTests/Helpers/SharedDictionary.xaml @@ -0,0 +1,2 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> +</ResourceDictionary> diff --git a/test/GitHub.UI.UnitTests/LoadingResourceDictionaryIntegrationTests.cs b/test/GitHub.UI.UnitTests/LoadingResourceDictionaryIntegrationTests.cs new file mode 100644 index 0000000000..c44213450f --- /dev/null +++ b/test/GitHub.UI.UnitTests/LoadingResourceDictionaryIntegrationTests.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using System.Windows; +using NUnit.Framework; + +namespace GitHub.UI.Helpers.UnitTests +{ + public class LoadingResourceDictionaryIntegrationTests + { + public class TheSourceProperty + { + [Description("Load assembly using LoadFrom away from application base")] + [TestCase(Urls.GitHub_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + [RequiresThread(System.Threading.ApartmentState.STA)] + public void SetInLoadFromContext(string url) + { + var setup = new AppDomainSetup { ApplicationBase = "NOTHING_HERE" }; + AppDomainContext.Invoke(setup, () => + { + var target = new LoadingResourceDictionary(); + var packUri = ResourceDictionaryUtilities.ToPackUri(url); + + target.Source = packUri; + + Assert.That(target.MergedDictionaries.Count, Is.GreaterThan(0)); + }); + } + + [Description("Load assembly using LoadFrom on application base")] + [TestCase(Urls.GitHub_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + [RequiresThread(System.Threading.ApartmentState.STA)] + public void SetInLoadContext(string url) + { + var target = new LoadingResourceDictionary(); + var packUri = ResourceDictionaryUtilities.ToPackUri(url); + + target.Source = packUri; + + Assert.That(target.MergedDictionaries.Count, Is.GreaterThan(0)); + } + } + + public class TheResourceDictionarySourceProperty + { + [Description("This shows why LoadingResourceDictionary is necessary")] + [TestCase(Urls.GitHub_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + public void SetInLoadFromContext(string url) + { + var setup = new AppDomainSetup { ApplicationBase = "NOTHING_HERE" }; + using (var context = new AppDomainContext(setup)) + { + var remote = context.CreateInstance<ResourceDictionaryContext>(); + + Assert.Throws<FileNotFoundException>(() => remote.CountResourceDictionaryAndSetSource(url)); + } + } + + class ResourceDictionaryContext : MarshalByRefObject + { + internal void CountResourceDictionaryAndSetSource(string url) + { + var target = new ResourceDictionary(); + var packUri = ResourceDictionaryUtilities.ToPackUri(url); + target.Source = packUri; + } + } + } + } +} diff --git a/test/GitHub.UI.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.UI.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..67a96e0acc --- /dev/null +++ b/test/GitHub.UI.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using NUnit.Framework; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.QuickTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.QuickTests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("110b206f-8554-4b51-bf86-94daa32f5e26")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: Timeout(2 /*minutes*/ * 60 * 1000)] diff --git a/test/GitHub.UI.UnitTests/SharedDictionaryManagerIntegrationTests.cs b/test/GitHub.UI.UnitTests/SharedDictionaryManagerIntegrationTests.cs new file mode 100644 index 0000000000..780ac336bc --- /dev/null +++ b/test/GitHub.UI.UnitTests/SharedDictionaryManagerIntegrationTests.cs @@ -0,0 +1,65 @@ +using System; +using NUnit.Framework; + +namespace GitHub.UI.Helpers.UnitTests +{ + public partial class SharedDictionaryManagerIntegrationTests + { + public class TheSourceProperty + { + [TestCase(Urls.GitHub_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_UI_SharedDictionary_FileUrl, Description = "This is a design time URL")] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_FileUrl, Description = "This is a design time URL")] + [RequiresThread(System.Threading.ApartmentState.STA)] + public void SetSourceOnDifferentInstances_ExpectTheSameObjects(string url) + { + var setup = new AppDomainSetup { ApplicationBase = "NOTHING_HERE" }; + AppDomainContext.Invoke(setup, () => + { + var shared1 = new SharedDictionaryManager(); + var expectDump = ResourceDictionaryUtilities.DumpMergedDictionaries(shared1, url); + + var shared2 = new SharedDictionaryManager(); + var dump = ResourceDictionaryUtilities.DumpMergedDictionaries(shared2, url); + + Assert.That(dump, Is.EqualTo(expectDump)); + }); + } + + // This is why we need `SharedDictionaryManager`. + [TestCase(Urls.GitHub_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + [RequiresThread(System.Threading.ApartmentState.STA)] + public void SetResourceDictionarySourceOnDifferentInstances_ExpectDifferentObjects(string url) + { + var setup = new AppDomainSetup { ApplicationBase = "NOTHING_HERE" }; + AppDomainContext.Invoke(setup, () => + { + var shared = new SharedDictionaryManager(); + var expectDump = ResourceDictionaryUtilities.DumpMergedDictionaries(shared, url); + + var loading = new LoadingResourceDictionary(); + var dump = ResourceDictionaryUtilities.DumpMergedDictionaries(loading, url); + + Assert.That(dump, Is.Not.EqualTo(expectDump)); + }); + } + + class SharedDictionaryManagerContext : MarshalByRefObject + { + internal string DumpMergedDictionariesLoadingResourceDictionary(string url) + { + var target = new LoadingResourceDictionary(); + return ResourceDictionaryUtilities.DumpMergedDictionaries(target, url); + } + + internal string DumpMergedDictionariesSharedDictionaryManager(string url) + { + var target = new SharedDictionaryManager(); + return ResourceDictionaryUtilities.DumpMergedDictionaries(target, url); + } + } + } + } +} diff --git a/test/GitHub.UI.UnitTests/SharedDictionaryManagerTests.cs b/test/GitHub.UI.UnitTests/SharedDictionaryManagerTests.cs new file mode 100644 index 0000000000..a324fa2df6 --- /dev/null +++ b/test/GitHub.UI.UnitTests/SharedDictionaryManagerTests.cs @@ -0,0 +1,155 @@ +using System; +using System.IO; +using System.Windows; +using System.Reflection; +using NSubstitute; +using NUnit.Framework; + +namespace GitHub.UI.Helpers.UnitTests +{ + public class SharedDictionaryManagerTests + { + public class TheCachingFactoryClass + { + public class TheGetOrCreateResourceDictionaryMethod + { + [Test] + public void ReturnsResourceDictionary() + { + using (var factory = new SharedDictionaryManager.CachingFactory()) + { + var uri = ResourceDictionaryUtilities.ToPackUri(Urls.Test_SharedDictionary_PackUrl); + var owner = new ResourceDictionary(); + + var resourceDictionary = factory.GetOrCreateResourceDictionary(owner, uri); + + Assert.That(resourceDictionary, Is.Not.Null); + } + } + + [Test] + public void ReturnsCachedResourceDictionary() + { + using (var factory = new SharedDictionaryManager.CachingFactory()) + { + var uri = ResourceDictionaryUtilities.ToPackUri(Urls.Test_SharedDictionary_PackUrl); + var owner = new ResourceDictionary(); + + var resourceDictionary1 = factory.GetOrCreateResourceDictionary(owner, uri); + var resourceDictionary2 = factory.GetOrCreateResourceDictionary(owner, uri); + + Assert.That(resourceDictionary1, Is.EqualTo(resourceDictionary2)); + } + } + } + + public class TheDisposeMethod + { + [Test] + public void CallsDisposeOnDisposable() + { + using (var factory = new SharedDictionaryManager.CachingFactory()) + { + var disposable = Substitute.For<IDisposable>(); + factory.TryAddDisposable(disposable); + + factory.Dispose(); + + disposable.Received(1).Dispose(); + } + } + + [Test] + public void AddedTwice_DisposeCalledOnce() + { + using (var factory = new SharedDictionaryManager.CachingFactory()) + { + var disposable = Substitute.For<IDisposable>(); + factory.TryAddDisposable(disposable); + factory.TryAddDisposable(disposable); + + factory.Dispose(); + + disposable.Received(1).Dispose(); + } + } + } + } + + public class TheGetCurrentDomainCachingFactoryMethod + { + [Test] + public void CalledTwice_DisposeNotCalled() + { + using (var factory = SharedDictionaryManager.CachingFactory.GetInstanceForDomain()) + { + var disposable = Substitute.For<IDisposable>(); + factory.TryAddDisposable(disposable); + + using (SharedDictionaryManager.CachingFactory.GetInstanceForDomain()) + { + disposable.Received(0).Dispose(); + } + } + } + + [Test] + public void InvokeMethodOnNewAssembly_DisposeCalled() + { + using (var factory = SharedDictionaryManager.CachingFactory.GetInstanceForDomain()) + { + var disposable = Substitute.For<IDisposable>(); + factory.TryAddDisposable(disposable); + + using (InvokeMethodOnNewAssembly(SharedDictionaryManager.CachingFactory.GetInstanceForDomain)) + { + disposable.Received(1).Dispose(); + } + } + } + + static IDisposable InvokeMethodOnNewAssembly<T>(Func<T> func) + { + var declaringType = func.Method.DeclaringType; + var location = declaringType.Assembly.Location; + var bytes = File.ReadAllBytes(location); + var asm = Assembly.Load(bytes); + var type = asm.GetType(declaringType.FullName); + var method = type.GetMethod(func.Method.Name); + return (IDisposable)method.Invoke(null, null); + } + } + + public class TheFixDesignTimeUriMethod + { + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl, Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_SharedDictionary_FileUrl, Urls.GitHub_VisualStudio_UI_SharedDictionary_PackUrl)] + [TestCase(Urls.GitHub_VisualStudio_UI_Styles_GitHubComboBox_FileUrl, Urls.GitHub_VisualStudio_UI_Styles_GitHubComboBox_PackUrl)] + public void FixDesignTimeUri(string inUrl, string outUrl) + { + var inUri = ResourceDictionaryUtilities.ToPackUri(inUrl); + + var outUri = SharedDictionaryManager.FixDesignTimeUri(inUri); + + Assert.That(outUri.ToString(), Is.EqualTo(outUrl)); + } + } + + public class TheSourceProperty + { + [TestCase(Urls.Test_SharedDictionary_PackUrl)] + public void IsEqualToSet(string url) + { + using (SharedDictionaryManager.CachingFactory.GetInstanceForDomain()) + { + var uri = ResourceDictionaryUtilities.ToPackUri(url); + var target = new SharedDictionaryManager(); + + target.Source = uri; + + Assert.That(target.Source, Is.EqualTo(uri)); + } + } + } + } +} diff --git a/test/GitHub.UI.UnitTests/TestAutomation/ResourceValueTests.cs b/test/GitHub.UI.UnitTests/TestAutomation/ResourceValueTests.cs new file mode 100644 index 0000000000..7da7fa3468 --- /dev/null +++ b/test/GitHub.UI.UnitTests/TestAutomation/ResourceValueTests.cs @@ -0,0 +1,23 @@ +using System.Resources; +using System.Collections; +using System.Globalization; +using NUnit.Framework; + +namespace GitHub.UI.TestAutomation +{ + public class ResourceValueTest + { + [Test] + public void ValueAndNameAreTheSame() + { + ResourceSet autoIDResourceSet = AutomationIDs.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true); + foreach (DictionaryEntry entry in autoIDResourceSet) + { + var key = entry.Key.ToString(); + var value = entry.Value.ToString(); + + Assert.That(value, Is.EqualTo(key)); + } + } + } +} \ No newline at end of file diff --git a/test/GitHub.UI.UnitTests/TwoFactorInputTests.cs b/test/GitHub.UI.UnitTests/TwoFactorInputTests.cs new file mode 100644 index 0000000000..53e25df814 --- /dev/null +++ b/test/GitHub.UI.UnitTests/TwoFactorInputTests.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using GitHub.UI; +using NUnit.Framework; + +public class TwoFactorInputTests +{ + public class TheTextProperty : TestBaseClass + { + + [Test, Apartment(ApartmentState.STA)] + public void SetsTextBoxesToIndividualCharacters() + { + var twoFactorInput = new TwoFactorInput(); + var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); + + twoFactorInput.Text = "012345"; + + Assert.That("012345", Is.EqualTo(twoFactorInput.Text)); + Assert.That("0", Is.EqualTo(textBoxes[0].Text)); + Assert.That("1", Is.EqualTo(textBoxes[1].Text)); + Assert.That("2", Is.EqualTo(textBoxes[2].Text)); + Assert.That("3", Is.EqualTo(textBoxes[3].Text)); + Assert.That("4", Is.EqualTo(textBoxes[4].Text)); + Assert.That("5", Is.EqualTo(textBoxes[5].Text)); + } + + [Test, Apartment(ApartmentState.STA)] + public void IgnoresNonDigitCharacters() + { + var twoFactorInput = new TwoFactorInput(); + var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); + + twoFactorInput.Text = "01xyz2345"; + + Assert.That("012345", Is.EqualTo(twoFactorInput.Text)); + Assert.That("0", Is.EqualTo(textBoxes[0].Text)); + Assert.That("1", Is.EqualTo(textBoxes[1].Text)); + Assert.That("2", Is.EqualTo(textBoxes[2].Text)); + Assert.That("3", Is.EqualTo(textBoxes[3].Text)); + Assert.That("4", Is.EqualTo(textBoxes[4].Text)); + Assert.That("5", Is.EqualTo(textBoxes[5].Text)); + } + + [Test, Apartment(ApartmentState.STA)] + public void HandlesNotEnoughCharacters() + { + var twoFactorInput = new TwoFactorInput(); + var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); + + twoFactorInput.Text = "012"; + + Assert.That("012", Is.EqualTo(twoFactorInput.Text)); + Assert.That("0", Is.EqualTo(textBoxes[0].Text)); + Assert.That("1", Is.EqualTo(textBoxes[1].Text)); + Assert.That("2", Is.EqualTo(textBoxes[2].Text)); + Assert.That("", Is.EqualTo(textBoxes[3].Text)); + Assert.That("", Is.EqualTo(textBoxes[4].Text)); + Assert.That("", Is.EqualTo(textBoxes[5].Text)); + } + + [TestCase(null, null), Apartment(ApartmentState.STA)] + [TestCase("", "")] + [TestCase("xxxx", "")] + public void HandlesNullAndStringsWithNoDigits(string input, string expected) + { + var twoFactorInput = new TwoFactorInput(); + var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList(); + + twoFactorInput.Text = input; + + Assert.That(expected, Is.EqualTo(twoFactorInput.Text)); + Assert.That("", Is.EqualTo(textBoxes[0].Text)); + Assert.That("", Is.EqualTo(textBoxes[1].Text)); + Assert.That("", Is.EqualTo(textBoxes[2].Text)); + Assert.That("", Is.EqualTo(textBoxes[3].Text)); + Assert.That("", Is.EqualTo(textBoxes[4].Text)); + Assert.That("", Is.EqualTo(textBoxes[5].Text)); + } + + static IEnumerable<FrameworkElement> GetChildrenRecursive(FrameworkElement element) + { + yield return element; + foreach (var child in LogicalTreeHelper.GetChildren(element) + .Cast<FrameworkElement>() + .SelectMany(GetChildrenRecursive)) + { + yield return child; + } + } + } +} diff --git a/test/GitHub.UI.UnitTests/packages.config b/test/GitHub.UI.UnitTests/packages.config new file mode 100644 index 0000000000..d6681c84e9 --- /dev/null +++ b/test/GitHub.UI.UnitTests/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/GitHub.VisualStudio.UnitTests/Args.cs b/test/GitHub.VisualStudio.UnitTests/Args.cs new file mode 100644 index 0000000000..2a04705cde --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Args.cs @@ -0,0 +1,35 @@ +using System; +using GitHub.Api; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using LibGit2Sharp; +using Microsoft.VisualStudio.Text; +using NSubstitute; +using Octokit; + +internal static class Args +{ + public static bool Boolean { get { return Arg.Any<bool>(); } } + public static int Int32 { get { return Arg.Any<int>(); } } + public static string String { get { return Arg.Any<string>(); } } + public static Span Span { get { return Arg.Any<Span>(); } } + public static SnapshotPoint SnapshotPoint { get { return Arg.Any<SnapshotPoint>(); } } + public static NewRepository NewRepository { get { return Arg.Any<NewRepository>(); } } + public static IAccount Account { get { return Arg.Any<IAccount>(); } } + public static IApiClient ApiClient { get { return Arg.Any<IApiClient>(); } } + public static IServiceProvider ServiceProvider { get { return Arg.Any<IServiceProvider>(); } } + public static IAvatarProvider AvatarProvider { get { return Arg.Any<IAvatarProvider>(); } } + public static HostAddress HostAddress { get { return Arg.Any<HostAddress>(); } } + public static Uri Uri { get { return Arg.Any<Uri>(); } } + public static LibGit2Sharp.IRepository LibGit2Repo { get { return Arg.Any<LibGit2Sharp.IRepository>(); } } + public static LibGit2Sharp.Branch LibGit2Branch { get { return Arg.Any<LibGit2Sharp.Branch>(); } } + public static Remote LibgGit2Remote { get { return Arg.Any<Remote>(); } } + public static ILocalRepositoryModel LocalRepositoryModel { get { return Arg.Any<ILocalRepositoryModel>(); } } + public static IRemoteRepositoryModel RemoteRepositoryModel { get { return Arg.Any<IRemoteRepositoryModel>(); } } + public static IBranch Branch { get { return Arg.Any<IBranch>(); } } + public static IGitService GitService { get { return Arg.Any<IGitService>(); } } + public static Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> + TwoFactorChallengCallback + { get { return Arg.Any<Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>> (); } } +} diff --git a/test/GitHub.VisualStudio.UnitTests/Commands/OpenFromClipboardCommandTests.cs b/test/GitHub.VisualStudio.UnitTests/Commands/OpenFromClipboardCommandTests.cs new file mode 100644 index 0000000000..cd1f36b60e --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Commands/OpenFromClipboardCommandTests.cs @@ -0,0 +1,208 @@ +using System; +using System.Threading.Tasks; +using GitHub.Exports; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.VisualStudio.Commands; +using Microsoft.VisualStudio; +using NSubstitute; +using NUnit.Framework; + +public class OpenFromClipboardCommandTests +{ + public class TheExecuteMethod + { + [Test] + public async Task NothingInClipboard() + { + var vsServices = Substitute.For<IVSServices>(); + vsServices.ShowMessageBoxInfo(null).Returns(VSConstants.MessageBoxResult.IDOK); + var target = CreateOpenFromClipboardCommand(vsServices: vsServices, contextFromClipboard: null); + + await target.Execute(null); + + vsServices.Received(1).ShowMessageBoxInfo(OpenFromClipboardCommand.NoGitHubUrlMessage); + } + + [Test] + public async Task NoLocalRepository() + { + var context = CreateGitHubContext(); + var repositoryDir = null as string; + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(vsServices: vsServices, contextFromClipboard: context, repositoryDir: repositoryDir); + + await target.Execute(null); + + vsServices.Received(1).ShowMessageBoxInfo(OpenFromClipboardCommand.NoActiveRepositoryMessage); + } + + [Test] + public async Task UnknownLinkType() + { + var context = new GitHubContext { LinkType = LinkType.Unknown }; + var expectMessage = string.Format(OpenFromClipboardCommand.UnknownLinkTypeMessage, context.Url); + var activeRepositoryDir = "activeRepositoryDir"; + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(vsServices: vsServices, contextFromClipboard: context, repositoryDir: activeRepositoryDir); + + await target.Execute(null); + + vsServices.Received(1).ShowMessageBoxInfo(expectMessage); + } + + [TestCase("targetRepositoryName", "activeRepositoryName", OpenFromClipboardCommand.DifferentRepositoryMessage)] + [TestCase("SameRepositoryName", "SameRepositoryName", null)] + [TestCase("same_repository_name", "SAME_REPOSITORY_NAME", null)] + public async Task DifferentLocalRepository(string targetRepositoryName, string activeRepositoryName, string expectMessage) + { + var activeRepositoryDir = "activeRepositoryDir"; + var context = CreateGitHubContext(repositoryName: targetRepositoryName); + var resolveBlobResult = ("commitish", "path", "SHA"); + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(vsServices: vsServices, + contextFromClipboard: context, repositoryDir: activeRepositoryDir, repositoryName: activeRepositoryName, resolveBlobResult: resolveBlobResult); + + await target.Execute(null); + + if (expectMessage != null) + { + vsServices.Received(1).ShowMessageBoxInfo(string.Format(expectMessage, context.RepositoryName)); + } + else + { + vsServices.DidNotReceiveWithAnyArgs().ShowMessageBoxInfo(null); + } + } + + [TestCase("TargetOwner", "CurrentOwner", OpenFromClipboardCommand.NoResolveDifferentOwnerMessage)] + [TestCase("SameOwner", "SameOwner", OpenFromClipboardCommand.NoResolveSameOwnerMessage)] + [TestCase("sameowner", "SAMEOWNER", OpenFromClipboardCommand.NoResolveSameOwnerMessage)] + public async Task CouldNotResolve(string targetOwner, string currentOwner, string expectMessage) + { + var repositoryDir = "repositoryDir"; + var repositoryName = "repositoryName"; + var context = CreateGitHubContext(repositoryName: repositoryName, owner: targetOwner); + (string, string, string)? resolveBlobResult = null; + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(vsServices: vsServices, + contextFromClipboard: context, repositoryDir: repositoryDir, repositoryOwner: currentOwner, repositoryName: repositoryName, resolveBlobResult: resolveBlobResult); + + await target.Execute(null); + + vsServices.Received(1).ShowMessageBoxInfo(expectMessage); + } + + [Test] + public async Task CouldResolve() + { + var repositoryName = "repositoryName"; + var context = CreateGitHubContext(repositoryName: repositoryName); + var repositoryDir = "repositoryDir"; + var resolveBlobResult = ("master", "foo.cs", ""); + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(vsServices: vsServices, + contextFromClipboard: context, repositoryDir: repositoryDir, repositoryName: repositoryName, resolveBlobResult: resolveBlobResult); + + await target.Execute(null); + + vsServices.DidNotReceiveWithAnyArgs().ShowMessageBoxInfo(null); + } + + [Test] + public async Task NoChangesInWorkingDirectory() + { + var repositoryDir = "repositoryDir"; + var repositoryName = "repositoryName"; + var context = CreateGitHubContext(repositoryName: repositoryName); + var gitHubContextService = Substitute.For<IGitHubContextService>(); + var resolveBlobResult = ("master", "foo.cs", ""); + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(gitHubContextService: gitHubContextService, vsServices: vsServices, + contextFromClipboard: context, repositoryDir: repositoryDir, repositoryName: repositoryName, resolveBlobResult: resolveBlobResult, hasChanges: false); + + await target.Execute(null); + + vsServices.DidNotReceiveWithAnyArgs().ShowMessageBoxInfo(null); + gitHubContextService.Received(1).TryOpenFile(repositoryDir, context); + } + + [TestCase(true, null, 1, 0)] + [TestCase(false, OpenFromClipboardCommand.ChangesInWorkingDirectoryMessage, 1, 1)] + public async Task HasChangesInWorkingDirectory(bool annotateFileSupported, string message, + int receivedTryAnnotateFile, int receivedTryOpenFile) + { + var repositoryDir = "repositoryDir"; + var repositoryName = "repositoryName"; + var targetBranch = "targetBranch"; + var context = CreateGitHubContext(repositoryName: repositoryName, branch: targetBranch); + var gitHubContextService = Substitute.For<IGitHubContextService>(); + gitHubContextService.TryAnnotateFile(null, null, null).ReturnsForAnyArgs(annotateFileSupported); + var currentBranch = "currentBranch"; + var resolveBlobResult = (targetBranch, "foo.cs", ""); + var vsServices = Substitute.For<IVSServices>(); + var target = CreateOpenFromClipboardCommand(gitHubContextService: gitHubContextService, vsServices: vsServices, + contextFromClipboard: context, repositoryDir: repositoryDir, repositoryName: repositoryName, currentBranch: currentBranch, resolveBlobResult: resolveBlobResult, hasChanges: true); + + await target.Execute(null); + + if (message != null) + { + vsServices.Received(1).ShowMessageBoxInfo(message); + } + else + { + vsServices.DidNotReceiveWithAnyArgs().ShowMessageBoxInfo(null); + } + + await gitHubContextService.Received(receivedTryAnnotateFile).TryAnnotateFile(repositoryDir, currentBranch, context); + gitHubContextService.Received(receivedTryOpenFile).TryOpenFile(repositoryDir, context); + } + + static GitHubContext CreateGitHubContext(UriString uri = null, string owner = "github", + string repositoryName = "VisualStudio", string branch = "master") + { + uri = uri ?? new UriString($"https://github.com/{owner}/{repositoryName}/blob/{branch}/README.md"); + + return new GitHubContextService(null, null).FindContextFromUrl(uri); + } + + static OpenFromClipboardCommand CreateOpenFromClipboardCommand( + IGitHubContextService gitHubContextService = null, + ITeamExplorerContext teamExplorerContext = null, + IVSServices vsServices = null, + GitHubContext contextFromClipboard = null, + string repositoryDir = null, + string repositoryName = null, + string repositoryOwner = null, + string currentBranch = null, + (string, string, string)? resolveBlobResult = null, + bool? hasChanges = null) + { + var sp = Substitute.For<IServiceProvider>(); + gitHubContextService = gitHubContextService ?? Substitute.For<IGitHubContextService>(); + teamExplorerContext = teamExplorerContext ?? Substitute.For<ITeamExplorerContext>(); + vsServices = vsServices ?? Substitute.For<IVSServices>(); + + gitHubContextService.FindContextFromClipboard().Returns(contextFromClipboard); + teamExplorerContext.ActiveRepository.LocalPath.Returns(repositoryDir); + teamExplorerContext.ActiveRepository.Name.Returns(repositoryName); + teamExplorerContext.ActiveRepository.Owner.Returns(repositoryOwner); + teamExplorerContext.ActiveRepository.CurrentBranch.Name.Returns(currentBranch); + if (resolveBlobResult != null) + { + gitHubContextService.ResolveBlob(repositoryDir, contextFromClipboard).Returns(resolveBlobResult.Value); + } + + if (hasChanges != null) + { + gitHubContextService.HasChangesInWorkingDirectory(repositoryDir, resolveBlobResult.Value.Item1, resolveBlobResult.Value.Item2).Returns(hasChanges.Value); + } + + return new OpenFromClipboardCommand( + new Lazy<IGitHubContextService>(() => gitHubContextService), + new Lazy<ITeamExplorerContext>(() => teamExplorerContext), + new Lazy<IVSServices>(() => vsServices)); + } + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/GitHub.VisualStudio.UnitTests.csproj b/test/GitHub.VisualStudio.UnitTests/GitHub.VisualStudio.UnitTests.csproj new file mode 100644 index 0000000000..f4c3f87e94 --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/GitHub.VisualStudio.UnitTests.csproj @@ -0,0 +1,301 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{8B14F90B-0781-465D-AB94-19C8C56E3A94}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GitHub.VisualStudio.UnitTests</RootNamespace> + <AssemblyName>GitHub.VisualStudio.UnitTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL"> + <HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.TeamFoundation.Controls"> + <HintPath>..\..\lib\14.0\Microsoft.TeamFoundation.Controls.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Testing.2.2.5-custom\lib\net45\Microsoft.Reactive.Testing.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.ComponentModelHost, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.CoreUtility, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Editor, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Language.Intellisense, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <EmbedInteropTypes>True</EmbedInteropTypes> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Logic, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> + </Reference> + <Reference Include="NSubstitute, Version=2.0.3.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL, Version=0.0.6.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.0.6-alpha\lib\netstandard1.1\Octokit.GraphQL.dll</HintPath> + </Reference> + <Reference Include="Octokit.GraphQL.Core, Version=0.0.6.0, Culture=neutral, PublicKeyToken=0be8860aee462442, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Octokit.GraphQL.0.0.6-alpha\lib\netstandard1.1\Octokit.GraphQL.Core.dll</HintPath> + </Reference> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="rothko, Version=0.0.3.0, Culture=neutral, PublicKeyToken=9f664c41f503810a, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rothko.0.0.3-ghfvs\lib\net45\rothko.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Core" /> + <Reference Include="System.IO.Compression.FileSystem" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Core.2.2.5-custom\lib\net45\System.Reactive.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Interfaces.2.2.5-custom\lib\net45\System.Reactive.Interfaces.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-Linq.2.2.5-custom\lib\net45\System.Reactive.Linq.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-PlatformServices.2.2.5-custom\lib\net45\System.Reactive.PlatformServices.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Reactive.Windows.Threading, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> + <HintPath>..\..\packages\Rx-XAML.2.2.5-custom\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> + </Reference> + <Reference Include="System.Xaml" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Xml" /> + <Reference Include="WindowsBase" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> + <Compile Include="..\Helpers\CommandTestHelpers.cs" /> + <Compile Include="..\Helpers\LazySubstitute.cs" /> + <Compile Include="..\Helpers\ReactiveTestHelper.cs" /> + <Compile Include="..\Helpers\RepositoryHelpers.cs" /> + <Compile Include="..\Helpers\SimpleJson.cs" /> + <Compile Include="..\Helpers\TestBaseClass.cs" /> + <Compile Include="..\Helpers\TestImageCache.cs" /> + <Compile Include="..\Helpers\TestSharedCache.cs" /> + <Compile Include="Args.cs" /> + <Compile Include="Commands\OpenFromClipboardCommandTests.cs" /> + <Compile Include="Services\ConnectionManagerTests.cs" /> + <Compile Include="Services\JsonConnectionCacheTests.cs" /> + <Compile Include="Services\MetricsTests.cs" /> + <Compile Include="Services\RepositoryPublishServiceTests.cs" /> + <Compile Include="Substitutes.cs" /> + <Compile Include="TeamExplorer\Home\GraphsNavigationItemTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\GitHub.Services.Vssdk\GitHub.Services.Vssdk.csproj"> + <Project>{2D3D2834-33BE-45CA-B3CC-12F853557D7B}</Project> + <Name>GitHub.Services.Vssdk</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\akavache\Akavache\Akavache_Net45.csproj"> + <Project>{B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203}</Project> + <Name>Akavache_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\reactiveui\ReactiveUI\ReactiveUI_Net45.csproj"> + <Project>{1ce2d235-8072-4649-ba5a-cfb1af8776e0}</Project> + <Name>ReactiveUI_Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\submodules\splat\Splat\Splat-Net45.csproj"> + <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> + <Name>Splat-Net45</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Api\GitHub.Api.csproj"> + <Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project> + <Name>GitHub.Api</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.App\GitHub.App.csproj"> + <Project>{1A1DA411-8D1F-4578-80A6-04576BEA2DC5}</Project> + <Name>GitHub.App</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> + <Name>GitHub.Exports.Reactive</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.Extensions\GitHub.Extensions.csproj"> + <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> + <Name>GitHub.Extensions</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.TeamFoundation.14\GitHub.TeamFoundation.14.csproj"> + <Project>{161DBF01-1DBF-4B00-8551-C5C00F26720D}</Project> + <Name>GitHub.TeamFoundation.14</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.VisualStudio\GitHub.VisualStudio.csproj"> + <Project>{11569514-5ae5-4b5b-92a2-f10b0967de5f}</Project> + <Name>GitHub.VisualStudio</Name> + </ProjectReference> + <ProjectReference Include="..\..\src\GitHub.VisualStudio.UI\GitHub.VisualStudio.UI.csproj"> + <Project>{d1dfbb0c-b570-4302-8f1e-2e3a19c41961}</Project> + <Name>GitHub.VisualStudio.UI</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="GitHub.VisualStudio.UnitTests.dll.config"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/GitHub.VisualStudio.UnitTests/GitHub.VisualStudio.UnitTests.dll.config b/test/GitHub.VisualStudio.UnitTests/GitHub.VisualStudio.UnitTests.dll.config new file mode 100644 index 0000000000..c86d85c954 --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/GitHub.VisualStudio.UnitTests.dll.config @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <runtime> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <dependentAssembly> + <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30AD4FE6B2A6AEED" culture="neutral"/> + <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0"/> + </dependentAssembly> + </assemblyBinding> + </runtime> +</configuration> \ No newline at end of file diff --git a/test/GitHub.VisualStudio.UnitTests/Properties/AssemblyInfo.cs b/test/GitHub.VisualStudio.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6e74c893ea --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GitHub.VisualStudio.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GitHub.VisualStudio.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8b14f90b-0781-465d-ab94-19c8c56e3a94")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/GitHub.VisualStudio.UnitTests/Services/ConnectionManagerTests.cs b/test/GitHub.VisualStudio.UnitTests/Services/ConnectionManagerTests.cs new file mode 100644 index 0000000000..e5c61e6071 --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Services/ConnectionManagerTests.cs @@ -0,0 +1,272 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using GitHub.VisualStudio; +using NSubstitute; +using Octokit; +using NUnit.Framework; + +public class ConnectionManagerTests +{ + public class TheGetInitializedConnectionsMethod + { + [Test] + public async Task ReturnsValidConnections() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github", "valid"), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + var result = await target.GetLoadedConnections(); + + Assert.That(2, Is.EqualTo(result.Count)); + Assert.That("https://github.com/", Is.EqualTo(result[0].HostAddress.WebUri.ToString())); + Assert.That("https://valid.com/", Is.EqualTo(result[1].HostAddress.WebUri.ToString())); + Assert.That(result[0].ConnectionError, Is.Null); + Assert.That(result[1].ConnectionError, Is.Null); + } + + [Test] + public async Task ReturnsInvalidConnections() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github", "invalid"), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + var result = await target.GetLoadedConnections(); + + Assert.That(2, Is.EqualTo(result.Count)); + Assert.That("https://github.com/", Is.EqualTo(result[0].HostAddress.WebUri.ToString())); + Assert.That("https://invalid.com/", Is.EqualTo(result[1].HostAddress.WebUri.ToString())); + Assert.That(result[0].ConnectionError, Is.Null); + Assert.That(result[1].ConnectionError, Is.Not.Null); + } + } + + public class TheGetConnectionMethod + { + [Test] + public async Task ReturnsCorrectConnection() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github", "valid"), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + var result = await target.GetConnection(HostAddress.Create("valid.com")); + + Assert.That("https://valid.com/", Is.EqualTo(result.HostAddress.WebUri.ToString())); + } + + [Test] + public async Task ReturnsCorrectNullForNotFoundConnection() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github", "valid"), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + var result = await target.GetConnection(HostAddress.Create("another.com")); + + Assert.That(result, Is.Null); + } + } + + public class TheLoginMethod + { + [Test] + public async Task ReturnsLoggedInConnection() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache(), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + var result = await target.LogIn(HostAddress.GitHubDotComHostAddress, "user", "pass"); + + Assert.That(result, Is.Not.Null); + } + + [Test] + public async Task AddsLoggedInConnectionToConnections() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache(), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + await target.LogIn(HostAddress.GitHubDotComHostAddress, "user", "pass"); + + Assert.That(1, Is.EqualTo(target.Connections.Count)); + } + + [Test] + public void ThrowsWhenLoginFails() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache(), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + Assert.ThrowsAsync<AuthorizationException>(async () => + await target.LogIn(HostAddress.Create("invalid.com"), "user", "pass")); + } + + [Test] + public void ThrowsWhenExistingConnectionExists() + { + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github"), + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + Assert.ThrowsAsync<InvalidOperationException>(async () => + await target.LogIn(HostAddress.GitHubDotComHostAddress, "user", "pass")); + } + + [Test] + public async Task SavesConnectionToCache() + { + var cache = CreateConnectionCache(); + var target = new ConnectionManager( + CreateProgram(), + cache, + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + await target.LogIn(HostAddress.GitHubDotComHostAddress, "user", "pass"); + + await cache.Received(1).Save(Arg.Is<IEnumerable<ConnectionDetails>>(x => + x.Count() == 1 && x.ElementAt(0).HostAddress == HostAddress.Create("https://github.com"))); + } + } + + public class TheLogOutMethod + { + [Test] + public async Task CallsLoginManagerLogOut() + { + var loginManager = CreateLoginManager(); + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github"), + Substitute.For<IKeychain>(), + loginManager, + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + await target.LogOut(HostAddress.GitHubDotComHostAddress); + + await loginManager.Received().Logout( + HostAddress.GitHubDotComHostAddress, + Arg.Any<IGitHubClient>()); + } + + [Test] + public async Task RemovesConnectionFromConnections() + { + var loginManager = CreateLoginManager(); + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("github"), + Substitute.For<IKeychain>(), + loginManager, + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + await target.LogOut(HostAddress.GitHubDotComHostAddress); + + Assert.That(target.Connections, Is.Empty); + } + + [Test] + public void ThrowsIfConnectionDoesntExist() + { + var loginManager = CreateLoginManager(); + var target = new ConnectionManager( + CreateProgram(), + CreateConnectionCache("valid"), + Substitute.For<IKeychain>(), + loginManager, + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + Assert.ThrowsAsync<KeyNotFoundException>(async () => + await target.LogOut(HostAddress.GitHubDotComHostAddress)); + } + + [Test] + public async Task RemovesConnectionFromCache() + { + var cache = CreateConnectionCache("github"); + var target = new ConnectionManager( + CreateProgram(), + cache, + Substitute.For<IKeychain>(), + CreateLoginManager(), + Substitute.For<IUsageTracker>(), + Substitute.For<IVisualStudioBrowser>()); + + await target.LogOut(HostAddress.GitHubDotComHostAddress); + + await cache.Received(1).Save(Arg.Is<IEnumerable<ConnectionDetails>>(x => x.Count() == 0)); + } + } + + static IProgram CreateProgram() + { + var result = Substitute.For<IProgram>(); + result.ProductHeader.Returns(new ProductHeaderValue("foo")); + return result; + } + + static IConnectionCache CreateConnectionCache(params string[] hosts) + { + var result = Substitute.For<IConnectionCache>(); + var details = hosts.Select(x => new ConnectionDetails( + HostAddress.Create($"https://{x}.com"), + "user")); + result.Load().Returns(details.ToList()); + return result; + } + + static ILoginManager CreateLoginManager() + { + var result = Substitute.For<ILoginManager>(); + result.Login(HostAddress.Create("invalid.com"), Arg.Any<IGitHubClient>(), Arg.Any<string>(), Arg.Any<string>()) + .Returns<User>(_ => { throw new AuthorizationException(); }); + result.LoginFromCache(null, null) + .ReturnsForAnyArgs(new User()); + result.LoginFromCache(HostAddress.Create("invalid.com"), Arg.Any<IGitHubClient>()) + .Returns<User>(_ => { throw new AuthorizationException(); }); + return result; + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/Services/JsonConnectionCacheTests.cs b/test/GitHub.VisualStudio.UnitTests/Services/JsonConnectionCacheTests.cs new file mode 100644 index 0000000000..fb3d28a118 --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Services/JsonConnectionCacheTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.VisualStudio; +using NSubstitute; +using Rothko; +using NUnit.Framework; + +public class JsonConnectionCacheTests +{ + public class TheLoadMethod : TestBaseClass + { + [Test] + public async Task IsLoadedFromCache() + { + const string cacheData = @"{""connections"":[{""HostUrl"":""https://github.com"", ""UserName"":""shana""},{""HostUrl"":""https://github.enterprise"", ""UserName"":""haacked""}]}"; + var program = Substitute.For<IProgram>(); + program.ApplicationName.Returns("GHfVS"); + var operatingSystem = Substitute.For<IOperatingSystem>(); + operatingSystem.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) + .Returns(@"c:\fake"); + operatingSystem.File.Exists(@"c:\fake\GHfVS\ghfvs.connections").Returns(true); + operatingSystem.File.ReadAllText(@"c:\fake\GHfVS\ghfvs.connections", Encoding.UTF8).Returns(cacheData); + var cache = new JsonConnectionCache(program, operatingSystem); + + var connections = (await cache.Load()).ToList(); + Assert.That(2, Is.EqualTo(connections.Count)); + Assert.That(new Uri("https://api.github.com"), Is.EqualTo(connections[0].HostAddress.ApiUri)); + Assert.That(new Uri("https://github.enterprise/api/v3/"), Is.EqualTo(connections[1].HostAddress.ApiUri)); + } + + [TestCase("|!This ain't no JSON what even is this?")] + [TestCase(null)] + [TestCase("")] + [TestCase("{}")] + [TestCase(@"{""connections"":null}")] + [TestCase(@"{""connections"":{}}")] + public async Task IsEmptyWhenCacheCorrupt(string cacheJson) + { + var program = Substitute.For<IProgram>(); + program.ApplicationName.Returns("GHfVS"); + var operatingSystem = Substitute.For<IOperatingSystem>(); + operatingSystem.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) + .Returns(@"c:\fake"); + operatingSystem.File.Exists(@"c:\fake\GHfVS\ghfvs.connections").Returns(true); + operatingSystem.File.ReadAllText(@"c:\fake\GHfVS\ghfvs.connections", Encoding.UTF8).Returns(cacheJson); + var cache = new JsonConnectionCache(program, operatingSystem); + + var connections = (await cache.Load()).ToList(); + + Assert.That(connections, Is.Empty); + operatingSystem.File.Received().Delete(@"c:\fake\GHfVS\ghfvs.connections"); + } + + [Test] + public async Task IsSavedToDisk() + { + var program = Substitute.For<IProgram>(); + program.ApplicationName.Returns("GHfVS"); + var operatingSystem = Substitute.For<IOperatingSystem>(); + operatingSystem.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) + .Returns(@"c:\fake"); + operatingSystem.File.Exists(@"c:\fake\GHfVS\ghfvs.connections").Returns(true); + operatingSystem.File.ReadAllText(@"c:\fake\GHfVS\ghfvs.connections", Encoding.UTF8).Returns(""); + var cache = new JsonConnectionCache(program, operatingSystem); + var connections = (await cache.Load()).ToList(); + + connections.Add(new ConnectionDetails(HostAddress.GitHubDotComHostAddress, "coolio")); + await cache.Save(connections); + + operatingSystem.File.Received().WriteAllText(@"c:\fake\GHfVS\ghfvs.connections", + @"{""connections"":[{""HostUrl"":""https://github.com/"",""UserName"":""coolio""}]}"); + } + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/Services/MetricsTests.cs b/test/GitHub.VisualStudio.UnitTests/Services/MetricsTests.cs new file mode 100644 index 0000000000..a311dbc3ac --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Services/MetricsTests.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using GitHub; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using GitHub.Settings; +using NSubstitute; +using NUnit.Framework; +using Rothko; +using Environment = System.Environment; + +namespace MetricsTests +{ + public class UsageTrackerTests : TestBaseClass + { + [Test] + public void ShouldStartTimer() + { + var service = Substitute.For<IUsageService>(); + var target = new UsageTracker(CreateServiceProvider(), service, CreatePackageSettings()); + + service.Received(1).StartTimer(Arg.Any<Func<Task>>(), TimeSpan.FromMinutes(3), TimeSpan.FromDays(1)); + } + + [Test] + public async Task FirstTickShouldIncrementLaunchCount() + { + var service = CreateUsageService(UsageModel.Create(Guid.NewGuid())); + var targetAndTick = CreateTargetAndGetTick(CreateServiceProvider(), service); + + await targetAndTick.Item2(); + + await service.Received(1).WriteLocalData(Arg.Any<UsageData>()); + } + + [Test] + public async Task SubsequentTickShouldNotIncrementLaunchCount() + { + var service = CreateUsageService(UsageModel.Create(Guid.NewGuid())); + var targetAndTick = CreateTargetAndGetTick(CreateServiceProvider(), service); + + await targetAndTick.Item2(); + service.ClearReceivedCalls(); + await targetAndTick.Item2(); + + await service.DidNotReceiveWithAnyArgs().WriteLocalData(null); + } + + [Test] + public async Task ShouldDisposeTimerIfMetricsServiceNotFound() + { + var service = CreateUsageService(UsageModel.Create(Guid.NewGuid())); + var disposed = false; + var disposable = Disposable.Create(() => disposed = true); + service.StartTimer(null, new TimeSpan(), new TimeSpan()).ReturnsForAnyArgs(disposable); + + var targetAndTick = CreateTargetAndGetTick( + CreateServiceProvider(hasMetricsService: false), + service); + + await targetAndTick.Item2(); + + Assert.True(disposed); + } + + [Test] + public async Task TickShouldNotSendDataIfSameDay() + { + var serviceProvider = CreateServiceProvider(); + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService(UsageModel.Create(Guid.NewGuid()))); + + await targetAndTick.Item2(); + + var metricsService = serviceProvider.TryGetService<IMetricsService>(); + await metricsService.DidNotReceive().PostUsage(Arg.Any<UsageModel>()); + } + + [Test] + public async Task TickShouldSendDataIfDifferentDay() + { + var model = UsageModel.Create(Guid.NewGuid()); + model.Dimensions.Date = DateTimeOffset.Now.AddDays(-2); + + var serviceProvider = CreateServiceProvider(); + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService(model)); + + await targetAndTick.Item2(); + + var metricsService = serviceProvider.TryGetService<IMetricsService>(); + await metricsService.Received(1).PostUsage(Arg.Any<UsageModel>()); + } + + [Test] + public async Task ShouldIncrementCounter() + { + var model = UsageModel.Create(Guid.NewGuid()); + model.Measures.NumberOfClones = 4; + var usageService = CreateUsageService(model); + var target = new UsageTracker( + CreateServiceProvider(), + usageService, + CreatePackageSettings()); + + await target.IncrementCounter(x => x.NumberOfClones); + UsageData result = usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData; + + Assert.AreEqual(5, result.Reports[0].Measures.NumberOfClones); + } + + [Test] + public async Task ShouldWriteData() + { + var service = CreateUsageService(); + + var target = new UsageTracker( + CreateServiceProvider(), + service, + CreatePackageSettings()); + + await target.IncrementCounter(x => x.NumberOfClones); + await service.Received(1).WriteLocalData(Arg.Is<UsageData>(data => + data.Reports.Count == 1 && + data.Reports[0].Dimensions.Date.Date == DateTimeOffset.Now.Date && + //data.Reports[0].Dimensions.AppVersion == AssemblyVersionInformation.Version && + data.Reports[0].Dimensions.Lang == CultureInfo.InstalledUICulture.IetfLanguageTag && + data.Reports[0].Dimensions.CurrentLang == CultureInfo.CurrentCulture.IetfLanguageTag && + data.Reports[0].Measures.NumberOfClones == 1 + )); + } + + [Test] + public async Task ShouldWriteUpdatedData() + { + var model = UsageModel.Create(Guid.NewGuid()); + //model.Dimensions.AppVersion = AssemblyVersionInformation.Version; + model.Dimensions.Lang = CultureInfo.InstalledUICulture.IetfLanguageTag; + model.Dimensions.CurrentLang = CultureInfo.CurrentCulture.IetfLanguageTag; + model.Measures.NumberOfClones = 1; + var service = CreateUsageService(model); + + var target = new UsageTracker( + CreateServiceProvider(), + service, + CreatePackageSettings()); + + await target.IncrementCounter(x => x.NumberOfClones); + await service.Received(1).WriteLocalData(Arg.Is<UsageData>(data => + data.Reports.Count == 1 && + data.Reports[0].Dimensions.Date.Date == DateTimeOffset.Now.Date && + //data.Reports[0].Dimensions.AppVersion == AssemblyVersionInformation.Version && + data.Reports[0].Dimensions.Lang == CultureInfo.InstalledUICulture.IetfLanguageTag && + data.Reports[0].Dimensions.CurrentLang == CultureInfo.CurrentCulture.IetfLanguageTag && + data.Reports[0].Measures.NumberOfClones == 2 + )); + } + + static Tuple<UsageTracker, Func<Task>> CreateTargetAndGetTick( + IGitHubServiceProvider serviceProvider, + IUsageService service) + { + Func<Task> tick = null; + + service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) + .Do(x => tick = x.ArgAt<Func<Task>>(0)); + + var target = new UsageTracker(serviceProvider, service, CreatePackageSettings()); + + return Tuple.Create(target, tick); + } + + static IGitHubServiceProvider CreateServiceProvider(bool hasMetricsService = true) + { + var result = Substitute.For<IGitHubServiceProvider>(); + var connectionManager = Substitute.For<IConnectionManager>(); + var metricsService = Substitute.For<IMetricsService>(); + var vsservices = Substitute.For<IVSServices>(); + + connectionManager.Connections.Returns(new ObservableCollectionEx<IConnection>()); + + result.GetService<IConnectionManager>().Returns(connectionManager); + result.GetService<IVSServices>().Returns(vsservices); + result.TryGetService<IMetricsService>().Returns(hasMetricsService ? metricsService : null); + + return result; + } + + static IPackageSettings CreatePackageSettings() + { + var result = Substitute.For<IPackageSettings>(); + result.CollectMetrics.Returns(true); + return result; + } + + static IUsageService CreateUsageService( + UsageModel model = null) + { + return CreateUsageService(new UsageData + { + Reports = model != null ? new List<UsageModel>{ model } : new List<UsageModel>() + }); + } + + static IUsageService CreateUsageService(UsageData data) + { + var result = Substitute.For<IUsageService>(); + result.ReadLocalData().Returns(data); + return result; + } + } + + public class UsageServiceTests : TestBaseClass + { + static readonly Guid UserGuid = Guid.NewGuid(); + static readonly string DefaultUserStoreContent = @"{""UserGuid"":""" + UserGuid + @"""}"; + + static readonly string DefaultUsageContent = @"{""Reports"":[{""Dimensions"":{""Guid"":""26fa0c25-653f-4fa5-ad83-7438ad526b0a"",""Date"":""2018-03-13T18:45:19.0453424Z"",""IsGitHubUser"":false,""IsEnterpriseUser"":false,""AppVersion"":null,""VSVersion"":null,""Lang"":null,""CurrentLang"":null},""Measures"":{""NumberOfStartups"":0,""NumberOfUpstreamPullRequests"":0,""NumberOfClones"":1,""NumberOfReposCreated"":0,""NumberOfReposPublished"":2,""NumberOfGists"":0,""NumberOfOpenInGitHub"":0,""NumberOfLinkToGitHub"":0,""NumberOfLogins"":0,""NumberOfOAuthLogins"":0,""NumberOfTokenLogins"":0,""NumberOfPullRequestsOpened"":3,""NumberOfLocalPullRequestsCheckedOut"":0,""NumberOfLocalPullRequestPulls"":0,""NumberOfLocalPullRequestPushes"":0,""NumberOfForkPullRequestsCheckedOut"":0,""NumberOfForkPullRequestPulls"":0,""NumberOfForkPullRequestPushes"":0,""NumberOfSyncSubmodules"":0,""NumberOfWelcomeDocsClicks"":0,""NumberOfWelcomeTrainingClicks"":0,""NumberOfGitHubPaneHelpClicks"":0,""NumberOfPRDetailsViewChanges"":0,""NumberOfPRDetailsViewFile"":0,""NumberOfPRDetailsCompareWithSolution"":0,""NumberOfPRDetailsOpenFileInSolution"":0,""NumberOfPRDetailsNavigateToEditor"":0,""NumberOfPRReviewDiffViewInlineCommentOpen"":0,""NumberOfPRReviewDiffViewInlineCommentPost"":0,""NumberOfShowCurrentPullRequest"":0}}]}"; + + string usageFileName; + string userFileName; + string localApplicationDataPath; + IEnvironment environment; + + [SetUp] + public void SetUp() + { + localApplicationDataPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + if (File.Exists(localApplicationDataPath)) + { + File.Delete(localApplicationDataPath); + } + + if (Directory.Exists(localApplicationDataPath)) + { + Directory.Delete(localApplicationDataPath); + } + + Directory.CreateDirectory(localApplicationDataPath); + + usageFileName = Path.Combine(localApplicationDataPath, "metrics.json"); + userFileName = Path.Combine(localApplicationDataPath, "user.json"); + + environment = Substitute.For<IEnvironment>(); + environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + .Returns(localApplicationDataPath); + + WriteUsageFileContent(DefaultUsageContent); + WriteUserFileContent(DefaultUserStoreContent); + } + + void WriteUsageFileContent(string content) + { + File.WriteAllText(usageFileName, content); + } + + void WriteUserFileContent(string content) + { + File.WriteAllText(userFileName, content); + } + + [Test] + public async Task GetUserGuidWorks() + { + var usageService = new UsageService(Substitute.For<IGitHubServiceProvider>(), environment); + var guid = await usageService.GetUserGuid(); + Assert.IsTrue(guid.Equals(UserGuid)); + } + + [Test] + public async Task GetUserGuidWorksWhenFileMissing() + { + File.Delete(userFileName); + + var usageService = new UsageService(Substitute.For<IGitHubServiceProvider>(), environment); + var guid = await usageService.GetUserGuid(); + Assert.AreNotEqual(guid, Guid.Empty); + } + + [Test] + public async Task ReadUsageDataWorks() + { + var usageService = new UsageService(Substitute.For<IGitHubServiceProvider>(), environment); + var usageData = await usageService.ReadLocalData(); + + Assert.IsNotNull(usageData); + Assert.IsNotNull(usageData.Reports); + Assert.AreEqual(1, usageData.Reports.Count); + Assert.AreEqual(1, usageData.Reports[0].Measures.NumberOfClones); + Assert.AreEqual(2, usageData.Reports[0].Measures.NumberOfReposPublished); + Assert.AreEqual(3, usageData.Reports[0].Measures.NumberOfPullRequestsOpened); + } + + [Test] + public async Task ReadUsageDataWorksWhenFileMissing() + { + File.Delete(usageFileName); + + var usageService = new UsageService(Substitute.For<IGitHubServiceProvider>(), environment); + var usageData = await usageService.ReadLocalData(); + + Assert.IsNotNull(usageData); + Assert.IsNotNull(usageData.Reports); + Assert.AreEqual(0, usageData.Reports.Count); + } + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/Services/RepositoryPublishServiceTests.cs b/test/GitHub.VisualStudio.UnitTests/Services/RepositoryPublishServiceTests.cs new file mode 100644 index 0000000000..e121d04433 --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Services/RepositoryPublishServiceTests.cs @@ -0,0 +1,35 @@ +using System; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Models; +using GitHub.Services; +using Microsoft.VisualStudio.Shell.Interop; +using NSubstitute; +using NUnit.Framework; + +public class RepositoryPublishServiceTests +{ + public class ThePublishMethod : TestBaseClass + { + [Test] + public async Task CreatesRepositoryAndPushesLocalToIt() + { + var solution = Substitute.For<IVsSolution>(); + var gitClient = Substitute.For<IGitClient>(); + //var service = new RepositoryPublishService(gitClient, Substitutes.IVSGitServices); + var service = new RepositoryPublishService(gitClient, Substitute.For<IVSGitServices>()); + var newRepository = new Octokit.NewRepository("test"); + var account = Substitute.For<IAccount>(); + account.Login.Returns("monalisa"); + account.IsUser.Returns(true); + var gitHubRepository = CreateRepository(account.Login, newRepository.Name); + var apiClient = Substitute.For<IApiClient>(); + apiClient.CreateRepository(newRepository, "monalisa", true).Returns(Observable.Return(gitHubRepository)); + + var repository = await service.PublishRepository(newRepository, account, apiClient); + + Assert.That("https://github.com/monalisa/test", Is.EqualTo(repository.CloneUrl)); + } + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/Substitutes.cs b/test/GitHub.VisualStudio.UnitTests/Substitutes.cs new file mode 100644 index 0000000000..9792829b8c --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/Substitutes.cs @@ -0,0 +1,203 @@ +using GitHub.Authentication; +using GitHub.Models; +using GitHub.Services; +using GitHub.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using NSubstitute; +using Rothko; +using System; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using GitHub.Factories; + +namespace UnitTests +{ + internal static class Substitutes + { + public static T1 For<T1, T2, T3, T4>(params object[] constructorArguments) + where T1 : class + where T2 : class + where T3 : class + where T4 : class + { + return (T1)Substitute.For(new Type[4] + { + typeof (T1), + typeof (T2), + typeof (T3), + typeof (T4) + }, constructorArguments); + } + + + // public static IGitRepositoriesExt IGitRepositoriesExt { get { return Substitute.For<IGitRepositoriesExt>(); } } + public static IGitService IGitService { get { return Substitute.For<IGitService>(); } } + + public static IVSGitServices IVSGitServices + { + get + { + var ret = Substitute.For<IVSGitServices>(); + ret.GetLocalClonePathFromGitProvider().Returns(@"c:\foo\bar"); + return ret; + } + } + + public static IOperatingSystem OperatingSystem + { + get + { + var ret = Substitute.For<IOperatingSystem>(); + // this expansion happens when the GetLocalClonePathFromGitProvider call is setup by default + // see IVSServices property above + ret.Environment.ExpandEnvironmentVariables(Args.String).Returns(x => x[0]); + return ret; + } + } + + public static IViewViewModelFactory ViewViewModelFactory { get { return Substitute.For<IViewViewModelFactory>(); } } + + public static IRepositoryCreationService RepositoryCreationService { get { return Substitute.For<IRepositoryCreationService>(); } } + public static IRepositoryCloneService RepositoryCloneService { get { return Substitute.For<IRepositoryCloneService>(); } } + + public static IConnection Connection { get { return Substitute.For<IConnection>(); } } + public static IConnectionManager ConnectionManager { get { return Substitute.For<IConnectionManager>(); } } + public static IDelegatingTwoFactorChallengeHandler TwoFactorChallengeHandler { get { return Substitute.For<IDelegatingTwoFactorChallengeHandler>(); } } + public static IGistPublishService GistPublishService { get { return Substitute.For<IGistPublishService>(); } } + public static IPullRequestService PullRequestService { get { return Substitute.For<IPullRequestService>(); } } + + /// <summary> + /// This returns a service provider with everything mocked except for + /// RepositoryCloneService and RepositoryCreationService, which are real + /// instances. + /// </summary> + public static IGitHubServiceProvider ServiceProvider { get { return GetServiceProvider(); } } + + /// <summary> + /// This returns a service provider with mocked IRepositoryCreationService and + /// IRepositoryCloneService as well as all other services mocked. The regular + /// GetServiceProvider method (and ServiceProvider property return a IServiceProvider + /// with real RepositoryCloneService and RepositoryCreationService instances. + /// </summary> + /// <returns></returns> + public static IServiceProvider GetFullyMockedServiceProvider() + { + return GetServiceProvider(RepositoryCloneService, RepositoryCreationService); + } + + /// <summary> + /// This returns a service provider with everything mocked except for + /// RepositoryCloneService and RepositoryCreationService, which are real + /// instances. + /// </summary> + /// <param name="cloneService"></param> + /// <param name="creationService"></param> + /// <returns></returns> + public static IGitHubServiceProvider GetServiceProvider( + IRepositoryCloneService cloneService = null, + IRepositoryCreationService creationService = null, + IAvatarProvider avatarProvider = null) + { + var ret = Substitute.For<IGitHubServiceProvider, IServiceProvider>(); + + var gitservice = IGitService; + var cm = Substitute.For<SComponentModel, IComponentModel>(); + var cc = new CompositionContainer(CompositionOptions.IsThreadSafe | CompositionOptions.DisableSilentRejection); + cc.ComposeExportedValue(gitservice); + ((IComponentModel)cm).DefaultExportProvider.Returns(cc); + ret.GetService(typeof(SComponentModel)).Returns(cm); + Services.UnitTestServiceProvider = ret; + + var os = OperatingSystem; + var vsgit = IVSGitServices; + var clone = cloneService ?? new RepositoryCloneService(os, vsgit, Substitute.For<IUsageTracker>()); + var create = creationService ?? new RepositoryCreationService(clone); + avatarProvider = avatarProvider ?? Substitute.For<IAvatarProvider>(); + //ret.GetService(typeof(IGitRepositoriesExt)).Returns(IGitRepositoriesExt); + ret.GetService(typeof(IGitService)).Returns(gitservice); + ret.GetService(typeof(IVSServices)).Returns(Substitute.For<IVSServices>()); + ret.GetService(typeof(IVSGitServices)).Returns(vsgit); + ret.GetService(typeof(IOperatingSystem)).Returns(os); + ret.GetService(typeof(IRepositoryCloneService)).Returns(clone); + ret.GetService(typeof(IRepositoryCreationService)).Returns(create); + ret.GetService(typeof(IViewViewModelFactory)).Returns(ViewViewModelFactory); + ret.GetService(typeof(IConnection)).Returns(Connection); + ret.GetService(typeof(IConnectionManager)).Returns(ConnectionManager); + ret.GetService(typeof(IAvatarProvider)).Returns(avatarProvider); + ret.GetService(typeof(IDelegatingTwoFactorChallengeHandler)).Returns(TwoFactorChallengeHandler); + ret.GetService(typeof(IGistPublishService)).Returns(GistPublishService); + ret.GetService(typeof(IPullRequestService)).Returns(PullRequestService); + return ret; + } + + //public static IGitRepositoriesExt GetGitExt(this IServiceProvider provider) + //{ + // return provider.GetService(typeof(IGitRepositoriesExt)) as IGitRepositoriesExt; + //} + + public static IVSServices GetVSServices(this IServiceProvider provider) + { + return provider.GetService(typeof(IVSServices)) as IVSServices; + } + + public static IVSGitServices GetVSGitServices(this IServiceProvider provider) + { + return provider.GetService(typeof(IVSGitServices)) as IVSGitServices; + } + + public static IGitService GetGitService(this IServiceProvider provider) + { + return provider.GetService(typeof(IGitService)) as IGitService; + } + + public static IOperatingSystem GetOperatingSystem(this IServiceProvider provider) + { + return provider.GetService(typeof(IOperatingSystem)) as IOperatingSystem; + } + + public static IRepositoryCloneService GetRepositoryCloneService(this IServiceProvider provider) + { + return provider.GetService(typeof(IRepositoryCloneService)) as IRepositoryCloneService; + } + + public static IRepositoryCreationService GetRepositoryCreationService(this IServiceProvider provider) + { + return provider.GetService(typeof(IRepositoryCreationService)) as IRepositoryCreationService; + } + + public static IViewViewModelFactory GetExportFactoryProvider(this IServiceProvider provider) + { + return provider.GetService(typeof(IViewViewModelFactory)) as IViewViewModelFactory; + } + + public static IConnection GetConnection(this IServiceProvider provider) + { + return provider.GetService(typeof(IConnection)) as IConnection; + } + + public static IConnectionManager GetConnectionManager(this IServiceProvider provider) + { + return provider.GetService(typeof(IConnectionManager)) as IConnectionManager; + } + + public static IAvatarProvider GetAvatarProvider(this IServiceProvider provider) + { + return provider.GetService(typeof(IAvatarProvider)) as IAvatarProvider; + } + + public static IDelegatingTwoFactorChallengeHandler GetTwoFactorChallengeHandler(this IServiceProvider provider) + { + return provider.GetService(typeof(IDelegatingTwoFactorChallengeHandler)) as IDelegatingTwoFactorChallengeHandler; + } + + public static IGistPublishService GetGistPublishService(this IServiceProvider provider) + { + return provider.GetService(typeof(IGistPublishService)) as IGistPublishService; + } + + public static IPullRequestService GetPullRequestsService(this IServiceProvider provider) + { + return provider.GetService(typeof(IPullRequestService)) as IPullRequestService; + } + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/TeamExplorer/Home/GraphsNavigationItemTests.cs b/test/GitHub.VisualStudio.UnitTests/TeamExplorer/Home/GraphsNavigationItemTests.cs new file mode 100644 index 0000000000..f67c9c6e56 --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/TeamExplorer/Home/GraphsNavigationItemTests.cs @@ -0,0 +1,34 @@ +using System; +using GitHub.Api; +using GitHub.Services; +using GitHub.VisualStudio.TeamExplorer.Home; +using NSubstitute; +using NUnit.Framework; +using UnitTests; + +public class GraphsNavigationItemTests +{ + public class TheExecuteMethod : TestBaseClass + { + [TestCase("https://github.com/foo/bar.git", "https://github.com/foo/bar/graphs")] + [TestCase("https://haacked@github.com/foo/bar.git", "https://github.com/foo/bar/graphs")] + [TestCase("https://github.com/foo/bar", "https://github.com/foo/bar/graphs")] + [TestCase("https://github.com/foo/bar/", "https://github.com/foo/bar/graphs")] + [Ignore("Needs fixing with new TeamFoundation split assemblies")] + public void BrowsesToTheCorrectURL(string origin, string expectedUrl) + { + var apiFactory = Substitute.For<ISimpleApiClientFactory>(); + var browser = Substitute.For<IVisualStudioBrowser>(); + var lazyBrowser = new Lazy<IVisualStudioBrowser>(() => browser); + var holder = Substitute.For<ITeamExplorerServiceHolder>(); + var graphsNavigationItem = new GraphsNavigationItem(Substitutes.ServiceProvider, apiFactory, lazyBrowser, holder) + { + ActiveRepoUri = origin + }; + + graphsNavigationItem.Execute(); + + browser.Received().OpenUrl(new Uri(expectedUrl)); + } + } +} diff --git a/test/GitHub.VisualStudio.UnitTests/packages.config b/test/GitHub.VisualStudio.UnitTests/packages.config new file mode 100644 index 0000000000..fac77cd62c --- /dev/null +++ b/test/GitHub.VisualStudio.UnitTests/packages.config @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" /> + <package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.CoreUtility" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Editor" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Language.Intellisense" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.11.0" version="11.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.12.0" version="12.0.21003" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Immutable.14.0" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.10.0" version="10.0.30319" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.11.0" version="11.0.61030" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.12.0" version="12.0.30110" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shell.Interop.9.0" version="9.0.30729" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Data" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.Logic" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="14.3.25407" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Threading" version="14.1.131" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Validation" version="14.1.111" targetFramework="net461" /> + <package id="NSubstitute" version="2.0.3" targetFramework="net461" /> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-Testing" version="2.2.5-custom" targetFramework="net45" /> + <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net45" /> + <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/Helpers/AppDomainContext.cs b/test/Helpers/AppDomainContext.cs new file mode 100644 index 0000000000..4fdc9273e2 --- /dev/null +++ b/test/Helpers/AppDomainContext.cs @@ -0,0 +1,76 @@ +using System; +using System.Reflection; +using System.Collections.Generic; + +namespace GitHub.UI.Helpers.UnitTests +{ + public class AppDomainContext : IDisposable + { + AppDomain domain; + + public static void Invoke(AppDomainSetup setup, Action remoteAction) + { + using (var context = new AppDomainContext(setup)) + { + context.Invoke(remoteAction); + } + } + + public AppDomainContext(AppDomainSetup setup = null) + { + if (setup == null) + { + setup = new AppDomainSetup(); + setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; // don't use the process base dir + } + + var friendlyName = GetType().FullName; + domain = AppDomain.CreateDomain(friendlyName, null, setup); + } + + public void Dispose() + { + AppDomain.Unload(domain); + } + + public void Invoke(Action remoteAction) + { + var targetType = remoteAction.Target.GetType(); + var fieldValues = new Dictionary<string, object>(); + foreach (var field in targetType.GetFields()) + { + var value = field.GetValue(remoteAction.Target); + fieldValues[field.Name] = value; + } + + var remove = CreateInstance<Remote>(); + var assemblyFile = targetType.Assembly.Location; + var typeName = targetType.FullName; + var methodName = remoteAction.Method.Name; + remove.Invoke(assemblyFile, typeName, methodName, fieldValues); + } + + public T CreateInstance<T>() + { + return (T)domain.CreateInstanceFromAndUnwrap(typeof(T).Assembly.CodeBase, typeof(T).FullName); + } + + class Remote : MarshalByRefObject + { + internal void Invoke(string assemblyFile, string typeName, string methodName, Dictionary<string, object> fieldValues) + { + var assembly = Assembly.LoadFrom(assemblyFile); + var type = assembly.GetType(typeName); + var obj = Activator.CreateInstance(type); + foreach (var field in type.GetFields()) + { + var value = fieldValues[field.Name]; + field.SetValue(obj, value); + } + + var dele = (Action)Delegate.CreateDelegate(typeof(Action), obj, methodName); + dele(); + } + } + } +} diff --git a/src/UnitTests/Helpers/CommandTestHelpers.cs b/test/Helpers/CommandTestHelpers.cs similarity index 100% rename from src/UnitTests/Helpers/CommandTestHelpers.cs rename to test/Helpers/CommandTestHelpers.cs diff --git a/src/UnitTests/Helpers/LazySubstitute.cs b/test/Helpers/LazySubstitute.cs similarity index 100% rename from src/UnitTests/Helpers/LazySubstitute.cs rename to test/Helpers/LazySubstitute.cs diff --git a/src/UnitTests/Helpers/ReactiveTestHelper.cs b/test/Helpers/ReactiveTestHelper.cs similarity index 100% rename from src/UnitTests/Helpers/ReactiveTestHelper.cs rename to test/Helpers/ReactiveTestHelper.cs diff --git a/test/Helpers/RepositoryHelpers.cs b/test/Helpers/RepositoryHelpers.cs new file mode 100644 index 0000000000..8a3bb61cb3 --- /dev/null +++ b/test/Helpers/RepositoryHelpers.cs @@ -0,0 +1,54 @@ +using LibGit2Sharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +public static class RepositoryHelpers +{ + public static void UpdateSubmodules(Repository repo) + { + foreach (var submodule in repo.Submodules) + { + var subDir = Path.Combine(repo.Info.WorkingDirectory, submodule.Path); + Directory.CreateDirectory(subDir); // Required to avoid NotFoundException + repo.Submodules.Update(submodule.Name, new SubmoduleUpdateOptions { Init = true }); + } + } + + public static void CommitFile(Repository repo, string path, string content, Signature author) + { + var contentFile = Path.Combine(repo.Info.WorkingDirectory, path); + File.WriteAllText(contentFile, content); + Commands.Stage(repo, path); + repo.Commit("message", author, author); + } + + public static void AddSubmodule(Repository repo, string name, string path, Repository subRepo) + { + var modulesPath = ".gitmodules"; + var modulesFile = Path.Combine(repo.Info.WorkingDirectory, modulesPath); + if (!File.Exists(modulesFile)) + { + File.WriteAllText(modulesFile, ""); + } + + var modulesConfig = Configuration.BuildFrom(modulesFile); + modulesConfig.Set($"submodule.{name}.path", path, ConfigurationLevel.Local); + modulesConfig.Set($"submodule.{name}.url", subRepo.Info.WorkingDirectory, ConfigurationLevel.Local); + Commands.Stage(repo, modulesPath); + + AddGitLinkToTheIndex(repo.Index, path, subRepo.Head.Tip.Sha); + } + + public static void AddGitLinkToTheIndex(Index index, string path, string sha) + { + var id = new ObjectId(sha); + var mode = Mode.GitLink; + index.GetType().InvokeMember("AddEntryToTheIndex", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, + index, new object[] { path, id, mode }); + } +} diff --git a/test/Helpers/ResourceDictionaryUtilities.cs b/test/Helpers/ResourceDictionaryUtilities.cs new file mode 100644 index 0000000000..1da340e812 --- /dev/null +++ b/test/Helpers/ResourceDictionaryUtilities.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.IO.Packaging; +using System.Windows; + +namespace GitHub.UI.Helpers.UnitTests +{ + class ResourceDictionaryUtilities + { + public static string PackUriScheme { get; private set; } + + public static Uri ToPackUri(string url) + { + // Calling `Application.Current` will install pack URI scheme via Application.cctor. + // This is needed when unit testing for the pack:// URL format to be understood. + if (Application.Current != null) { } + + return new Uri(url); + } + + public static string DumpMergedDictionaries(ResourceDictionary target, string url) + { + SetProperty(target, "Source", ToPackUri(url)); + return DumpResourceDictionary(target); + } + + static void SetProperty(object target, string name, object value) + { + var prop = target.GetType().GetProperty(name); + prop.SetValue(target, value); + } + + public static string DumpResourceDictionary(ResourceDictionary rd, string indent = "") + { + var writer = new StringWriter(); + DumpResourceDictionary(writer, rd); + return writer.ToString(); + } + + public static void DumpResourceDictionary(TextWriter writer, ResourceDictionary rd, string indent = "") + { + var source = rd.Source; + if (source != null) + { + writer.WriteLine(indent + source + " (" + rd.GetType().FullName + ") # " + rd.GetHashCode()); + indent += " "; + } + + foreach (var child in rd.MergedDictionaries) + { + DumpResourceDictionary(writer, child, indent); + } + } + } +} diff --git a/test/Helpers/SimpleJson.cs b/test/Helpers/SimpleJson.cs new file mode 100644 index 0000000000..d2fe9ca4f5 --- /dev/null +++ b/test/Helpers/SimpleJson.cs @@ -0,0 +1,607 @@ +//----------------------------------------------------------------------- +// <copyright file="SimpleJson.cs" company="The Outercurve Foundation"> +// Copyright (c) 2011, The Outercurve Foundation. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.opensource.org/licenses/mit-license.php +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author> +// <website>https://github.com/facebook-csharp-sdk/simple-json</website> +//----------------------------------------------------------------------- + +// VERSION: 0.38.0 + +// NOTE: uncomment the following line to make SimpleJson class internal. +//#define SIMPLE_JSON_INTERNAL + +// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. +//#define SIMPLE_JSON_OBJARRAYINTERNAL + +// NOTE: uncomment the following line to enable dynamic support. +//#define SIMPLE_JSON_DYNAMIC + +// NOTE: uncomment the following line to enable DataContract support. +//#define SIMPLE_JSON_DATACONTRACT + +// NOTE: uncomment the following line to enable IReadOnlyCollection<T> and IReadOnlyList<T> support. +//#define SIMPLE_JSON_READONLY_COLLECTIONS + +// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). +// define if you are using .net framework <= 3.0 or < WP7.5 +//#define SIMPLE_JSON_NO_LINQ_EXPRESSION + +// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. +// usually already defined in properties +//#define NETFX_CORE; + +// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; + +// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +#if NETFX_CORE +#define SIMPLE_JSON_TYPEINFO +#endif + +using System; +using System.CodeDom.Compiler; +using System.Collections; +using System.Collections.Generic; +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION +using System.Linq.Expressions; +#endif +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +#if SIMPLE_JSON_DYNAMIC +using System.Dynamic; +#endif +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using GitHub.Reflection; + +// ReSharper disable LoopCanBeConvertedToQuery +// ReSharper disable RedundantExplicitArrayCreation +// ReSharper disable SuggestUseVarKeywordEvident +namespace GitHub +{ + namespace Reflection + { + // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules + // that might be in place in the target project. + [GeneratedCode("reflection-utils", "1.0.0")] +#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC + public +#else + internal +#endif + class ReflectionUtils + { + private static readonly object[] EmptyObjects = new object[] { }; + + public delegate object GetDelegate(object source); + public delegate void SetDelegate(object source, object value); + public delegate object ConstructorDelegate(params object[] args); + + public delegate TValue ThreadSafeDictionaryValueFactory<TKey, TValue>(TKey key); + +#if SIMPLE_JSON_TYPEINFO + public static TypeInfo GetTypeInfo(Type type) + { + return type.GetTypeInfo(); + } +#else + public static Type GetTypeInfo(Type type) + { + return type; + } +#endif + + public static Attribute GetAttribute(MemberInfo info, Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (info == null || type == null || !info.IsDefined(type)) + return null; + return info.GetCustomAttribute(type); +#else + if (info == null || type == null || !Attribute.IsDefined(info, type)) + return null; + return Attribute.GetCustomAttribute(info, type); +#endif + } + + public static Type GetGenericListElementType(Type type) + { + IEnumerable<Type> interfaces; +#if SIMPLE_JSON_TYPEINFO + interfaces = type.GetTypeInfo().ImplementedInterfaces; +#else + interfaces = type.GetInterfaces(); +#endif + foreach (Type implementedInterface in interfaces) + { + if (IsTypeGeneric(implementedInterface) && + implementedInterface.GetGenericTypeDefinition() == typeof (IList<>)) + { + return GetGenericTypeArguments(implementedInterface)[0]; + } + } + return GetGenericTypeArguments(type)[0]; + } + + public static Attribute GetAttribute(Type objectType, Type attributeType) + { + +#if SIMPLE_JSON_TYPEINFO + if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) + return null; + return objectType.GetTypeInfo().GetCustomAttribute(attributeType); +#else + if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) + return null; + return Attribute.GetCustomAttribute(objectType, attributeType); +#endif + } + + public static Type[] GetGenericTypeArguments(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().GenericTypeArguments; +#else + return type.GetGenericArguments(); +#endif + } + + public static bool IsTypeGeneric(Type type) + { + return GetTypeInfo(type).IsGenericType; + } + + public static bool IsTypeGenericeCollectionInterface(Type type) + { + if (!IsTypeGeneric(type)) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(IList<>) + || genericDefinition == typeof(ICollection<>) + || genericDefinition == typeof(IEnumerable<>) +#if SIMPLE_JSON_READONLY_COLLECTIONS + || genericDefinition == typeof(IReadOnlyCollection<>) + || genericDefinition == typeof(IReadOnlyList<>) +#endif + ); + } + + public static bool IsAssignableFrom(Type type1, Type type2) + { + return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); + } + + public static bool IsTypeDictionary(Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + return true; +#else + if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) + return true; +#endif + if (!GetTypeInfo(type).IsGenericType) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + return genericDefinition == typeof(IDictionary<,>); + } + + public static bool IsNullableType(Type type) + { + return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + public static object ToNullableType(object obj, Type nullableType) + { + return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); + } + + public static bool IsValueType(Type type) + { + return GetTypeInfo(type).IsValueType; + } + + public static IEnumerable<ConstructorInfo> GetConstructors(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().DeclaredConstructors; +#else + return type.GetConstructors(); +#endif + } + + public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) + { + IEnumerable<ConstructorInfo> constructorInfos = GetConstructors(type); + int i; + bool matches; + foreach (ConstructorInfo constructorInfo in constructorInfos) + { + ParameterInfo[] parameters = constructorInfo.GetParameters(); + if (argsType.Length != parameters.Length) + continue; + + i = 0; + matches = true; + foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) + { + if (parameterInfo.ParameterType != argsType[i]) + { + matches = false; + break; + } + } + + if (matches) + return constructorInfo; + } + + return null; + } + + public static IEnumerable<PropertyInfo> GetProperties(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeProperties(); +#else + return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static IEnumerable<FieldInfo> GetFields(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeFields(); +#else + return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.GetMethod; +#else + return propertyInfo.GetGetMethod(true); +#endif + } + + public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.SetMethod; +#else + return propertyInfo.GetSetMethod(true); +#endif + } + + public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(constructorInfo); +#else + return GetConstructorByExpression(constructorInfo); +#endif + } + + public static ConstructorDelegate GetContructor(Type type, params Type[] argsType) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(type, argsType); +#else + return GetConstructorByExpression(type, argsType); +#endif + } + + public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) + { + return delegate(object[] args) { return constructorInfo.Invoke(args); }; + } + + public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) + { + ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); + ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); + Expression[] argsExp = new Expression[paramsInfo.Length]; + for (int i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + Expression paramAccessorExp = Expression.ArrayIndex(param, index); + Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); + argsExp[i] = paramCastExp; + } + NewExpression newExp = Expression.New(constructorInfo, argsExp); + Expression<Func<object[], object>> lambda = Expression.Lambda<Func<object[], object>>(newExp, param); + Func<object[], object> compiledLambda = lambda.Compile(); + return delegate(object[] args) { return compiledLambda(args); }; + } + + public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); + } + +#endif + + public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(propertyInfo); +#else + return GetGetMethodByExpression(propertyInfo); +#endif + } + + public static GetDelegate GetGetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(fieldInfo); +#else + return GetGetMethodByExpression(fieldInfo); +#endif + } + + public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); + return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; + } + + public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source) { return fieldInfo.GetValue(source); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + Func<object, object> compiled = Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + + public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); + GetDelegate compiled = Expression.Lambda<GetDelegate>(Expression.Convert(member, typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + +#endif + + public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(propertyInfo); +#else + return GetSetMethodByExpression(propertyInfo); +#endif + } + + public static SetDelegate GetSetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(fieldInfo); +#else + return GetSetMethodByExpression(fieldInfo); +#endif + } + + public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); + return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; + } + + public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); + Action<object, object> compiled = Expression.Lambda<Action<object, object>>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + Action<object, object> compiled = Expression.Lambda<Action<object, object>>( + Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static BinaryExpression Assign(Expression left, Expression right) + { +#if SIMPLE_JSON_TYPEINFO + return Expression.Assign(left, right); +#else + MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); + BinaryExpression assignExpr = Expression.Add(left, right, assign); + return assignExpr; +#endif + } + + private static class Assigner<T> + { + public static T Assign(ref T left, T right) + { + return (left = right); + } + } + +#endif + + public sealed class ThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue> + { + private readonly object _lock = new object(); + private readonly ThreadSafeDictionaryValueFactory<TKey, TValue> _valueFactory; + private Dictionary<TKey, TValue> _dictionary; + + public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory<TKey, TValue> valueFactory) + { + _valueFactory = valueFactory; + } + + private TValue Get(TKey key) + { + if (_dictionary == null) + return AddValue(key); + TValue value; + if (!_dictionary.TryGetValue(key, out value)) + return AddValue(key); + return value; + } + + private TValue AddValue(TKey key) + { + TValue value = _valueFactory(key); + lock (_lock) + { + if (_dictionary == null) + { + _dictionary = new Dictionary<TKey, TValue>(); + _dictionary[key] = value; + } + else + { + TValue val; + if (_dictionary.TryGetValue(key, out val)) + return val; + Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(_dictionary); + dict[key] = value; + _dictionary = dict; + } + } + return value; + } + + public void Add(TKey key, TValue value) + { + throw new NotImplementedException(); + } + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public ICollection<TKey> Keys + { + get { return _dictionary.Keys; } + } + + public bool Remove(TKey key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { + value = this[key]; + return true; + } + + public ICollection<TValue> Values + { + get { return _dictionary.Values; } + } + + public TValue this[TKey key] + { + get { return Get(key); } + set { throw new NotImplementedException(); } + } + + public void Add(KeyValuePair<TKey, TValue> item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(KeyValuePair<TKey, TValue> item) + { + throw new NotImplementedException(); + } + + public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public int Count + { + get { return _dictionary.Count; } + } + + public bool IsReadOnly + { + get { throw new NotImplementedException(); } + } + + public bool Remove(KeyValuePair<TKey, TValue> item) + { + throw new NotImplementedException(); + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + } + + } + } +} +// ReSharper restore LoopCanBeConvertedToQuery +// ReSharper restore RedundantExplicitArrayCreation +// ReSharper restore SuggestUseVarKeywordEvident diff --git a/test/Helpers/SplatModeDetectorSetUp.cs b/test/Helpers/SplatModeDetectorSetUp.cs new file mode 100644 index 0000000000..d68966b191 --- /dev/null +++ b/test/Helpers/SplatModeDetectorSetUp.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; +using System.Reflection; +using NUnit.Framework; + +[SetUpFixture] +public class SplatModeDetectorSetUp +{ + [OneTimeSetUp] + public void RunBeforeAnyTests() + { + Splat.ModeDetector.Current.SetInUnitTestRunner(true); + } +} \ No newline at end of file diff --git a/test/Helpers/TestBaseClass.cs b/test/Helpers/TestBaseClass.cs new file mode 100644 index 0000000000..1ade36b11e --- /dev/null +++ b/test/Helpers/TestBaseClass.cs @@ -0,0 +1,114 @@ +using Octokit; +using System; +using System.IO; +using System.IO.Compression; + +/// <summary> +/// This base class will get its methods called by the most-derived +/// classes. The calls are injected by the EntryExitMethodDecorator Fody +/// addin, so don't be surprised if you don't see any calls in the code. +/// </summary> +public class TestBaseClass +{ + + protected static User CreateOctokitUser(string login = "login", string url = "https://url") + { + return new User("https://url", "bio", "blog", 1, "GitHub", + DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0, "email", 100, 100, true, url, + 10, 42, "location", login, "name", 1, new Plan(), + 1, 1, 1, "https://url", new RepositoryPermissions(true, true, true), + false, null, null); + } + + protected static Organization CreateOctokitOrganization(string login) + { + return new Organization("https://url", "", "", 1, "GitHub", DateTimeOffset.UtcNow, 0, "email", 100, 100, true, "http://url", 10, 42, "somewhere", login, "Who cares", 1, new Plan(), 1, 1, 1, "https://url", "billing"); + } + + protected static Repository CreateRepository(string owner, string name, string domain = "github.com", long id = 1, Repository parent = null) + { + var cloneUrl = "https://" + domain + "/" + owner + "/" + name; + string notCloneUrl = cloneUrl + "-x"; + return new Repository(notCloneUrl, notCloneUrl, cloneUrl, notCloneUrl, notCloneUrl, notCloneUrl, notCloneUrl, + id, CreateOctokitUser(owner), + name, "fullname", "description", notCloneUrl, "c#", false, parent != null, 0, 0, "master", + 0, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, + new RepositoryPermissions(), parent, null, null, true, false, false, false, 0, 0, null, null, null); + } + + protected static PullRequest CreatePullRequest(User user, int id, ItemState state, string title, + DateTimeOffset createdAt, DateTimeOffset updatedAt, int commentCount = 0, int reviewCommentCount = 0) + { + var uri = new Uri("https://url"); + var uris = uri.ToString(); + var repo = new Repository(uris, uris, uris, uris, uris, uris, uris, + 1, user, "Repo", "Repo", string.Empty, string.Empty, string.Empty, + false, false, 0, 0, "master", + 0, null, createdAt, updatedAt, + null, null, null, null, + false, false, false, + false, 0, 0, + null, null, null); + return new PullRequest(0, uris, uris, uris, uris, uris, uris, + id, state, title, "", createdAt, updatedAt, + null, null, + new GitReference(uri.ToString(), "foo:bar", "bar", "123", user, repo), + new GitReference(uri.ToString(), "foo:baz", "baz", "123", user, repo), + user, null, null, false, null, null, null, + commentCount, 0, 0, 0, 0, + null, false, null); + } + + protected class TempDirectory : IDisposable + { + public TempDirectory() + { + var f = Path.GetTempFileName(); + var name = Path.GetFileNameWithoutExtension(f); + File.Delete(f); + Directory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), name)); + Directory.Create(); + } + + public DirectoryInfo Directory { get; } + + public void Dispose() + { + // Remove any read-only attributes + SetFileAttributes(Directory, FileAttributes.Normal); + Directory.Delete(true); + } + + static void SetFileAttributes(DirectoryInfo dir, FileAttributes attributes) + { + foreach (DirectoryInfo subdir in dir.GetDirectories()) + { + SetFileAttributes(subdir, attributes); + } + + foreach (var file in dir.GetFiles()) + { + File.SetAttributes(file.FullName, attributes); + } + } + } + + protected class TempRepository : TempDirectory + { + public TempRepository(string name, byte[] repositoryZip) + : base() + { + var outputZip = Path.Combine(Directory.FullName, name + ".zip"); + var outputDir = Path.Combine(Directory.FullName, name); + var repositoryPath = Path.Combine(outputDir, name); + File.WriteAllBytes(outputZip, repositoryZip); + ZipFile.ExtractToDirectory(outputZip, outputDir); + Repository = new LibGit2Sharp.Repository(repositoryPath); + } + + public LibGit2Sharp.Repository Repository + { + get; + } + } +} diff --git a/src/UnitTests/Helpers/TestImageCache.cs b/test/Helpers/TestImageCache.cs similarity index 100% rename from src/UnitTests/Helpers/TestImageCache.cs rename to test/Helpers/TestImageCache.cs diff --git a/src/UnitTests/Helpers/TestSharedCache.cs b/test/Helpers/TestSharedCache.cs similarity index 80% rename from src/UnitTests/Helpers/TestSharedCache.cs rename to test/Helpers/TestSharedCache.cs index e50a11e74c..30c9177930 100644 --- a/src/UnitTests/Helpers/TestSharedCache.cs +++ b/test/Helpers/TestSharedCache.cs @@ -5,7 +5,7 @@ namespace UnitTests.Helpers { public class TestSharedCache : SharedCache { - public TestSharedCache() : base(new InMemoryBlobCache(), new InMemoryBlobCache(), new InMemoryBlobCache()) + public TestSharedCache() : base(new InMemoryBlobCache(), new InMemoryBlobCache()) { } } diff --git a/test/Helpers/Urls.cs b/test/Helpers/Urls.cs new file mode 100644 index 0000000000..28038382c2 --- /dev/null +++ b/test/Helpers/Urls.cs @@ -0,0 +1,16 @@ +namespace GitHub.UI.Helpers.UnitTests +{ + public class Urls + { + public const string Test_SharedDictionary_PackUrl = "pack://application:,,,/GitHub.UI.UnitTests;component/Helpers/SharedDictionary.xaml"; + + public const string GitHub_UI_SharedDictionary_PackUrl = "pack://application:,,,/GitHub.UI;component/SharedDictionary.xaml"; + public const string GitHub_UI_SharedDictionary_FileUrl = "file:///x:/Project/src/GitHub.UI/SharedDictionary.xaml", Description = "This is a design time URL"; + + public const string GitHub_VisualStudio_UI_SharedDictionary_PackUrl = "pack://application:,,,/GitHub.VisualStudio.UI;component/SharedDictionary.xaml"; + public const string GitHub_VisualStudio_UI_SharedDictionary_FileUrl = "file:///x:/Project/src/GitHub.VisualStudio.UI/SharedDictionary.xaml"; + + public const string GitHub_VisualStudio_UI_Styles_GitHubComboBox_PackUrl = "pack://application:,,,/GitHub.VisualStudio.UI;component/Styles/GitHubComboBox.xaml"; + public const string GitHub_VisualStudio_UI_Styles_GitHubComboBox_FileUrl = "file:///x:/solution/src/GitHub.VisualStudio.UI/Styles/GitHubComboBox.xaml"; + } +} diff --git a/test/MetricsTests/MetricsServer/App.config b/test/MetricsTests/MetricsServer/App.config new file mode 100644 index 0000000000..5534e28762 --- /dev/null +++ b/test/MetricsTests/MetricsServer/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> + </startup> +</configuration> \ No newline at end of file diff --git a/test/MetricsTests/MetricsServer/MetricsServer.csproj b/test/MetricsTests/MetricsServer/MetricsServer.csproj new file mode 100644 index 0000000000..7c6bf3f9e2 --- /dev/null +++ b/test/MetricsTests/MetricsServer/MetricsServer.csproj @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{14FDEE91-7301-4247-846C-049647BF8E99}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>MetricsServer</RootNamespace> + <AssemblyName>MetricsServer</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <NoWarn>8002,618</NoWarn> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup> + <StartupObject /> + </PropertyGroup> + <PropertyGroup> + <SignAssembly>false</SignAssembly> + </PropertyGroup> + <PropertyGroup> + <AssemblyOriginatorKeyFile> + </AssemblyOriginatorKeyFile> + </PropertyGroup> + <ItemGroup> + <Reference Include="Nancy, Version=1.4.1.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>$(SolutionDir)\packages\Nancy.1.4.1\lib\net40\Nancy.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Nancy.Hosting.Self, Version=1.4.1.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>$(SolutionDir)\packages\Nancy.Hosting.Self.1.4.1\lib\net40\Nancy.Hosting.Self.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <None Include="..\..\..\src\GitHub.Exports\Models\UsageData.cs"> + <Link>UsageData.cs</Link> + </None> + <None Include="..\..\..\src\GitHub.Exports\Models\UsageModel.cs"> + <Link>UsageModel.cs</Link> + </None> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9AEA02DB-02B5-409C-B0CA-115D05331A6B}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/MetricsTests/MetricsServer/Program.cs b/test/MetricsTests/MetricsServer/Program.cs new file mode 100644 index 0000000000..6206483a69 --- /dev/null +++ b/test/MetricsTests/MetricsServer/Program.cs @@ -0,0 +1,74 @@ +using GitHub.Models; +using Nancy; +using Nancy.Hosting.Self; +using Nancy.ModelBinding; +using Nancy.Responses.Negotiation; +using System; +using System.Collections.Generic; + +namespace MetricsServer +{ + public class Server + { + readonly string host; + readonly int port; + NancyHost server; + public Server(string host, int port) + { + this.host = host; + this.port = port; + } + + public void Start() + { + var conf = new HostConfiguration { RewriteLocalhost = false }; + server = new NancyHost(conf, new Uri($"http://{host}:{port}")); + server.Start(); + } + + public void Stop() + { + server.Stop(); + } + } + + public class UsageModule : NancyModule + { + public UsageModule() + { + Post["/api/usage/visualstudio"] = p => + { + var errors = new List<string>(); + var usage = this.Bind<UsageModel>(); + if (String.IsNullOrEmpty(usage.Dimensions.AppVersion)) + errors.Add("Empty appVersion"); + Version result = null; + if (!Version.TryParse(usage.Dimensions.AppVersion, out result)) + errors.Add("Invalid appVersion"); + if (String.IsNullOrEmpty(usage.Dimensions.Lang)) + errors.Add("Empty lang"); + if (String.IsNullOrEmpty(usage.Dimensions.VSVersion)) + errors.Add("Empty vSVersion"); + if (usage.Dimensions.Date == DateTimeOffset.MinValue) + errors.Add("Empty date"); + if (usage.Measures.NumberOfStartups == 0) + errors.Add("Startups is 0"); + if (errors.Count > 0) + { + return Negotiate + .WithStatusCode(HttpStatusCode.InternalServerError) + .WithAllowedMediaRange(MediaRange.FromString("application/json")) + .WithMediaRangeModel( + MediaRange.FromString("application/json"), + new { result = errors }); // Model for 'application/json'; + } + + return Negotiate + .WithAllowedMediaRange(MediaRange.FromString("application/json")) + .WithMediaRangeModel( + MediaRange.FromString("application/json"), + new { result = "Cool usage" }); // Model for 'application/json'; + }; + } + } +} diff --git a/test/MetricsTests/MetricsServer/Properties/AssemblyInfo.cs b/test/MetricsTests/MetricsServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e69b57a52c --- /dev/null +++ b/test/MetricsTests/MetricsServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MetricsServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MetricsServer")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("14fdee91-7301-4247-846c-049647bf8e99")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/MetricsTests/MetricsServer/packages.config b/test/MetricsTests/MetricsServer/packages.config new file mode 100644 index 0000000000..ec90bda6f5 --- /dev/null +++ b/test/MetricsTests/MetricsServer/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Nancy" version="1.4.1" targetFramework="net461" /> + <package id="Nancy.Hosting.Self" version="1.4.1" targetFramework="net461" /> +</packages> \ No newline at end of file diff --git a/test/MetricsTests/MetricsServerApp/App.config b/test/MetricsTests/MetricsServerApp/App.config new file mode 100644 index 0000000000..5534e28762 --- /dev/null +++ b/test/MetricsTests/MetricsServerApp/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> + </startup> +</configuration> \ No newline at end of file diff --git a/test/MetricsTests/MetricsServerApp/MetricsServerApp.csproj b/test/MetricsTests/MetricsServerApp/MetricsServerApp.csproj new file mode 100644 index 0000000000..c971d77ce2 --- /dev/null +++ b/test/MetricsTests/MetricsServerApp/MetricsServerApp.csproj @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{A0E20ED2-0C69-469F-BC77-37112000F71D}</ProjectGuid> + <OutputType>Exe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>MetricsServerApp</RootNamespace> + <AssemblyName>MetricsServerApp</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08dd4305-7787-4823-a53f-4d0f725a07f3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\MetricsServer\MetricsServer.csproj"> + <Project>{14fdee91-7301-4247-846c-049647bf8e99}</Project> + <Name>MetricsServer</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/MetricsTests/MetricsServerApp/Program.cs b/test/MetricsTests/MetricsServerApp/Program.cs new file mode 100644 index 0000000000..5576cd3146 --- /dev/null +++ b/test/MetricsTests/MetricsServerApp/Program.cs @@ -0,0 +1,118 @@ +using GitHub.Models; +using Octokit.Internal; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace MetricsServerApp +{ + class Program + { + static void Main(string[] args) + { + var p = new Program(); + p.Run(); + Console.Read(); + } + + void Run() + { + var uri = new Uri("http://localhost:40000"); + var server = new MetricsServer.Server(uri.Host, uri.Port); + + server.Start(); + } + + async Task Send() + { + var uri = new Uri("http://localhost:40000"); + var server = new MetricsServer.Server(uri.Host, uri.Port); + + server.Start(); + + var client = new HttpClient(); + client.DefaultRequestHeaders + .Accept + .Add(new MediaTypeWithQualityHeaderValue("application/json")); + var request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri, "/api/usage/visualstudio")); + + var model = UsageModel.Create(Guid.NewGuid()); + model.Dimensions.AppVersion = "9.9.9"; + model.Dimensions.Lang = "en-us"; + model.Dimensions.VSVersion = "14"; + model.Measures.NumberOfStartups = 1; + + var data = new UsageData(); + data.Reports = new List<UsageModel> { model }; + + request.Content = SerializeRequest(model); + + HttpResponseMessage response = null; + try + { + response = await client.SendAsync(request); + } + catch (Exception ex) + { + Debugger.Break(); + } + var ret = await response.Content.ReadAsStringAsync(); + Console.WriteLine(response.ToString()); + Console.WriteLine(ret); + + server.Stop(); + } + + static StringContent SerializeRequest(UsageModel model) + { + var serializer = new SimpleJsonSerializer(); + var dictionary = ToModelDictionary(model); + return new StringContent(serializer.Serialize(dictionary), Encoding.UTF8, "application/json"); + } + + static Dictionary<string, object> ToModelDictionary(object model) + { + var dict = new Dictionary<string, object>(); + var type = model.GetType(); + + foreach (var prop in type.GetProperties()) + { + if (prop.PropertyType.IsValueType || prop.PropertyType == typeof(string)) + { + dict.Add(ToJsonPropertyName(prop.Name), prop.GetValue(model)); + } + else + { + var value = prop.GetValue(model); + + if (value == null) + { + dict.Add(ToJsonPropertyName(prop.Name), value); + } + else + { + dict.Add(ToJsonPropertyName(prop.Name), ToModelDictionary(value)); + } + } + } + + return dict; + } + + static string ToJsonPropertyName(string propertyName) + { + if (propertyName.Length < 2) + { + return propertyName.ToLowerInvariant(); + } + + return propertyName.Substring(0, 1).ToLowerInvariant() + propertyName.Substring(1); + } + } +} diff --git a/test/MetricsTests/MetricsServerApp/Properties/AssemblyInfo.cs b/test/MetricsTests/MetricsServerApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..bdf214a7dc --- /dev/null +++ b/test/MetricsTests/MetricsServerApp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MetricsServerApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MetricsServerApp")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a0e20ed2-0c69-469f-bc77-37112000f71d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/MetricsTests/MetricsTests.sln b/test/MetricsTests/MetricsTests.sln new file mode 100644 index 0000000000..e0dce20a60 --- /dev/null +++ b/test/MetricsTests/MetricsTests.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsServer", "MetricsServer\MetricsServer.csproj", "{14FDEE91-7301-4247-846C-049647BF8E99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsServerApp", "MetricsServerApp\MetricsServerApp.csproj", "{A0E20ED2-0C69-469F-BC77-37112000F71D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit", "..\..\submodules\octokit.net\Octokit\Octokit.csproj", "{08DD4305-7787-4823-A53F-4D0F725A07F3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14FDEE91-7301-4247-846C-049647BF8E99}.Release|Any CPU.Build.0 = Release|Any CPU + {A0E20ED2-0C69-469F-BC77-37112000F71D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0E20ED2-0C69-469F-BC77-37112000F71D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0E20ED2-0C69-469F-BC77-37112000F71D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0E20ED2-0C69-469F-BC77-37112000F71D}.Release|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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08DD4305-7787-4823-A53F-4D0F725A07F3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/test/MetricsTests/MetricsTests/MetricsTests.cs b/test/MetricsTests/MetricsTests/MetricsTests.cs new file mode 100644 index 0000000000..41ccd777d6 --- /dev/null +++ b/test/MetricsTests/MetricsTests/MetricsTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using System.Net.Http; +using System.Net.Http.Headers; +using GitHub.Models; +using System.Threading.Tasks; +using System.Net; + +namespace MetricsTests +{ + [TestFixture] + public class Submissions + { + HttpClient client; + Uri uri; + MetricsServer.Server server; + + [OneTimeSetUp] + public void Setup() + { + uri = new Uri("http://localhost:4000"); + if (uri.Host == "localhost") + { + server = new MetricsServer.Server(uri.Host, uri.Port); + server.Start(); + } + + client = new HttpClient(); + client.DefaultRequestHeaders + .Accept + .Add(new MediaTypeWithQualityHeaderValue("application/json")); + var request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri, "/api/usage/visualstudio")); + } + + [OneTimeTearDown] + public void TearDown() + { + server?.Stop(); + } + + [Test] + public async Task ValidDimensions() + { + var request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri, "/api/usage/visualstudio")); + var data = new UsageData(); + data.Reports = new List<UsageModel> { UsageModel.Create(Guid.NewGuid()) }; + var model = data.Reports[0]; + model.Dimensions.AppVersion = "9.9.9"; + model.Dimensions.Lang = "en-us"; + model.Dimensions.VSVersion = "14"; + model.Measures.NumberOfStartups = 1; + + request.Content = GitHub.Services.MetricsService.SerializeRequest(model); + + HttpResponseMessage response = null; + Assert.DoesNotThrowAsync(async () => response = await client.SendAsync(request)); + var ret = await response.Content.ReadAsStringAsync(); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + [Test] + public async Task InvalidAppVersion() + { + var request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri, "/api/usage/visualstudio")); + var data = new UsageData(); + data.Reports = new List<UsageModel> { UsageModel.Create(Guid.NewGuid()) }; + var model = data.Reports[0]; + model.Dimensions.AppVersion = "nope"; + model.Dimensions.Lang = "en-us"; + model.Dimensions.VSVersion = "14"; + model.Measures.NumberOfStartups = 1; + + request.Content = GitHub.Services.MetricsService.SerializeRequest(model); + + HttpResponseMessage response = null; + Assert.DoesNotThrowAsync(async () => response = await client.SendAsync(request)); + var ret = await response.Content.ReadAsStringAsync(); + Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode); + } + } +} diff --git a/test/MetricsTests/MetricsTests/MetricsTests.csproj b/test/MetricsTests/MetricsTests/MetricsTests.csproj new file mode 100644 index 0000000000..48e442f2d1 --- /dev/null +++ b/test/MetricsTests/MetricsTests/MetricsTests.csproj @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\..\packages\NUnit3TestAdapter.3.9.0\build\net35\NUnit3TestAdapter.props" Condition="Exists('..\..\..\packages\NUnit3TestAdapter.3.9.0\build\net35\NUnit3TestAdapter.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{09313E65-7ADB-48C1-AD3A-572020C5BDCB}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>MetricsTests</RootNamespace> + <AssemblyName>MetricsTests</AssemblyName> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> + <IsCodedUITest>False</IsCodedUITest> + <TestProjectType>UnitTest</TestProjectType> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + <TargetFrameworkProfile /> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>0</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup> + <SignAssembly>false</SignAssembly> + </PropertyGroup> + <PropertyGroup> + <AssemblyOriginatorKeyFile> + </AssemblyOriginatorKeyFile> + </PropertyGroup> + <ItemGroup> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Net.Http" /> + </ItemGroup> + <Choose> + <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'"> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> + </ItemGroup> + </When> + <Otherwise> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" /> + </ItemGroup> + </Otherwise> + </Choose> + <ItemGroup> + <Compile Include="MetricsTests.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\src\GitHub.Exports\GitHub.Exports.csproj"> + <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> + <Name>GitHub.Exports</Name> + </ProjectReference> + <ProjectReference Include="..\..\..\submodules\octokit.net\Octokit\Octokit.csproj"> + <Project>{08DD4305-7787-4823-A53F-4D0F725A07F3}</Project> + <Name>Octokit</Name> + </ProjectReference> + <ProjectReference Include="..\MetricsServer\MetricsServer.csproj"> + <Project>{14fdee91-7301-4247-846c-049647bf8e99}</Project> + <Name>MetricsServer</Name> + </ProjectReference> + </ItemGroup> + <Choose> + <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + </ItemGroup> + </When> + </Choose> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>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}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\..\packages\NUnit3TestAdapter.3.9.0\build\net35\NUnit3TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\NUnit3TestAdapter.3.9.0\build\net35\NUnit3TestAdapter.props'))" /> + </Target> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file diff --git a/test/MetricsTests/MetricsTests/Properties/AssemblyInfo.cs b/test/MetricsTests/MetricsTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..06c71c376d --- /dev/null +++ b/test/MetricsTests/MetricsTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MetricsTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MetricsTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("09313e65-7adb-48c1-ad3a-572020c5bdcb")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/MetricsTests/MetricsTests/packages.config b/test/MetricsTests/MetricsTests/packages.config new file mode 100644 index 0000000000..c797843e29 --- /dev/null +++ b/test/MetricsTests/MetricsTests/packages.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="NUnit" version="3.9.0" targetFramework="net452" /> + <package id="NUnit.ConsoleRunner" version="3.7.0" targetFramework="net452" /> + <package id="NUnit3TestAdapter" version="3.9.0" targetFramework="net452" /> +</packages> \ No newline at end of file diff --git a/src/TrackingCollectionTests/ITestOutputHelper.cs b/test/TrackingCollectionTests/ITestOutputHelper.cs similarity index 100% rename from src/TrackingCollectionTests/ITestOutputHelper.cs rename to test/TrackingCollectionTests/ITestOutputHelper.cs diff --git a/test/TrackingCollectionTests/ListenerCollectionTests.cs b/test/TrackingCollectionTests/ListenerCollectionTests.cs new file mode 100644 index 0000000000..0b4b158dd8 --- /dev/null +++ b/test/TrackingCollectionTests/ListenerCollectionTests.cs @@ -0,0 +1,146 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using GitHub.Collections; +using NUnit.Framework; + +[TestFixture] +public class ListenerCollectionTests : TestBase +{ +#if !DISABLE_REACTIVE_UI + [OneTimeSetUp] + public void Setup() + { + Splat.ModeDetector.Current.SetInUnitTestRunner(true); + } +#endif + + [Test] + public void StickyItemShouldNotBePresentInitiallyWhereNoSelectionHasHappened() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = Observable.Empty<Thing>(); + var target = source.CreateListenerCollection(stickie, selection); + + CollectionAssert.AreEqual(source, target); + } + + [Test] + public void StickyItemShouldNotBePresentAfterCreationWhenSelectionNull() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = Observable.Return<Thing>(null); + var target = source.CreateListenerCollection(stickie, selection); + + CollectionAssert.AreEqual(source, target); + } + + [Test] + public void StickyItemShouldBePresentAfterCreationWhenSelectionNotNull() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = Observable.Return(source[0]); + var target = source.CreateListenerCollection(stickie, selection); + + var expected = new[] { stickie }.Concat(source); + CollectionAssert.AreEqual(expected, target); + } + + [Test] + public void StickyItemShouldNotBePresentAfterCreationWhenSelectionIsStickyItem() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = Observable.Return(stickie); + var target = source.CreateListenerCollection(stickie, selection); + + CollectionAssert.AreEqual(source, target); + } + + [Test] + public void StickyItemShouldNotBePresentAfterCreationWhenSelectionEqualsStickyItem() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = Observable.Return(new Thing()); + var target = source.CreateListenerCollection(stickie, selection); + + CollectionAssert.AreEqual(source, target); + } + + [Test] + public void StickyItemShouldBeAddedWhenSelectionChangesFromNull() + { + var source = CreateSource(); + var selection = new BehaviorSubject<Thing>(null); + var stickie = new Thing(); + var target = source.CreateListenerCollection(stickie, selection); + + CollectionAssert.AreEqual(source, target); + + selection.OnNext(source[0]); + + var expected = new[] { stickie }.Concat(source); + CollectionAssert.AreEqual(expected, target); + } + + [Test] + public void StickyItemShouldBeRemovedWhenSelectionChangesToNull() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = new BehaviorSubject<Thing>(source[0]); + var target = source.CreateListenerCollection(stickie, selection); + + var expected = new[] { stickie }.Concat(source); + CollectionAssert.AreEqual(expected, target); + + selection.OnNext(null); + + CollectionAssert.AreEqual(source, target); + } + + [Test] + public void StickyItemShouldBeRemovedWhenSelectionChangesToStickyItem() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = new BehaviorSubject<Thing>(source[0]); + var target = source.CreateListenerCollection(stickie, selection); + + var expected = new[] { stickie }.Concat(source); + CollectionAssert.AreEqual(expected, target); + + selection.OnNext(stickie); + + CollectionAssert.AreEqual(source, target); + } + + [Test] + public void ResetingTrackingCollectionWorks() + { + var source = CreateSource(); + var stickie = new Thing(); + var selection = new ReplaySubject<Thing>(); + var target = source.CreateListenerCollection(stickie, selection); + selection.OnNext(stickie); + selection.OnNext(null); + CollectionAssert.AreEqual(source, target); + source.Filter = (a,b,c) => true; + CollectionAssert.AreEqual(source, target); + } + + TrackingCollection<Thing> CreateSource() + { + var result = new TrackingCollection<Thing>(Observable.Empty<Thing>()); + result.Subscribe(); + result.AddItem(new Thing(1, "item1", DateTimeOffset.MinValue)); + result.AddItem(new Thing(2, "item2", DateTimeOffset.MinValue)); + result.AddItem(new Thing(3, "item3", DateTimeOffset.MinValue)); + return result; + } +} diff --git a/test/TrackingCollectionTests/Properties/AssemblyInfo.cs b/test/TrackingCollectionTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f7baaa48da --- /dev/null +++ b/test/TrackingCollectionTests/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using NUnit.Framework; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TrackingCollectionTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TrackingCollectionTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7b835a7d-cf94-45e8-b191-96f5a4fe26a8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: Timeout(2 /*minutes*/ * 60 * 1000)] diff --git a/src/TrackingCollectionTests/TestBase.cs b/test/TrackingCollectionTests/TestBase.cs similarity index 84% rename from src/TrackingCollectionTests/TestBase.cs rename to test/TrackingCollectionTests/TestBase.cs index e7fb90e6c1..41db705c27 100644 --- a/src/TrackingCollectionTests/TestBase.cs +++ b/test/TrackingCollectionTests/TestBase.cs @@ -74,24 +74,29 @@ protected void Add(Subject<Thing> source, Thing item) source.OnNext(item); } + /// <summary> + /// This will create a new Thing with CreatedAt and UpdatedAt set to 0 + /// </summary> + /// <param name="id"></param> + /// <returns></returns> protected Thing GetThing(int id) { - return new Thing { Number = id }; + return GetThing(id, 0, 0, "Run 1"); } protected Thing GetThing(int id, int minutes) { - return new Thing { Number = id, Title = "Run 1", CreatedAt = Now + TimeSpan.FromMinutes(minutes), UpdatedAt = Now + TimeSpan.FromMinutes(minutes) }; + return GetThing(id, minutes, minutes, "Run 1"); } protected Thing GetThing(int id, int minutesc, int minutesu) { - return new Thing { Number = id, Title = "Run 1", CreatedAt = Now + TimeSpan.FromMinutes(minutesc), UpdatedAt = Now + TimeSpan.FromMinutes(minutesu) }; + return GetThing(id, minutesc, minutesu, "Run 1"); } protected Thing GetThing(int id, string title) { - return new Thing { Number = id, Title = "Run 1" }; + return GetThing(id, 0, 0, title); } protected Thing GetThing(int id, int minutesc, int minutesu, string title) diff --git a/src/TrackingCollectionTests/Thing.cs b/test/TrackingCollectionTests/Thing.cs similarity index 100% rename from src/TrackingCollectionTests/Thing.cs rename to test/TrackingCollectionTests/Thing.cs diff --git a/src/TrackingCollectionTests/TrackingCollectionTests.cs b/test/TrackingCollectionTests/TrackingCollectionTests.cs similarity index 75% rename from src/TrackingCollectionTests/TrackingCollectionTests.cs rename to test/TrackingCollectionTests/TrackingCollectionTests.cs index 0377d7a1cf..54860ecc52 100644 --- a/src/TrackingCollectionTests/TrackingCollectionTests.cs +++ b/test/TrackingCollectionTests/TrackingCollectionTests.cs @@ -10,23 +10,32 @@ using System.Reactive.Subjects; using System.Threading; using NUnit.Framework; +using System.Reactive; +using System.Threading.Tasks; +using System.Reactive.Threading.Tasks; +using GitHub; [TestFixture] public class TrackingTests : TestBase { - [TestFixtureSetUp] + const int Timeout = 2000; + +#if !DISABLE_REACTIVE_UI + [OneTimeSetUp] public void Setup() { Splat.ModeDetector.Current.SetInUnitTestRunner(true); } +#endif [Test] public void OrderByUpdatedNoFilter() { var count = 6; - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( Observable.Never<Thing>(), OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); @@ -69,10 +78,11 @@ public void OrderByUpdatedNoFilter() public void OrderByUpdatedFilter() { var count = 3; - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( Observable.Never<Thing>(), OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, - (item, position, list) => true); + (item, position, list) => true, + OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); col.ProcessingDelay = TimeSpan.Zero; var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); @@ -118,10 +128,11 @@ public void OnlyIndexes2To4() var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( Observable.Never<Thing>(), OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, (item, position, list) => position >= 2 && position <= 4); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var evt = new ManualResetEvent(false); @@ -142,7 +153,7 @@ public void OnlyIndexes2To4() Assert.AreEqual(3, col.Count); #if DEBUG - CollectionAssert.AreEqual(list1.Reverse<Thing>(), col.DebugInternalList); + CollectionAssert.AreEqual(list1.Reverse<Thing>(), (col as TrackingCollection<Thing>).DebugInternalList); #endif CollectionAssert.AreEqual(col, new List<Thing>() { list1[3], list1[2], list1[1] }); @@ -157,10 +168,11 @@ public void OnlyTimesEqualOrHigherThan3Minutes() var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( Observable.Never<Thing>(), OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, (item, position, list) => item.UpdatedAt >= Now + TimeSpan.FromMinutes(3) && item.UpdatedAt <= Now + TimeSpan.FromMinutes(5)); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var evt = new ManualResetEvent(false); @@ -181,7 +193,7 @@ public void OnlyTimesEqualOrHigherThan3Minutes() Assert.AreEqual(3, col.Count); #if DEBUG - CollectionAssert.AreEqual(list1.Reverse<Thing>(), col.DebugInternalList); + CollectionAssert.AreEqual(list1.Reverse<Thing>(), (col as TrackingCollection<Thing>).DebugInternalList); #endif CollectionAssert.AreEqual(col, new List<Thing>() { list1[2], list1[1], list1[0] }); col.Dispose(); @@ -195,9 +207,10 @@ public void OrderByDescendingNoFilter() var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); var list2 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, i, "Run 2")).ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( Observable.Never<Thing>(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var evt = new ManualResetEvent(false); @@ -217,7 +230,7 @@ public void OrderByDescendingNoFilter() Assert.AreEqual(6, col.Count); #if DEBUG - CollectionAssert.AreEqual(list1, col.DebugInternalList); + CollectionAssert.AreEqual(list1, (col as TrackingCollection<Thing>).DebugInternalList); #endif CollectionAssert.AreEqual(col, list1); @@ -243,9 +256,10 @@ public void OrderByDescendingNoFilter1000() var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); var list2 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 2")).ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( Observable.Never<Thing>(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var evt = new ManualResetEvent(false); @@ -285,74 +299,24 @@ public void OrderByDescendingNoFilter1000() } - [Test, Category("Timings")] - public void ProcessingDelayPingsRegularly() - { - int count, total; - count = total = 400; - - var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i)).ToList()); - - var col = new TrackingCollection<Thing>( - list1.ToObservable().Delay(TimeSpan.FromMilliseconds(10)), - OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); - col.ProcessingDelay = TimeSpan.FromMilliseconds(10); - - var sub = new Subject<Thing>(); - var times = new List<DateTimeOffset>(); - sub.Subscribe(t => - { - times.Add(DateTimeOffset.UtcNow); - }); - - count = 0; - - var evt = new ManualResetEvent(false); - col.Subscribe(t => - { - sub.OnNext(t); - if (++count == list1.Count) - { - sub.OnCompleted(); - evt.Set(); - } - }, () => { }); - - - evt.WaitOne(); - evt.Reset(); - - Assert.AreEqual(total, col.Count); - - CollectionAssert.AreEqual(col, list1); - - long totalTime = 0; - - for (var j = 1; j < times.Count; j++) - totalTime += (times[j] - times[j - 1]).Ticks; - var avg = TimeSpan.FromTicks(totalTime / times.Count).TotalMilliseconds; - Assert.GreaterOrEqual(avg, 9); - Assert.LessOrEqual(avg, 12); - col.Dispose(); - } [Test] public void NotInitializedCorrectlyThrows1() { - var col = new TrackingCollection<Thing>(OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); Assert.Throws<InvalidOperationException>(() => col.Subscribe()); } [Test] public void NotInitializedCorrectlyThrows2() { - var col = new TrackingCollection<Thing>(OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); Assert.Throws<InvalidOperationException>(() => col.Subscribe(_ => { }, () => { })); } [Test] public void NoChangingAfterDisposed1() { - var col = new TrackingCollection<Thing>(Observable.Never<Thing>(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(Observable.Never<Thing>(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); col.Dispose(); Assert.Throws<ObjectDisposedException>(() => col.AddItem(new Thing())); } @@ -360,7 +324,7 @@ public void NoChangingAfterDisposed1() [Test] public void NoChangingAfterDisposed2() { - var col = new TrackingCollection<Thing>(Observable.Never<Thing>(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(Observable.Never<Thing>(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); col.Dispose(); Assert.Throws<ObjectDisposedException>(() => col.RemoveItem(new Thing())); } @@ -372,12 +336,13 @@ public void FilterTitleRun2() var total = 1000; var list1 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i, "Run 1")).ToList()); - var list2 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i, "Run 2")).ToList()); + var list2 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i + 1, "Run 2")).ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( list1.ToObservable(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => item.Title.Equals("Run 2")); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var evt = new ManualResetEvent(false); @@ -416,16 +381,18 @@ public void OrderByDoesntMatchOriginalOrderTimings() var total = 1000; var list1 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i, "Run 1")).ToList()); - var list2 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i, "Run 2")).ToList()); + var list2 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i + 1, "Run 2")).ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( list1.ToObservable(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => item.Title.Equals("Run 2")); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; var evt = new ManualResetEvent(false); var start = DateTimeOffset.UtcNow; + col.Subscribe(t => { if (++count == list1.Count) @@ -465,13 +432,14 @@ public void OrderByMatchesOriginalOrder() var count = 0; var total = 1000; - var list1 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, total - i, "Run 1")).ToList()); - var list2 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, total - i, "Run 2")).ToList()); + var list1 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i, "Run 1")).Reverse().ToList()); + var list2 = new List<Thing>(Enumerable.Range(1, total).Select(i => GetThing(i, i, i + 1, "Run 2")).Reverse().ToList()); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( list1.ToObservable(), OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => item.Title.Equals("Run 2")); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; col.ProcessingDelay = TimeSpan.Zero; count = 0; @@ -509,7 +477,7 @@ public void SortingTest() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); col.ProcessingDelay = TimeSpan.Zero; @@ -703,7 +671,7 @@ public void SortingTestWithFilterTrue() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => true); @@ -891,7 +859,7 @@ public void SortingTestWithFilterBetween6And12() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => item.UpdatedAt.Minute >= 6 && item.UpdatedAt.Minute <= 12); @@ -1045,7 +1013,7 @@ public void SortingTestWithFilterPosition2to4() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => position >= 2 && position <= 4); @@ -1192,7 +1160,7 @@ public void SortingTestWithFilterPosition1And3to4() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, position, list) => position == 1 || (position >= 3 && position <= 4)); @@ -1355,11 +1323,11 @@ public void SortingTestWithFilterMoves() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, - (item, position, list) => (position >= 1 && position <= 2) || (position >= 5 && position <= 7)) - { ProcessingDelay = TimeSpan.Zero }; + (item, position, list) => position == 1 || position == 2 || position == 5 || position == 6 || position == 7); + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var expectedCount = 0; @@ -1424,11 +1392,12 @@ public void ChangingItemContentRemovesItFromFilteredList() var source = new Subject<Thing>(); var now = new DateTimeOffset(0, TimeSpan.FromTicks(0)); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderBy(x => x.CreatedAt).Compare, - (item, position, list) => item.UpdatedAt < now + TimeSpan.FromMinutes(6)) - { ProcessingDelay = TimeSpan.Zero }; + (item, position, list) => item.UpdatedAt < now + TimeSpan.FromMinutes(6)); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var expectedCount = 0; @@ -1471,11 +1440,11 @@ public void ChangingItemContentRemovesItFromFilteredList2() var source = new Subject<Thing>(); var now = new DateTimeOffset(0, TimeSpan.FromTicks(0)); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderBy(x => x.CreatedAt).Compare, - (item, position, list) => item.UpdatedAt > now + TimeSpan.FromMinutes(2) && item.UpdatedAt < now + TimeSpan.FromMinutes(8)) - { ProcessingDelay = TimeSpan.Zero }; + (item, position, list) => item.UpdatedAt > now + TimeSpan.FromMinutes(2) && item.UpdatedAt < now + TimeSpan.FromMinutes(8)); + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var expectedCount = 0; @@ -1554,12 +1523,12 @@ public void ChangingItemContentRemovesItFromFilteredList2() public void ChangingFilterUpdatesCollection() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, - (item, position, list) => item.UpdatedAt < Now + TimeSpan.FromMinutes(10)) - { ProcessingDelay = TimeSpan.Zero }; - + (item, position, list) => item.UpdatedAt < Now + TimeSpan.FromMinutes(10)); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var expectedCount = 0; @@ -1595,7 +1564,7 @@ public void ChangingFilterUpdatesCollection() GetThing(9, 9), }); - col.SetFilter((item, position, list) => item.UpdatedAt < Now + TimeSpan.FromMinutes(8)); + col.Filter = (item, position, list) => item.UpdatedAt < Now + TimeSpan.FromMinutes(8); CollectionAssert.AreEqual(col, new List<Thing> { GetThing(1, 1), @@ -1613,11 +1582,12 @@ public void ChangingFilterUpdatesCollection() public void ChangingSortUpdatesCollection() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, - (item, position, list) => item.UpdatedAt < Now + TimeSpan.FromMinutes(10)) - { ProcessingDelay = TimeSpan.Zero }; + (item, position, list) => item.UpdatedAt < Now + TimeSpan.FromMinutes(10)); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var evt = new ManualResetEvent(false); @@ -1646,7 +1616,7 @@ public void ChangingSortUpdatesCollection() evt.Reset(); CollectionAssert.AreEqual(col, list1); - col.SetComparer(OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare); + col.Comparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; CollectionAssert.AreEqual(col, list1.Reverse<Thing>().ToArray()); col.Dispose(); @@ -1655,7 +1625,7 @@ public void ChangingSortUpdatesCollection() [Test] public void AddingItemsToCollectionManuallyThrows() { - var col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); Assert.Throws<InvalidOperationException>(() => col.Add(GetThing(1))); col.Dispose(); } @@ -1663,7 +1633,7 @@ public void AddingItemsToCollectionManuallyThrows() [Test] public void InsertingItemsIntoCollectionManuallyThrows() { - var col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); Assert.Throws<InvalidOperationException>(() => col.Insert(0, GetThing(1))); col.Dispose(); } @@ -1672,7 +1642,7 @@ public void InsertingItemsIntoCollectionManuallyThrows() public void MovingItemsIntoCollectionManuallyThrows() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>(source) { ProcessingDelay = TimeSpan.Zero }; + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(source) { ProcessingDelay = TimeSpan.Zero }; var count = 0; var expectedCount = 2; var evt = new ManualResetEvent(false); @@ -1687,7 +1657,7 @@ public void MovingItemsIntoCollectionManuallyThrows() Add(source, GetThing(2, 2)); evt.WaitOne(); evt.Reset(); - Assert.Throws<InvalidOperationException>(() => col.Move(0, 1)); + Assert.Throws<InvalidOperationException>(() => (col as TrackingCollection<Thing>).Move(0, 1)); col.Dispose(); } @@ -1695,7 +1665,7 @@ public void MovingItemsIntoCollectionManuallyThrows() public void RemovingItemsFromCollectionManuallyThrows() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>(source) { ProcessingDelay = TimeSpan.Zero }; + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(source) { ProcessingDelay = TimeSpan.Zero }; var count = 0; var expectedCount = 2; var evt = new ManualResetEvent(false); @@ -1718,7 +1688,7 @@ public void RemovingItemsFromCollectionManuallyThrows() public void RemovingItemsFromCollectionManuallyThrows2() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>(source) { ProcessingDelay = TimeSpan.Zero }; + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(source) { ProcessingDelay = TimeSpan.Zero }; var count = 0; var expectedCount = 2; var evt = new ManualResetEvent(false); @@ -1743,7 +1713,9 @@ public void ChangingComparers() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>(source, OrderedComparer<Thing>.OrderBy(x => x.CreatedAt).Compare) { ProcessingDelay = TimeSpan.Zero }; + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(source, OrderedComparer<Thing>.OrderBy(x => x.CreatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var evt = new ManualResetEvent(false); @@ -1771,27 +1743,25 @@ public void ChangingComparers() evt.WaitOne(); evt.Reset(); CollectionAssert.AreEqual(col, list1); - col.SetComparer(null); + col.Comparer = null; CollectionAssert.AreEqual(col, list1.Reverse<Thing>().ToArray()); col.Dispose(); } - [Test] public void Removing() { var source = new Subject<Thing>(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( source, OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, - (item, position, list) => (position > 2 && position < 5) || (position > 6 && position < 8)) - { ProcessingDelay = TimeSpan.Zero }; + (item, position, list) => (position > 2 && position < 5) || (position > 6 && position < 8)); + col.ProcessingDelay = TimeSpan.Zero; var count = 0; var expectedCount = 0; var evt = new ManualResetEvent(false); - col.Subscribe(t => { if (++count == expectedCount) @@ -1810,7 +1780,8 @@ public void Removing() Add(source, GetThing(8, 8)); Add(source, GetThing(9, 9)); Add(source, GetThing(10, 10)); - evt.WaitOne(); + + Assert.True(evt.WaitOne(80)); evt.Reset(); CollectionAssert.AreEqual(col, new List<Thing> { GetThing(3, 3), @@ -1820,7 +1791,7 @@ public void Removing() expectedCount = 12; col.RemoveItem(GetThing(2)); - evt.WaitOne(); + Assert.True(evt.WaitOne(40)); evt.Reset(); CollectionAssert.AreEqual(col, new List<Thing> { GetThing(4, 4), @@ -1830,7 +1801,7 @@ public void Removing() expectedCount = 13; col.RemoveItem(GetThing(5)); - evt.WaitOne(); + Assert.True(evt.WaitOne(40)); evt.Reset(); CollectionAssert.AreEqual(col, new List<Thing> { GetThing(4, 4), @@ -1838,12 +1809,12 @@ public void Removing() GetThing(9, 9), }); - col.SetFilter(null); + col.Filter = null; expectedCount = 14; col.RemoveItem(GetThing(100)); // this one won't result in a new element from the observable col.RemoveItem(GetThing(10)); - evt.WaitOne(); + Assert.True(evt.WaitOne(40)); evt.Reset(); Assert.AreEqual(8, col.Count); @@ -1857,105 +1828,418 @@ public void Removing() GetThing(8, 8), GetThing(9, 9), }); + col.Dispose(); } + [Test] + public void RemovingFirstItemWithFilterWorks() + { + var source = new Subject<Thing>(); + + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( + Observable.Range(0, 5).Select(x => GetThing(x, x)), + OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare, + (item, position, list) => true); + col.ProcessingDelay = TimeSpan.Zero; + + var count = 0; + var expectedCount = 5; + var evt = new ManualResetEvent(false); + col.Subscribe(t => + { + if (++count == expectedCount) + evt.Set(); + }, () => { }); + + Assert.True(evt.WaitOne(40)); + evt.Reset(); + + expectedCount = 6; + col.RemoveItem(GetThing(0)); + + Assert.True(evt.WaitOne(40)); + evt.Reset(); + + CollectionAssert.AreEqual(col, Enumerable.Range(1, 4).Select(x => GetThing(x, x))); + + col.Dispose(); + + } + [Test] public void DisposingThrows() { - var col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); col.Dispose(); - Assert.Throws<ObjectDisposedException>(() => col.SetFilter(null)); - Assert.Throws<ObjectDisposedException>(() => col.SetComparer(null)); + Assert.Throws<ObjectDisposedException>(() => col.Filter = null); + Assert.Throws<ObjectDisposedException>(() => col.Comparer = null); Assert.Throws<ObjectDisposedException>(() => col.Subscribe()); Assert.Throws<ObjectDisposedException>(() => col.AddItem(GetThing(1))); Assert.Throws<ObjectDisposedException>(() => col.RemoveItem(GetThing(1))); } - [Test] - public void MultipleSortingAndFiltering() + [Test, Category("Timings")] + public async Task MultipleSortingAndFiltering() { var expectedTotal = 20; var rnd = new Random(214748364); - var titles1 = Enumerable.Range(1, expectedTotal).Select(x => ((char)('a' + x)).ToString()).ToList(); - var dates1 = Enumerable.Range(1, expectedTotal).Select(x => Now + TimeSpan.FromMinutes(x)).ToList(); - - var idstack1 = new Stack<int>(Enumerable.Range(1, expectedTotal).OrderBy(rnd.Next)); - var datestack1 = new Stack<DateTimeOffset>(dates1); - var titlestack1 = new Stack<string>(titles1.OrderBy(_ => rnd.Next())); - - var titles2 = Enumerable.Range(1, expectedTotal).Select(x => ((char)('c' + x)).ToString()).ToList(); - var dates2 = Enumerable.Range(1, expectedTotal).Select(x => Now + TimeSpan.FromMinutes(x)).ToList(); - - var idstack2 = new Stack<int>(Enumerable.Range(1, expectedTotal).OrderBy(rnd.Next)); - var datestack2 = new Stack<DateTimeOffset>(new List<DateTimeOffset>() { - dates2[2], dates2[0], dates2[1], dates2[3], dates2[5], - dates2[9], dates2[15], dates2[6], dates2[7], dates2[8], - dates2[13], dates2[10], dates2[16], dates2[11], dates2[12], - dates2[14], dates2[17], dates2[18], dates2[19], dates2[4], - }); - var titlestack2 = new Stack<string>(titles2.OrderBy(_ => rnd.Next())); + var updatedAtMinutesStack = new Stack<int>(Enumerable.Range(1, expectedTotal).OrderBy(rnd.Next)); var list1 = Observable.Defer(() => Enumerable.Range(1, expectedTotal) .OrderBy(rnd.Next) - .Select(x => new Thing(idstack1.Pop(), titlestack1.Pop(), datestack1.Pop())) + .Select(x => GetThing(x, x, x, ((char)('a' + x)).ToString())) .ToObservable()) .Replay() .RefCount(); var list2 = Observable.Defer(() => Enumerable.Range(1, expectedTotal) .OrderBy(rnd.Next) - .Select(x => new Thing(idstack2.Pop(), titlestack2.Pop(), datestack2.Pop())) + .Select(x => GetThing(x, x, updatedAtMinutesStack.Pop(), ((char)('c' + x)).ToString())) .ToObservable()) .Replay() .RefCount(); - var col = new TrackingCollection<Thing>( + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( list1.Concat(list2), - OrderedComparer<Thing>.OrderByDescending(x => x.CreatedAt).Compare, + OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare, (item, idx, list) => idx < 5 - ) - { ProcessingDelay = TimeSpan.Zero }; + ); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.Subscribe(); - var count = 0; - var evt = new ManualResetEvent(false); - col.Subscribe(t => - { - if (++count == expectedTotal * 2) - evt.Set(); - }, () => { }); - - evt.WaitOne(); - evt.Reset(); + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout)); // it's initially sorted by date, so id list should not match CollectionAssert.AreNotEqual(list1.Select(x => x.Number).ToEnumerable(), list2.Select(x => x.Number).ToEnumerable()); - var sortlist = list1.ToEnumerable().ToArray(); - Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing>.OrderByDescending(x => x.CreatedAt).Compare)); + var sortlist = col.ToArray(); + Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing> + .OrderByDescending(x => x.UpdatedAt) + .ThenByDescending(x => x.CreatedAt).Compare)); CollectionAssert.AreEqual(sortlist.Take(5), col); - col.SetComparer(OrderedComparer<Thing>.OrderBy(x => x.Number).Compare); - sortlist = list1.ToEnumerable().ToArray(); + col.Comparer = OrderedComparer<Thing>.OrderBy(x => x.Number).Compare; + sortlist = col.ToArray(); Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing>.OrderBy(x => x.Number).Compare)); CollectionAssert.AreEqual(sortlist.Take(5), col); - col.SetComparer(OrderedComparer<Thing>.OrderBy(x => x.CreatedAt).Compare); - sortlist = list1.ToEnumerable().ToArray(); - Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing>.OrderBy(x => x.CreatedAt).Compare)); + col.Comparer = OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare; + sortlist = col.ToArray(); + Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing> + .OrderBy(x => x.UpdatedAt) + .ThenBy(x => x.CreatedAt).Compare)); CollectionAssert.AreEqual(sortlist.Take(5), col); - col.SetComparer(OrderedComparer<Thing>.OrderByDescending(x => x.Title).Compare); - sortlist = list1.ToEnumerable().ToArray(); + col.Comparer = OrderedComparer<Thing>.OrderByDescending(x => x.Title).Compare; + sortlist = col.ToArray(); Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing>.OrderByDescending(x => x.Title).Compare)); CollectionAssert.AreEqual(sortlist.Take(5), col); - col.SetComparer(OrderedComparer<Thing>.OrderBy(x => x.Title).Compare); - sortlist = list1.ToEnumerable().ToArray(); + col.Comparer = OrderedComparer<Thing>.OrderBy(x => x.Title).Compare; + sortlist = col.ToArray(); Array.Sort(sortlist, new LambdaComparer<Thing>(OrderedComparer<Thing>.OrderBy(x => x.Title).Compare)); CollectionAssert.AreEqual(sortlist.Take(5), col); col.Dispose(); } + + [Test] + public async Task ListeningTwiceWorks() + { + var count = 10; + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; + + var list1 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, count - i, "Run 1")).ToList()); + var list2 = new List<Thing>(Enumerable.Range(1, count).Select(i => GetThing(i, i, i + count, "Run 2")).ToList()); + +#pragma warning disable 4014 + col.Listen(list1.ToObservable()); + col.Subscribe(); + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout)); + + col.Listen(list2.ToObservable()); + col.Subscribe(); + await col.OriginalCompleted.Timeout(TimeSpan.FromMilliseconds(Timeout)); +#pragma warning restore 4014 + + CollectionAssert.AreEqual(list2, col); + } + + [Test] + public void AddingWithNoObservableSetThrows() + { + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(); + Assert.Throws<InvalidOperationException>(() => col.AddItem(new Thing())); + } + + [Test] + public void RemovingWithNoObservableSetThrows() + { + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(); + Assert.Throws<InvalidOperationException>(() => col.RemoveItem(new Thing())); + } + + [Test] + public async Task AddingBeforeSubscribingWorks() + { + ITrackingCollection<Thing> col = new TrackingCollection<Thing>(Observable.Empty<Thing>()); + ReplaySubject<Thing> done = new ReplaySubject<Thing>(); + col.AddItem(GetThing(1)); + col.AddItem(GetThing(2)); + var count = 0; + done.OnNext(null); + col.Subscribe(t => + { + done.OnNext(t); + if (++count == 2) + done.OnCompleted(); + }, () => {}); + + await done.Timeout(TimeSpan.FromMilliseconds(500)); + Assert.AreEqual(2, col.Count); + } + + [Test] + public void DoesNotUpdateThingIfTimeIsOlder() + { + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( + Observable.Never<Thing>(), + OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; + + var evt = new ManualResetEvent(false); + col.Subscribe(t => + { + evt.Set(); + }, () => { }); + + var createdAndUpdatedTime = DateTimeOffset.Now; + var olderUpdateTime = createdAndUpdatedTime.Subtract(TimeSpan.FromMinutes(1)); + + const string originalTitle = "Original Thing"; + + var originalThing = new Thing(1, originalTitle, createdAndUpdatedTime, createdAndUpdatedTime); + col.AddItem(originalThing); + + evt.WaitOne(); + evt.Reset(); + + Assert.AreEqual(originalTitle, col[0].Title); + + const string updatedTitle = "Updated Thing"; + + var updatedThing = new Thing(1, updatedTitle, createdAndUpdatedTime, olderUpdateTime); + col.AddItem(updatedThing); + + evt.WaitOne(); + evt.Reset(); + + Assert.AreEqual(originalTitle, col[0].Title); + + col.Dispose(); + } + + [Test] + public void DoesNotUpdateThingIfTimeIsEqual() + { + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( + Observable.Never<Thing>(), + OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; + + var evt = new ManualResetEvent(false); + col.Subscribe(t => + { + evt.Set(); + }, () => { }); + + var createdAndUpdatedTime = DateTimeOffset.Now; + + const string originalTitle = "Original Thing"; + + var originalThing = new Thing(1, originalTitle, createdAndUpdatedTime, createdAndUpdatedTime); + col.AddItem(originalThing); + + evt.WaitOne(); + evt.Reset(); + + Assert.AreEqual(originalTitle, col[0].Title); + + const string updatedTitle = "Updated Thing"; + + var updatedThing = new Thing(1, updatedTitle, createdAndUpdatedTime, createdAndUpdatedTime); + col.AddItem(updatedThing); + + evt.WaitOne(); + evt.Reset(); + + Assert.AreEqual(originalTitle, col[0].Title); + + col.Dispose(); + } + + [Test] + public void DoesUpdateThingIfTimeIsNewer() + { + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( + Observable.Never<Thing>(), + OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare); + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.ProcessingDelay = TimeSpan.Zero; + + var evt = new ManualResetEvent(false); + col.Subscribe(t => + { + evt.Set(); + }, () => { }); + + var createdAndUpdatedTime = DateTimeOffset.Now; + var newerUpdateTime = createdAndUpdatedTime.Add(TimeSpan.FromMinutes(1)); + + const string originalTitle = "Original Thing"; + + var originalThing = new Thing(1, originalTitle, createdAndUpdatedTime, createdAndUpdatedTime); + col.AddItem(originalThing); + + evt.WaitOne(); + evt.Reset(); + + Assert.AreEqual(originalTitle, col[0].Title); + + const string updatedTitle = "Updated Thing"; + + var updatedThing = new Thing(1, updatedTitle, createdAndUpdatedTime, newerUpdateTime); + col.AddItem(updatedThing); + + evt.WaitOne(); + evt.Reset(); + + Assert.AreEqual(updatedTitle, col[0].Title); + + col.Dispose(); + } + + + [Test] + public void ChangingSortingAndUpdatingItemsUpdatesSortCorrectly() + { + var source = new Subject<Thing>(); + + ITrackingCollection<Thing> col = new TrackingCollection<Thing>( + source); + col.Comparer = OrderedComparer<Thing>.OrderBy(x => x.UpdatedAt).Compare; + col.NewerComparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + col.Filter = (item, position, list) => + position == 2 || position == 3 || position == 5 || position == 7; + col.ProcessingDelay = TimeSpan.Zero; + + var count = 0; + var expectedCount = 0; + var evt = new ManualResetEvent(false); + + col.Subscribe(t => + { + if (++count == expectedCount) + evt.Set(); + }, () => { }); + + expectedCount = 9; + Enumerable.Range(0, expectedCount) + .Select(x => GetThing(x, x)) + .ForEach(x => Add(source, x)); + + evt.WaitOne(); + evt.Reset(); + CollectionAssert.AreEqual(new List<Thing> { + GetThing(2, 2), + GetThing(3, 3), + GetThing(5, 5), + GetThing(7, 7), + }, col); + + expectedCount = 10; + Add(source, GetThing(3, 3, 2)); + evt.WaitOne(); + evt.Reset(); + CollectionAssert.AreEqual(new List<Thing> { + GetThing(2, 2), + GetThing(3, 3), + GetThing(5, 5), + GetThing(7, 7), + }, col); + + expectedCount = 11; + Add(source, GetThing(3, 3, 4)); + evt.WaitOne(); + evt.Reset(); + CollectionAssert.AreEqual(new List<Thing> { + GetThing(2, 2), + GetThing(3, 3, 4), + GetThing(5, 5), + GetThing(7, 7), + }, col); + + expectedCount = 12; + Add(source, GetThing(3, 3, 6)); + evt.WaitOne(); + evt.Reset(); + CollectionAssert.AreEqual(new List<Thing> { + GetThing(2, 2), + GetThing(4, 4), + GetThing(3, 3, 6), + GetThing(7, 7), + }, col); + + col.Comparer = OrderedComparer<Thing>.OrderByDescending(x => x.UpdatedAt).Compare; + CollectionAssert.AreEqual(new List<Thing> { + GetThing(3, 3, 6), + GetThing(6, 6), + GetThing(4, 4), + GetThing(1, 1), + }, col); + + expectedCount = 13; + Add(source, GetThing(4, 4)); + evt.WaitOne(); + evt.Reset(); + + CollectionAssert.AreEqual(new List<Thing> { + GetThing(3, 3, 6), + GetThing(6, 6), + GetThing(4, 4), + GetThing(1, 1), + }, col); + + expectedCount = 14; + Add(source, GetThing(4, 4, 6)); + evt.WaitOne(); + evt.Reset(); + + CollectionAssert.AreEqual(new List<Thing> { + GetThing(3, 3, 6), + GetThing(6, 6), + GetThing(5, 5), + GetThing(1, 1), + }, col); + + expectedCount = 15; + Add(source, GetThing(5, 5, 6)); + evt.WaitOne(); + evt.Reset(); + + CollectionAssert.AreEqual(new List<Thing> { + GetThing(3, 3, 6), + GetThing(6, 6), + GetThing(5, 5, 6), + GetThing(1, 1), + }, col); + + col.Dispose(); + } } diff --git a/src/TrackingCollectionTests/TrackingCollectionTests.csproj b/test/TrackingCollectionTests/TrackingCollectionTests.csproj similarity index 78% rename from src/TrackingCollectionTests/TrackingCollectionTests.csproj rename to test/TrackingCollectionTests/TrackingCollectionTests.csproj index 0be83b2e4b..ccd62141fd 100644 --- a/src/TrackingCollectionTests/TrackingCollectionTests.csproj +++ b/test/TrackingCollectionTests/TrackingCollectionTests.csproj @@ -10,7 +10,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>TrackingCollectionTests</RootNamespace> <AssemblyName>TrackingCollectionTests</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> @@ -42,25 +42,8 @@ <StartupObject /> </PropertyGroup> <ItemGroup> - <Reference Include="nunit.core, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="nunit.core.interfaces, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.interfaces.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="nunit.util, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NUnitTestAdapter.2.0.0\lib\nunit.util.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="NUnit.VisualStudio.TestAdapter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=4cb40d35494691ac, processorArchitecture=MSIL"> - <HintPath>..\..\packages\NUnitTestAdapter.2.0.0\lib\NUnit.VisualStudio.TestAdapter.dll</HintPath> - <Private>False</Private> + <Reference Include="nunit.framework, Version=3.9.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> + <HintPath>..\..\packages\NUnit.3.9.0\lib\net45\nunit.framework.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=62aa029873c516b4, processorArchitecture=MSIL"> @@ -90,16 +73,16 @@ <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> </ItemGroup> </When> - <Otherwise> - <ItemGroup> - <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" /> - </ItemGroup> - </Otherwise> + <Otherwise /> </Choose> <ItemGroup> + <Compile Include="..\Helpers\SplatModeDetectorSetUp.cs"> + <Link>SplatModeDetectorSetUp.cs</Link> + </Compile> <Compile Include="ITestOutputHelper.cs" /> <Compile Include="TestBase.cs" /> <Compile Include="Thing.cs" /> + <Compile Include="ListenerCollectionTests.cs" /> <Compile Include="TrackingCollectionTests.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> @@ -117,15 +100,15 @@ <Project>{252ce1c2-027a-4445-a3c2-e4d6c80a935a}</Project> <Name>Splat-Net45</Name> </ProjectReference> - <ProjectReference Include="..\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> + <ProjectReference Include="..\..\src\GitHub.Exports.Reactive\GitHub.Exports.Reactive.csproj"> <Project>{e4ed0537-d1d9-44b6-9212-3096d7c3f7a1}</Project> <Name>GitHub.Exports.Reactive</Name> </ProjectReference> - <ProjectReference Include="..\GitHub.Exports\GitHub.Exports.csproj"> + <ProjectReference Include="..\..\src\GitHub.Exports\GitHub.Exports.csproj"> <Project>{9aea02db-02b5-409c-b0ca-115d05331a6b}</Project> <Name>GitHub.Exports</Name> </ProjectReference> - <ProjectReference Include="..\GitHub.Extensions\GitHub.Extensions.csproj"> + <ProjectReference Include="..\..\src\GitHub.Extensions\GitHub.Extensions.csproj"> <Project>{6afe2e2d-6db0-4430-a2ea-f5f5388d2f78}</Project> <Name>GitHub.Extensions</Name> </ProjectReference> diff --git a/test/TrackingCollectionTests/packages.config b/test/TrackingCollectionTests/packages.config new file mode 100644 index 0000000000..e212deab8f --- /dev/null +++ b/test/TrackingCollectionTests/packages.config @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="NUnit" version="3.9.0" targetFramework="net461" /> + <package id="Rx-Core" version="2.2.5-custom" targetFramework="net452" /> + <package id="Rx-Interfaces" version="2.2.5-custom" targetFramework="net452" /> + <package id="Rx-Linq" version="2.2.5-custom" targetFramework="net452" /> + <package id="Rx-Main" version="2.2.5-custom" targetFramework="net452" /> + <package id="Rx-PlatformServices" version="2.2.5-custom" targetFramework="net452" /> + <package id="Rx-XAML" version="2.2.5-custom" targetFramework="net452" /> +</packages> \ No newline at end of file diff --git a/tools/Debugging Tools for Windows/cdb.exe b/tools/Debugging Tools for Windows/cdb.exe new file mode 100644 index 0000000000..efcb638cab Binary files /dev/null and b/tools/Debugging Tools for Windows/cdb.exe differ diff --git a/tools/Debugging Tools for Windows/dbgeng.dll b/tools/Debugging Tools for Windows/dbgeng.dll new file mode 100644 index 0000000000..c15fb3d070 Binary files /dev/null and b/tools/Debugging Tools for Windows/dbgeng.dll differ diff --git a/tools/Debugging Tools for Windows/dbghelp.dll b/tools/Debugging Tools for Windows/dbghelp.dll new file mode 100644 index 0000000000..59b92251fa Binary files /dev/null and b/tools/Debugging Tools for Windows/dbghelp.dll differ diff --git a/tools/Debugging Tools for Windows/psscor4.dll b/tools/Debugging Tools for Windows/psscor4.dll new file mode 100644 index 0000000000..d185bbc7a5 Binary files /dev/null and b/tools/Debugging Tools for Windows/psscor4.dll differ diff --git a/tools/Debugging Tools for Windows/symsrv.dll b/tools/Debugging Tools for Windows/symsrv.dll new file mode 100644 index 0000000000..08a24e8224 Binary files /dev/null and b/tools/Debugging Tools for Windows/symsrv.dll differ diff --git a/tools/Debugging Tools for Windows/symstore.exe b/tools/Debugging Tools for Windows/symstore.exe new file mode 100644 index 0000000000..1413262815 Binary files /dev/null and b/tools/Debugging Tools for Windows/symstore.exe differ diff --git a/tools/Debugging Tools for Windows/winext/ext.dll b/tools/Debugging Tools for Windows/winext/ext.dll new file mode 100644 index 0000000000..70f4ddab21 Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/ext.dll differ diff --git a/tools/Debugging Tools for Windows/winext/kext.dll b/tools/Debugging Tools for Windows/winext/kext.dll new file mode 100644 index 0000000000..ad42047f31 Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/kext.dll differ diff --git a/tools/Debugging Tools for Windows/winext/logexts.dll b/tools/Debugging Tools for Windows/winext/logexts.dll new file mode 100644 index 0000000000..dba67417d9 Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/logexts.dll differ diff --git a/tools/Debugging Tools for Windows/winext/manifest/advapi32.h b/tools/Debugging Tools for Windows/winext/manifest/advapi32.h new file mode 100644 index 0000000000..9b4da0328e --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/advapi32.h @@ -0,0 +1,915 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// AdvApi32 Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category AdvApi32: + + +module ADVAPI32.DLL: + +typedef PVOID PSID; + +typedef ULONG SECURITY_INFORMATION; +typedef SECURITY_INFORMATION *PSECURITY_INFORMATION; +typedef PVOID PSECURITY_DESCRIPTOR; +typedef PVOID PGENERIC_MAPPING; +typedef PVOID PPRIVILEGE_SET; + +value int SidNameUse +{ +#define SidTypeUser 1 +#define SidTypeGroup 2 +#define SidTypeDomain 3 +#define SidTypeAlias 4 +#define SidTypeWellKnownGroup 5 +#define SidTypeDeletedAccount 6 +#define SidTypeInvalid 7 +#define SidTypeUnknown 8 +#define SidTypeComputer 9 +}; + +typedef struct _SID_IDENTIFIER_AUTHORITY { + BYTE Value[6]; +} SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY; + +FailOnFalse [gle] AccessCheck([in] PSECURITY_DESCRIPTOR pSecurityDescriptor, + [in] HANDLE ClientToken, + [in] DWORD DesiredAccess, + [in] PGENERIC_MAPPING GenericMapping, + [out] PPRIVILEGE_SET PrivilegeSet, + [out] LPDWORD PrivilegeSetLength, + [out] LPDWORD GrantedAccess, + [out] LPBOOL AccessStatus); + +FailOnFalse [gle] AllocateAndInitializeSid(PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority, + BYTE nSubAuthorityCount, + DWORD nSubAuthority0, + DWORD nSubAuthority1, + DWORD nSubAuthority2, + DWORD nSubAuthority3, + DWORD nSubAuthority4, + DWORD nSubAuthority5, + DWORD nSubAuthority6, + DWORD nSubAuthority7, + [out] PSID* pSid); + +FailOnFalse [gle] EqualSid(PSID pSid1, + PSID pSid2); + +FailOnFalse [gle] GetFileSecurityA(LPCSTR lpFileName, + SECURITY_INFORMATION RequestedInformation, + [out] PSECURITY_DESCRIPTOR pSecurityDescriptor, + DWORD nLength, + [out] LPDWORD lpnLengthNeeded); + +FailOnFalse [gle] GetFileSecurityW(LPCWSTR lpFileName, + SECURITY_INFORMATION RequestedInformation, + [out] PSECURITY_DESCRIPTOR pSecurityDescriptor, + DWORD nLength, + [out] LPDWORD lpnLengthNeeded); + +FailOnFalse [gle] GetKernelObjectSecurity(HANDLE Handle, + SECURITY_INFORMATION RequestedInformation, + [out] PSECURITY_DESCRIPTOR pSecurityDescriptor, + DWORD nLength, + [out] LPDWORD lpnLengthNeeded); + +DWORD GetLengthSid(PSID pSid); + +FailOnFalse [gle] GetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR pSecurityDescriptor, + [out] PSID *pGroup, + [out] LPBOOL lpbGroupDefaulted); + +FailOnFalse [gle] GetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR pSecurityDescriptor, + [out] PSID *pOwner, + [out] LPBOOL lpbOwnerDefaulted); + +PSID_IDENTIFIER_AUTHORITY GetSidIdentifierAuthority(PSID pSid); + +PDWORD GetSidSubAuthority(PSID pSid, + DWORD nSubAuthority); + +PBYTE GetSidSubAuthorityCount(PSID pSid); + +FailOnFalse IsValidSid(PSID pSid); + +FailOnFalse [gle] LookupAccountNameA(LPCSTR lpSystemName, + LPCSTR lpAccountName, + [out] PSID Sid, + [out] LPDWORD cbSid, + [out] LPSTR ReferencedDomainName, + [out] LPDWORD cbReferencedDomainName, + [out] SidNameUse* peUse); + +FailOnFalse [gle] LookupAccountNameW(LPCWSTR lpSystemName, + LPCWSTR lpAccountName, + [out] PSID Sid, + [out] LPDWORD cbSid, + [out] LPWSTR ReferencedDomainName, + [out] LPDWORD cbReferencedDomainName, + [out] SidNameUse* peUse); + +FailOnFalse [gle] LookupAccountSidA(LPCSTR lpSystemName, + PSID Sid, + [out] LPSTR Name, + [out] LPDWORD cbName, + [out] LPSTR ReferencedDomainName, + [out] LPDWORD cbReferencedDomainName, + [out] SidNameUse* peUse); + +FailOnFalse [gle] LookupAccountSidW(LPCWSTR lpSystemName, + PSID Sid, + [out] LPWSTR Name, + [out] LPDWORD cbName, + [out] LPWSTR ReferencedDomainName, + [out] LPDWORD cbReferencedDomainName, + [out] SidNameUse* peUse); + +// ------------------------------------------------------------ +// +// Services functions +// +// ------------------------------------------------------------ + +// +// Value to indicate no change to an optional parameter +// + + +// +// Service Types (Bit Mask) +// +mask DWORD ServiceType +{ +#define SERVICE_KERNEL_DRIVER 0x00000001 +#define SERVICE_FILE_SYSTEM_DRIVER 0x00000002 +#define SERVICE_ADAPTER 0x00000004 +#define SERVICE_RECOGNIZER_DRIVER 0x00000008 + +#define SERVICE_WIN32_OWN_PROCESS 0x00000010 +#define SERVICE_WIN32_SHARE_PROCESS 0x00000020 + +#define SERVICE_INTERACTIVE_PROCESS 0x00000100 + +}; + + +// +// Start Type +// + +value DWORD ServiceStartType +{ +#define SERVICE_BOOT_START 0x00000000 +#define SERVICE_SYSTEM_START 0x00000001 +#define SERVICE_AUTO_START 0x00000002 +#define SERVICE_DEMAND_START 0x00000003 +#define SERVICE_DISABLED 0x00000004 +}; + +// +// Error control type +// +value DWORD ServiceErrorControlType +{ +#define SERVICE_ERROR_IGNORE 0x00000000 +#define SERVICE_ERROR_NORMAL 0x00000001 +#define SERVICE_ERROR_SEVERE 0x00000002 +#define SERVICE_ERROR_CRITICAL 0x00000003 +}; + +// +// Service State -- for Enum Requests (Bit Mask) +// + +value DWORD ServiceState +{ +#define SERVICE_ACTIVE 0x00000001 +#define SERVICE_INACTIVE 0x00000002 +#define SERVICE_STATE_ALL 0x00000003 +#define SERVICE_NO_CHANGE 0xffffffff +}; + +// +// Controls +// +value DWORD ServiceControl +{ +#define SERVICE_CONTROL_STOP 0x00000001 +#define SERVICE_CONTROL_PAUSE 0x00000002 +#define SERVICE_CONTROL_CONTINUE 0x00000003 +#define SERVICE_CONTROL_INTERROGATE 0x00000004 +#define SERVICE_CONTROL_SHUTDOWN 0x00000005 +#define SERVICE_CONTROL_PARAMCHANGE 0x00000006 +#define SERVICE_CONTROL_NETBINDADD 0x00000007 +#define SERVICE_CONTROL_NETBINDREMOVE 0x00000008 +#define SERVICE_CONTROL_NETBINDENABLE 0x00000009 +#define SERVICE_CONTROL_NETBINDDISABLE 0x0000000A +#define SERVICE_CONTROL_DEVICEEVENT 0x0000000B +#define SERVICE_CONTROL_HARDWAREPROFILECHANGE 0x0000000C +#define SERVICE_CONTROL_POWEREVENT 0x0000000D +#define SERVICE_CONTROL_SESSIONCHANGE 0x0000000E +}; + +// +// Service State -- for CurrentState +// +value DWORD ServiceCurrentState +{ +#define SERVICE_STOPPED 0x00000001 +#define SERVICE_START_PENDING 0x00000002 +#define SERVICE_STOP_PENDING 0x00000003 +#define SERVICE_RUNNING 0x00000004 +#define SERVICE_CONTINUE_PENDING 0x00000005 +#define SERVICE_PAUSE_PENDING 0x00000006 +#define SERVICE_PAUSED 0x00000007 +}; + +// +// Controls Accepted (Bit Mask) +// +mask DWORD ServiceControlsAccepted +{ +#define SERVICE_ACCEPT_STOP 0x00000001 +#define SERVICE_ACCEPT_PAUSE_CONTINUE 0x00000002 +#define SERVICE_ACCEPT_SHUTDOWN 0x00000004 +#define SERVICE_ACCEPT_PARAMCHANGE 0x00000008 +#define SERVICE_ACCEPT_NETBINDCHANGE 0x00000010 +#define SERVICE_ACCEPT_HARDWAREPROFILECHANGE 0x00000020 +#define SERVICE_ACCEPT_POWEREVENT 0x00000040 +#define SERVICE_ACCEPT_SESSIONCHANGE 0x00000080 +}; + +// +// Service Control Manager object specific access types +// +mask DWORD SCManagerAccess +{ +#define SC_MANAGER_CONNECT 0x0001 +#define SC_MANAGER_CREATE_SERVICE 0x0002 +#define SC_MANAGER_ENUMERATE_SERVICE 0x0004 +#define SC_MANAGER_LOCK 0x0008 +#define SC_MANAGER_QUERY_LOCK_STATUS 0x0010 +#define SC_MANAGER_MODIFY_BOOT_CONFIG 0x0020 +}; + +// +// Service object specific access type +// +mask DWORD ServiceObjectAccess +{ +#define SERVICE_QUERY_CONFIG 0x0001 +#define SERVICE_CHANGE_CONFIG 0x0002 +#define SERVICE_QUERY_STATUS 0x0004 +#define SERVICE_ENUMERATE_DEPENDENTS 0x0008 +#define SERVICE_START 0x0010 +#define SERVICE_STOP 0x0020 +#define SERVICE_PAUSE_CONTINUE 0x0040 +#define SERVICE_INTERROGATE 0x0080 +#define SERVICE_USER_DEFINED_CONTROL 0x0100 +}; + +// +// Service flags for QueryServiceStatusEx +// +mask DWORD QueryServiceStatusExFlags +{ +#define SERVICE_RUNS_IN_SYSTEM_PROCESS 0x00000001 +}; + +// +// Info levels for ChangeServiceConfig2 and QueryServiceConfig2 +// +value LONG ServiceConfig2Values +{ +#define SERVICE_CONFIG_DESCRIPTION 1 +#define SERVICE_CONFIG_FAILURE_ACTIONS 2 +}; + +// +// Service description string +// +typedef struct _SERVICE_DESCRIPTIONA { + LPSTR lpDescription; +} SERVICE_DESCRIPTIONA, *LPSERVICE_DESCRIPTIONA; + +// +// Service description string +// +typedef struct _SERVICE_DESCRIPTIONW { + LPWSTR lpDescription; +} SERVICE_DESCRIPTIONW, *LPSERVICE_DESCRIPTIONW; + +// +// Actions to take on service failure +// +value LONG SC_ACTION_TYPE +{ +#define SC_ACTION_NONE 0 +#define SC_ACTION_RESTART 1 +#define SC_ACTION_REBOOT 2 +#define SC_ACTION_RUN_COMMAND 3 +}; + +typedef struct _SC_ACTION { + SC_ACTION_TYPE Type; + DWORD Delay; +} SC_ACTION, *LPSC_ACTION; + +typedef struct _SERVICE_FAILURE_ACTIONSA { + DWORD dwResetPeriod; + LPSTR lpRebootMsg; + LPSTR lpCommand; + DWORD cActions; + SC_ACTION * lpsaActions; +} SERVICE_FAILURE_ACTIONSA, *LPSERVICE_FAILURE_ACTIONSA; + +typedef struct _SERVICE_FAILURE_ACTIONSW { + DWORD dwResetPeriod; + LPWSTR lpRebootMsg; + LPWSTR lpCommand; + DWORD cActions; + SC_ACTION * lpsaActions; +} SERVICE_FAILURE_ACTIONSW, *LPSERVICE_FAILURE_ACTIONSW; + +typedef HANDLE SC_HANDLE; +typedef SC_HANDLE *LPSC_HANDLE ; +typedef HANDLE SERVICE_STATUS_HANDLE; + +// +// Info levels for QueryServiceStatusEx +// + +value LONG SC_STATUS_TYPE +{ +#define SC_STATUS_PROCESS_INFO 0 +}; + +// +// Info levels for EnumServicesStatusEx +// +value LONG SC_ENUM_TYPE +{ +#define SC_ENUM_PROCESS_INFO 0 +}; + +// +// Service Status Structures +// + +typedef struct _SERVICE_STATUS { + ServiceType dwServiceType; + ServiceCurrentState dwCurrentState; + ServiceControlsAccepted dwControlsAccepted; + DWORD dwWin32ExitCode; + DWORD dwServiceSpecificExitCode; + DWORD dwCheckPoint; + DWORD dwWaitHint; +} SERVICE_STATUS, *LPSERVICE_STATUS; + +typedef struct _SERVICE_STATUS_PROCESS { + ServiceType dwServiceType; + ServiceCurrentState dwCurrentState; + ServiceControlsAccepted dwControlsAccepted; + DWORD dwWin32ExitCode; + DWORD dwServiceSpecificExitCode; + DWORD dwCheckPoint; + DWORD dwWaitHint; + DWORD dwProcessId; + DWORD dwServiceFlags; +} SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS; + +// +// Service Status Enumeration Structure +// + +typedef struct _ENUM_SERVICE_STATUSA { + LPSTR lpServiceName; + LPSTR lpDisplayName; + SERVICE_STATUS ServiceStatus; +} ENUM_SERVICE_STATUSA, *LPENUM_SERVICE_STATUSA; +typedef struct _ENUM_SERVICE_STATUSW { + LPWSTR lpServiceName; + LPWSTR lpDisplayName; + SERVICE_STATUS ServiceStatus; +} ENUM_SERVICE_STATUSW, *LPENUM_SERVICE_STATUSW; + +typedef struct _ENUM_SERVICE_STATUS_PROCESSA { + LPSTR lpServiceName; + LPSTR lpDisplayName; + SERVICE_STATUS_PROCESS ServiceStatusProcess; +} ENUM_SERVICE_STATUS_PROCESSA, *LPENUM_SERVICE_STATUS_PROCESSA; +typedef struct _ENUM_SERVICE_STATUS_PROCESSW { + LPWSTR lpServiceName; + LPWSTR lpDisplayName; + SERVICE_STATUS_PROCESS ServiceStatusProcess; +} ENUM_SERVICE_STATUS_PROCESSW, *LPENUM_SERVICE_STATUS_PROCESSW; + +// +// Structures for the Lock API functions +// + +typedef LPVOID SC_LOCK; + +typedef struct _QUERY_SERVICE_LOCK_STATUSA { + DWORD fIsLocked; + LPSTR lpLockOwner; + DWORD dwLockDuration; +} QUERY_SERVICE_LOCK_STATUSA, *LPQUERY_SERVICE_LOCK_STATUSA; +typedef struct _QUERY_SERVICE_LOCK_STATUSW { + DWORD fIsLocked; + LPWSTR lpLockOwner; + DWORD dwLockDuration; +} QUERY_SERVICE_LOCK_STATUSW, *LPQUERY_SERVICE_LOCK_STATUSW; + +// +// Query Service Configuration Structure +// + +typedef struct _QUERY_SERVICE_CONFIGA { + ServiceType dwServiceType; + ServiceStartType dwStartType; + ServiceErrorControlType dwErrorControl; + LPSTR lpBinaryPathName; + LPSTR lpLoadOrderGroup; + DWORD dwTagId; + LPSTR lpDependencies; + LPSTR lpServiceStartName; + LPSTR lpDisplayName; +} QUERY_SERVICE_CONFIGA, *LPQUERY_SERVICE_CONFIGA; +typedef struct _QUERY_SERVICE_CONFIGW { + ServiceType dwServiceType; + ServiceStartType dwStartType; + ServiceErrorControlType dwErrorControl; + LPWSTR lpBinaryPathName; + LPWSTR lpLoadOrderGroup; + DWORD dwTagId; + LPWSTR lpDependencies; + LPWSTR lpServiceStartName; + LPWSTR lpDisplayName; +} QUERY_SERVICE_CONFIGW, *LPQUERY_SERVICE_CONFIGW; + +// +// Service Start Table +// + +typedef struct _SERVICE_TABLE_ENTRYA { + LPSTR lpServiceName; + LPVOID lpServiceProc; +}SERVICE_TABLE_ENTRYA, *LPSERVICE_TABLE_ENTRYA; +typedef struct _SERVICE_TABLE_ENTRYW { + LPWSTR lpServiceName; + LPVOID lpServiceProc; +}SERVICE_TABLE_ENTRYW, *LPSERVICE_TABLE_ENTRYW; + + +BOOL + +ChangeServiceConfigA( + SC_HANDLE hService, + ServiceType dwServiceType, + ServiceStartType dwStartType, + ServiceErrorControlType dwErrorControl, + LPCSTR lpBinaryPathName, + LPCSTR lpLoadOrderGroup, + [out] LPDWORD lpdwTagId, + LPCSTR lpDependencies, + LPCSTR lpServiceStartName, + LPCSTR lpPassword, + LPCSTR lpDisplayName + ); + +BOOL + +ChangeServiceConfigW( + SC_HANDLE hService, + ServiceType dwServiceType, + ServiceStartType dwStartType, + ServiceErrorControlType dwErrorControl, + LPCWSTR lpBinaryPathName, + LPCWSTR lpLoadOrderGroup, + [out] LPDWORD lpdwTagId, + LPCWSTR lpDependencies, + LPCWSTR lpServiceStartName, + LPCWSTR lpPassword, + LPCWSTR lpDisplayName + ); + + +BOOL + +ChangeServiceConfig2A( + SC_HANDLE hService, + ServiceConfig2Values dwInfoLevel, + LPVOID lpInfo + ); + +BOOL + +ChangeServiceConfig2W( + SC_HANDLE hService, + ServiceConfig2Values dwInfoLevel, + LPVOID lpInfo + ); + + +BOOL + +CloseServiceHandle( + SC_HANDLE hSCObject + ); + + +BOOL + +ControlService( + SC_HANDLE hService, + ServiceControl dwControl, + LPSERVICE_STATUS lpServiceStatus + ); + + +SC_HANDLE + +CreateServiceA( + SC_HANDLE hSCManager, + LPCSTR lpServiceName, + LPCSTR lpDisplayName, + AccessMode dwDesiredAccess, + ServiceType dwServiceType, + ServiceStartType dwStartType, + ServiceErrorControlType dwErrorControl, + LPCSTR lpBinaryPathName, + LPCSTR lpLoadOrderGroup, + [out] LPDWORD lpdwTagId, + LPCSTR lpDependencies, + LPCSTR lpServiceStartName, + LPCSTR lpPassword + ); + +SC_HANDLE + +CreateServiceW( + SC_HANDLE hSCManager, + LPCWSTR lpServiceName, + LPCWSTR lpDisplayName, + AccessMode dwDesiredAccess, + ServiceType dwServiceType, + ServiceStartType dwStartType, + ServiceErrorControlType dwErrorControl, + LPCWSTR lpBinaryPathName, + LPCWSTR lpLoadOrderGroup, + [out] LPDWORD lpdwTagId, + LPCWSTR lpDependencies, + LPCWSTR lpServiceStartName, + LPCWSTR lpPassword + ); + + +BOOL + +DeleteService( + SC_HANDLE hService + ); + + +BOOL + +EnumDependentServicesA( + SC_HANDLE hService, + ServiceState dwServiceState, + [out] LPENUM_SERVICE_STATUSA lpServices, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded, + [out] LPDWORD lpServicesReturned + ); + +BOOL + +EnumDependentServicesW( + SC_HANDLE hService, + ServiceState dwServiceState, + [out] LPENUM_SERVICE_STATUSW lpServices, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded, + [out] LPDWORD lpServicesReturned + ); + + +BOOL + +EnumServicesStatusA( + SC_HANDLE hSCManager, + ServiceType dwServiceType, + ServiceState dwServiceState, + [out] LPENUM_SERVICE_STATUSA lpServices, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded, + [out] LPDWORD lpServicesReturned, + [out] LPDWORD lpResumeHandle + ); + +BOOL + +EnumServicesStatusW( + SC_HANDLE hSCManager, + ServiceType dwServiceType, + ServiceState dwServiceState, + [out] LPENUM_SERVICE_STATUSW lpServices, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded, + [out] LPDWORD lpServicesReturned, + [out] LPDWORD lpResumeHandle + ); + + +BOOL + +EnumServicesStatusExA( + SC_HANDLE hSCManager, + SC_ENUM_TYPE InfoLevel, + ServiceType dwServiceType, + ServiceState dwServiceState, + LPBYTE lpServices, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded, + [out] LPDWORD lpServicesReturned, + [out] LPDWORD lpResumeHandle, + LPCSTR pszGroupName + ); + +BOOL + +EnumServicesStatusExW( + SC_HANDLE hSCManager, + SC_ENUM_TYPE InfoLevel, + ServiceType dwServiceType, + ServiceState dwServiceState, + LPBYTE lpServices, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded, + [out] LPDWORD lpServicesReturned, + [out] LPDWORD lpResumeHandle, + LPCWSTR pszGroupName + ); + + +BOOL + +GetServiceKeyNameA( + SC_HANDLE hSCManager, + LPCSTR lpDisplayName, + [out] LPSTR lpServiceName, + [out] LPDWORD lpcchBuffer + ); + +BOOL + +GetServiceKeyNameW( + SC_HANDLE hSCManager, + LPCWSTR lpDisplayName, + [out] LPWSTR lpServiceName, + [out] LPDWORD lpcchBuffer + ); + + +BOOL + +GetServiceDisplayNameA( + SC_HANDLE hSCManager, + LPCSTR lpServiceName, + [out] LPSTR lpDisplayName, + [out] LPDWORD lpcchBuffer + ); + +BOOL + +GetServiceDisplayNameW( + SC_HANDLE hSCManager, + LPCWSTR lpServiceName, + [out] LPWSTR lpDisplayName, + [out] LPDWORD lpcchBuffer + ); + + +SC_LOCK + +LockServiceDatabase( + SC_HANDLE hSCManager + ); + + +BOOL + +NotifyBootConfigStatus( + BOOL BootAcceptable + ); + + +SC_HANDLE + +OpenSCManagerA( + LPCSTR lpMachineName, + LPCSTR lpDatabaseName, + AccessMode dwDesiredAccess + ); + +SC_HANDLE + +OpenSCManagerW( + LPCWSTR lpMachineName, + LPCWSTR lpDatabaseName, + AccessMode dwDesiredAccess + ); + + +SC_HANDLE + +OpenServiceA( + SC_HANDLE hSCManager, + LPCSTR lpServiceName, + AccessMode dwDesiredAccess + ); + +SC_HANDLE + +OpenServiceW( + SC_HANDLE hSCManager, + LPCWSTR lpServiceName, + AccessMode dwDesiredAccess + ); + + +BOOL + +QueryServiceConfigA( + SC_HANDLE hService, + [out] LPQUERY_SERVICE_CONFIGA lpServiceConfig, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded + ); + +BOOL + +QueryServiceConfigW( + SC_HANDLE hService, + [out] LPQUERY_SERVICE_CONFIGW lpServiceConfig, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded + ); + + +BOOL + +QueryServiceConfig2A( + SC_HANDLE hService, + ServiceConfig2Values dwInfoLevel, + LPBYTE lpBuffer, + DWORD cbBufSize, + [out] LPDWORD pcbBytesNeeded + ); + +BOOL + +QueryServiceConfig2W( + SC_HANDLE hService, + ServiceConfig2Values dwInfoLevel, + LPBYTE lpBuffer, + DWORD cbBufSize, + LPDWORD pcbBytesNeeded + ); + + +BOOL + +QueryServiceLockStatusA( + SC_HANDLE hSCManager, + [OUT] LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus, + DWORD cbBufSize, + [OUT] LPDWORD pcbBytesNeeded + ); + +BOOL + +QueryServiceLockStatusW( + SC_HANDLE hSCManager, + [OUT] LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus, + DWORD cbBufSize, + [OUT] LPDWORD pcbBytesNeeded + ); + + +BOOL + +QueryServiceObjectSecurity( + SC_HANDLE hService, + SECURITY_INFORMATION dwSecurityInformation, + [OUT] PSECURITY_DESCRIPTOR lpSecurityDescriptor, + DWORD cbBufSize, + [OUT] LPDWORD pcbBytesNeeded + ); + + +BOOL + +QueryServiceStatus( + SC_HANDLE hService, + [OUT] LPSERVICE_STATUS lpServiceStatus + ); + + +BOOL + +QueryServiceStatusEx( + SC_HANDLE hService, + SC_STATUS_TYPE InfoLevel, + LPBYTE lpBuffer, + DWORD cbBufSize, + [OUT] LPDWORD pcbBytesNeeded + ); + + +SERVICE_STATUS_HANDLE + +RegisterServiceCtrlHandlerA( + LPCSTR lpServiceName, + LPVOID lpHandlerProc + ); + +SERVICE_STATUS_HANDLE + +RegisterServiceCtrlHandlerW( + LPCWSTR lpServiceName, + LPVOID lpHandlerProc + ); + + +SERVICE_STATUS_HANDLE + +RegisterServiceCtrlHandlerExA( + LPCSTR lpServiceName, + LPVOID lpHandlerProc, + LPVOID lpContext + ); + +SERVICE_STATUS_HANDLE + +RegisterServiceCtrlHandlerExW( + LPCWSTR lpServiceName, + LPVOID lpHandlerProc, + LPVOID lpContext + ); + + +BOOL + +SetServiceObjectSecurity( + SC_HANDLE hService, + SECURITY_INFORMATION dwSecurityInformation, + [OUT] PSECURITY_DESCRIPTOR lpSecurityDescriptor + ); + + +BOOL + +SetServiceStatus( + SERVICE_STATUS_HANDLE hServiceStatus, + LPSERVICE_STATUS lpServiceStatus + ); + + +BOOL + +StartServiceCtrlDispatcherA( + SERVICE_TABLE_ENTRYA *lpServiceStartTable + ); + +BOOL + +StartServiceCtrlDispatcherW( + SERVICE_TABLE_ENTRYW *lpServiceStartTable + ); + + + +BOOL + +StartServiceA( + SC_HANDLE hService, + DWORD dwNumServiceArgs, + LPCSTR *lpServiceArgVectors + ); + +BOOL + +StartServiceW( + SC_HANDLE hService, + DWORD dwNumServiceArgs, + LPCWSTR *lpServiceArgVectors + ); + +BOOL +UnlockServiceDatabase( + SC_LOCK ScLock + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/avifile.h b/tools/Debugging Tools for Windows/winext/manifest/avifile.h new file mode 100644 index 0000000000..9b0db54a3e --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/avifile.h @@ -0,0 +1,56 @@ +category AVIFileExports: + +typedef struct _AVIFILEINFOA { + DWORD dwMaxBytesPerSec; // max. transfer rate + DWORD dwFlags; // the ever-present flags + DWORD dwCaps; + DWORD dwStreams; + DWORD dwSuggestedBufferSize; + + DWORD dwWidth; + DWORD dwHeight; + + DWORD dwScale; + DWORD dwRate; /* dwRate / dwScale == samples/second */ + DWORD dwLength; + + DWORD dwEditCount; + + char szFileType[64]; // descriptive string for file type? +} AVIFILEINFOA; +typedef AVIFILEINFOA * LPAVIFILEINFOA; + +typedef struct _AVIFILEINFOW { + DWORD dwMaxBytesPerSec; // max. transfer rate + DWORD dwFlags; // the ever-present flags + DWORD dwCaps; + DWORD dwStreams; + DWORD dwSuggestedBufferSize; + + DWORD dwWidth; + DWORD dwHeight; + + DWORD dwScale; + DWORD dwRate; /* dwRate / dwScale == samples/second */ + DWORD dwLength; + + DWORD dwEditCount; + + WCHAR szFileType[64]; // descriptive string for file type? +} AVIFILEINFOW; +typedef AVIFILEINFOW * LPAVIFILEINFOW; + +typedef DWORD AVIFILE; +typedef DWORD* PAVIFILE; + +VOID AVIFileInit(); +VOID AVIFileExit(); + +ULONG AVIFileAddRef(PAVIFILE pfile); +ULONG AVIFileRelease(PAVIFILE pfile); + +STDAPI AVIFileOpenA(PAVIFILE* ppfile, LPCSTR szFile, UINT mode, CLSID pclsidHandler); +STDAPI AVIFileOpenW(PAVIFILE* ppfile, LPCWSTR szFile, UINT mode, CLSID pclsidHandler); + +STDAPI AVIFileInfoA(PAVIFILE pfile, LPAVIFILEINFOA pfi, LONG lSize); +STDAPI AVIFileInfoW(PAVIFILE pfile, LPAVIFILEINFOW pfi, LONG lSize); diff --git a/tools/Debugging Tools for Windows/winext/manifest/clipboard.h b/tools/Debugging Tools for Windows/winext/manifest/clipboard.h new file mode 100644 index 0000000000..5b3c500897 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/clipboard.h @@ -0,0 +1,80 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Clipboard Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +value UINT ClipboardFormats +{ +#define CF_TEXT 1 +#define CF_BITMAP 2 +#define CF_METAFILEPICT 3 +#define CF_SYLK 4 +#define CF_DIF 5 +#define CF_TIFF 6 +#define CF_OEMTEXT 7 +#define CF_DIB 8 +#define CF_PALETTE 9 +#define CF_PENDATA 10 +#define CF_RIFF 11 +#define CF_WAVE 12 +#define CF_UNICODETEXT 13 +#define CF_ENHMETAFILE 14 +#define CF_HDROP 15 +#define CF_LOCALE 16 +#define CF_MAX 17 +}; + +value INT ClipboardStatus +{ +#define ClipboardEmpty 0 +#define ClipboardContainsUnknownFormat -1 +}; + +category Clipboard: + +BOOL ChangeClipboardChain( + HWND hWndRemove, + HWND hWndNewNext); + +FailOnFalse [gle] CloseClipboard(); + +LongFailIfZero [gle] CountClipboardFormats(); + +FailOnFalse [gle] EmptyClipboard(); + +LongFailIfZero [gle] EnumClipboardFormats(ClipboardFormats format); + +HANDLE [gle] GetClipboardData(ClipboardFormats uFormat); + +LongFailIfZero [gle] GetClipboardFormatNameA(UINT format, + [out] LPSTR lpszFormatName, + int cchMaxCount); + +LongFailIfZero [gle] GetClipboardFormatNameW(UINT format, + [out] LPWSTR lpszFormatName, + int cchMaxCount); + +DwordFailIfZero [gle] GetClipboardOwner(); + +DWORD GetClipboardSequenceNumber(); + +DwordFailIfZero [gle] GetClipboardViewer(); + +DwordFailIfZero [gle] GetOpenClipboardWindow(); + +ClipboardStatus [gle] GetPriorityClipboardFormat(ClipboardFormats* paFormatPriorityList, + int cFormats); + +BOOL [gle] IsClipboardFormatAvailable(ClipboardFormats format); + +FailOnFalse [gle] OpenClipboard(HWND hWndNewOwner); + +LongFailIfZero [gle] RegisterClipboardFormatA(LPCSTR lpszFormat); + +LongFailIfZero [gle] RegisterClipboardFormatW(LPCWSTR lpszFormat); + +HANDLE [gle] SetClipboardData(ClipboardFormats uFormat, + HANDLE hMem); + +HWND [gle] SetClipboardViewer(HWND hWndNewViewer); diff --git a/tools/Debugging Tools for Windows/winext/manifest/com.h b/tools/Debugging Tools for Windows/winext/manifest/com.h new file mode 100644 index 0000000000..094e4130f1 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/com.h @@ -0,0 +1,224 @@ +category ComponentObjectModel: + +interface IUnknown +{ + HRESULT QueryInterface( + [IID] REFIID iid, + [out] COM_INTERFACE_PTR* ppvObject ); + + ULONG AddRef(); + ULONG Release(); +}; + +typedef IUnknown* LPUNKNOWN; + +interface IClassFactory : IUnknown +{ + HRESULT CreateInstance( + IUnknown * pUnkOuter, + [IID] REFIID riid, + [out] COM_INTERFACE_PTR* ppvObject ); + + HRESULT LockServer( BOOL fLock ); +}; + +interface IDispatch : IUnknown +{ + HRESULT GetTypeInfoCount( UINT pctinfo ); + + HRESULT GetTypeInfo( + UINT iTInfo, + LCID lcid, + LPVOID ppTInfo ); + + HRESULT GetIDsOfNames( + REFIID riid, + LPOLECHAR* rgszNames, + UINT cNames, + LCID lcid, + [out] DISPID* rgDispId ); + + HRESULT Invoke( + DISPID dispIdMember, + REFIID riid, + LCID lcid, + WORD wFlags, + DISPPARAMS* pDispParams, + VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, + UINT* puArgErr ); +}; + + +interface IPersist : IUnknown +{ + HRESULT GetClassID( + [out] CLSID *pClassID //Pointer to CLSID of object + ); +}; + +interface IPersistFile : IPersist +{ + HRESULT IsDirty(); + + HRESULT Load( + LPCOLESTR pszFileName, + //Pointer to absolute path of the file to open + DWORD dwMode //Specifies the access mode from the STGM enumeration + ); + + HRESULT Save( + LPCOLESTR pszFileName, //Pointer to absolute path of the file + //where the object is saved + BOOL fRemember //Specifies whether the file is to be the + //current working file or not + ); + + HRESULT SaveCompleted( + LPCOLESTR pszFileName //Pointer to absolute path of the file + //where the object was saved + ); + + HRESULT GetCurFile( + LPOLESTR *ppszFileName //Pointer to the path for the current file + //or the default save prompt + ); +}; + +typedef VOID DVTARGETDEVICE; +typedef LPVOID CONTINUEPROC; +typedef LPVOID IAdviseSink; + +interface IViewObject : IUnknown +{ + + + HRESULT Draw + ( + [in] DWORD dwDrawAspect, + [in] LONG lindex, + [in] void * pvAspect, + [in] DVTARGETDEVICE *ptd, + [in] HDC hdcTargetDev, + [in] HDC hdcDraw, + [in] LPCRECTL lprcBounds, + [in] LPCRECTL lprcWBounds, + [in] CONTINUEPROC ContinueProc, + [in] ULONG_PTR dwContinue + ); + + HRESULT GetColorSet + ( + [in] DWORD dwDrawAspect, + [in] LONG lindex, + [in] void *pvAspect, + [in] DVTARGETDEVICE *ptd, + [in] HDC hicTargetDev, + [out] LOGPALETTE **ppColorSet + ); + + HRESULT Freeze + ( + [in] DWORD dwDrawAspect, + [in] LONG lindex, + [in] void *pvAspect, + [out] DWORD *pdwFreeze + ); + + HRESULT Unfreeze + ( + [in] DWORD dwFreeze + ); + + HRESULT SetAdvise + ( + [in] DWORD aspects, + [in] DWORD advf, + [in] IAdviseSink *pAdvSink + ); + + HRESULT GetAdvise + ( + [out] DWORD *pAspects, + [out] DWORD *pAdvf, + [out] IAdviseSink **ppAdvSink + ); +}; + +interface IViewObject2 : IViewObject +{ + HRESULT GetExtent + ( + [in] DWORD dwDrawAspect, + [in] LONG lindex, + [in] DVTARGETDEVICE* ptd, + [out] LPSIZEL lpsizel + ); +}; + +typedef struct tagExtentInfo { + ULONG cb; + DWORD dwExtentMode; + SIZEL sizelProposed; +} DVEXTENTINFO; + +interface IViewObjectEx : IViewObject2 +{ + + HRESULT GetRect( + [in] DWORD dwAspect, + [out] LPRECTL pRect + ); + + HRESULT GetViewStatus( + [out] DWORD * pdwStatus + ); + + HRESULT QueryHitPoint( + [in] DWORD dwAspect, + [in] LPCRECT pRectBounds, + [in] POINT ptlLoc, + [in] LONG lCloseHint, + [out] DWORD * pHitResult + ); + + HRESULT QueryHitRect( + [in] DWORD dwAspect, + [in] LPCRECT pRectBounds, + [in] LPCRECT pRectLoc, + [in] LONG lCloseHint, + [out] DWORD * pHitResult + ); + + HRESULT GetNaturalExtent ( + [in] DWORD dwAspect, + [in] LONG lindex, + [in] DVTARGETDEVICE * ptd, + [in] HDC hicTargetDev, + [in] DVEXTENTINFO * pExtentInfo, + [out] LPSIZEL pSizel + ); +}; + +// We can't log IMalloc because it is a global object. +// It causes recursion problems in LogProcessHook because +// StringFromCLSID uses the global interface. +/* +interface IMalloc : IUnknown +{ + PVOID Alloc( SIZE_T cb ); + + PVOID Realloc( + PVOID pv, + SIZE_T cb ); + + VOID Free( PVOID pv); + + SIZE_T GetSize( PVOID pv ); + + int DidAlloc( PVOID pv ); + + VOID HeapMinimize(); + +}; +*/ diff --git a/tools/Debugging Tools for Windows/winext/manifest/d3d.h b/tools/Debugging Tools for Windows/winext/manifest/d3d.h new file mode 100644 index 0000000000..39dd7089d0 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/d3d.h @@ -0,0 +1,550 @@ +module D3D.DLL: +category Direct3D: + +#include "d3dtypes.h" +#include "d3dcaps.h" + +// +// GUIDs +// + +struct __declspec(uuid("64108800-957D-11D0-89AB-00A0C9054129")) IDirect3DDevice; +struct __declspec(uuid("2CDCD9E0-25A0-11CF-A31A-00AA00B93356")) IDirect3DTexture; +struct __declspec(uuid("93281500-8CF8-11D0-89AB-00A0C9054129")) IDirect3DViewport2; +struct __declspec(uuid("6AAE1EC1-662A-11D0-889D-00AA00BBB76A")) IDirect3D2; +struct __declspec(uuid("B0AB3B61-33D7-11D1-A981-00C04FD7B174")) IDirect3DViewport3; +struct __declspec(uuid("F5049E78-4861-11D2-A407-00A0C90629A8")) IDirect3DTnLHalDevice; +struct __declspec(uuid("BB223240-E72B-11D0-A9B4-00AA00C0993E")) IDirect3D3; +struct __declspec(uuid("F5049E77-4861-11D2-A407-00A0C90629A8")) IDirect3D7; +struct __declspec(uuid("7A503555-4A83-11D1-A5DB-00A0C90367F8")) IDirect3DVertexBuffer; +struct __declspec(uuid("4417C142-33AD-11CF-816F-0000C020156E")) IDirect3DLight; +struct __declspec(uuid("4417C146-33AD-11CF-816F-0000C020156E")) IDirect3DViewport; +struct __declspec(uuid("3BBA0080-2421-11CF-A31A-00AA00B93356")) IDirect3D; +struct __declspec(uuid("F2086B20-259F-11CF-A31A-00AA00B93356")) IDirect3DRampDevice; +struct __declspec(uuid("93281503-8CF8-11D0-89AB-00A0C9054129")) IDirect3DMaterial2; +struct __declspec(uuid("CA9C46F4-D3C5-11D1-B75A-00600852B312")) IDirect3DMaterial3; +struct __declspec(uuid("881949A1-D6F3-11D0-89AB-00A0C9054129")) IDirect3DMMXDevice; +struct __declspec(uuid("93281501-8CF8-11D0-89AB-00A0C9054129")) IDirect3DDevice2; +struct __declspec(uuid("B0AB3B60-33D7-11D1-A981-00C04FD7B174")) IDirect3DDevice3; +struct __declspec(uuid("93281502-8CF8-11D0-89AB-00A0C9054129")) IDirect3DTexture2; +struct __declspec(uuid("84E63DE0-46AA-11CF-816F-0000C020156E")) IDirect3DHALDevice; +struct __declspec(uuid("F5049E79-4861-11D2-A407-00A0C90629A8")) IDirect3DDevice7; +struct __declspec(uuid("8767DF22-BACC-11D1-8969-00A0C90629A8")) IDirect3DNullDevice; +struct __declspec(uuid("A4665C60-2673-11CF-A31A-00AA00B93356")) IDirect3DRGBDevice; +struct __declspec(uuid("4417C145-33AD-11CF-816F-0000C020156E")) IDirect3DExecuteBuffer; +struct __declspec(uuid("50936643-13E9-11D1-89AA-00A0C9054129")) IDirect3DRefDevice; +struct __declspec(uuid("F5049E7D-4861-11D2-A407-00A0C90629A8")) IDirect3DVertexBuffer7; +struct __declspec(uuid("4417C144-33AD-11CF-816F-0000C020156E")) IDirect3DMaterial; + + +// +// Typedefs +// + +typedef IDirect3D *LPDIRECT3D; +typedef IDirect3DDevice *LPDIRECT3DDEVICE; +typedef IDirect3DExecuteBuffer *LPDIRECT3DEXECUTEBUFFER; +typedef IDirect3DLight *LPDIRECT3DLIGHT; +typedef IDirect3DMaterial *LPDIRECT3DMATERIAL; +typedef IDirect3DTexture *LPDIRECT3DTEXTURE; +typedef IDirect3DViewport *LPDIRECT3DVIEWPORT; +typedef IDirect3D2 *LPDIRECT3D2; +typedef IDirect3DDevice2 *LPDIRECT3DDEVICE2; +typedef IDirect3DMaterial2 *LPDIRECT3DMATERIAL2; +typedef IDirect3DTexture2 *LPDIRECT3DTEXTURE2; +typedef IDirect3DViewport2 *LPDIRECT3DVIEWPORT2; +typedef IDirect3D3 *LPDIRECT3D3; +typedef IDirect3DDevice3 *LPDIRECT3DDEVICE3; +typedef IDirect3DMaterial3 *LPDIRECT3DMATERIAL3; +typedef IDirect3DViewport3 *LPDIRECT3DVIEWPORT3; +typedef IDirect3DVertexBuffer *LPDIRECT3DVERTEXBUFFER; +typedef IDirect3D7 *LPDIRECT3D7; +typedef IDirect3DDevice7 *LPDIRECT3DDEVICE7; +typedef IDirect3DVertexBuffer7 *LPDIRECT3DVERTEXBUFFER7; +typedef IDirect3D IDIRECT3D; +typedef IDirect3D * LPIDIRECT3D; +typedef IDirect3D ** LPLPIDIRECT3D; +typedef IDirect3D2 IDIRECT3D2; +typedef IDirect3D2 * LPIDIRECT3D2; +typedef IDirect3D2 ** LPLPIDIRECT3D2; +typedef IDirect3D3 IDIRECT3D3; +typedef IDirect3D3 * LPIDIRECT3D3; +typedef IDirect3D3 ** LPLPIDIRECT3D3; +typedef IDirect3D7 IDIRECT3D7; +typedef IDirect3D7 * LPIDIRECT3D7; +typedef IDirect3D7 ** LPLPIDIRECT3D7; +typedef IDirect3DDevice IDIRECT3DDEVICE; +typedef IDirect3DDevice * LPIDIRECT3DDEVICE; +typedef IDirect3DDevice ** LPLPIDIRECT3DDEVICE; +typedef IDirect3DDevice2 IDIRECT3DDEVICE2; +typedef IDirect3DDevice2 * LPIDIRECT3DDEVICE2; +typedef IDirect3DDevice2 ** LPLPIDIRECT3DDEVICE2; +typedef IDirect3DDevice3 IDIRECT3DDEVICE3; +typedef IDirect3DDevice3 * LPIDIRECT3DDEVICE3; +typedef IDirect3DDevice3 ** LPLPIDIRECT3DDEVICE3; +typedef IDirect3DDevice7 IDIRECT3DDEVICE7; +typedef IDirect3DDevice7 * LPIDIRECT3DDEVICE7; +typedef IDirect3DDevice7 ** LPLPIDIRECT3DDEVICE7; +typedef IDirect3DExecuteBuffer IDIRECT3DEXECUTEBUFFER; +typedef IDirect3DExecuteBuffer * LPIDIRECT3DEXECUTEBUFFER; +typedef IDirect3DExecuteBuffer ** LPLPIDIRECT3DEXECUTEBUFFER; +typedef IDirect3DLight IDIRECT3DLIGHT; +typedef IDirect3DLight * LPIDIRECT3DLIGHT; +typedef IDirect3DLight ** LPLPIDIRECT3DLIGHT; +typedef IDirect3DMaterial IDIRECT3DMATERIAL; +typedef IDirect3DMaterial * LPIDIRECT3DMATERIAL; +typedef IDirect3DMaterial ** LPLPIDIRECT3DMATERIAL; +typedef IDirect3DMaterial2 IDIRECT3DMATERIAL2; +typedef IDirect3DMaterial2 * LPIDIRECT3DMATERIAL2; +typedef IDirect3DMaterial2 ** LPLPIDIRECT3DMATERIAL2; +typedef IDirect3DMaterial3 IDIRECT3DMATERIAL3; +typedef IDirect3DMaterial3 * LPIDIRECT3DMATERIAL3; +typedef IDirect3DMaterial3 ** LPLPIDIRECT3DMATERIAL3; +typedef IDirect3DTexture IDIRECT3DTEXTURE; +typedef IDirect3DTexture * LPIDIRECT3DTEXTURE; +typedef IDirect3DTexture ** LPLPIDIRECT3DTEXTURE; +typedef IDirect3DTexture2 IDIRECT3DTEXTURE2; +typedef IDirect3DTexture2 * LPIDIRECT3DTEXTURE2; +typedef IDirect3DTexture2 ** LPLPIDIRECT3DTEXTURE2; +typedef IDirect3DViewport IDIRECT3DVIEWPORT; +typedef IDirect3DViewport * LPIDIRECT3DVIEWPORT; +typedef IDirect3DViewport ** LPLPIDIRECT3DVIEWPORT; +typedef IDirect3DViewport2 IDIRECT3DVIEWPORT2; +typedef IDirect3DViewport2 * LPIDIRECT3DVIEWPORT2; +typedef IDirect3DViewport2 ** LPLPIDIRECT3DVIEWPORT2; +typedef IDirect3DViewport3 IDIRECT3DVIEWPORT3; +typedef IDirect3DViewport3 * LPIDIRECT3DVIEWPORT3; +typedef IDirect3DViewport3 ** LPLPIDIRECT3DVIEWPORT3; +typedef IDirect3DVertexBuffer IDIRECT3DVERTEXBUFFER; +typedef IDirect3DVertexBuffer * LPIDIRECT3DVERTEXBUFFER; +typedef IDirect3DVertexBuffer ** LPLPIDIRECT3DVERTEXBUFFER; +typedef IDirect3DVertexBuffer7 IDIRECT3DVERTEXBUFFER7; +typedef IDirect3DVertexBuffer7 * LPIDIRECT3DVERTEXBUFFER7; +typedef IDirect3DVertexBuffer7 ** LPLPIDIRECT3DVERTEXBUFFER7; + + +// +// Masks +// + +mask DWORD d3dnextFlags +{ + #define D3DNEXT_NEXT 0x00000001l + #define D3DNEXT_HEAD 0x00000002l + #define D3DNEXT_TAIL 0x00000004l +}; + +mask DWORD direct3dFlags +{ + #define DIRECT3D_VERSION 0x0700 +}; + +mask DWORD D3D7RESULT +{ + #define D3D_OK 0 + #define D3DERR_BADMAJORVERSION 0x887602BCL + #define D3DERR_BADMINORVERSION 0x887602BDL + #define D3DERR_INVALID_DEVICE 0x887602C1L + #define D3DERR_INITFAILED 0x887602C2L + #define D3DERR_DEVICEAGGREGATED 0x887602C3L + #define D3DERR_EXECUTE_CREATE_FAILED 0x887602C6L + #define D3DERR_EXECUTE_DESTROY_FAILED 0x887602C7L + #define D3DERR_EXECUTE_LOCK_FAILED 0x887602C8L + #define D3DERR_EXECUTE_UNLOCK_FAILED 0x887602C9L + #define D3DERR_EXECUTE_LOCKED 0x887602CAL + #define D3DERR_EXECUTE_NOT_LOCKED 0x887602CBL + #define D3DERR_EXECUTE_FAILED 0x887602CCL + #define D3DERR_EXECUTE_CLIPPED_FAILED 0x887602CDL + #define D3DERR_TEXTURE_NO_SUPPORT 0x887602D0L + #define D3DERR_TEXTURE_CREATE_FAILED 0x887602D1L + #define D3DERR_TEXTURE_DESTROY_FAILED 0x887602D2L + #define D3DERR_TEXTURE_LOCK_FAILED 0x887602D3L + #define D3DERR_TEXTURE_UNLOCK_FAILED 0x887602D4L + #define D3DERR_TEXTURE_LOAD_FAILED 0x887602D5L + #define D3DERR_TEXTURE_SWAP_FAILED 0x887602D6L + #define D3DERR_TEXTURE_LOCKED 0x887602D7L + #define D3DERR_TEXTURE_NOT_LOCKED 0x887602D8L + #define D3DERR_TEXTURE_GETSURF_FAILED 0x887602D9L + #define D3DERR_MATRIX_CREATE_FAILED 0x887602DAL + #define D3DERR_MATRIX_DESTROY_FAILED 0x887602DBL + #define D3DERR_MATRIX_SETDATA_FAILED 0x887602DCL + #define D3DERR_MATRIX_GETDATA_FAILED 0x887602DDL + #define D3DERR_SETVIEWPORTDATA_FAILED 0x887602DEL + #define D3DERR_INVALIDCURRENTVIEWPORT 0x887602DFL + #define D3DERR_INVALIDPRIMITIVETYPE 0x887602E0L + #define D3DERR_INVALIDVERTEXTYPE 0x887602E1L + #define D3DERR_TEXTURE_BADSIZE 0x887602E2L + #define D3DERR_INVALIDRAMPTEXTURE 0x887602E3L + #define D3DERR_MATERIAL_CREATE_FAILED 0x887602E4L + #define D3DERR_MATERIAL_DESTROY_FAILED 0x887602E5L + #define D3DERR_MATERIAL_SETDATA_FAILED 0x887602E6L + #define D3DERR_MATERIAL_GETDATA_FAILED 0x887602E7L + #define D3DERR_INVALIDPALETTE 0x887602E8L + #define D3DERR_ZBUFF_NEEDS_SYSTEMMEMORY 0x887602E9L + #define D3DERR_ZBUFF_NEEDS_VIDEOMEMORY 0x887602EAL + #define D3DERR_SURFACENOTINVIDMEM 0x887602EBL + #define D3DERR_LIGHT_SET_FAILED 0x887602EEL + #define D3DERR_LIGHTHASVIEWPORT 0x887602EFL + #define D3DERR_LIGHTNOTINTHISVIEWPORT 0x887602F0L + #define D3DERR_SCENE_IN_SCENE 0x887602F8L + #define D3DERR_SCENE_NOT_IN_SCENE 0x887602F9L + #define D3DERR_SCENE_BEGIN_FAILED 0x887602FAL + #define D3DERR_SCENE_END_FAILED 0x887602FBL + #define D3DERR_INBEGIN 0x88760302L + #define D3DERR_NOTINBEGIN 0x88760303L + #define D3DERR_NOVIEWPORTS 0x88760304L + #define D3DERR_VIEWPORTDATANOTSET 0x88760305L + #define D3DERR_VIEWPORTHASNODEVICE 0x88760306L + #define D3DERR_NOCURRENTVIEWPORT 0x88760307L + #define D3DERR_INVALIDVERTEXFORMAT 0x88760800L + #define D3DERR_COLORKEYATTACHED 0x88760802L + #define D3DERR_VERTEXBUFFEROPTIMIZED 0x8876080CL + #define D3DERR_VBUF_CREATE_FAILED 0x8876080DL + #define D3DERR_VERTEXBUFFERLOCKED 0x8876080EL + #define D3DERR_VERTEXBUFFERUNLOCKFAILED 0x8876080FL + #define D3DERR_ZBUFFER_NOTPRESENT 0x88760816L + #define D3DERR_STENCILBUFFER_NOTPRESENT 0x88760817L + #define D3DERR_WRONGTEXTUREFORMAT 0x88760818L + #define D3DERR_UNSUPPORTEDCOLOROPERATION 0x88760819L + #define D3DERR_UNSUPPORTEDCOLORARG 0x8876081AL + #define D3DERR_UNSUPPORTEDALPHAOPERATION 0x8876081BL + #define D3DERR_UNSUPPORTEDALPHAARG 0x8876081CL + #define D3DERR_TOOMANYOPERATIONS 0x8876081DL + #define D3DERR_CONFLICTINGTEXTUREFILTER 0x8876081EL + #define D3DERR_UNSUPPORTEDFACTORVALUE 0x8876081FL + #define D3DERR_CONFLICTINGRENDERSTATE 0x88760821L + #define D3DERR_UNSUPPORTEDTEXTUREFILTER 0x88760822L + #define D3DERR_TOOMANYPRIMITIVES 0x88760823L + #define D3DERR_INVALIDMATRIX 0x88760824L + #define D3DERR_TOOMANYVERTICES 0x88760825L + #define D3DERR_CONFLICTINGTEXTUREPALETTE 0x88760826L + #define D3DERR_INVALIDSTATEBLOCK 0x88760834L + #define D3DERR_INBEGINSTATEBLOCK 0x88760835L + #define D3DERR_NOTINBEGINSTATEBLOCK 0x88760836L +}; + +mask DWORD d3ddpFlags +{ + #define D3DDP_WAIT 0x00000001l + #define D3DDP_OUTOFORDER 0x00000002l + #define D3DDP_DONOTCLIP 0x00000004l + #define D3DDP_DONOTUPDATEEXTENTS 0x00000008l + #define D3DDP_DONOTLIGHT 0x00000010l +}; + + + +// +// Values +// + + + +// +// Structs +// + + + +// +// Interfaces +// + +interface IDirect3DDevice7 : IUnknown +{ + D3D7RESULT GetCaps([out] LPD3DDEVICEDESC7 arg0); + D3D7RESULT EnumTextureFormats(LPD3DENUMPIXELFORMATSCALLBACK arg0, LPVOID arg1); + D3D7RESULT BeginScene(); + D3D7RESULT EndScene(); + D3D7RESULT GetDirect3D([out] LPDIRECT3D7* arg0); + D3D7RESULT SetRenderTarget(LPDIRECTDRAWSURFACE7 arg0, DWORD arg1); + D3D7RESULT GetRenderTarget([out] LPDIRECTDRAWSURFACE7 * arg0); + D3D7RESULT Clear(DWORD arg0, LPD3DRECT arg1, DWORD arg2, D3DCOLOR arg3, D3DVALUE arg4, DWORD arg5); + D3D7RESULT SetTransform(D3DTRANSFORMSTATETYPE arg0, LPD3DMATRIX arg1); + D3D7RESULT GetTransform(D3DTRANSFORMSTATETYPE arg0, [out] LPD3DMATRIX arg1); + D3D7RESULT SetViewport(LPD3DVIEWPORT7 arg0); + D3D7RESULT MultiplyTransform(D3DTRANSFORMSTATETYPE arg0, LPD3DMATRIX arg1); + D3D7RESULT GetViewport([out] LPD3DVIEWPORT7 arg0); + D3D7RESULT SetMaterial(LPD3DMATERIAL7 arg0); + D3D7RESULT GetMaterial([out] LPD3DMATERIAL7 arg0); + D3D7RESULT SetLight(DWORD arg0, LPD3DLIGHT7 arg1); + D3D7RESULT GetLight(DWORD arg0, LPD3DLIGHT7 arg1); + D3D7RESULT SetRenderState(D3DRENDERSTATETYPE arg0, DWORD arg1); + D3D7RESULT GetRenderState(D3DRENDERSTATETYPE arg0, [out] LPDWORD arg1); + D3D7RESULT BeginStateBlock(); + D3D7RESULT EndStateBlock([out] LPDWORD arg0); + D3D7RESULT PreLoad(LPDIRECTDRAWSURFACE7 arg0); + D3D7RESULT DrawPrimitive(D3DPRIMITIVETYPE arg0, DWORD arg1, LPVOID arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitive(D3DPRIMITIVETYPE arg0, DWORD arg1, LPVOID arg2, DWORD arg3, LPWORD arg4, DWORD arg5, DWORD arg6); + D3D7RESULT SetClipStatus(LPD3DCLIPSTATUS arg0); + D3D7RESULT GetClipStatus([out] LPD3DCLIPSTATUS arg0); + D3D7RESULT DrawPrimitiveStrided(D3DPRIMITIVETYPE arg0, DWORD arg1, LPD3DDRAWPRIMITIVESTRIDEDDATA arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitiveStrided(D3DPRIMITIVETYPE arg0, DWORD arg1, LPD3DDRAWPRIMITIVESTRIDEDDATA arg2, DWORD arg3, LPWORD arg4, DWORD arg5, DWORD arg6); + D3D7RESULT DrawPrimitiveVB(D3DPRIMITIVETYPE arg0, LPDIRECT3DVERTEXBUFFER7 arg1, DWORD arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitiveVB(D3DPRIMITIVETYPE arg0, LPDIRECT3DVERTEXBUFFER7 arg1, DWORD arg2, DWORD arg3, LPWORD arg4, DWORD arg5, DWORD arg6); + D3D7RESULT ComputeSphereVisibility(LPD3DVECTOR arg0, LPD3DVALUE arg1, DWORD arg2, DWORD arg3, LPDWORD arg4); + D3D7RESULT GetTexture(DWORD arg0, [out] LPDIRECTDRAWSURFACE7 * arg1); + D3D7RESULT SetTexture(DWORD arg0, LPDIRECTDRAWSURFACE7 arg1); + D3D7RESULT GetTextureStageState(DWORD arg0, D3DTEXTURESTAGESTATETYPE arg1, [out] LPDWORD arg2); + D3D7RESULT SetTextureStageState(DWORD arg0, D3DTEXTURESTAGESTATETYPE arg1, DWORD arg2); + D3D7RESULT ValidateDevice([out] LPDWORD arg0); + D3D7RESULT ApplyStateBlock(DWORD arg0); + D3D7RESULT CaptureStateBlock(DWORD arg0); + D3D7RESULT DeleteStateBlock(DWORD arg0); + D3D7RESULT CreateStateBlock(D3DSTATEBLOCKTYPE arg0, [out] LPDWORD arg1); + D3D7RESULT Load(LPDIRECTDRAWSURFACE7 arg0, LPPOINT arg1, LPDIRECTDRAWSURFACE7 arg2, LPRECT arg3, DWORD arg4); + D3D7RESULT LightEnable(DWORD arg0, BOOL arg1); + D3D7RESULT GetLightEnable(DWORD arg0, [out] BOOL* arg1); + D3D7RESULT SetClipPlane(DWORD arg0, D3DVALUE* arg1); + D3D7RESULT GetClipPlane(DWORD arg0, [out] D3DVALUE* arg1); + D3D7RESULT GetInfo(DWORD arg0, [out] LPVOID arg1, DWORD arg2); +}; + +interface IDirect3D7 : IUnknown +{ + D3D7RESULT EnumDevices(LPD3DENUMDEVICESCALLBACK7 arg0, LPVOID arg1); + D3D7RESULT CreateDevice(REFCLSID arg0, LPDIRECTDRAWSURFACE7 arg1, [out] LPDIRECT3DDEVICE7* arg2); + D3D7RESULT CreateVertexBuffer(LPD3DVERTEXBUFFERDESC arg0, [out] LPDIRECT3DVERTEXBUFFER7* arg1, DWORD arg2); + D3D7RESULT EnumZBufferFormats(REFCLSID arg0, LPD3DENUMPIXELFORMATSCALLBACK arg1, LPVOID arg2); + D3D7RESULT EvictManagedTextures(); +}; + +interface IDirect3DVertexBuffer : IUnknown +{ + D3D7RESULT Lock(DWORD arg0, LPVOID* arg1, LPDWORD arg2); + D3D7RESULT Unlock(); + D3D7RESULT ProcessVertices(DWORD arg0, DWORD arg1, DWORD arg2, LPDIRECT3DVERTEXBUFFER arg3, DWORD arg4, LPDIRECT3DDEVICE3 arg5, DWORD arg6); + D3D7RESULT GetVertexBufferDesc([out] LPD3DVERTEXBUFFERDESC arg0); + D3D7RESULT Optimize(LPDIRECT3DDEVICE3 arg0, DWORD arg1); +}; + +interface IDirect3DLight : IUnknown +{ + D3D7RESULT Initialize(LPDIRECT3D arg0); + D3D7RESULT SetLight(LPD3DLIGHT arg0); + D3D7RESULT GetLight([out] LPD3DLIGHT arg0); +}; + +interface IDirect3DExecuteBuffer : IUnknown +{ + D3D7RESULT Initialize(LPDIRECT3DDEVICE arg0, LPD3DEXECUTEBUFFERDESC arg1); + D3D7RESULT Lock(LPD3DEXECUTEBUFFERDESC arg0); + D3D7RESULT Unlock(); + D3D7RESULT SetExecuteData(LPD3DEXECUTEDATA arg0); + D3D7RESULT GetExecuteData([out] LPD3DEXECUTEDATA arg0); + D3D7RESULT Validate(LPDWORD arg0, LPD3DVALIDATECALLBACK arg1, LPVOID arg2, DWORD arg3); + D3D7RESULT Optimize(DWORD arg0); +}; + +interface IDirect3DVertexBuffer7 : IUnknown +{ + D3D7RESULT Lock(DWORD arg0, LPVOID* arg1, LPDWORD arg2); + D3D7RESULT Unlock(); + D3D7RESULT ProcessVertices(DWORD arg0, DWORD arg1, DWORD arg2, LPDIRECT3DVERTEXBUFFER7 arg3, DWORD arg4, LPDIRECT3DDEVICE7 arg5, DWORD arg6); + D3D7RESULT GetVertexBufferDesc([out] LPD3DVERTEXBUFFERDESC arg0); + D3D7RESULT Optimize(LPDIRECT3DDEVICE7 arg0, DWORD arg1); + D3D7RESULT ProcessVerticesStrided(DWORD arg0, DWORD arg1, DWORD arg2, LPD3DDRAWPRIMITIVESTRIDEDDATA arg3, DWORD arg4, LPDIRECT3DDEVICE7 arg5, DWORD arg6); +}; + +interface IDirect3DMaterial : IUnknown +{ + D3D7RESULT Initialize(LPDIRECT3D arg0); + D3D7RESULT SetMaterial(LPD3DMATERIAL arg0); + D3D7RESULT GetMaterial([out] LPD3DMATERIAL arg0); + D3D7RESULT GetHandle(LPDIRECT3DDEVICE arg0, [out] LPD3DMATERIALHANDLE arg1); + D3D7RESULT Reserve(); + D3D7RESULT Unreserve(); +}; + +interface IDirect3DViewport : IUnknown +{ + D3D7RESULT Initialize(LPDIRECT3D arg0); + D3D7RESULT GetViewport([out] LPD3DVIEWPORT arg0); + D3D7RESULT SetViewport(LPD3DVIEWPORT arg0); + D3D7RESULT TransformVertices(DWORD arg0, LPD3DTRANSFORMDATA arg1, DWORD arg2, LPDWORD arg3); + D3D7RESULT LightElements(DWORD arg0, LPD3DLIGHTDATA arg1); + D3D7RESULT SetBackground(D3DMATERIALHANDLE arg0); + D3D7RESULT GetBackground([out] LPD3DMATERIALHANDLE arg0, [out] LPBOOL arg1); + D3D7RESULT SetBackgroundDepth(LPDIRECTDRAWSURFACE arg0); + D3D7RESULT GetBackgroundDepth([out] LPDIRECTDRAWSURFACE* arg0, [out] LPBOOL arg1); + D3D7RESULT Clear(DWORD arg0, LPD3DRECT arg1, DWORD arg2); + D3D7RESULT AddLight(LPDIRECT3DLIGHT arg0); + D3D7RESULT DeleteLight(LPDIRECT3DLIGHT arg0); + D3D7RESULT NextLight(LPDIRECT3DLIGHT arg0, [out] LPDIRECT3DLIGHT* arg1, DWORD arg2); +}; + +interface IDirect3D : IUnknown +{ + D3D7RESULT Initialize(REFCLSID arg0); + D3D7RESULT EnumDevices(LPD3DENUMDEVICESCALLBACK arg0, LPVOID arg1); + D3D7RESULT CreateLight([out] LPDIRECT3DLIGHT* arg0, IUnknown* arg1); + D3D7RESULT CreateMaterial([out] LPDIRECT3DMATERIAL* arg0, IUnknown* arg1); + D3D7RESULT CreateViewport([out] LPDIRECT3DVIEWPORT* arg0, IUnknown* arg1); + D3D7RESULT FindDevice(LPD3DFINDDEVICESEARCH arg0, LPD3DFINDDEVICERESULT arg1); +}; + +interface IDirect3DDevice : IUnknown +{ + D3D7RESULT Initialize(LPDIRECT3D arg0, LPGUID arg1, LPD3DDEVICEDESC arg2); + D3D7RESULT GetCaps([out] LPD3DDEVICEDESC arg0, [out] LPD3DDEVICEDESC arg1); + D3D7RESULT SwapTextureHandles(LPDIRECT3DTEXTURE arg0, LPDIRECT3DTEXTURE arg1); + D3D7RESULT CreateExecuteBuffer(LPD3DEXECUTEBUFFERDESC arg0, [out] LPDIRECT3DEXECUTEBUFFER* arg1, IUnknown* arg2); + D3D7RESULT GetStats(LPD3DSTATS arg0); + D3D7RESULT Execute(LPDIRECT3DEXECUTEBUFFER arg0, LPDIRECT3DVIEWPORT arg1, DWORD arg2); + D3D7RESULT AddViewport(LPDIRECT3DVIEWPORT arg0); + D3D7RESULT DeleteViewport(LPDIRECT3DVIEWPORT arg0); + D3D7RESULT NextViewport(LPDIRECT3DVIEWPORT arg0, [out] LPDIRECT3DVIEWPORT* arg1, DWORD arg2); + D3D7RESULT Pick(LPDIRECT3DEXECUTEBUFFER arg0, LPDIRECT3DVIEWPORT arg1, DWORD arg2, LPD3DRECT arg3); + D3D7RESULT GetPickRecords(LPDWORD arg0, LPD3DPICKRECORD arg1); + D3D7RESULT EnumTextureFormats(LPD3DENUMTEXTUREFORMATSCALLBACK arg0, LPVOID arg1); + D3D7RESULT CreateMatrix([out] LPD3DMATRIXHANDLE arg0); + D3D7RESULT SetMatrix(D3DMATRIXHANDLE arg0, LPD3DMATRIX arg1); + D3D7RESULT GetMatrix(D3DMATRIXHANDLE arg0, [out] LPD3DMATRIX arg1); + D3D7RESULT DeleteMatrix(D3DMATRIXHANDLE arg0); + D3D7RESULT BeginScene(); + D3D7RESULT EndScene(); + D3D7RESULT GetDirect3D([out] LPDIRECT3D* arg0); +}; + +interface IDirect3DTexture : IUnknown +{ + D3D7RESULT Initialize(LPDIRECT3DDEVICE arg0, LPDIRECTDRAWSURFACE arg1); + D3D7RESULT GetHandle(LPDIRECT3DDEVICE arg0, LPD3DTEXTUREHANDLE arg1); + D3D7RESULT PaletteChanged(DWORD arg0, DWORD arg1); + D3D7RESULT Load(LPDIRECT3DTEXTURE arg0); + D3D7RESULT Unload(); +}; + +interface IDirect3DMaterial2 : IUnknown +{ + D3D7RESULT SetMaterial(LPD3DMATERIAL arg0); + D3D7RESULT GetMaterial([out] LPD3DMATERIAL arg0); + D3D7RESULT GetHandle(LPDIRECT3DDEVICE2 arg0, [out] LPD3DMATERIALHANDLE arg1); +}; + +interface IDirect3DViewport2 : IDirect3DViewport +{ + D3D7RESULT GetViewport2([out] LPD3DVIEWPORT2 arg0); + D3D7RESULT SetViewport2(LPD3DVIEWPORT2 arg0); +}; + +interface IDirect3DMaterial3 : IUnknown +{ + D3D7RESULT SetMaterial(LPD3DMATERIAL arg0); + D3D7RESULT GetMaterial([out] LPD3DMATERIAL arg0); + D3D7RESULT GetHandle(LPDIRECT3DDEVICE3 arg0, [out] LPD3DMATERIALHANDLE arg1); +}; + +interface IDirect3D2 : IUnknown +{ + D3D7RESULT EnumDevices(LPD3DENUMDEVICESCALLBACK arg0, LPVOID arg1); + D3D7RESULT CreateLight([out] LPDIRECT3DLIGHT* arg0, IUnknown* arg1); + D3D7RESULT CreateMaterial([out] LPDIRECT3DMATERIAL2* arg0, IUnknown* arg1); + D3D7RESULT CreateViewport([out] LPDIRECT3DVIEWPORT2* arg0, IUnknown* arg1); + D3D7RESULT FindDevice(LPD3DFINDDEVICESEARCH arg0, LPD3DFINDDEVICERESULT arg1); + D3D7RESULT CreateDevice(REFCLSID arg0, LPDIRECTDRAWSURFACE arg1, [out] LPDIRECT3DDEVICE2* arg2); +}; + +interface IDirect3DDevice2 : IUnknown +{ + D3D7RESULT GetCaps([out] LPD3DDEVICEDESC arg0, [out] LPD3DDEVICEDESC arg1); + D3D7RESULT SwapTextureHandles(LPDIRECT3DTEXTURE2 arg0, LPDIRECT3DTEXTURE2 arg1); + D3D7RESULT GetStats([out] LPD3DSTATS arg0); + D3D7RESULT AddViewport(LPDIRECT3DVIEWPORT2 arg0); + D3D7RESULT DeleteViewport(LPDIRECT3DVIEWPORT2 arg0); + D3D7RESULT NextViewport(LPDIRECT3DVIEWPORT2 arg0, [out] LPDIRECT3DVIEWPORT2* arg1, DWORD arg2); + D3D7RESULT EnumTextureFormats(LPD3DENUMTEXTUREFORMATSCALLBACK arg0, LPVOID arg1); + D3D7RESULT BeginScene(); + D3D7RESULT EndScene(); + D3D7RESULT GetDirect3D([out] LPDIRECT3D2* arg0); + D3D7RESULT SetCurrentViewport(LPDIRECT3DVIEWPORT2 arg0); + D3D7RESULT GetCurrentViewport(LPDIRECT3DVIEWPORT2 * arg0); + D3D7RESULT SetRenderTarget(LPDIRECTDRAWSURFACE arg0, DWORD arg1); + D3D7RESULT GetRenderTarget([out] LPDIRECTDRAWSURFACE * arg0); + D3D7RESULT Begin(D3DPRIMITIVETYPE arg0, D3DVERTEXTYPE arg1, DWORD arg2); + D3D7RESULT BeginIndexed(D3DPRIMITIVETYPE arg0, D3DVERTEXTYPE arg1, LPVOID arg2, DWORD arg3, DWORD arg4); + D3D7RESULT Vertex(LPVOID arg0); + D3D7RESULT Index(WORD arg0); + D3D7RESULT End(DWORD arg0); + D3D7RESULT GetRenderState(D3DRENDERSTATETYPE arg0, [out] LPDWORD arg1); + D3D7RESULT SetRenderState(D3DRENDERSTATETYPE arg0, DWORD arg1); + D3D7RESULT GetLightState(D3DLIGHTSTATETYPE arg0, [out] LPDWORD arg1); + D3D7RESULT SetLightState(D3DLIGHTSTATETYPE arg0, DWORD arg1); + D3D7RESULT SetTransform(D3DTRANSFORMSTATETYPE arg0, LPD3DMATRIX arg1); + D3D7RESULT GetTransform(D3DTRANSFORMSTATETYPE arg0, [out] LPD3DMATRIX arg1); + D3D7RESULT MultiplyTransform(D3DTRANSFORMSTATETYPE arg0, [out] LPD3DMATRIX arg1); + D3D7RESULT DrawPrimitive(D3DPRIMITIVETYPE arg0, D3DVERTEXTYPE arg1, LPVOID arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitive(D3DPRIMITIVETYPE arg0, D3DVERTEXTYPE arg1, LPVOID arg2, DWORD arg3, LPWORD arg4, DWORD arg5, DWORD arg6); + D3D7RESULT SetClipStatus(LPD3DCLIPSTATUS arg0); + D3D7RESULT GetClipStatus(LPD3DCLIPSTATUS arg0); +}; + +interface IDirect3DViewport3 : IDirect3DViewport2 +{ + D3D7RESULT SetBackgroundDepth2(LPDIRECTDRAWSURFACE4 arg0); + D3D7RESULT GetBackgroundDepth2([out] LPDIRECTDRAWSURFACE4* arg0, LPBOOL arg1); + D3D7RESULT Clear2(DWORD arg0, LPD3DRECT arg1, DWORD arg2, D3DCOLOR arg3, D3DVALUE arg4, DWORD arg5); +}; + +interface IDirect3DTexture2 : IUnknown +{ + D3D7RESULT GetHandle(LPDIRECT3DDEVICE2 arg0, LPD3DTEXTUREHANDLE arg1); + D3D7RESULT PaletteChanged(DWORD arg0, DWORD arg1); + D3D7RESULT Load(LPDIRECT3DTEXTURE2 arg0); +}; + +interface IDirect3D3 : IUnknown +{ + D3D7RESULT EnumDevices(LPD3DENUMDEVICESCALLBACK arg0, LPVOID arg1); + D3D7RESULT CreateLight([out] LPDIRECT3DLIGHT* arg0, LPUNKNOWN arg1); + D3D7RESULT CreateMaterial([out] LPDIRECT3DMATERIAL3* arg0, LPUNKNOWN arg1); + D3D7RESULT CreateViewport([out] LPDIRECT3DVIEWPORT3* arg0, LPUNKNOWN arg1); + D3D7RESULT FindDevice(LPD3DFINDDEVICESEARCH arg0, [out] LPD3DFINDDEVICERESULT arg1); + D3D7RESULT CreateDevice(REFCLSID arg0, LPDIRECTDRAWSURFACE4 arg1, [out] LPDIRECT3DDEVICE3* arg2, LPUNKNOWN arg3); + D3D7RESULT CreateVertexBuffer(LPD3DVERTEXBUFFERDESC arg0, [out] LPDIRECT3DVERTEXBUFFER* arg1, DWORD arg2, LPUNKNOWN arg3); + D3D7RESULT EnumZBufferFormats(REFCLSID arg0, LPD3DENUMPIXELFORMATSCALLBACK arg1, LPVOID arg2); + D3D7RESULT EvictManagedTextures(); +}; + +interface IDirect3DDevice3 : IUnknown +{ + D3D7RESULT GetCaps([out] LPD3DDEVICEDESC arg0, [out] LPD3DDEVICEDESC arg1); + D3D7RESULT GetStats([out] LPD3DSTATS arg0); + D3D7RESULT AddViewport(LPDIRECT3DVIEWPORT3 arg0); + D3D7RESULT DeleteViewport(LPDIRECT3DVIEWPORT3 arg0); + D3D7RESULT NextViewport(LPDIRECT3DVIEWPORT3 arg0, [out] LPDIRECT3DVIEWPORT3* arg1, DWORD arg2); + D3D7RESULT EnumTextureFormats(LPD3DENUMPIXELFORMATSCALLBACK arg0, LPVOID arg1); + D3D7RESULT BeginScene(); + D3D7RESULT EndScene(); + D3D7RESULT GetDirect3D([out] LPDIRECT3D3* arg0); + D3D7RESULT SetCurrentViewport(LPDIRECT3DVIEWPORT3 arg0); + D3D7RESULT GetCurrentViewport([out] LPDIRECT3DVIEWPORT3 * arg0); + D3D7RESULT SetRenderTarget(LPDIRECTDRAWSURFACE4 arg0, DWORD arg1); + D3D7RESULT GetRenderTarget([out] LPDIRECTDRAWSURFACE4 * arg0); + D3D7RESULT Begin(D3DPRIMITIVETYPE arg0, DWORD arg1, DWORD arg2); + D3D7RESULT BeginIndexed(D3DPRIMITIVETYPE arg0, DWORD arg1, LPVOID arg2, DWORD arg3, DWORD arg4); + D3D7RESULT Vertex(LPVOID arg0); + D3D7RESULT Index(WORD arg0); + D3D7RESULT End(DWORD arg0); + D3D7RESULT GetRenderState(D3DRENDERSTATETYPE arg0, [out] LPDWORD arg1); + D3D7RESULT SetRenderState(D3DRENDERSTATETYPE arg0, DWORD arg1); + D3D7RESULT GetLightState(D3DLIGHTSTATETYPE arg0, [out] LPDWORD arg1); + D3D7RESULT SetLightState(D3DLIGHTSTATETYPE arg0, DWORD arg1); + D3D7RESULT SetTransform(D3DTRANSFORMSTATETYPE arg0, LPD3DMATRIX arg1); + D3D7RESULT GetTransform(D3DTRANSFORMSTATETYPE arg0, [out] LPD3DMATRIX arg1); + D3D7RESULT MultiplyTransform(D3DTRANSFORMSTATETYPE arg0, [out] LPD3DMATRIX arg1); + D3D7RESULT DrawPrimitive(D3DPRIMITIVETYPE arg0, DWORD arg1, LPVOID arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitive(D3DPRIMITIVETYPE arg0, DWORD arg1, LPVOID arg2, DWORD arg3, LPWORD arg4, DWORD arg5, DWORD arg6); + D3D7RESULT SetClipStatus(LPD3DCLIPSTATUS arg0); + D3D7RESULT GetClipStatus([out] LPD3DCLIPSTATUS arg0); + D3D7RESULT DrawPrimitiveStrided(D3DPRIMITIVETYPE arg0, DWORD arg1, LPD3DDRAWPRIMITIVESTRIDEDDATA arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitiveStrided(D3DPRIMITIVETYPE arg0, DWORD arg1, LPD3DDRAWPRIMITIVESTRIDEDDATA arg2, DWORD arg3, LPWORD arg4, DWORD arg5, DWORD arg6); + D3D7RESULT DrawPrimitiveVB(D3DPRIMITIVETYPE arg0, LPDIRECT3DVERTEXBUFFER arg1, DWORD arg2, DWORD arg3, DWORD arg4); + D3D7RESULT DrawIndexedPrimitiveVB(D3DPRIMITIVETYPE arg0, LPDIRECT3DVERTEXBUFFER arg1, LPWORD arg2, DWORD arg3, DWORD arg4); + D3D7RESULT ComputeSphereVisibility(LPD3DVECTOR arg0, LPD3DVALUE arg1, DWORD arg2, DWORD arg3, LPDWORD arg4); + D3D7RESULT GetTexture(DWORD arg0, [out] LPDIRECT3DTEXTURE2 * arg1); + D3D7RESULT SetTexture(DWORD arg0, LPDIRECT3DTEXTURE2 arg1); + D3D7RESULT GetTextureStageState(DWORD arg0, D3DTEXTURESTAGESTATETYPE arg1, [out] LPDWORD arg2); + D3D7RESULT SetTextureStageState(DWORD arg0, D3DTEXTURESTAGESTATETYPE arg1, DWORD arg2); + D3D7RESULT ValidateDevice(LPDWORD arg0); +}; diff --git a/tools/Debugging Tools for Windows/winext/manifest/d3d8.h b/tools/Debugging Tools for Windows/winext/manifest/d3d8.h new file mode 100644 index 0000000000..71977a59ac --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/d3d8.h @@ -0,0 +1,369 @@ +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +// D3D8 API Set +// +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +module D3D8.DLL: +category Direct3D: + +#include "d3d8types.h" +#include "d3d8caps.h" + +// +// GUIDs +// + +struct __declspec(uuid("1dd9e8da-1c77-4d40-b0cf-98fefdff9512")) IDirect3D8; +struct __declspec(uuid("7385e5df-8fe8-41d5-86b6-d7b48547b6cf")) IDirect3DDevice8; +struct __declspec(uuid("1b36bb7b-09b7-410a-b445-7d1430d7b33f")) IDirect3DResource8; +struct __declspec(uuid("b4211cfa-51b9-4a9f-ab78-db99b2bb678e")) IDirect3DBaseTexture8; +struct __declspec(uuid("e4cdd575-2866-4f01-b12e-7eece1ec9358")) IDirect3DTexture8; +struct __declspec(uuid("3ee5b968-2aca-4c34-8bb5-7e0c3d19b750")) IDirect3DCubeTexture8; +struct __declspec(uuid("4b8aaafa-140f-42ba-9131-597eafaa2ead")) IDirect3DVolumeTexture8; +struct __declspec(uuid("8aeeeac7-05f9-44d4-b591-000b0df1cb95")) IDirect3DVertexBuffer8; +struct __declspec(uuid("0e689c9a-053d-44a0-9d92-db0e3d750f86")) IDirect3DIndexBuffer8; +struct __declspec(uuid("b96eebca-b326-4ea5-882f-2ff5bae021dd")) IDirect3DSurface8; +struct __declspec(uuid("bd7349f5-14f1-42e4-9c79-972380db40c0")) IDirect3DVolume8; +struct __declspec(uuid("928c088b-76b9-4c6b-a536-a590853876cd")) IDirect3DSwapChain8; + + +// +// Typedefs +// + +typedef IDirect3D8 *LPDIRECT3D8; +typedef IDirect3DDevice8 *LPDIRECT3DDEVICE8; +typedef IDirect3DResource8 *LPDIRECT3DRESOURCE8; +typedef IDirect3DBaseTexture8 *LPDIRECT3DBASETEXTURE8; +typedef IDirect3DTexture8 *LPDIRECT3DTEXTURE8; +typedef IDirect3DVolumeTexture8 *LPDIRECT3DVOLUMETEXTURE8; +typedef IDirect3DCubeTexture8 *LPDIRECT3DCUBETEXTURE8; +typedef IDirect3DVertexBuffer8 *LPDIRECT3DVERTEXBUFFER8; +typedef IDirect3DIndexBuffer8 *LPDIRECT3DINDEXBUFFER8; +typedef IDirect3DSurface8 *LPDIRECT3DSURFACE8; +typedef IDirect3DVolume8 *LPDIRECT3DVOLUME8; +typedef IDirect3DSwapChain8 *LPDIRECT3DSWAPCHAIN8; + +typedef IDirect3D8 **LPLPDIRECT3D8; +typedef IDirect3DDevice8 **LPLPDIRECT3DDEVICE8; +typedef IDirect3DResource8 **LPLPDIRECT3DRESOURCE8; +typedef IDirect3DBaseTexture8 **LPLPDIRECT3DBASETEXTURE8; +typedef IDirect3DTexture8 **LPLPDIRECT3DTEXTURE8; +typedef IDirect3DVolumeTexture8 **LPLPDIRECT3DVOLUMETEXTURE8; +typedef IDirect3DCubeTexture8 **LPLPDIRECT3DCUBETEXTURE8; +typedef IDirect3DVertexBuffer8 **LPLPDIRECT3DVERTEXBUFFER8; +typedef IDirect3DIndexBuffer8 **LPLPDIRECT3DINDEXBUFFER8; +typedef IDirect3DSurface8 **LPLPDIRECT3DSURFACE8; +typedef IDirect3DVolume8 **LPLPDIRECT3DVOLUME8; +typedef IDirect3DSwapChain8 **LPLPDIRECT3DSWAPCHAIN8; + +alias LPDIRECT3D8; +alias LPDIRECT3DDEVICE8; +alias LPDIRECT3DRESOURCE8; +alias LPDIRECT3DBASETEXTURE8; +alias LPDIRECT3DTEXTURE8; +alias LPDIRECT3DVOLUMETEXTURE8; +alias LPDIRECT3DCUBETEXTURE8; +alias LPDIRECT3DVERTEXBUFFER8; +alias LPDIRECT3DINDEXBUFFER8; +alias LPDIRECT3DSURFACE8; +alias LPDIRECT3DVOLUME8; +alias LPDIRECT3DSWAPCHAIN8; +alias LPLPDIRECT3D8; +alias LPLPDIRECT3DDEVICE8; +alias LPLPDIRECT3DRESOURCE8; +alias LPLPDIRECT3DBASETEXTURE8; +alias LPLPDIRECT3DTEXTURE8; +alias LPLPDIRECT3DVOLUMETEXTURE8; +alias LPLPDIRECT3DCUBETEXTURE8; +alias LPLPDIRECT3DVERTEXBUFFER8; +alias LPLPDIRECT3DINDEXBUFFER8; +alias LPLPDIRECT3DSURFACE8; +alias LPLPDIRECT3DVOLUME8; +alias LPLPDIRECT3DSWAPCHAIN8; + +// +// Masks +// + +mask DWORD d3dAdaperIdentifierFlags8 +{ +#define D3DENUM_NO_WHQL_LEVEL 0x00000002 +}; + +mask DWORD d3dSetPrivateDataFlags8 +{ +#define D3DSPD_IUNKNOWN 0x00000001 +}; + +mask DWORD d3dSetCursorPositionFlags8 +{ +#define D3DCURSOR_IMMEDIATE_UPDATE 0x00000001 +}; + +// +// Values +// + +value LONG d3dCreateVersion8 +{ +#define DX_VERSION_80 120 +#define DX_VERSION_81 220 +}; + +value UINT d3dMode8 +{ +#define D3DCURRENT_DISPLAY_MODE 0x00EFFFFF +}; + +value DWORD d3dSetGammaRamp8 +{ +#define D3DSGR_NO_CALIBRATION 0x00000000 +#define D3DSGR_CALIBRATE 0x00000001 +}; + +value DWORD D3DRESULT8 +{ +#define D3D_OK 0 +#define D3DERR_WRONGTEXTUREFORMAT 0x88760818 [fail] +#define D3DERR_UNSUPPORTEDCOLOROPERATION 0x88760819 [fail] +#define D3DERR_UNSUPPORTEDCOLORARG 0x8876081a [fail] +#define D3DERR_UNSUPPORTEDALPHAOPERATION 0x8876081b [fail] +#define D3DERR_UNSUPPORTEDALPHAARG 0x8876081c [fail] +#define D3DERR_TOOMANYOPERATIONS 0x8876081d [fail] +#define D3DERR_CONFLICTINGTEXTUREFILTER 0x8876081e [fail] +#define D3DERR_UNSUPPORTEDFACTORVALUE 0x8876081f [fail] +#define D3DERR_CONFLICTINGRENDERSTATE 0x88760821 [fail] +#define D3DERR_UNSUPPORTEDTEXTUREFILTER 0x88760822 [fail] +#define D3DERR_CONFLICTINGTEXTUREPALETTE 0x88760826 [fail] +#define D3DERR_DRIVERINTERNALERROR 0x88760827 [fail] +#define D3DERR_NOTFOUND 0x88760866 [fail] +#define D3DERR_MOREDATA 0x88760867 [fail] +#define D3DERR_DEVICELOST 0x88760868 [fail] +#define D3DERR_DEVICENOTRESET 0x88760869 [fail] +#define D3DERR_NOTAVAILABLE 0x8876086a [fail] +#define D3DERR_OUTOFVIDEOMEMORY 0x8876017c [fail] +#define D3DERR_INVALIDDEVICE 0x8876086b [fail] +#define D3DERR_INVALIDCALL 0x8876086c [fail] +#define D3DERR_DRIVERINVALIDCALL 0x8876086d [fail] +}; + +// +// Functions +// + +LPDIRECT3D8 Direct3DCreate8(d3dCreateVersion8 SDKVersion); + + +// +// Interfaces +// + +interface IDirect3D8 : IUnknown +{ + D3DRESULT8 RegisterSoftwareDevice(LPVOID pInitializeFunction); + UINT GetAdapterCount(); + D3DRESULT8 GetAdapterIdentifier(d3dAdapterID8 Adapter, d3dAdaperIdentifierFlags8 Flags,[out] LPD3DADAPTER_IDENTIFIER8 pIdentifier); + UINT GetAdapterModeCount(d3dAdapterID8 Adapter); + D3DRESULT8 EnumAdapterModes(d3dAdapterID8 Adapter, d3dMode8 Mode,[out] LPD3DDISPLAYMODE pMode); + D3DRESULT8 GetAdapterDisplayMode(d3dAdapterID8 Adapter,[out] LPD3DDISPLAYMODE pMode); + D3DRESULT8 CheckDeviceType(d3dAdapterID8 Adapter, D3DDEVTYPE CheckType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL Windowed); + D3DRESULT8 CheckDeviceFormat(d3dAdapterID8 Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, d3dUsage8 Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat); + D3DRESULT8 CheckDeviceMultiSampleType(d3dAdapterID8 Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType); + D3DRESULT8 CheckDepthStencilMatch(d3dAdapterID8 Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat); + D3DRESULT8 GetDeviceCaps(d3dAdapterID8 Adapter, D3DDEVTYPE DeviceType,[out] LPD3DCAPS8 pCaps); + HMONITOR GetAdapterMonitor(d3dAdapterID8 Adapter); + D3DRESULT8 CreateDevice(d3dAdapterID8 Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, d3dBehaviorFlags8 BehaviorFlags, [out] LPD3DPRESENT_PARAMETERS pPresentationParameters, [out] LPLPDIRECT3DDEVICE8 ppReturnedDeviceInterface); +}; + +interface IDirect3DDevice8 : IUnknown +{ + D3DRESULT8 TestCooperativeLevel(); + UINT GetAvailableTextureMem(); + D3DRESULT8 ResourceManagerDiscardBytes(DWORD Bytes); + D3DRESULT8 GetDirect3D([out] LPLPDIRECT3D8 ppD3D8); + D3DRESULT8 GetDeviceCaps([out] LPD3DCAPS8 pCaps); + D3DRESULT8 GetDisplayMode([out] LPD3DDISPLAYMODE pMode); + D3DRESULT8 GetCreationParameters([out] LPD3DDEVICE_CREATION_PARAMETERS pParameters); + D3DRESULT8 SetCursorProperties(UINT XHotSpot, UINT YHotSpot, LPDIRECT3DSURFACE8 pCursorBitmap); + void SetCursorPosition(UINT XScreenSpace, UINT YScreenSpace, d3dSetCursorPositionFlags8 Flags); + BOOL ShowCursor(BOOL bShow); + D3DRESULT8 CreateAdditionalSwapChain([out] LPD3DPRESENT_PARAMETERS pPresentationParameters,[out] LPLPDIRECT3DSWAPCHAIN8 ppSwapChain); + D3DRESULT8 Reset([out] LPD3DPRESENT_PARAMETERS pPresentationParameters); + D3DRESULT8 Present(RECT* pSourceRect, RECT* pDestRect, HWND hDestWindowOverride, RGNDATA* pDirtyRegion); + D3DRESULT8 GetBackBuffer(UINT BackBuffer, D3DBACKBUFFER_TYPE Type, [out] LPLPDIRECT3DSURFACE8 ppBackBuffer); + D3DRESULT8 GetRasterStatus(LPD3DRASTER_STATUS pRasterStatus); + void SetGammaRamp(d3dSetGammaRamp8 Flags, LPD3DGAMMARAMP pRamp); + void GetGammaRamp([out] LPD3DGAMMARAMP pRamp); + D3DRESULT8 CreateTexture(UINT Width, UINT Height, UINT Levels, d3dUsage8 Usage, D3DFORMAT Format, D3DPOOL Pool, [out] LPLPDIRECT3DTEXTURE8 ppTexture); + D3DRESULT8 CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, d3dUsage8 Usage, D3DFORMAT Format, D3DPOOL Pool, [out] LPLPDIRECT3DVOLUMETEXTURE8 ppVolumeTexture); + D3DRESULT8 CreateCubeTexture(UINT EdgeLength, UINT Levels, d3dUsage8 Usage, D3DFORMAT Format, D3DPOOL Pool, [out] LPLPDIRECT3DCUBETEXTURE8 ppCubeTexture); + D3DRESULT8 CreateVertexBuffer(UINT Length, d3dUsage8 Usage, d3dFVF8 FVF, D3DPOOL Pool, [out] LPLPDIRECT3DVERTEXBUFFER8 ppVertexBuffer); + D3DRESULT8 CreateIndexBuffer(UINT Length, d3dUsage8 Usage, D3DFORMAT Format, D3DPOOL Pool, [out] LPLPDIRECT3DINDEXBUFFER8 ppIndexBuffer); + D3DRESULT8 CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, BOOL Lockable, [out] LPLPDIRECT3DSURFACE8 ppSurface); + D3DRESULT8 CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, [out] LPLPDIRECT3DSURFACE8 ppSurface); + D3DRESULT8 CreateImageSurface(UINT Width, UINT Height, D3DFORMAT Format, [out] LPLPDIRECT3DSURFACE8 ppSurface); + D3DRESULT8 CopyRects(LPDIRECT3DSURFACE8 pSourceSurface, RECT* pSourceRectsArray, UINT cRects, LPDIRECT3DSURFACE8 pDestinationSurface, POINT* pDestPointsArray); + D3DRESULT8 UpdateTexture(LPDIRECT3DBASETEXTURE8 pSourceTexture, LPDIRECT3DBASETEXTURE8 pDestinationTexture); + D3DRESULT8 GetFrontBuffer(LPDIRECT3DSURFACE8 pDestSurface); + D3DRESULT8 SetRenderTarget(LPDIRECT3DSURFACE8 pRenderTarget, LPDIRECT3DSURFACE8 pNewZStencil); + D3DRESULT8 GetRenderTarget([out] LPLPDIRECT3DSURFACE8 ppRenderTarget); + D3DRESULT8 GetDepthStencilSurface([out] LPLPDIRECT3DSURFACE8 ppZStencilSurface); + D3DRESULT8 BeginScene(); + D3DRESULT8 EndScene(); + D3DRESULT8 Clear(UINT Count, LPD3DRECT pRects, d3dClearFlags8 Flags, D3DCOLOR Color, float Z, DWORD Stencil); + D3DRESULT8 SetTransform(D3DTRANSFORMSTATETYPE8 State, LPD3DMATRIX8 pMatrix); + D3DRESULT8 GetTransform(D3DTRANSFORMSTATETYPE8 State, [out] LPD3DMATRIX8 pMatrix); + D3DRESULT8 MultiplyTransform(D3DTRANSFORMSTATETYPE8 State, LPD3DMATRIX8 pMatrix); + D3DRESULT8 SetViewport(LPD3DVIEWPORT8 pViewport); + D3DRESULT8 GetViewport([out] LPD3DVIEWPORT8 pViewport); + D3DRESULT8 SetMaterial(LPD3DMATERIAL8 pMaterial); + D3DRESULT8 GetMaterial([out] LPD3DMATERIAL8 pMaterial); + D3DRESULT8 SetLight(DWORD Index, LPD3DLIGHT8 pLight); + D3DRESULT8 GetLight(DWORD Index, [out] LPD3DLIGHT8 pLight); + D3DRESULT8 LightEnable(DWORD Index, BOOL Enable); + D3DRESULT8 GetLightEnable(DWORD Index, [out] BOOL* pEnable); + D3DRESULT8 SetClipPlane(DWORD Index, float* pPlane); + D3DRESULT8 GetClipPlane(DWORD Index, [out] float* pPlane); + D3DRESULT8 SetRenderState(D3DRENDERSTATETYPE8 State, DWORD Value); + D3DRESULT8 GetRenderState(D3DRENDERSTATETYPE8 State, [out] LPDWORD pValue); + D3DRESULT8 BeginStateBlock(); + D3DRESULT8 EndStateBlock([out] LPDWORD pToken); + D3DRESULT8 ApplyStateBlock(DWORD Token); + D3DRESULT8 CaptureStateBlock(DWORD Token); + D3DRESULT8 DeleteStateBlock(DWORD Token); + D3DRESULT8 CreateStateBlock(D3DSTATEBLOCKTYPE8 Type, [out] LPDWORD pToken); + D3DRESULT8 SetClipStatus(LPD3DCLIPSTATUS8 pClipStatus); + D3DRESULT8 GetClipStatus([out] LPD3DCLIPSTATUS8 pClipStatus); + D3DRESULT8 GetTexture(DWORD Stage, [out] LPLPDIRECT3DBASETEXTURE8 ppTexture); + D3DRESULT8 SetTexture(DWORD Stage, LPDIRECT3DBASETEXTURE8 pTexture); + D3DRESULT8 GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE8 Type, [out] LPDWORD pValue); + D3DRESULT8 SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE8 Type, DWORD Value); + D3DRESULT8 ValidateDevice([out] LPDWORD pNumPasses); + D3DRESULT8 GetInfo(DWORD DevInfoID, [out] LPVOID pDevInfoStruct, DWORD DevInfoStructSize); + D3DRESULT8 SetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries); + D3DRESULT8 GetPaletteEntries(UINT PaletteNumber, [out] PALETTEENTRY* pEntries); + D3DRESULT8 SetCurrentTexturePalette(UINT PaletteNumber); + D3DRESULT8 GetCurrentTexturePalette([out] UINT *PaletteNumber); + D3DRESULT8 DrawPrimitive(D3DPRIMITIVETYPE8 PrimitiveType, UINT StartVertex, UINT PrimitiveCount); + D3DRESULT8 DrawIndexedPrimitive(D3DPRIMITIVETYPE8 PrimitiveType, UINT minIndex, UINT NumVertices, UINT startIndex, UINT primCount); + D3DRESULT8 DrawPrimitiveUP(D3DPRIMITIVETYPE8 PrimitiveType, UINT PrimitiveCount, LPVOID pVertexStreamZeroData, UINT VertexStreamZeroStride); + D3DRESULT8 DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE8 PrimitiveType, UINT MinVertexIndex, UINT NumVertexIndices, UINT PrimitiveCount, LPVOID pIndexData, D3DFORMAT IndexDataFormat, LPVOID pVertexStreamZeroData, UINT VertexStreamZeroStride); + D3DRESULT8 ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, LPDIRECT3DVERTEXBUFFER8 pDestBuffer, d3dProcessVerticesFlags Flags); + D3DRESULT8 CreateVertexShader(LPDWORD pDeclaration, LPDWORD pFunction, [out] LPDWORD pHandle, d3dUsage8 Usage); + D3DRESULT8 SetVertexShader(DWORD Handle); // let's not use fvf here - VB fvf can be seen in CreateVB + D3DRESULT8 GetVertexShader([out] LPDWORD pHandle); + D3DRESULT8 DeleteVertexShader(DWORD Handle); + D3DRESULT8 SetVertexShaderConstant(DWORD Register, LPVOID pConstantData, DWORD ConstantCount); + D3DRESULT8 GetVertexShaderConstant(DWORD Register, [out] LPVOID pConstantData, DWORD ConstantCount); + D3DRESULT8 GetVertexShaderDeclaration(DWORD Handle, [out] LPVOID pData, [out] LPDWORD pSizeOfData); + D3DRESULT8 GetVertexShaderFunction(DWORD Handle, [out] LPVOID pData, [out] LPDWORD pSizeOfData); + D3DRESULT8 SetStreamSource(UINT StreamNumber, LPDIRECT3DVERTEXBUFFER8 pStreamData, UINT Stride); + D3DRESULT8 GetStreamSource(UINT StreamNumber, [out] LPLPDIRECT3DVERTEXBUFFER8 ppStreamData, [out] UINT* pStride); + D3DRESULT8 SetIndices(LPDIRECT3DINDEXBUFFER8 pIndexData, UINT BaseVertexIndex); + D3DRESULT8 GetIndices([out] LPLPDIRECT3DINDEXBUFFER8 ppIndexData, [out] UINT* pBaseVertexIndex); + D3DRESULT8 CreatePixelShader(LPDWORD pFunction, [out] LPDWORD pHandle); + D3DRESULT8 SetPixelShader(DWORD Handle); + D3DRESULT8 GetPixelShader([out] LPDWORD pHandle); + D3DRESULT8 DeletePixelShader(DWORD Handle); + D3DRESULT8 SetPixelShaderConstant(DWORD Register, LPVOID pConstantData, DWORD ConstantCount); + D3DRESULT8 GetPixelShaderConstant(DWORD Register, [out] LPVOID pConstantData, DWORD ConstantCount); + D3DRESULT8 GetPixelShaderFunction(DWORD Handle, [out] LPVOID pData, [out] LPDWORD pSizeOfData); + D3DRESULT8 DrawRectPatch(UINT Handle, float* pNumSegs, LPD3DRECTPATCH_INFO pRectPatchInfo); + D3DRESULT8 DrawTriPatch(UINT Handle, float* pNumSegs, LPD3DTRIPATCH_INFO pTriPatchInfo); + D3DRESULT8 DeletePatch(UINT Handle); +}; + + +interface IDirect3DResource8 : IUnknown +{ + D3DRESULT8 GetDevice([out] LPLPDIRECT3DDEVICE8 ppDevice); + D3DRESULT8 SetPrivateData(REFGUID refguid, LPVOID pData, DWORD SizeOfData, d3dSetPrivateDataFlags8 Flags); + D3DRESULT8 GetPrivateData(REFGUID refguid, LPVOID pData,[out] LPDWORD pSizeOfData); + D3DRESULT8 FreePrivateData(REFGUID refguid); + DWORD SetPriority(DWORD PriorityNew); + DWORD GetPriority(); + void PreLoad(); + D3DRESOURCETYPE GetType(); +}; + +interface IDirect3DBaseTexture8 : IDirect3DResource8 +{ + DWORD SetLOD(DWORD LODNew); + DWORD GetLOD(); + DWORD GetLevelCount(); +}; + +interface IDirect3DTexture8 : IDirect3DBaseTexture8 +{ + D3DRESULT8 GetLevelDesc(UINT Level, [out] LPD3DSURFACE_DESC pDesc); + D3DRESULT8 GetSurfaceLevel(UINT Level, [out] LPLPDIRECT3DSURFACE8 ppSurfaceLevel); + D3DRESULT8 LockRect(UINT Level, [out] LPD3DLOCKED_RECT pLockedRect, RECT* pRect, d3dLockFlags8 Flags); + D3DRESULT8 UnlockRect(UINT Level); + D3DRESULT8 AddDirtyRect(RECT* pDirtyRect); +}; + +interface IDirect3DVolumeTexture8 : IDirect3DBaseTexture8 +{ + D3DRESULT8 GetLevelDesc(UINT Level, [out] LPD3DVOLUME_DESC pDesc); + D3DRESULT8 GetVolumeLevel(UINT Level, [out] LPLPDIRECT3DVOLUME8 ppVolumeLevel); + D3DRESULT8 LockBox(UINT Level, [out] LPD3DLOCKED_BOX pLockedVolume, LPD3DBOX pBox, d3dLockFlags8 Flags); + D3DRESULT8 UnlockBox(UINT Level); + D3DRESULT8 AddDirtyBox(LPD3DBOX pDirtyBox); +}; + +interface IDirect3DCubeTexture8 : IDirect3DBaseTexture8 +{ + D3DRESULT8 GetLevelDesc(UINT Level, [out] LPD3DSURFACE_DESC pDesc); + D3DRESULT8 GetCubeMapSurface(D3DCUBEMAP_FACES FaceType, UINT Level, [out] LPLPDIRECT3DSURFACE8 ppCubeMapSurface); + D3DRESULT8 LockRect(D3DCUBEMAP_FACES FaceType, UINT Level, [out] LPD3DLOCKED_RECT pLockedRect, RECT* pRect, d3dLockFlags8 Flags); + D3DRESULT8 UnlockRect(D3DCUBEMAP_FACES FaceType, UINT Level); + D3DRESULT8 AddDirtyRect(D3DCUBEMAP_FACES FaceType, RECT* pDirtyRect); +}; + +interface IDirect3DVertexBuffer8 : IDirect3DResource8 +{ + D3DRESULT8 Lock(UINT OffsetToLock, UINT SizeToLock, [out] BYTE** ppbData, d3dLockFlags8 Flags); + D3DRESULT8 Unlock(); + D3DRESULT8 GetDesc([out] LPD3DVERTEXBUFFER_DESC pDesc); +}; + +interface IDirect3DIndexBuffer8 : IDirect3DResource8 +{ + D3DRESULT8 Lock(UINT OffsetToLock, UINT SizeToLock, [out] BYTE** ppbData, d3dLockFlags8 Flags); + D3DRESULT8 Unlock(); + D3DRESULT8 GetDesc([out] LPD3DINDEXBUFFER_DESC pDesc); +}; + +interface IDirect3DSurface8 : IUnknown +{ + D3DRESULT8 GetDevice([out] LPLPDIRECT3DDEVICE8 ppDevice); + D3DRESULT8 SetPrivateData(REFGUID refguid, LPVOID pData, DWORD SizeOfData, d3dSetPrivateDataFlags8 Flags); + D3DRESULT8 GetPrivateData(REFGUID refguid, LPVOID pData, [out] LPDWORD pSizeOfData); + D3DRESULT8 FreePrivateData(REFGUID refguid); + D3DRESULT8 GetContainer(REFIID riid, [out] LPVOID* ppContainer); + D3DRESULT8 GetDesc([out] LPD3DSURFACE_DESC pDesc); + D3DRESULT8 LockRect([out] LPD3DLOCKED_RECT pLockedRect, RECT* pRect, d3dLockFlags8 Flags); + D3DRESULT8 UnlockRect(); +}; + +interface IDirect3DVolume8 : IUnknown +{ + D3DRESULT8 GetDevice([out] LPLPDIRECT3DDEVICE8 ppDevice); + D3DRESULT8 SetPrivateData(REFGUID refguid, LPVOID pData, DWORD SizeOfData, d3dSetPrivateDataFlags8 Flags); + D3DRESULT8 GetPrivateData(REFGUID refguid, LPVOID pData, [out] LPDWORD pSizeOfData); + D3DRESULT8 FreePrivateData(REFGUID refguid); + D3DRESULT8 GetContainer(REFIID riid, [out] LPVOID* ppContainer); + D3DRESULT8 GetDesc([out] LPD3DVOLUME_DESC pDesc); + D3DRESULT8 LockBox([out] LPD3DLOCKED_BOX pLockedVolume, LPD3DBOX pBox, d3dLockFlags8 Flags); + D3DRESULT8 UnlockBox(); +}; + +interface IDirect3DSwapChain8 : IUnknown +{ + D3DRESULT8 Present(RECT* pSourceRect, RECT* pDestRect, HWND hDestWindowOverride, RGNDATA* pDirtyRegion); + D3DRESULT8 GetBackBuffer(UINT BackBuffer, D3DBACKBUFFER_TYPE Type, [out] LPLPDIRECT3DSURFACE8 ppBackBuffer); +}; diff --git a/tools/Debugging Tools for Windows/winext/manifest/d3d8caps.h b/tools/Debugging Tools for Windows/winext/manifest/d3d8caps.h new file mode 100644 index 0000000000..01b529b763 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/d3d8caps.h @@ -0,0 +1,361 @@ +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +// D3D8 Caps +// +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +// +// Masks +// + +// +// Caps +// +mask DWORD d3dCaps8 +{ +#define D3DCAPS_READ_SCANLINE 0x00020000L +}; + +// +// Caps2 +// +mask DWORD d3dCaps28 +{ +#define D3DCAPS2_NO2DDURING3DSCENE 0x00000002L +#define D3DCAPS2_FULLSCREENGAMMA 0x00020000L +#define D3DCAPS2_CANRENDERWINDOWED 0x00080000L +#define D3DCAPS2_CANCALIBRATEGAMMA 0x00100000L +#define D3DCAPS2_RESERVED 0x02000000L +}; + +// +// Caps3 +// +mask DWORD d3dCaps38 +{ +#define D3DCAPS3_RESERVED 0x8000001fL +}; + +// +// CursorCaps +// +mask DWORD d3dCursorCaps8 +{ +#define D3DCURSORCAPS_COLOR 0x00000001L +#define D3DCURSORCAPS_LOWRES 0x00000002L +}; + +// +// DevCaps +// +mask DWORD d3dDevCaps8 +{ +#define D3DDEVCAPS_EXECUTESYSTEMMEMORY 0x00000010L +#define D3DDEVCAPS_EXECUTEVIDEOMEMORY 0x00000020L +#define D3DDEVCAPS_TLVERTEXSYSTEMMEMORY 0x00000040L +#define D3DDEVCAPS_TLVERTEXVIDEOMEMORY 0x00000080L +#define D3DDEVCAPS_TEXTURESYSTEMMEMORY 0x00000100L +#define D3DDEVCAPS_TEXTUREVIDEOMEMORY 0x00000200L +#define D3DDEVCAPS_DRAWPRIMTLVERTEX 0x00000400L +#define D3DDEVCAPS_CANRENDERAFTERFLIP 0x00000800L +#define D3DDEVCAPS_TEXTURENONLOCALVIDMEM 0x00001000L +#define D3DDEVCAPS_DRAWPRIMITIVES2 0x00002000L +#define D3DDEVCAPS_SEPARATETEXTUREMEMORIES 0x00004000L +#define D3DDEVCAPS_DRAWPRIMITIVES2EX 0x00008000L +#define D3DDEVCAPS_HWTRANSFORMANDLIGHT 0x00010000L +#define D3DDEVCAPS_CANBLTSYSTONONLOCAL 0x00020000L +#define D3DDEVCAPS_HWRASTERIZATION 0x00080000L +#define D3DDEVCAPS_PUREDEVICE 0x00100000L +#define D3DDEVCAPS_QUINTICRTPATCHES 0x00200000L +#define D3DDEVCAPS_RTPATCHES 0x00400000L +#define D3DDEVCAPS_RTPATCHHANDLEZERO 0x00800000L +#define D3DDEVCAPS_NPATCHES 0x01000000L +}; + +// +// PrimitiveMiscCaps +// +mask DWORD d3dPrimitiveMiscCaps8 +{ +#define D3DPMISCCAPS_MASKZ 0x00000002L +#define D3DPMISCCAPS_LINEPATTERNREP 0x00000004L +#define D3DPMISCCAPS_CULLNONE 0x00000010L +#define D3DPMISCCAPS_CULLCW 0x00000020L +#define D3DPMISCCAPS_CULLCCW 0x00000040L +#define D3DPMISCCAPS_COLORWRITEENABLE 0x00000080L +#define D3DPMISCCAPS_CLIPPLANESCALEDPOINTS 0x00000100L +#define D3DPMISCCAPS_CLIPTLVERTS 0x00000200L +#define D3DPMISCCAPS_TSSARGTEMP 0x00000400L +#define D3DPMISCCAPS_BLENDOP 0x00000800L +}; + +// +// LineCaps +// +mask DWORD d3dLineCaps8 +{ +#define D3DLINECAPS_TEXTURE 0x00000001L +#define D3DLINECAPS_ZTEST 0x00000002L +#define D3DLINECAPS_BLEND 0x00000004L +#define D3DLINECAPS_ALPHACMP 0x00000008L +#define D3DLINECAPS_FOG 0x00000010L +}; + +// +// RasterCaps +// +mask DWORD d3dRasterCaps8 +{ +#define D3DPRASTERCAPS_DITHER 0x00000001L +#define D3DPRASTERCAPS_PAT 0x00000008L +#define D3DPRASTERCAPS_ZTEST 0x00000010L +#define D3DPRASTERCAPS_FOGVERTEX 0x00000080L +#define D3DPRASTERCAPS_FOGTABLE 0x00000100L +#define D3DPRASTERCAPS_ANTIALIASEDGES 0x00001000L +#define D3DPRASTERCAPS_MIPMAPLODBIAS 0x00002000L +#define D3DPRASTERCAPS_ZBIAS 0x00004000L +#define D3DPRASTERCAPS_ZBUFFERLESSHSR 0x00008000L +#define D3DPRASTERCAPS_FOGRANGE 0x00010000L +#define D3DPRASTERCAPS_ANISOTROPY 0x00020000L +#define D3DPRASTERCAPS_WBUFFER 0x00040000L +#define D3DPRASTERCAPS_WFOG 0x00100000L +#define D3DPRASTERCAPS_ZFOG 0x00200000L +#define D3DPRASTERCAPS_COLORPERSPECTIVE 0x00400000L +#define D3DPRASTERCAPS_STRETCHBLTMULTISAMPLE 0x00800000L +}; + +// +// ZCmpCaps, AlphaCmpCaps +// +mask DWORD d3dCmpCaps8 +{ +#define D3DPCMPCAPS_NEVER 0x00000001L +#define D3DPCMPCAPS_LESS 0x00000002L +#define D3DPCMPCAPS_EQUAL 0x00000004L +#define D3DPCMPCAPS_LESSEQUAL 0x00000008L +#define D3DPCMPCAPS_GREATER 0x00000010L +#define D3DPCMPCAPS_NOTEQUAL 0x00000020L +#define D3DPCMPCAPS_GREATEREQUAL 0x00000040L +#define D3DPCMPCAPS_ALWAYS 0x00000080L +}; + +// +// SourceBlendCaps, DestBlendCaps +// +mask DWORD d3dBlendCaps8 +{ +#define D3DPBLENDCAPS_ZERO 0x00000001L +#define D3DPBLENDCAPS_ONE 0x00000002L +#define D3DPBLENDCAPS_SRCCOLOR 0x00000004L +#define D3DPBLENDCAPS_INVSRCCOLOR 0x00000008L +#define D3DPBLENDCAPS_SRCALPHA 0x00000010L +#define D3DPBLENDCAPS_INVSRCALPHA 0x00000020L +#define D3DPBLENDCAPS_DESTALPHA 0x00000040L +#define D3DPBLENDCAPS_INVDESTALPHA 0x00000080L +#define D3DPBLENDCAPS_DESTCOLOR 0x00000100L +#define D3DPBLENDCAPS_INVDESTCOLOR 0x00000200L +#define D3DPBLENDCAPS_SRCALPHASAT 0x00000400L +#define D3DPBLENDCAPS_BOTHSRCALPHA 0x00000800L +#define D3DPBLENDCAPS_BOTHINVSRCALPHA 0x00001000L +}; + +// +// ShadeCaps +// +mask DWORD d3dShadeCaps8 +{ +#define D3DPSHADECAPS_COLORGOURAUDRGB 0x00000008L +#define D3DPSHADECAPS_SPECULARGOURAUDRGB 0x00000200L +#define D3DPSHADECAPS_ALPHAGOURAUDBLEND 0x00004000L +#define D3DPSHADECAPS_FOGGOURAUD 0x00080000L +}; + +// +// TextureCaps +// +mask DWORD d3dTextureCaps8 +{ +#define D3DPTEXTURECAPS_PERSPECTIVE 0x00000001L +#define D3DPTEXTURECAPS_POW2 0x00000002L +#define D3DPTEXTURECAPS_ALPHA 0x00000004L +#define D3DPTEXTURECAPS_SQUAREONLY 0x00000020L +#define D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE 0x00000040L +#define D3DPTEXTURECAPS_ALPHAPALETTE 0x00000080L +#define D3DPTEXTURECAPS_NONPOW2CONDITIONAL 0x00000100L +#define D3DPTEXTURECAPS_PROJECTED 0x00000400L +#define D3DPTEXTURECAPS_CUBEMAP 0x00000800L +#define D3DPTEXTURECAPS_VOLUMEMAP 0x00002000L +#define D3DPTEXTURECAPS_MIPMAP 0x00004000L +#define D3DPTEXTURECAPS_MIPVOLUMEMAP 0x00008000L +#define D3DPTEXTURECAPS_MIPCUBEMAP 0x00010000L +#define D3DPTEXTURECAPS_CUBEMAP_POW2 0x00020000L +#define D3DPTEXTURECAPS_VOLUMEMAP_POW2 0x00040000L +}; + +// +// TextureFilterCaps +// +mask DWORD d3dTextureFilterCaps8 +{ +#define D3DPTFILTERCAPS_MINFPOINT 0x00000100L +#define D3DPTFILTERCAPS_MINFLINEAR 0x00000200L +#define D3DPTFILTERCAPS_MINFANISOTROPIC 0x00000400L +#define D3DPTFILTERCAPS_MIPFPOINT 0x00010000L +#define D3DPTFILTERCAPS_MIPFLINEAR 0x00020000L +#define D3DPTFILTERCAPS_MAGFPOINT 0x01000000L +#define D3DPTFILTERCAPS_MAGFLINEAR 0x02000000L +#define D3DPTFILTERCAPS_MAGFANISOTROPIC 0x04000000L +#define D3DPTFILTERCAPS_MAGFAFLATCUBIC 0x08000000L +#define D3DPTFILTERCAPS_MAGFGAUSSIANCUBIC 0x10000000L +}; + +// +// TextureAddressCaps +// +mask DWORD d3dTextureAddressCaps8 +{ +#define D3DPTADDRESSCAPS_WRAP 0x00000001L +#define D3DPTADDRESSCAPS_MIRROR 0x00000002L +#define D3DPTADDRESSCAPS_CLAMP 0x00000004L +#define D3DPTADDRESSCAPS_BORDER 0x00000008L +#define D3DPTADDRESSCAPS_INDEPENDENTUV 0x00000010L +#define D3DPTADDRESSCAPS_MIRRORONCE 0x00000020L +}; + +// +// StencilCaps +// +mask DWORD d3dStencilCaps8 +{ +#define D3DSTENCILCAPS_KEEP 0x00000001L +#define D3DSTENCILCAPS_ZERO 0x00000002L +#define D3DSTENCILCAPS_REPLACE 0x00000004L +#define D3DSTENCILCAPS_INCRSAT 0x00000008L +#define D3DSTENCILCAPS_DECRSAT 0x00000010L +#define D3DSTENCILCAPS_INVERT 0x00000020L +#define D3DSTENCILCAPS_INCR 0x00000040L +#define D3DSTENCILCAPS_DECR 0x00000080L +}; + +// +// TextureOpCaps +// +mask DWORD d3dTextureOpCaps8 +{ +#define D3DTEXOPCAPS_DISABLE 0x00000001L +#define D3DTEXOPCAPS_SELECTARG1 0x00000002L +#define D3DTEXOPCAPS_SELECTARG2 0x00000004L +#define D3DTEXOPCAPS_MODULATE 0x00000008L +#define D3DTEXOPCAPS_MODULATE2X 0x00000010L +#define D3DTEXOPCAPS_MODULATE4X 0x00000020L +#define D3DTEXOPCAPS_ADD 0x00000040L +#define D3DTEXOPCAPS_ADDSIGNED 0x00000080L +#define D3DTEXOPCAPS_ADDSIGNED2X 0x00000100L +#define D3DTEXOPCAPS_SUBTRACT 0x00000200L +#define D3DTEXOPCAPS_ADDSMOOTH 0x00000400L +#define D3DTEXOPCAPS_BLENDDIFFUSEALPHA 0x00000800L +#define D3DTEXOPCAPS_BLENDTEXTUREALPHA 0x00001000L +#define D3DTEXOPCAPS_BLENDFACTORALPHA 0x00002000L +#define D3DTEXOPCAPS_BLENDTEXTUREALPHAPM 0x00004000L +#define D3DTEXOPCAPS_BLENDCURRENTALPHA 0x00008000L +#define D3DTEXOPCAPS_PREMODULATE 0x00010000L +#define D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR 0x00020000L +#define D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA 0x00040000L +#define D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR 0x00080000L +#define D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA 0x00100000L +#define D3DTEXOPCAPS_BUMPENVMAP 0x00200000L +#define D3DTEXOPCAPS_BUMPENVMAPLUMINANCE 0x00400000L +#define D3DTEXOPCAPS_DOTPRODUCT3 0x00800000L +#define D3DTEXOPCAPS_MULTIPLYADD 0x01000000L +#define D3DTEXOPCAPS_LERP 0x02000000L +}; + +// +// FVFCaps +// +mask DWORD d3dFVFCaps8 +{ +#define D3DFVFCAPS_TEXCOORDCOUNTMASK 0x0000ffffL +#define D3DFVFCAPS_DONOTSTRIPELEMENTS 0x00080000L +#define D3DFVFCAPS_PSIZE 0x00100000L +}; + +// +// VertexProcessingCaps +// +mask DWORD d3dVertexProcessingCaps8 +{ +#define D3DVTXPCAPS_TEXGEN 0x00000001L +#define D3DVTXPCAPS_MATERIALSOURCE7 0x00000002L +#define D3DVTXPCAPS_DIRECTIONALLIGHTS 0x00000008L +#define D3DVTXPCAPS_POSITIONALLIGHTS 0x00000010L +#define D3DVTXPCAPS_LOCALVIEWER 0x00000020L +#define D3DVTXPCAPS_TWEENING 0x00000040L +#define D3DVTXPCAPS_NO_VSDT_UBYTE4 0x00000080L +}; + + +// +// Structs +// + +typedef struct _D3DCAPS8 +{ + D3DDEVTYPE DeviceType; + d3dAdapterID8 AdapterOrdinal; + d3dCaps8 Caps; + d3dCaps28 Caps2; + d3dCaps38 Caps3; + d3dPresentationIntervals8 PresentationIntervals; + d3dCursorCaps8 CursorCaps; + d3dDevCaps8 DevCaps; + d3dPrimitiveMiscCaps8 PrimitiveMiscCaps; + d3dRasterCaps8 RasterCaps; + d3dCmpCaps8 ZCmpCaps; + d3dBlendCaps8 SrcBlendCaps; + d3dBlendCaps8 DestBlendCaps; + d3dCmpCaps8 AlphaCmpCaps; + d3dShadeCaps8 ShadeCaps; + d3dTextureCaps8 TextureCaps; + d3dTextureFilterCaps8 TextureFilterCaps; + d3dTextureFilterCaps8 CubeTextureFilterCaps; + d3dTextureFilterCaps8 VolumeTextureFilterCaps; + d3dTextureAddressCaps8 TextureAddressCaps; + d3dTextureAddressCaps8 VolumeTextureAddressCaps; + d3dLineCaps8 LineCaps; + DWORD MaxTextureWidth; + DWORD MaxTextureHeight; + DWORD MaxVolumeExtent; + DWORD MaxTextureRepeat; + DWORD MaxTextureAspectRatio; + DWORD MaxAnisotropy; + float MaxVertexW; + float GuardBandLeft; + float GuardBandTop; + float GuardBandRight; + float GuardBandBottom; + float ExtentsAdjust; + d3dStencilCaps8 StencilCaps; + d3dFVFCaps8 FVFCaps; + d3dTextureOpCaps8 TextureOpCaps; + DWORD MaxTextureBlendStages; + DWORD MaxSimultaneousTextures; + d3dVertexProcessingCaps8 VertexProcessingCaps; + DWORD MaxActiveLights; + DWORD MaxUserClipPlanes; + DWORD MaxVertexBlendMatrices; + DWORD MaxVertexBlendMatrixIndex; + float MaxPointSize; + DWORD MaxPrimitiveCount; + DWORD MaxVertexIndex; + DWORD MaxStreams; + DWORD MaxStreamStride; + DWORD VertexShaderVersion; + DWORD MaxVertexShaderConst; + DWORD PixelShaderVersion; + float MaxPixelShaderValue; +} D3DCAPS8, *LPD3DCAPS8; + +alias LPD3DCAPS8; diff --git a/tools/Debugging Tools for Windows/winext/manifest/d3d8types.h b/tools/Debugging Tools for Windows/winext/manifest/d3d8types.h new file mode 100644 index 0000000000..b0615695da --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/d3d8types.h @@ -0,0 +1,639 @@ +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +// D3D8 Types +// +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +// +// Typedefs +// + + +// +// Masks +// + +mask DWORD d3dUsage8 +{ +#define D3DUSAGE_RENDERTARGET 0x00000001 +#define D3DUSAGE_DEPTHSTENCIL 0x00000002 +#define D3DUSAGE_WRITEONLY 0x00000008 +#define D3DUSAGE_SOFTWAREPROCESSING 0x00000010 +#define D3DUSAGE_DONOTCLIP 0x00000020 +#define D3DUSAGE_POINTS 0x00000040 +#define D3DUSAGE_RTPATCHES 0x00000080 +#define D3DUSAGE_NPATCHES 0x00000100 +#define D3DUSAGE_DYNAMIC 0x00000200 +}; + +mask DWORD d3dPresentParamsFlags8 +{ +#define D3DPRESENTFLAG_LOCKABLE_BACKBUFFER 0x00000001 +}; + +mask DWORD d3dPresentationIntervals8 +{ +#define D3DPRESENT_INTERVAL_ONE 0x00000001 +#define D3DPRESENT_INTERVAL_TWO 0x00000002 +#define D3DPRESENT_INTERVAL_THREE 0x00000004 +#define D3DPRESENT_INTERVAL_FOUR 0x00000008 +#define D3DPRESENT_INTERVAL_IMMEDIATE 0x80000000 +}; + +mask DWORD d3dLockFlags8 +{ +#define D3DLOCK_READONLY 0x00000010 +#define D3DLOCK_DISCARD 0x00002000 +#define D3DLOCK_NOOVERWRITE 0x00001000 +#define D3DLOCK_NOSYSLOCK 0x00000800 +#define D3DLOCK_NO_DIRTY_UPDATE 0x00008000 +}; + +mask DWORD d3dFVF8 +{ +#define D3DFVF_RESERVED0 0x001 +#define D3DFVF_XYZ 0x002 +#define D3DFVF_XYZRHW 0x004 +#define D3DFVF_XYZB2 0x008 +#define D3DFVF_NORMAL 0x010 +#define D3DFVF_PSIZE 0x020 +#define D3DFVF_DIFFUSE 0x040 +#define D3DFVF_SPECULAR 0x080 +#define D3DFVF_TEX1 0x100 +#define D3DFVF_TEX2 0x200 +#define D3DFVF_TEX4 0x400 +#define D3DFVF_TEX8 0x800 +#define D3DFVF_LASTBETA_UBYTE4 0x1000 +#define D3DFVF_RESERVED 0xFFFFE000 +}; + +mask DWORD d3dBehaviorFlags8 +{ +#define D3DCREATE_FPU_PRESERVE 0x00000002 +#define D3DCREATE_MULTITHREADED 0x00000004 +#define D3DCREATE_PUREDEVICE 0x00000010 +#define D3DCREATE_SOFTWARE_VERTEXPROCESSING 0x00000020 +#define D3DCREATE_HARDWARE_VERTEXPROCESSING 0x00000040 +#define D3DCREATE_MIXED_VERTEXPROCESSING 0x00000080 +}; + +mask DWORD d3dClearFlags8 +{ +#define D3DCLEAR_TARGET 0x00000001 +#define D3DCLEAR_ZBUFFER 0x00000002 +#define D3DCLEAR_STENCIL 0x00000004 +}; + +mask DWORD d3dClipStatus8 +{ +#define D3DCS_LEFT 0x00000001L +#define D3DCS_RIGHT 0x00000002L +#define D3DCS_TOP 0x00000004L +#define D3DCS_BOTTOM 0x00000008L +#define D3DCS_FRONT 0x00000010L +#define D3DCS_BACK 0x00000020L +#define D3DCS_PLANE0 0x00000040L +#define D3DCS_PLANE1 0x00000080L +#define D3DCS_PLANE2 0x00000100L +#define D3DCS_PLANE3 0x00000200L +#define D3DCS_PLANE4 0x00000400L +#define D3DCS_PLANE5 0x00000800L +}; + +mask DWORD d3dProcessVerticesFlags +{ +#define D3DPV_DONOTCOPYDATA 1 +}; + +// +// Values +// + + +value UINT d3dAdapterID8 +{ +#define D3DADAPTER_DEFAULT 0 +}; + +value DWORD D3DDEVTYPE +{ +#define D3DDEVTYPE_HAL 1 +#define D3DDEVTYPE_REF 2 +#define D3DDEVTYPE_SW 3 +}; + +value DWORD D3DMULTISAMPLE_TYPE +{ +#define D3DMULTISAMPLE_NONE 0 +#define D3DMULTISAMPLE_2_SAMPLES 2 +#define D3DMULTISAMPLE_3_SAMPLES 3 +#define D3DMULTISAMPLE_4_SAMPLES 4 +#define D3DMULTISAMPLE_5_SAMPLES 5 +#define D3DMULTISAMPLE_6_SAMPLES 6 +#define D3DMULTISAMPLE_7_SAMPLES 7 +#define D3DMULTISAMPLE_8_SAMPLES 8 +#define D3DMULTISAMPLE_9_SAMPLES 9 +#define D3DMULTISAMPLE_10_SAMPLES 10 +#define D3DMULTISAMPLE_11_SAMPLES 11 +#define D3DMULTISAMPLE_12_SAMPLES 12 +#define D3DMULTISAMPLE_13_SAMPLES 13 +#define D3DMULTISAMPLE_14_SAMPLES 14 +#define D3DMULTISAMPLE_15_SAMPLES 15 +#define D3DMULTISAMPLE_16_SAMPLES 16 +}; + +value DWORD D3DRESOURCETYPE +{ +#define D3DRTYPE_SURFACE 1 +#define D3DRTYPE_VOLUME 2 +#define D3DRTYPE_TEXTURE 3 +#define D3DRTYPE_VOLUMETEXTURE 4 +#define D3DRTYPE_CUBETEXTURE 5 +#define D3DRTYPE_VERTEXBUFFER 6 +#define D3DRTYPE_INDEXBUFFER 7 +}; + +value DWORD D3DSWAPEFFECT +{ +#define D3DSWAPEFFECT_DEFAULT 0 +#define D3DSWAPEFFECT_DISCARD 1 +#define D3DSWAPEFFECT_FLIP 2 +#define D3DSWAPEFFECT_COPY 3 +#define D3DSWAPEFFECT_COPY_VSYNC 4 +}; + +value UINT d3dRefreshRate8 +{ +#define D3DPRESENT_RATE_DEFAULT 0x00000000 +#define D3DPRESENT_RATE_UNLIMITED 0x7fffffff +}; + +value DWORD D3DPOOL +{ +#define D3DPOOL_DEFAULT 0 +#define D3DPOOL_MANAGED 1 +#define D3DPOOL_SYSTEMMEM 2 +}; + +value DWORD D3DCUBEMAP_FACES +{ +#define D3DCUBEMAP_FACE_POSITIVE_X 0 +#define D3DCUBEMAP_FACE_NEGATIVE_X 1 +#define D3DCUBEMAP_FACE_POSITIVE_Y 2 +#define D3DCUBEMAP_FACE_NEGATIVE_Y 3 +#define D3DCUBEMAP_FACE_POSITIVE_Z 4 +#define D3DCUBEMAP_FACE_NEGATIVE_Z 5 +}; + +value DWORD D3DBACKBUFFER_TYPE +{ +#define D3DBACKBUFFER_TYPE_MONO 0 +#define D3DBACKBUFFER_TYPE_LEFT 1 +#define D3DBACKBUFFER_TYPE_RIGHT 2 +}; + +value DWORD D3DTRANSFORMSTATETYPE8 +{ +#define D3DTS_VIEW 2 +#define D3DTS_PROJECTION 3 +#define D3DTS_TEXTURE0 16 +#define D3DTS_TEXTURE1 17 +#define D3DTS_TEXTURE2 18 +#define D3DTS_TEXTURE3 19 +#define D3DTS_TEXTURE4 20 +#define D3DTS_TEXTURE5 21 +#define D3DTS_TEXTURE6 22 +#define D3DTS_TEXTURE7 23 +#define D3DTS_WORLD 256 +#define D3DTS_WORLD1 257 +#define D3DTS_WORLD2 258 +#define D3DTS_WORLD3 259 +}; + +value DWORD D3DLIGHTTYPE8 +{ +#define D3DLIGHT_POINT 1 +#define D3DLIGHT_SPOT 2 +#define D3DLIGHT_DIRECTIONAL 3 +}; + +value DWORD D3DRENDERSTATETYPE8 +{ +#define D3DRS_ZENABLE 7 +#define D3DRS_FILLMODE 8 +#define D3DRS_SHADEMODE 9 +#define D3DRS_LINEPATTERN 10 +#define D3DRS_ZWRITEENABLE 14 +#define D3DRS_ALPHATESTENABLE 15 +#define D3DRS_LASTPIXEL 16 +#define D3DRS_SRCBLEND 19 +#define D3DRS_DESTBLEND 20 +#define D3DRS_CULLMODE 22 +#define D3DRS_ZFUNC 23 +#define D3DRS_ALPHAREF 24 +#define D3DRS_ALPHAFUNC 25 +#define D3DRS_DITHERENABLE 26 +#define D3DRS_ALPHABLENDENABLE 27 +#define D3DRS_FOGENABLE 28 +#define D3DRS_SPECULARENABLE 29 +#define D3DRS_ZVISIBLE 30 +#define D3DRS_FOGCOLOR 34 +#define D3DRS_FOGTABLEMODE 35 +#define D3DRS_FOGSTART 36 +#define D3DRS_FOGEND 37 +#define D3DRS_FOGDENSITY 38 +#define D3DRS_EDGEANTIALIAS 40 +#define D3DRS_ZBIAS 47 +#define D3DRS_RANGEFOGENABLE 48 +#define D3DRS_STENCILENABLE 52 +#define D3DRS_STENCILFAIL 53 +#define D3DRS_STENCILZFAIL 54 +#define D3DRS_STENCILPASS 55 +#define D3DRS_STENCILFUNC 56 +#define D3DRS_STENCILREF 57 +#define D3DRS_STENCILMASK 58 +#define D3DRS_STENCILWRITEMASK 59 +#define D3DRS_TEXTUREFACTOR 60 +#define D3DRS_WRAP0 128 +#define D3DRS_WRAP1 129 +#define D3DRS_WRAP2 130 +#define D3DRS_WRAP3 131 +#define D3DRS_WRAP4 132 +#define D3DRS_WRAP5 133 +#define D3DRS_WRAP6 134 +#define D3DRS_WRAP7 135 +#define D3DRS_CLIPPING 136 +#define D3DRS_LIGHTING 137 +#define D3DRS_AMBIENT 139 +#define D3DRS_FOGVERTEXMODE 140 +#define D3DRS_COLORVERTEX 141 +#define D3DRS_LOCALVIEWER 142 +#define D3DRS_NORMALIZENORMALS 143 +#define D3DRS_DIFFUSEMATERIALSOURCE 145 +#define D3DRS_SPECULARMATERIALSOURCE 146 +#define D3DRS_AMBIENTMATERIALSOURCE 147 +#define D3DRS_EMISSIVEMATERIALSOURCE 148 +#define D3DRS_VERTEXBLEND 151 +#define D3DRS_CLIPPLANEENABLE 152 +#define D3DRS_SOFTWAREVERTEXPROCESSING 153 +#define D3DRS_POINTSIZE 154 +#define D3DRS_POINTSIZE_MIN 155 +#define D3DRS_POINTSPRITEENABLE 156 +#define D3DRS_POINTSCALEENABLE 157 +#define D3DRS_POINTSCALE_A 158 +#define D3DRS_POINTSCALE_B 159 +#define D3DRS_POINTSCALE_C 160 +#define D3DRS_MULTISAMPLEANTIALIAS 161 +#define D3DRS_MULTISAMPLEMASK 162 +#define D3DRS_PATCHEDGESTYLE 163 +#define D3DRS_PATCHSEGMENTS 164 +#define D3DRS_DEBUGMONITORTOKEN 165 +#define D3DRS_POINTSIZE_MAX 166 +#define D3DRS_INDEXEDVERTEXBLENDENABLE 167 +#define D3DRS_COLORWRITEENABLE 168 +#define D3DRS_TWEENFACTOR 170 +#define D3DRS_BLENDOP 171 +}; + +value DWORD D3DTEXTURESTAGESTATETYPE8 +{ +#define D3DTSS_COLOROP 1 +#define D3DTSS_COLORARG1 2 +#define D3DTSS_COLORARG2 3 +#define D3DTSS_ALPHAOP 4 +#define D3DTSS_ALPHAARG1 5 +#define D3DTSS_ALPHAARG2 6 +#define D3DTSS_BUMPENVMAT00 7 +#define D3DTSS_BUMPENVMAT01 8 +#define D3DTSS_BUMPENVMAT10 9 +#define D3DTSS_BUMPENVMAT11 10 +#define D3DTSS_TEXCOORDINDEX 11 +#define D3DTSS_ADDRESSU 13 +#define D3DTSS_ADDRESSV 14 +#define D3DTSS_BORDERCOLOR 15 +#define D3DTSS_MAGFILTER 16 +#define D3DTSS_MINFILTER 17 +#define D3DTSS_MIPFILTER 18 +#define D3DTSS_MIPMAPLODBIAS 19 +#define D3DTSS_MAXMIPLEVEL 20 +#define D3DTSS_MAXANISOTROPY 21 +#define D3DTSS_BUMPENVLSCALE 22 +#define D3DTSS_BUMPENVLOFFSET 23 +#define D3DTSS_TEXTURETRANSFORMFLAGS 24 +#define D3DTSS_ADDRESSW 25 +#define D3DTSS_COLORARG0 26 +#define D3DTSS_ALPHAARG0 27 +#define D3DTSS_RESULTARG 28 +}; + +value DWORD D3DSTATEBLOCKTYPE8 +{ +#define D3DSBT_ALL 1 +#define D3DSBT_PIXELSTATE 2 +#define D3DSBT_VERTEXSTATE 3 +}; + +value DWORD D3DPRIMITIVETYPE8 +{ +#define D3DPT_POINTLIST 1 +#define D3DPT_LINELIST 2 +#define D3DPT_LINESTRIP 3 +#define D3DPT_TRIANGLELIST 4 +#define D3DPT_TRIANGLESTRIP 5 +#define D3DPT_TRIANGLEFAN 6 +}; + +value DWORD D3DBASISTYPE +{ +#define D3DBASIS_BEZIER 0 +#define D3DBASIS_BSPLINE 1 +#define D3DBASIS_INTERPOLATE 2 +}; + +value DWORD D3DORDERTYPE +{ +#define D3DORDER_LINEAR 1 +#define D3DORDER_CUBIC 3 +#define D3DORDER_QUINTIC 5 +}; + +value DWORD D3DFORMAT +{ +#define D3DFMT_UNKNOWN 0 +#define D3DFMT_R8G8B8 20 +#define D3DFMT_A8R8G8B8 21 +#define D3DFMT_X8R8G8B8 22 +#define D3DFMT_R5G6B5 23 +#define D3DFMT_X1R5G5B5 24 +#define D3DFMT_A1R5G5B5 25 +#define D3DFMT_A4R4G4B4 26 +#define D3DFMT_R3G3B2 27 +#define D3DFMT_A8 28 +#define D3DFMT_A8R3G3B2 29 +#define D3DFMT_X4R4G4B4 30 +#define D3DFMT_A8P8 40 +#define D3DFMT_P8 41 +#define D3DFMT_L8 50 +#define D3DFMT_A8L8 51 +#define D3DFMT_A4L4 52 +#define D3DFMT_V8U8 60 +#define D3DFMT_L6V5U5 61 +#define D3DFMT_X8L8V8U8 62 +#define D3DFMT_Q8W8V8U8 63 +#define D3DFMT_V16U16 64 +#define D3DFMT_W11V11U10 65 +#define D3DFMT_UYVY 0x59565955 +#define D3DFMT_YUY2 0x32595559 +#define D3DFMT_DXT1 0x31545844 +#define D3DFMT_DXT2 0x32545844 +#define D3DFMT_DXT3 0x33545844 +#define D3DFMT_DXT4 0x34545844 +#define D3DFMT_DXT5 0x35545844 +#define D3DFMT_D16_LOCKABLE 70 +#define D3DFMT_D32 71 +#define D3DFMT_D15S1 73 +#define D3DFMT_D24S8 75 +#define D3DFMT_D16 80 +#define D3DFMT_D24X8 77 +#define D3DFMT_D24X4S4 79 +#define D3DFMT_VERTEXDATA 100 +#define D3DFMT_INDEX16 101 +#define D3DFMT_INDEX32 102 +}; + + +// +// structs +// + +typedef struct _D3DDISPLAYMODE +{ + UINT Width; + UINT Height; + UINT RefreshRate; + D3DFORMAT Format; +} D3DDISPLAYMODE,*LPD3DDISPLAYMODE; + +typedef struct _D3DADAPTER_IDENTIFIER8 +{ + char Driver[512]; + char Description[512]; + DWORD DriverVersionLowPart; + DWORD DriverVersionHighPart; + DWORD VendorId; + DWORD DeviceId; + DWORD SubSysId; + DWORD Revision; + GUID DeviceIdentifier; + DWORD WHQLLevel; +} D3DADAPTER_IDENTIFIER8,*LPD3DADAPTER_IDENTIFIER8; + +typedef struct _D3DPRESENT_PARAMETERS_ +{ + UINT BackBufferWidth; + UINT BackBufferHeight; + D3DFORMAT BackBufferFormat; + UINT BackBufferCount; + D3DMULTISAMPLE_TYPE MultiSampleType; + D3DSWAPEFFECT SwapEffect; + HWND hDeviceWindow; + BOOL Windowed; + BOOL EnableAutoDepthStencil; + D3DFORMAT AutoDepthStencilFormat; + d3dPresentParamsFlags8 Flags; + d3dRefreshRate8 FullScreen_RefreshRateInHz; + d3dPresentationIntervals8 FullScreen_PresentationInterval; +} D3DPRESENT_PARAMETERS,*LPD3DPRESENT_PARAMETERS; + +typedef struct _D3DSURFACE_DESC +{ + D3DFORMAT Format; + D3DRESOURCETYPE Type; + d3dUsage8 Usage; + D3DPOOL Pool; + UINT Size; + D3DMULTISAMPLE_TYPE MultiSampleType; + UINT Width; + UINT Height; +} D3DSURFACE_DESC,*LPD3DSURFACE_DESC; + +typedef struct _D3DVOLUME_DESC +{ + D3DFORMAT Format; + D3DRESOURCETYPE Type; + d3dUsage8 Usage; + D3DPOOL Pool; + UINT Size; + UINT Width; + UINT Height; + UINT Depth; +} D3DVOLUME_DESC,*LPD3DVOLUME_DESC; + +typedef struct _D3DLOCKED_RECT +{ + INT Pitch; + LPVOID pBits; +} D3DLOCKED_RECT,*LPD3DLOCKED_RECT; + +typedef struct _D3DBOX +{ + UINT Left; + UINT Top; + UINT Right; + UINT Bottom; + UINT Front; + UINT Back; +} D3DBOX,*LPD3DBOX; + +typedef struct _D3DLOCKED_BOX +{ + INT RowPitch; + INT SlicePitch; + LPVOID pBits; +} D3DLOCKED_BOX,*LPD3DLOCKED_BOX; + +typedef struct _D3DVERTEXBUFFER_DESC +{ + D3DFORMAT Format; + D3DRESOURCETYPE Type; + d3dUsage8 Usage; + D3DPOOL Pool; + UINT Size; + d3dFVF8 FVF; + +} D3DVERTEXBUFFER_DESC,*LPD3DVERTEXBUFFER_DESC; + +typedef struct _D3DINDEXBUFFER_DESC +{ + D3DFORMAT Format; + D3DRESOURCETYPE Type; + d3dUsage8 Usage; + D3DPOOL Pool; + UINT Size; +} D3DINDEXBUFFER_DESC,*LPD3DINDEXBUFFER_DESC; + +typedef struct _D3DDEVICE_CREATION_PARAMETERS +{ + d3dAdapterID8 AdapterOrdinal; + D3DDEVTYPE DeviceType; + HWND hFocusWindow; + d3dBehaviorFlags8 BehaviorFlags; +} D3DDEVICE_CREATION_PARAMETERS,*LPD3DDEVICE_CREATION_PARAMETERS; + +typedef struct _D3DRASTER_STATUS +{ + BOOL InVBlank; + UINT ScanLine; +} D3DRASTER_STATUS,*LPD3DRASTER_STATUS; + +typedef struct _D3DMATRIX8 +{ + float _11; + float _12; + float _13; + float _14; + float _21; + float _22; + float _23; + float _24; + float _31; + float _32; + float _33; + float _34; + float _41; + float _42; + float _43; + float _44; +} D3DMATRIX8,*LPD3DMATRIX8; + +typedef struct _D3DVIEWPORT8 +{ + UINT X; + UINT Y; + UINT Width; + UINT Height; + float MinZ; + float MaxZ; +} D3DVIEWPORT8,*LPD3DVIEWPORT8; + +typedef struct _D3DMATERIAL8 +{ + D3DCOLORVALUE Diffuse; + D3DCOLORVALUE Ambient; + D3DCOLORVALUE Specular; + D3DCOLORVALUE Emissive; + float Power; +} D3DMATERIAL8,*LPD3DMATERIAL8; + +typedef struct _D3DCLIPSTATUS8 { + d3dClipStatus8 ClipUnion; + d3dClipStatus8 ClipIntersection; +} D3DCLIPSTATUS8,*LPD3DCLIPSTATUS8; + +typedef struct _D3DLIGHT8 { + D3DLIGHTTYPE8 Type; + D3DCOLORVALUE Diffuse; + D3DCOLORVALUE Specular; + D3DCOLORVALUE Ambient; + D3DVECTOR Position; + D3DVECTOR Direction; + float Range; + float Falloff; + float Attenuation0; + float Attenuation1; + float Attenuation2; + float Theta; + float Phi; +} D3DLIGHT8,*LPD3DLIGHT8; + +typedef struct _D3DRECTPATCH_INFO +{ + UINT StartVertexOffsetWidth; + UINT StartVertexOffsetHeight; + UINT Width; + UINT Height; + UINT Stride; + D3DBASISTYPE Basis; + D3DORDERTYPE Order; +} D3DRECTPATCH_INFO,*LPD3DRECTPATCH_INFO; + +typedef struct _D3DTRIPATCH_INFO +{ + UINT StartVertexOffset; + UINT NumVertices; + D3DBASISTYPE Basis; + D3DORDERTYPE Order; +} D3DTRIPATCH_INFO,*LPD3DTRIPATCH_INFO; + +typedef struct _D3DGAMMARAMP +{ + WORD red [256]; + WORD green[256]; + WORD blue [256]; +} D3DGAMMARAMP,*LPD3DGAMMARAMP; + +alias LPD3DDISPLAYMODE; +alias LPD3DADAPTER_IDENTIFIER8; +alias LPD3DPRESENT_PARAMETERS; +alias LPD3DSURFACE_DESC; +alias LPD3DVOLUME_DESC; +alias LPD3DLOCKED_RECT; +alias LPD3DBOX; +alias LPD3DLOCKED_BOX; +alias LPD3DVERTEXBUFFER_DESC; +alias LPD3DINDEXBUFFER_DESC; +alias LPD3DDEVICE_CREATION_PARAMETERS; +alias LPD3DRASTER_STATUS; +alias LPD3DMATRIX8; +alias LPD3DVIEWPORT8; +alias LPD3DVECTOR; +alias LPD3DCOLORVALUE; +alias LPD3DRECT; +alias LPD3DMATERIAL8; +alias LPD3DCLIPSTATUS8; +alias LPD3DLIGHT8; +alias LPD3DRECTPATCH_INFO; +alias LPD3DTRIPATCH_INFO; +alias LPD3DGAMMARAMP; diff --git a/tools/Debugging Tools for Windows/winext/manifest/d3dcaps.h b/tools/Debugging Tools for Windows/winext/manifest/d3dcaps.h new file mode 100644 index 0000000000..3257b77683 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/d3dcaps.h @@ -0,0 +1,509 @@ + + +// +// GUIDs +// + + + +// +// Typedefs +// + +typedef VOID * LPD3DENUMDEVICESCALLBACK; +typedef VOID * LPD3DENUMDEVICESCALLBACK7; + + +// +// Masks +// + +mask DWORD d3ddevcapsFlags +{ + #define D3DDEVCAPS_FLOATTLVERTEX 0x00000001L + #define D3DDEVCAPS_SORTINCREASINGZ 0x00000002L + #define D3DDEVCAPS_SORTDECREASINGZ 0X00000004L + #define D3DDEVCAPS_SORTEXACT 0x00000008L + #define D3DDEVCAPS_EXECUTESYSTEMMEMORY 0x00000010L + #define D3DDEVCAPS_EXECUTEVIDEOMEMORY 0x00000020L + #define D3DDEVCAPS_TLVERTEXSYSTEMMEMORY 0x00000040L + #define D3DDEVCAPS_TLVERTEXVIDEOMEMORY 0x00000080L + #define D3DDEVCAPS_TEXTURESYSTEMMEMORY 0x00000100L + #define D3DDEVCAPS_TEXTUREVIDEOMEMORY 0x00000200L + #define D3DDEVCAPS_DRAWPRIMTLVERTEX 0x00000400L + #define D3DDEVCAPS_CANRENDERAFTERFLIP 0x00000800L + #define D3DDEVCAPS_TEXTURENONLOCALVIDMEM 0x00001000L + #define D3DDEVCAPS_DRAWPRIMITIVES2 0x00002000L + #define D3DDEVCAPS_SEPARATETEXTUREMEMORIES 0x00004000L + #define D3DDEVCAPS_DRAWPRIMITIVES2EX 0x00008000L + #define D3DDEVCAPS_HWTRANSFORMANDLIGHT 0x00010000L + #define D3DDEVCAPS_CANBLTSYSTONONLOCAL 0x00020000L + #define D3DDEVCAPS_HWRASTERIZATION 0x00080000L +}; + +mask DWORD d3dlightingmodelFlags +{ + #define D3DLIGHTINGMODEL_RGB 0x00000001L + #define D3DLIGHTINGMODEL_MONO 0x00000002L +}; + +mask DWORD d3dddFlags +{ + #define D3DDD_COLORMODEL 0x00000001L + #define D3DDD_DEVCAPS 0x00000002L + #define D3DDD_TRANSFORMCAPS 0x00000004L + #define D3DDD_LIGHTINGCAPS 0x00000008L + #define D3DDD_BCLIPPING 0x00000010L + #define D3DDD_LINECAPS 0x00000020L + #define D3DDD_TRICAPS 0x00000040L + #define D3DDD_DEVICERENDERBITDEPTH 0x00000080L + #define D3DDD_DEVICEZBUFFERBITDEPTH 0x00000100L + #define D3DDD_MAXBUFFERSIZE 0x00000200L + #define D3DDD_MAXVERTEXCOUNT 0x00000400L +}; + +mask DWORD d3dptexturecapsFlags +{ + #define D3DPTEXTURECAPS_PERSPECTIVE 0x00000001L + #define D3DPTEXTURECAPS_POW2 0x00000002L + #define D3DPTEXTURECAPS_ALPHA 0x00000004L + #define D3DPTEXTURECAPS_TRANSPARENCY 0x00000008L + #define D3DPTEXTURECAPS_BORDER 0x00000010L + #define D3DPTEXTURECAPS_SQUAREONLY 0x00000020L + #define D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE 0x00000040L + #define D3DPTEXTURECAPS_ALPHAPALETTE 0x00000080L + #define D3DPTEXTURECAPS_NONPOW2CONDITIONAL 0x00000100L + #define D3DPTEXTURECAPS_PROJECTED 0x00000400L + #define D3DPTEXTURECAPS_CUBEMAP 0x00000800L + #define D3DPTEXTURECAPS_COLORKEYBLEND 0x00001000L +}; + +mask DWORD d3dprastercapsFlags +{ + #define D3DPRASTERCAPS_DITHER 0x00000001L + #define D3DPRASTERCAPS_ROP2 0x00000002L + #define D3DPRASTERCAPS_XOR 0x00000004L + #define D3DPRASTERCAPS_PAT 0x00000008L + #define D3DPRASTERCAPS_ZTEST 0x00000010L + #define D3DPRASTERCAPS_SUBPIXEL 0x00000020L + #define D3DPRASTERCAPS_SUBPIXELX 0x00000040L + #define D3DPRASTERCAPS_FOGVERTEX 0x00000080L + #define D3DPRASTERCAPS_FOGTABLE 0x00000100L + #define D3DPRASTERCAPS_STIPPLE 0x00000200L + #define D3DPRASTERCAPS_ANTIALIASSORTDEPENDENT 0x00000400L + #define D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT 0x00000800L + #define D3DPRASTERCAPS_ANTIALIASEDGES 0x00001000L + #define D3DPRASTERCAPS_MIPMAPLODBIAS 0x00002000L + #define D3DPRASTERCAPS_ZBIAS 0x00004000L + #define D3DPRASTERCAPS_ZBUFFERLESSHSR 0x00008000L + #define D3DPRASTERCAPS_FOGRANGE 0x00010000L + #define D3DPRASTERCAPS_ANISOTROPY 0x00020000L + #define D3DPRASTERCAPS_WBUFFER 0x00040000L + #define D3DPRASTERCAPS_TRANSLUCENTSORTINDEPENDENT 0x00080000L + #define D3DPRASTERCAPS_WFOG 0x00100000L + #define D3DPRASTERCAPS_ZFOG 0x00200000L +}; + +mask DWORD d3dfvfcapsFlags +{ + #define D3DFVFCAPS_TEXCOORDCOUNTMASK 0x0000ffffL + #define D3DFVFCAPS_DONOTSTRIPELEMENTS 0x00080000L +}; + +mask DWORD d3ddebFlags +{ + #define D3DDEB_BUFSIZE 0x00000001l + #define D3DDEB_CAPS 0x00000002l + #define D3DDEB_LPDATA 0x00000004l +}; + +mask DWORD d3dptblendcapsFlags +{ + #define D3DPTBLENDCAPS_DECAL 0x00000001L + #define D3DPTBLENDCAPS_MODULATE 0x00000002L + #define D3DPTBLENDCAPS_DECALALPHA 0x00000004L + #define D3DPTBLENDCAPS_MODULATEALPHA 0x00000008L + #define D3DPTBLENDCAPS_DECALMASK 0x00000010L + #define D3DPTBLENDCAPS_MODULATEMASK 0x00000020L + #define D3DPTBLENDCAPS_COPY 0x00000040L + #define D3DPTBLENDCAPS_ADD 0x00000080L +}; + +mask DWORD d3dptaddresscapsFlags +{ + #define D3DPTADDRESSCAPS_WRAP 0x00000001L + #define D3DPTADDRESSCAPS_MIRROR 0x00000002L + #define D3DPTADDRESSCAPS_CLAMP 0x00000004L + #define D3DPTADDRESSCAPS_BORDER 0x00000008L + #define D3DPTADDRESSCAPS_INDEPENDENTUV 0x00000010L +}; + +mask DWORD d3dptfiltercapsFlags +{ + #define D3DPTFILTERCAPS_NEAREST 0x00000001L + #define D3DPTFILTERCAPS_LINEAR 0x00000002L + #define D3DPTFILTERCAPS_MIPNEAREST 0x00000004L + #define D3DPTFILTERCAPS_MIPLINEAR 0x00000008L + #define D3DPTFILTERCAPS_LINEARMIPNEAREST 0x00000010L + #define D3DPTFILTERCAPS_LINEARMIPLINEAR 0x00000020L + #define D3DPTFILTERCAPS_MINFPOINT 0x00000100L + #define D3DPTFILTERCAPS_MINFLINEAR 0x00000200L + #define D3DPTFILTERCAPS_MINFANISOTROPIC 0x00000400L + #define D3DPTFILTERCAPS_MIPFPOINT 0x00010000L + #define D3DPTFILTERCAPS_MIPFLINEAR 0x00020000L + #define D3DPTFILTERCAPS_MAGFPOINT 0x01000000L + #define D3DPTFILTERCAPS_MAGFLINEAR 0x02000000L + #define D3DPTFILTERCAPS_MAGFANISOTROPIC 0x04000000L + #define D3DPTFILTERCAPS_MAGFAFLATCUBIC 0x08000000L + #define D3DPTFILTERCAPS_MAGFGAUSSIANCUBIC 0x10000000L +}; + +mask DWORD d3ddebcapsFlags +{ + #define D3DDEBCAPS_SYSTEMMEMORY 0x00000001l + #define D3DDEBCAPS_VIDEOMEMORY 0x00000002l + #define D3DDEBCAPS_MEM 3 +}; + +mask DWORD d3dpcmpcapsFlags +{ + #define D3DPCMPCAPS_NEVER 0x00000001L + #define D3DPCMPCAPS_LESS 0x00000002L + #define D3DPCMPCAPS_EQUAL 0x00000004L + #define D3DPCMPCAPS_LESSEQUAL 0x00000008L + #define D3DPCMPCAPS_GREATER 0x00000010L + #define D3DPCMPCAPS_NOTEQUAL 0x00000020L + #define D3DPCMPCAPS_GREATEREQUAL 0x00000040L + #define D3DPCMPCAPS_ALWAYS 0x00000080L +}; + +mask DWORD d3dtexopcapsFlags +{ + #define D3DTEXOPCAPS_DISABLE 0x00000001L + #define D3DTEXOPCAPS_SELECTARG1 0x00000002L + #define D3DTEXOPCAPS_SELECTARG2 0x00000004L + #define D3DTEXOPCAPS_MODULATE 0x00000008L + #define D3DTEXOPCAPS_MODULATE2X 0x00000010L + #define D3DTEXOPCAPS_MODULATE4X 0x00000020L + #define D3DTEXOPCAPS_ADD 0x00000040L + #define D3DTEXOPCAPS_ADDSIGNED 0x00000080L + #define D3DTEXOPCAPS_ADDSIGNED2X 0x00000100L + #define D3DTEXOPCAPS_SUBTRACT 0x00000200L + #define D3DTEXOPCAPS_ADDSMOOTH 0x00000400L + #define D3DTEXOPCAPS_BLENDDIFFUSEALPHA 0x00000800L + #define D3DTEXOPCAPS_BLENDTEXTUREALPHA 0x00001000L + #define D3DTEXOPCAPS_BLENDFACTORALPHA 0x00002000L + #define D3DTEXOPCAPS_BLENDTEXTUREALPHAPM 0x00004000L + #define D3DTEXOPCAPS_BLENDCURRENTALPHA 0x00008000L + #define D3DTEXOPCAPS_PREMODULATE 0x00010000L + #define D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR 0x00020000L + #define D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA 0x00040000L + #define D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR 0x00080000L + #define D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA 0x00100000L + #define D3DTEXOPCAPS_BUMPENVMAP 0x00200000L + #define D3DTEXOPCAPS_BUMPENVMAPLUMINANCE 0x00400000L + #define D3DTEXOPCAPS_DOTPRODUCT3 0x00800000L +}; + +mask DWORD d3dvtxpcapsFlags +{ + #define D3DVTXPCAPS_TEXGEN 0x00000001L + #define D3DVTXPCAPS_MATERIALSOURCE7 0x00000002L + #define D3DVTXPCAPS_VERTEXFOG 0x00000004L + #define D3DVTXPCAPS_DIRECTIONALLIGHTS 0x00000008L + #define D3DVTXPCAPS_POSITIONALLIGHTS 0x00000010L + #define D3DVTXPCAPS_LOCALVIEWER 0x00000020L +}; + +mask DWORD d3dstencilcapsFlags +{ + #define D3DSTENCILCAPS_KEEP 0x00000001L + #define D3DSTENCILCAPS_ZERO 0x00000002L + #define D3DSTENCILCAPS_REPLACE 0x00000004L + #define D3DSTENCILCAPS_INCRSAT 0x00000008L + #define D3DSTENCILCAPS_DECRSAT 0x00000010L + #define D3DSTENCILCAPS_INVERT 0x00000020L + #define D3DSTENCILCAPS_INCR 0x00000040L + #define D3DSTENCILCAPS_DECR 0x00000080L +}; + +mask DWORD d3dpblendcapsFlags +{ + #define D3DPBLENDCAPS_ZERO 0x00000001L + #define D3DPBLENDCAPS_ONE 0x00000002L + #define D3DPBLENDCAPS_SRCCOLOR 0x00000004L + #define D3DPBLENDCAPS_INVSRCCOLOR 0x00000008L + #define D3DPBLENDCAPS_SRCALPHA 0x00000010L + #define D3DPBLENDCAPS_INVSRCALPHA 0x00000020L + #define D3DPBLENDCAPS_DESTALPHA 0x00000040L + #define D3DPBLENDCAPS_INVDESTALPHA 0x00000080L + #define D3DPBLENDCAPS_DESTCOLOR 0x00000100L + #define D3DPBLENDCAPS_INVDESTCOLOR 0x00000200L + #define D3DPBLENDCAPS_SRCALPHASAT 0x00000400L + #define D3DPBLENDCAPS_BOTHSRCALPHA 0x00000800L + #define D3DPBLENDCAPS_BOTHINVSRCALPHA 0x00001000L +}; + +mask DWORD d3dpshadecapsFlags +{ + #define D3DPSHADECAPS_COLORFLATMONO 0x00000001L + #define D3DPSHADECAPS_COLORFLATRGB 0x00000002L + #define D3DPSHADECAPS_COLORGOURAUDMONO 0x00000004L + #define D3DPSHADECAPS_COLORGOURAUDRGB 0x00000008L + #define D3DPSHADECAPS_COLORPHONGMONO 0x00000010L + #define D3DPSHADECAPS_COLORPHONGRGB 0x00000020L + #define D3DPSHADECAPS_SPECULARFLATMONO 0x00000040L + #define D3DPSHADECAPS_SPECULARFLATRGB 0x00000080L + #define D3DPSHADECAPS_SPECULARGOURAUDMONO 0x00000100L + #define D3DPSHADECAPS_SPECULARGOURAUDRGB 0x00000200L + #define D3DPSHADECAPS_SPECULARPHONGMONO 0x00000400L + #define D3DPSHADECAPS_SPECULARPHONGRGB 0x00000800L + #define D3DPSHADECAPS_ALPHAFLATBLEND 0x00001000L + #define D3DPSHADECAPS_ALPHAFLATSTIPPLED 0x00002000L + #define D3DPSHADECAPS_ALPHAGOURAUDBLEND 0x00004000L + #define D3DPSHADECAPS_ALPHAGOURAUDSTIPPLED 0x00008000L + #define D3DPSHADECAPS_ALPHAPHONGBLEND 0x00010000L + #define D3DPSHADECAPS_ALPHAPHONGSTIPPLED 0x00020000L + #define D3DPSHADECAPS_FOGFLAT 0x00040000L + #define D3DPSHADECAPS_FOGGOURAUD 0x00080000L + #define D3DPSHADECAPS_FOGPHONG 0x00100000L +}; + +mask DWORD d3dfdsFlags +{ + #define D3DFDS_COLORMODEL 0x00000001L + #define D3DFDS_GUID 0x00000002L + #define D3DFDS_HARDWARE 0x00000004L + #define D3DFDS_TRIANGLES 0x00000008L + #define D3DFDS_LINES 0x00000010L + #define D3DFDS_MISCCAPS 0x00000020L + #define D3DFDS_RASTERCAPS 0x00000040L + #define D3DFDS_ZCMPCAPS 0x00000080L + #define D3DFDS_ALPHACMPCAPS 0x00000100L + #define D3DFDS_SRCBLENDCAPS 0x00000200L + #define D3DFDS_DSTBLENDCAPS 0x00000400L + #define D3DFDS_SHADECAPS 0x00000800L + #define D3DFDS_TEXTURECAPS 0x00001000L + #define D3DFDS_TEXTUREFILTERCAPS 0x00002000L + #define D3DFDS_TEXTUREBLENDCAPS 0x00004000L + #define D3DFDS_TEXTUREADDRESSCAPS 0x00008000L +}; + +mask DWORD d3dlightcapsFlags +{ + #define D3DLIGHTCAPS_POINT 0x00000001L + #define D3DLIGHTCAPS_SPOT 0x00000002L + #define D3DLIGHTCAPS_DIRECTIONAL 0x00000004L + #define D3DLIGHTCAPS_PARALLELPOINT 0x00000008L + #define D3DLIGHTCAPS_GLSPOT 0x00000010L +}; + +mask DWORD d3dpmisccapsFlags +{ + #define D3DPMISCCAPS_MASKPLANES 0x00000001L + #define D3DPMISCCAPS_MASKZ 0x00000002L + #define D3DPMISCCAPS_LINEPATTERNREP 0x00000004L + #define D3DPMISCCAPS_CONFORMANT 0x00000008L + #define D3DPMISCCAPS_CULLNONE 0x00000010L + #define D3DPMISCCAPS_CULLCW 0x00000020L + #define D3DPMISCCAPS_CULLCCW 0x00000040L +}; + +mask DWORD d3dtransformcapsFlags +{ + #define D3DTRANSFORMCAPS_CLIP 0x00000001L +}; + + + +// +// Values +// + + + +// +// Structs +// + +typedef struct _D3DDEVINFO_TEXTUREMANAGER +{ + BOOL bThrashing; /* indicates if thrashing */ + DWORD dwApproxBytesDownloaded; /* Approximate number of bytes downloaded by texture manager */ + DWORD dwNumEvicts; /* number of textures evicted */ + DWORD dwNumVidCreates; /* number of textures created in video memory */ + DWORD dwNumTexturesUsed; /* number of textures used */ + DWORD dwNumUsedTexInVid; /* number of used textures present in video memory */ + DWORD dwWorkingSet; /* number of textures in video memory */ + DWORD dwWorkingSetBytes; /* number of bytes in video memory */ + DWORD dwTotalManaged; /* total number of managed textures */ + DWORD dwTotalBytes; /* total number of bytes of managed textures */ + DWORD dwLastPri; /* priority of last texture evicted */ +} D3DDEVINFO_TEXTUREMANAGER, *LPD3DDEVINFO_TEXTUREMANAGER; + +typedef struct _D3DExecuteBufferDesc +{ + DWORD dwSize; /* size of this structure */ + DWORD dwFlags; /* flags indicating which fields are valid */ + DWORD dwCaps; /* capabilities of execute buffer */ + DWORD dwBufferSize; /* size of execute buffer data */ + LPVOID lpData; /* pointer to actual data */ +} D3DEXECUTEBUFFERDESC, *LPD3DEXECUTEBUFFERDESC; + +typedef struct _D3DLIGHTINGCAPS +{ + DWORD dwSize; + DWORD dwCaps; /* Lighting caps */ + DWORD dwLightingModel; /* Lighting model - RGB or mono */ + DWORD dwNumLights; /* Number of lights that can be handled */ +} D3DLIGHTINGCAPS, *LPD3DLIGHTINGCAPS; + +typedef struct _D3DPrimCaps +{ + DWORD dwSize; + DWORD dwMiscCaps; /* Capability flags */ + DWORD dwRasterCaps; + DWORD dwZCmpCaps; + DWORD dwSrcBlendCaps; + DWORD dwDestBlendCaps; + DWORD dwAlphaCmpCaps; + DWORD dwShadeCaps; + DWORD dwTextureCaps; + DWORD dwTextureFilterCaps; + DWORD dwTextureBlendCaps; + DWORD dwTextureAddressCaps; + DWORD dwStippleWidth; /* maximum width and height of */ + DWORD dwStippleHeight; /* of supported stipple (up to 32x32) */ +} D3DPRIMCAPS, *LPD3DPRIMCAPS; + +typedef struct _D3DFINDDEVICESEARCH +{ + DWORD dwSize; + DWORD dwFlags; + BOOL bHardware; + D3DCOLORMODEL dcmColorModel; + GUID guid; + DWORD dwCaps; + D3DPRIMCAPS dpcPrimCaps; +} D3DFINDDEVICESEARCH, *LPD3DFINDDEVICESEARCH; + +typedef struct _D3DTRANSFORMCAPS +{ + DWORD dwSize; + DWORD dwCaps; +} D3DTRANSFORMCAPS, *LPD3DTRANSFORMCAPS; + +typedef struct _D3DDEVINFO_TEXTURING +{ + DWORD dwNumLoads; /* counts Load() API calls */ + DWORD dwApproxBytesLoaded; /* Approximate number bytes loaded via Load() */ + DWORD dwNumPreLoads; /* counts PreLoad() API calls */ + DWORD dwNumSet; /* counts SetTexture() API calls */ + DWORD dwNumCreates; /* counts texture creates */ + DWORD dwNumDestroys; /* counts texture destroys */ + DWORD dwNumSetPriorities; /* counts SetPriority() API calls */ + DWORD dwNumSetLODs; /* counts SetLOD() API calls */ + DWORD dwNumLocks; /* counts number of texture locks */ + DWORD dwNumGetDCs; /* counts number of GetDCs to textures */ +} D3DDEVINFO_TEXTURING, *LPD3DDEVINFO_TEXTURING; + +typedef struct _D3DDeviceDesc +{ + DWORD dwSize; /* Size of D3DDEVICEDESC structure */ + DWORD dwFlags; /* Indicates which fields have valid data */ + D3DCOLORMODEL dcmColorModel; /* Color model of device */ + DWORD dwDevCaps; /* Capabilities of device */ + D3DTRANSFORMCAPS dtcTransformCaps; /* Capabilities of transform */ + BOOL bClipping; /* Device can do 3D clipping */ + D3DLIGHTINGCAPS dlcLightingCaps; /* Capabilities of lighting */ + D3DPRIMCAPS dpcLineCaps; + D3DPRIMCAPS dpcTriCaps; + DWORD dwDeviceRenderBitDepth; /* One of DDBB_8, 16, etc.. */ + DWORD dwDeviceZBufferBitDepth;/* One of DDBD_16, 32, etc.. */ + DWORD dwMaxBufferSize; /* Maximum execute buffer size */ + DWORD dwMaxVertexCount; /* Maximum vertex count */ + DWORD dwMinTextureWidth; + DWORD dwMinTextureHeight; + DWORD dwMaxTextureWidth; + DWORD dwMaxTextureHeight; + DWORD dwMinStippleWidth; + DWORD dwMaxStippleWidth; + DWORD dwMinStippleHeight; + DWORD dwMaxStippleHeight; + DWORD dwMaxTextureRepeat; + DWORD dwMaxTextureAspectRatio; + DWORD dwMaxAnisotropy; + + // Guard band that the rasterizer can accommodate + // Screen-space vertices inside this space but outside the viewport + // will get clipped properly. + D3DVALUE dvGuardBandLeft; + D3DVALUE dvGuardBandTop; + D3DVALUE dvGuardBandRight; + D3DVALUE dvGuardBandBottom; + + D3DVALUE dvExtentsAdjust; + DWORD dwStencilCaps; + + DWORD dwFVFCaps; + DWORD dwTextureOpCaps; + WORD wMaxTextureBlendStages; + WORD wMaxSimultaneousTextures; +} D3DDEVICEDESC, *LPD3DDEVICEDESC; +typedef struct _D3DDeviceDesc7 +{ + DWORD dwDevCaps; /* Capabilities of device */ + D3DPRIMCAPS dpcLineCaps; + D3DPRIMCAPS dpcTriCaps; + DWORD dwDeviceRenderBitDepth; /* One of DDBB_8, 16, etc.. */ + DWORD dwDeviceZBufferBitDepth;/* One of DDBD_16, 32, etc.. */ + + DWORD dwMinTextureWidth; + DWORD dwMinTextureHeight; + DWORD dwMaxTextureWidth; + DWORD dwMaxTextureHeight; + + DWORD dwMaxTextureRepeat; + DWORD dwMaxTextureAspectRatio; + DWORD dwMaxAnisotropy; + + D3DVALUE dvGuardBandLeft; + D3DVALUE dvGuardBandTop; + D3DVALUE dvGuardBandRight; + D3DVALUE dvGuardBandBottom; + + D3DVALUE dvExtentsAdjust; + DWORD dwStencilCaps; + + DWORD dwFVFCaps; + DWORD dwTextureOpCaps; + WORD wMaxTextureBlendStages; + WORD wMaxSimultaneousTextures; + + DWORD dwMaxActiveLights; + D3DVALUE dvMaxVertexW; + GUID deviceGUID; + + WORD wMaxUserClipPlanes; + WORD wMaxVertexBlendMatrices; + + DWORD dwVertexProcessingCaps; + + DWORD dwReserved1; + DWORD dwReserved2; + DWORD dwReserved3; + DWORD dwReserved4; +} D3DDEVICEDESC7, *LPD3DDEVICEDESC7; + +typedef struct _D3DFINDDEVICERESULT +{ + DWORD dwSize; + GUID guid; /* guid which matched */ + D3DDEVICEDESC ddHwDesc; /* hardware D3DDEVICEDESC */ + D3DDEVICEDESC ddSwDesc; /* software D3DDEVICEDESC */ +} D3DFINDDEVICERESULT, *LPD3DFINDDEVICERESULT; + + +// +// Interfaces +// diff --git a/tools/Debugging Tools for Windows/winext/manifest/d3dtypes.h b/tools/Debugging Tools for Windows/winext/manifest/d3dtypes.h new file mode 100644 index 0000000000..5128b7e582 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/d3dtypes.h @@ -0,0 +1,1138 @@ + + +// +// GUIDs +// + + + +// +// Typedefs +// + +typedef float D3DVALUE; +typedef float *LPD3DVALUE; +typedef LONG D3DFIXED; +typedef VOID *LPD3DVALIDATECALLBACK; +typedef VOID *LPD3DENUMTEXTUREFORMATSCALLBACK; +typedef VOID *LPD3DENUMPIXELFORMATSCALLBACK; +typedef DWORD D3DMATERIALHANDLE; +typedef DWORD *LPD3DMATERIALHANDLE; +typedef DWORD D3DTEXTUREHANDLE; +typedef DWORD *LPD3DTEXTUREHANDLE; +typedef DWORD D3DMATRIXHANDLE; +typedef DWORD *LPD3DMATRIXHANDLE; +typedef DWORD D3DCOLORMODEL; +typedef DWORD D3DCOLOR; + +typedef struct _D3DCOLORVALUE +{ + D3DVALUE dvR; + D3DVALUE dvG; + D3DVALUE dvB; + D3DVALUE dvA; +} D3DCOLORVALUE, *LPD3DCOLORVALUE; + +typedef struct _D3DVECTOR +{ + D3DVALUE dvX; + D3DVALUE dvY; + D3DVALUE dvZ; +} D3DVECTOR, *LPD3DVECTOR; + +typedef struct _D3DRECT +{ + LONG lX1; + LONG lY1; + LONG lX2; + LONG lY2; +} D3DRECT, *LPD3DRECT; + + +// +// Masks +// + +mask DWORD d3dpvFlags +{ + #define D3DPV_DONOTCOPYDATA 1 +}; + +mask DWORD d3dclearFlags +{ + #define D3DCLEAR_TARGET 0x00000001l + #define D3DCLEAR_ZBUFFER 0x00000002l + #define D3DCLEAR_STENCIL 0x00000004l +}; + +mask DWORD d3dclipFlags +{ + #define D3DCLIP_LEFT 0x00000001L + #define D3DCLIP_RIGHT 0x00000002L + #define D3DCLIP_TOP 0x00000004L + #define D3DCLIP_BOTTOM 0x00000008L + #define D3DCLIP_FRONT 0x00000010L + #define D3DCLIP_BACK 0x00000020L + #define D3DCLIP_GEN0 0x00000040L + #define D3DCLIP_GEN1 0x00000080L + #define D3DCLIP_GEN2 0x00000100L + #define D3DCLIP_GEN3 0x00000200L + #define D3DCLIP_GEN4 0x00000400L + #define D3DCLIP_GEN5 0x00000800L +}; + +mask DWORD d3dtriflagFlags +{ + #define D3DTRIFLAG_START 0x00000000L + #define D3DTRIFLAG_ODD 0x0000001eL + #define D3DTRIFLAG_EVEN 0x0000001fL + #define D3DTRIFLAG_EDGEENABLE1 0x00000100L + #define D3DTRIFLAG_EDGEENABLE2 0x00000200L + #define D3DTRIFLAG_EDGEENABLE3 0x00000400L +}; + +mask DWORD d3dclipstatusFlags +{ + #define D3DCLIPSTATUS_STATUS 0x00000001L + #define D3DCLIPSTATUS_EXTENTS2 0x00000002L + #define D3DCLIPSTATUS_EXTENTS3 0x00000004L +}; + +mask DWORD d3dsetstatusFlags +{ + #define D3DSETSTATUS_STATUS 0x00000001L + #define D3DSETSTATUS_EXTENTS 0x00000002L +}; + +mask DWORD d3dwrapcoordFlags +{ + #define D3DWRAPCOORD_0 0x00000001L + #define D3DWRAPCOORD_1 0x00000002L + #define D3DWRAPCOORD_2 0x00000004L + #define D3DWRAPCOORD_3 0x00000008L +}; + +mask DWORD d3dvisFlags +{ + #define D3DVIS_INSIDE_FRUSTUM 0 + #define D3DVIS_INTERSECT_FRUSTUM 1 + #define D3DVIS_OUTSIDE_FRUSTUM 2 + #define D3DVIS_INSIDE_LEFT 0 + #define D3DVIS_INTERSECT_LEFT 4 + #define D3DVIS_OUTSIDE_LEFT 8 + #define D3DVIS_INSIDE_RIGHT 0 + #define D3DVIS_INTERSECT_RIGHT 16 + #define D3DVIS_OUTSIDE_RIGHT 32 + #define D3DVIS_INSIDE_TOP 0 + #define D3DVIS_INTERSECT_TOP 64 + #define D3DVIS_OUTSIDE_TOP 128 + #define D3DVIS_INSIDE_BOTTOM 0 + #define D3DVIS_INTERSECT_BOTTOM 256 + #define D3DVIS_OUTSIDE_BOTTOM 512 + #define D3DVIS_INSIDE_NEAR 0 + #define D3DVIS_INTERSECT_NEAR 1024 + #define D3DVIS_OUTSIDE_NEAR 2048 + #define D3DVIS_INSIDE_FAR 0 + #define D3DVIS_INTERSECT_FAR 4096 + #define D3DVIS_OUTSIDE_FAR 8192 +}; + +mask DWORD d3dexecuteFlags +{ + #define D3DEXECUTE_CLIPPED 0x00000001l + #define D3DEXECUTE_UNCLIPPED 0x00000002l +}; + +mask DWORD d3dvopFlags +{ + #define D3DVOP_LIGHT 1024 + #define D3DVOP_TRANSFORM 1 + #define D3DVOP_CLIP 4 + #define D3DVOP_EXTENTS 8 +}; + +mask DWORD d3dtaFlags +{ + #define D3DTA_SELECTMASK 0x0000000f + #define D3DTA_DIFFUSE 0x00000000 + #define D3DTA_CURRENT 0x00000001 + #define D3DTA_TEXTURE 0x00000002 + #define D3DTA_TFACTOR 0x00000003 + #define D3DTA_SPECULAR 0x00000004 + #define D3DTA_COMPLEMENT 0x00000010 + #define D3DTA_ALPHAREPLICATE 0x00000020 +}; + +mask DWORD d3ddevinfoidFlags +{ + #define D3DDEVINFOID_TEXTUREMANAGER 1 + #define D3DDEVINFOID_D3DTEXTUREMANAGER 2 + #define D3DDEVINFOID_TEXTURING 3 +}; + +mask DWORD d3dlightFlags +{ + #define D3DLIGHT_ACTIVE 0x00000001 + #define D3DLIGHT_NO_SPECULAR 0x00000002 + #define D3DLIGHT_ALL 3 +}; + +mask DWORD d3dwrapFlags +{ + #define D3DWRAP_U 0x00000001L + #define D3DWRAP_V 0x00000002L +}; + +mask DWORD d3dtssFlags +{ + #define D3DTSS_TCI_PASSTHRU 0x00000000 + #define D3DTSS_TCI_CAMERASPACENORMAL 0x00010000 + #define D3DTSS_TCI_CAMERASPACEPOSITION 0x00020000 + #define D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR 0x00030000 +}; + +mask DWORD d3dtransformFlags +{ + #define D3DTRANSFORM_CLIPPED 0x00000001l + #define D3DTRANSFORM_UNCLIPPED 0x00000002l +}; + +mask DWORD d3dpalFlags +{ + #define D3DPAL_FREE 0x00 + #define D3DPAL_READONLY 0x40 + #define D3DPAL_RESERVED 0x80 +}; + +mask DWORD d3dvbcapsFlags +{ + #define D3DVBCAPS_SYSTEMMEMORY 0x00000800l + #define D3DVBCAPS_WRITEONLY 0x00010000l + #define D3DVBCAPS_OPTIMIZED 0x80000000l + #define D3DVBCAPS_DONOTCLIP 0x00000001l +}; + +mask DWORD d3dcolorFlags +{ + #define D3DCOLOR_MONO 1 + #define D3DCOLOR_RGB 2 +}; + +mask DWORD d3denumretFlags +{ + #define D3DENUMRET_CANCEL 1 + #define D3DENUMRET_OK 0 +}; + +mask DWORD d3dstateFlags +{ + #define D3DSTATE_OVERRIDE_BIAS 256 +}; + +mask DWORD d3drenderstateFlags +{ + #define D3DRENDERSTATE_BLENDENABLE 27 + #define D3DRENDERSTATE_WRAPBIAS 128UL +}; + +mask DWORD d3dfvfFlags +{ + #define D3DFVF_RESERVED0 0x001 + #define D3DFVF_POSITION_MASK 0x00E + #define D3DFVF_XYZ 0x002 + #define D3DFVF_XYZRHW 0x004 + #define D3DFVF_XYZB1 0x006 + #define D3DFVF_XYZB2 0x008 + #define D3DFVF_XYZB3 0x00a + #define D3DFVF_XYZB4 0x00c + #define D3DFVF_XYZB5 0x00e + #define D3DFVF_NORMAL 0x010 + #define D3DFVF_RESERVED1 0x020 + #define D3DFVF_DIFFUSE 0x040 + #define D3DFVF_SPECULAR 0x080 + #define D3DFVF_TEXCOUNT_MASK 0xf00 + #define D3DFVF_TEXCOUNT_SHIFT 8 + #define D3DFVF_TEX0 0x000 + #define D3DFVF_TEX1 0x100 + #define D3DFVF_TEX2 0x200 + #define D3DFVF_TEX3 0x300 + #define D3DFVF_TEX4 0x400 + #define D3DFVF_TEX5 0x500 + #define D3DFVF_TEX6 0x600 + #define D3DFVF_TEX7 0x700 + #define D3DFVF_TEX8 0x800 + #define D3DFVF_RESERVED2 0xf000 + #define D3DFVF_VERTEX 0x00000112 + #define D3DFVF_LVERTEX 0x000001E2 + #define D3DFVF_TLVERTEX 0x000001C4 + #define D3DFVF_TEXTUREFORMAT2 0 + #define D3DFVF_TEXTUREFORMAT1 3 + #define D3DFVF_TEXTUREFORMAT3 1 + #define D3DFVF_TEXTUREFORMAT4 2 +}; + +mask DWORD d3dstatusFlags +{ + #define D3DSTATUS_CLIPUNIONLEFT 1 + #define D3DSTATUS_CLIPUNIONRIGHT 2 + #define D3DSTATUS_CLIPUNIONTOP 4 + #define D3DSTATUS_CLIPUNIONBOTTOM 8 + #define D3DSTATUS_CLIPUNIONFRONT 16 + #define D3DSTATUS_CLIPUNIONBACK 32 + #define D3DSTATUS_CLIPUNIONGEN0 64 + #define D3DSTATUS_CLIPUNIONGEN1 128 + #define D3DSTATUS_CLIPUNIONGEN2 256 + #define D3DSTATUS_CLIPUNIONGEN3 512 + #define D3DSTATUS_CLIPUNIONGEN4 1024 + #define D3DSTATUS_CLIPUNIONGEN5 2048 + #define D3DSTATUS_CLIPINTERSECTIONLEFT 0x00001000L + #define D3DSTATUS_CLIPINTERSECTIONRIGHT 0x00002000L + #define D3DSTATUS_CLIPINTERSECTIONTOP 0x00004000L + #define D3DSTATUS_CLIPINTERSECTIONBOTTOM 0x00008000L + #define D3DSTATUS_CLIPINTERSECTIONFRONT 0x00010000L + #define D3DSTATUS_CLIPINTERSECTIONBACK 0x00020000L + #define D3DSTATUS_CLIPINTERSECTIONGEN0 0x00040000L + #define D3DSTATUS_CLIPINTERSECTIONGEN1 0x00080000L + #define D3DSTATUS_CLIPINTERSECTIONGEN2 0x00100000L + #define D3DSTATUS_CLIPINTERSECTIONGEN3 0x00200000L + #define D3DSTATUS_CLIPINTERSECTIONGEN4 0x00400000L + #define D3DSTATUS_CLIPINTERSECTIONGEN5 0x00800000L + #define D3DSTATUS_ZNOTVISIBLE 0x01000000L + #define D3DSTATUS_CLIPUNIONALL 0x00000FFFL + #define D3DSTATUS_CLIPINTERSECTIONALL 0x00FFF000L + #define D3DSTATUS_DEFAULT 0x01FFF000L +}; + +mask DWORD d3dprocessverticesFlags +{ + #define D3DPROCESSVERTICES_TRANSFORMLIGHT 0x00000000L + #define D3DPROCESSVERTICES_TRANSFORM 0x00000001L + #define D3DPROCESSVERTICES_COPY 0x00000002L + #define D3DPROCESSVERTICES_OPMASK 0x00000007L + #define D3DPROCESSVERTICES_UPDATEEXTENTS 0x00000008L + #define D3DPROCESSVERTICES_NOCOLOR 0x00000010L +}; + + + +// +// Values +// + +value DWORD D3DBLEND +{ + #define D3DBLEND_ZERO 1 + #define D3DBLEND_ONE 2 + #define D3DBLEND_SRCCOLOR 3 + #define D3DBLEND_INVSRCCOLOR 4 + #define D3DBLEND_SRCALPHA 5 + #define D3DBLEND_INVSRCALPHA 6 + #define D3DBLEND_DESTALPHA 7 + #define D3DBLEND_INVDESTALPHA 8 + #define D3DBLEND_DESTCOLOR 9 + #define D3DBLEND_INVDESTCOLOR 10 + #define D3DBLEND_SRCALPHASAT 11 + #define D3DBLEND_BOTHSRCALPHA 12 + #define D3DBLEND_BOTHINVSRCALPHA 13 + #define D3DBLEND_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTURESTAGESTATETYPE +{ + #define D3DTSS_COLOROP 1 + #define D3DTSS_COLORARG1 2 + #define D3DTSS_COLORARG2 3 + #define D3DTSS_ALPHAOP 4 + #define D3DTSS_ALPHAARG1 5 + #define D3DTSS_ALPHAARG2 6 + #define D3DTSS_BUMPENVMAT00 7 + #define D3DTSS_BUMPENVMAT01 8 + #define D3DTSS_BUMPENVMAT10 9 + #define D3DTSS_BUMPENVMAT11 10 + #define D3DTSS_TEXCOORDINDEX 11 + #define D3DTSS_ADDRESS 12 + #define D3DTSS_ADDRESSU 13 + #define D3DTSS_ADDRESSV 14 + #define D3DTSS_BORDERCOLOR 15 + #define D3DTSS_MAGFILTER 16 + #define D3DTSS_MINFILTER 17 + #define D3DTSS_MIPFILTER 18 + #define D3DTSS_MIPMAPLODBIAS 19 + #define D3DTSS_MAXMIPLEVEL 20 + #define D3DTSS_MAXANISOTROPY 21 + #define D3DTSS_BUMPENVLSCALE 22 + #define D3DTSS_BUMPENVLOFFSET 23 + #define D3DTSS_TEXTURETRANSFORMFLAGS 24 + #define D3DTSS_ADDRESSW 25 + #define D3DTSS_COLORARG0 26 + #define D3DTSS_ALPHAARG0 27 + #define D3DTSS_RESULTARG 28 + #define D3DTSS_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DSHADEMODE +{ + #define D3DSHADE_FLAT 1 + #define D3DSHADE_GOURAUD 2 + #define D3DSHADE_PHONG 3 + #define D3DSHADE_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTUREMAGFILTER +{ + #define D3DTFG_POINT 1 + #define D3DTFG_LINEAR 2 + #define D3DTFG_FLATCUBIC 3 + #define D3DTFG_GAUSSIANCUBIC 4 + #define D3DTFG_ANISOTROPIC 5 + #define D3DTFG_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DCMPFUNC +{ + #define D3DCMP_NEVER 1 + #define D3DCMP_LESS 2 + #define D3DCMP_EQUAL 3 + #define D3DCMP_LESSEQUAL 4 + #define D3DCMP_GREATER 5 + #define D3DCMP_NOTEQUAL 6 + #define D3DCMP_GREATEREQUAL 7 + #define D3DCMP_ALWAYS 8 + #define D3DCMP_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DFILLMODE +{ + #define D3DFILL_POINT 1 + #define D3DFILL_WIREFRAME 2 + #define D3DFILL_SOLID 3 + #define D3DFILL_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DSTATEBLOCKTYPE +{ + #define D3DSBT_ALL 1 + #define D3DSBT_PIXELSTATE 2 + #define D3DSBT_VERTEXSTATE 3 + #define D3DSBT_FORCE_DWORD 0xffffffff +}; + +value DWORD D3DVERTEXBLENDFLAGS +{ + #define D3DVBLEND_DISABLE 0 + #define D3DVBLEND_1WEIGHT 1 + #define D3DVBLEND_2WEIGHTS 2 + #define D3DVBLEND_3WEIGHTS 3 +}; + +value DWORD D3DTEXTUREOP +{ + #define D3DTOP_DISABLE 1 + #define D3DTOP_SELECTARG1 2 + #define D3DTOP_SELECTARG2 3 + #define D3DTOP_MODULATE 4 + #define D3DTOP_MODULATE2X 5 + #define D3DTOP_MODULATE4X 6 + #define D3DTOP_ADD 7 + #define D3DTOP_ADDSIGNED 8 + #define D3DTOP_ADDSIGNED2X 9 + #define D3DTOP_SUBTRACT 10 + #define D3DTOP_ADDSMOOTH 11 + #define D3DTOP_BLENDDIFFUSEALPHA 12 + #define D3DTOP_BLENDTEXTUREALPHA 13 + #define D3DTOP_BLENDFACTORALPHA 14 + #define D3DTOP_BLENDTEXTUREALPHAPM 15 + #define D3DTOP_BLENDCURRENTALPHA 16 + #define D3DTOP_PREMODULATE 17 + #define D3DTOP_MODULATEALPHA_ADDCOLOR 18 + #define D3DTOP_MODULATECOLOR_ADDALPHA 19 + #define D3DTOP_MODULATEINVALPHA_ADDCOLOR 20 + #define D3DTOP_MODULATEINVCOLOR_ADDALPHA 21 + #define D3DTOP_BUMPENVMAP 22 + #define D3DTOP_BUMPENVMAPLUMINANCE 23 + #define D3DTOP_DOTPRODUCT3 24 + #define D3DTOP_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DCULL +{ + #define D3DCULL_NONE 1 + #define D3DCULL_CW 2 + #define D3DCULL_CCW 3 + #define D3DCULL_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DSTENCILOP +{ + #define D3DSTENCILOP_KEEP 1 + #define D3DSTENCILOP_ZERO 2 + #define D3DSTENCILOP_REPLACE 3 + #define D3DSTENCILOP_INCRSAT 4 + #define D3DSTENCILOP_DECRSAT 5 + #define D3DSTENCILOP_INVERT 6 + #define D3DSTENCILOP_INCR 7 + #define D3DSTENCILOP_DECR 8 + #define D3DSTENCILOP_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTUREBLEND +{ + #define D3DTBLEND_DECAL 1 + #define D3DTBLEND_MODULATE 2 + #define D3DTBLEND_DECALALPHA 3 + #define D3DTBLEND_MODULATEALPHA 4 + #define D3DTBLEND_DECALMASK 5 + #define D3DTBLEND_MODULATEMASK 6 + #define D3DTBLEND_COPY 7 + #define D3DTBLEND_ADD 8 + #define D3DTBLEND_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DZBUFFERTYPE +{ + #define D3DZB_FALSE 0 + #define D3DZB_TRUE 1 + #define D3DZB_USEW 2 + #define D3DZB_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTUREFILTER +{ + #define D3DFILTER_NEAREST 1 + #define D3DFILTER_LINEAR 2 + #define D3DFILTER_MIPNEAREST 3 + #define D3DFILTER_MIPLINEAR 4 + #define D3DFILTER_LINEARMIPNEAREST 5 + #define D3DFILTER_LINEARMIPLINEAR 6 + #define D3DFILTER_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTUREADDRESS +{ + #define D3DTADDRESS_WRAP 1 + #define D3DTADDRESS_MIRROR 2 + #define D3DTADDRESS_CLAMP 3 + #define D3DTADDRESS_BORDER 4 + #define D3DTADDRESS_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTURETRANSFORMFLAGS +{ + #define D3DTTFF_DISABLE 0 + #define D3DTTFF_COUNT1 1 + #define D3DTTFF_COUNT2 2 + #define D3DTTFF_COUNT3 3 + #define D3DTTFF_COUNT4 4 + #define D3DTTFF_PROJECTED 256 + #define D3DTTFF_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DLIGHTTYPE +{ + #define D3DLIGHT_POINT 1 + #define D3DLIGHT_SPOT 2 + #define D3DLIGHT_DIRECTIONAL 3 + #define D3DLIGHT_PARALLELPOINT 4 + #define D3DLIGHT_GLSPOT 5 + #define D3DLIGHT_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DOPCODE +{ + #define D3DOP_POINT 1 + #define D3DOP_LINE 2 + #define D3DOP_TRIANGLE 3 + #define D3DOP_MATRIXLOAD 4 + #define D3DOP_MATRIXMULTIPLY 5 + #define D3DOP_STATETRANSFORM 6 + #define D3DOP_STATELIGHT 7 + #define D3DOP_STATERENDER 8 + #define D3DOP_PROCESSVERTICES 9 + #define D3DOP_TEXTURELOAD 10 + #define D3DOP_EXIT 11 + #define D3DOP_BRANCHFORWARD 12 + #define D3DOP_SPAN 13 + #define D3DOP_SETSTATUS 14 + #define D3DOP_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTUREMINFILTER +{ + #define D3DTFN_POINT 1 + #define D3DTFN_LINEAR 2 + #define D3DTFN_ANISOTROPIC 3 + #define D3DTFN_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DLIGHTSTATETYPE +{ + #define D3DLIGHTSTATE_MATERIAL 1 + #define D3DLIGHTSTATE_AMBIENT 2 + #define D3DLIGHTSTATE_COLORMODEL 3 + #define D3DLIGHTSTATE_FOGMODE 4 + #define D3DLIGHTSTATE_FOGSTART 5 + #define D3DLIGHTSTATE_FOGEND 6 + #define D3DLIGHTSTATE_FOGDENSITY 7 + #define D3DLIGHTSTATE_COLORVERTEX 8 + #define D3DLIGHTSTATE_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DANTIALIASMODE +{ + #define D3DANTIALIAS_NONE 0 + #define D3DANTIALIAS_SORTDEPENDENT 1 + #define D3DANTIALIAS_SORTINDEPENDENT 2 + #define D3DANTIALIAS_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DPRIMITIVETYPE +{ + #define D3DPT_POINTLIST 1 + #define D3DPT_LINELIST 2 + #define D3DPT_LINESTRIP 3 + #define D3DPT_TRIANGLELIST 4 + #define D3DPT_TRIANGLESTRIP 5 + #define D3DPT_TRIANGLEFAN 6 + #define D3DPT_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTEXTUREMIPFILTER +{ + #define D3DTFP_NONE 1 + #define D3DTFP_POINT 2 + #define D3DTFP_LINEAR 3 + #define D3DTFP_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DMATERIALCOLORSOURCE +{ + #define D3DMCS_MATERIAL 0 + #define D3DMCS_COLOR1 1 + #define D3DMCS_COLOR2 2 + #define D3DMCS_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DTRANSFORMSTATETYPE +{ + #define D3DTRANSFORMSTATE_WORLD 1 + #define D3DTRANSFORMSTATE_VIEW 2 + #define D3DTRANSFORMSTATE_PROJECTION 3 + #define D3DTRANSFORMSTATE_WORLD1 4 + #define D3DTRANSFORMSTATE_WORLD2 5 + #define D3DTRANSFORMSTATE_WORLD3 6 + #define D3DTRANSFORMSTATE_TEXTURE0 16 + #define D3DTRANSFORMSTATE_TEXTURE1 17 + #define D3DTRANSFORMSTATE_TEXTURE2 18 + #define D3DTRANSFORMSTATE_TEXTURE3 19 + #define D3DTRANSFORMSTATE_TEXTURE4 20 + #define D3DTRANSFORMSTATE_TEXTURE5 21 + #define D3DTRANSFORMSTATE_TEXTURE6 22 + #define D3DTRANSFORMSTATE_TEXTURE7 23 + #define D3DTRANSFORMSTATE_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DVERTEXTYPE +{ + #define D3DVT_VERTEX 1 + #define D3DVT_LVERTEX 2 + #define D3DVT_TLVERTEX 3 + #define D3DVT_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DFOGMODE +{ + #define D3DFOG_NONE 0 + #define D3DFOG_EXP 1 + #define D3DFOG_EXP2 2 + #define D3DFOG_LINEAR 3 + #define D3DFOG_FORCE_DWORD 0x7fffffff +}; + +value DWORD D3DRENDERSTATETYPE +{ + #define D3DRENDERSTATE_ANTIALIAS 2 + #define D3DRENDERSTATE_TEXTUREPERSPECTIVE 4 + #define D3DRENDERSTATE_ZENABLE 7 + #define D3DRENDERSTATE_FILLMODE 8 + #define D3DRENDERSTATE_SHADEMODE 9 + #define D3DRENDERSTATE_LINEPATTERN 10 + #define D3DRENDERSTATE_ZWRITEENABLE 14 + #define D3DRENDERSTATE_ALPHATESTENABLE 15 + #define D3DRENDERSTATE_LASTPIXEL 16 + #define D3DRENDERSTATE_SRCBLEND 19 + #define D3DRENDERSTATE_DESTBLEND 20 + #define D3DRENDERSTATE_CULLMODE 22 + #define D3DRENDERSTATE_ZFUNC 23 + #define D3DRENDERSTATE_ALPHAREF 24 + #define D3DRENDERSTATE_ALPHAFUNC 25 + #define D3DRENDERSTATE_DITHERENABLE 26 + #define D3DRENDERSTATE_ALPHABLENDENABLE 27 + #define D3DRENDERSTATE_FOGENABLE 28 + #define D3DRENDERSTATE_SPECULARENABLE 29 + #define D3DRENDERSTATE_ZVISIBLE 30 + #define D3DRENDERSTATE_STIPPLEDALPHA 33 + #define D3DRENDERSTATE_FOGCOLOR 34 + #define D3DRENDERSTATE_FOGTABLEMODE 35 + #define D3DRENDERSTATE_FOGSTART 36 + #define D3DRENDERSTATE_FOGEND 37 + #define D3DRENDERSTATE_FOGDENSITY 38 + #define D3DRENDERSTATE_EDGEANTIALIAS 40 + #define D3DRENDERSTATE_COLORKEYENABLE 41 + #define D3DRENDERSTATE_ZBIAS 47 + #define D3DRENDERSTATE_RANGEFOGENABLE 48 + #define D3DRENDERSTATE_STENCILENABLE 52 + #define D3DRENDERSTATE_STENCILFAIL 53 + #define D3DRENDERSTATE_STENCILZFAIL 54 + #define D3DRENDERSTATE_STENCILPASS 55 + #define D3DRENDERSTATE_STENCILFUNC 56 + #define D3DRENDERSTATE_STENCILREF 57 + #define D3DRENDERSTATE_STENCILMASK 58 + #define D3DRENDERSTATE_STENCILWRITEMASK 59 + #define D3DRENDERSTATE_TEXTUREFACTOR 60 + #define D3DRENDERSTATE_WRAP0 128 + #define D3DRENDERSTATE_WRAP1 129 + #define D3DRENDERSTATE_WRAP2 130 + #define D3DRENDERSTATE_WRAP3 131 + #define D3DRENDERSTATE_WRAP4 132 + #define D3DRENDERSTATE_WRAP5 133 + #define D3DRENDERSTATE_WRAP6 134 + #define D3DRENDERSTATE_WRAP7 135 + #define D3DRENDERSTATE_CLIPPING 136 + #define D3DRENDERSTATE_LIGHTING 137 + #define D3DRENDERSTATE_EXTENTS 138 + #define D3DRENDERSTATE_AMBIENT 139 + #define D3DRENDERSTATE_FOGVERTEXMODE 140 + #define D3DRENDERSTATE_COLORVERTEX 141 + #define D3DRENDERSTATE_LOCALVIEWER 142 + #define D3DRENDERSTATE_NORMALIZENORMALS 143 + #define D3DRENDERSTATE_COLORKEYBLENDENABLE 144 + #define D3DRENDERSTATE_DIFFUSEMATERIALSOURCE 145 + #define D3DRENDERSTATE_SPECULARMATERIALSOURCE 146 + #define D3DRENDERSTATE_AMBIENTMATERIALSOURCE 147 + #define D3DRENDERSTATE_EMISSIVEMATERIALSOURCE 148 + #define D3DRENDERSTATE_VERTEXBLEND 151 + #define D3DRENDERSTATE_CLIPPLANEENABLE 152 + #define D3DRENDERSTATE_TEXTUREHANDLE 1 + #define D3DRENDERSTATE_TEXTUREADDRESS 3 + #define D3DRENDERSTATE_WRAPU 5 + #define D3DRENDERSTATE_WRAPV 6 + #define D3DRENDERSTATE_MONOENABLE 11 + #define D3DRENDERSTATE_ROP2 12 + #define D3DRENDERSTATE_PLANEMASK 13 + #define D3DRENDERSTATE_TEXTUREMAG 17 + #define D3DRENDERSTATE_TEXTUREMIN 18 + #define D3DRENDERSTATE_TEXTUREMAPBLEND 21 + #define D3DRENDERSTATE_SUBPIXEL 31 + #define D3DRENDERSTATE_SUBPIXELX 32 + #define D3DRENDERSTATE_STIPPLEENABLE 39 + #define D3DRENDERSTATE_BORDERCOLOR 43 + #define D3DRENDERSTATE_TEXTUREADDRESSU 44 + #define D3DRENDERSTATE_TEXTUREADDRESSV 45 + #define D3DRENDERSTATE_MIPMAPLODBIAS 46 + #define D3DRENDERSTATE_ANISOTROPY 49 + #define D3DRENDERSTATE_FLUSHBATCH 50 + #define D3DRENDERSTATE_STIPPLEPATTERN00 64 + #define D3DRENDERSTATE_STIPPLEPATTERN01 65 + #define D3DRENDERSTATE_STIPPLEPATTERN02 66 + #define D3DRENDERSTATE_STIPPLEPATTERN03 67 + #define D3DRENDERSTATE_STIPPLEPATTERN04 68 + #define D3DRENDERSTATE_STIPPLEPATTERN05 69 + #define D3DRENDERSTATE_STIPPLEPATTERN06 70 + #define D3DRENDERSTATE_STIPPLEPATTERN07 71 + #define D3DRENDERSTATE_STIPPLEPATTERN08 72 + #define D3DRENDERSTATE_STIPPLEPATTERN09 73 + #define D3DRENDERSTATE_STIPPLEPATTERN10 74 + #define D3DRENDERSTATE_STIPPLEPATTERN11 75 + #define D3DRENDERSTATE_STIPPLEPATTERN12 76 + #define D3DRENDERSTATE_STIPPLEPATTERN13 77 + #define D3DRENDERSTATE_STIPPLEPATTERN14 78 + #define D3DRENDERSTATE_STIPPLEPATTERN15 79 + #define D3DRENDERSTATE_STIPPLEPATTERN16 80 + #define D3DRENDERSTATE_STIPPLEPATTERN17 81 + #define D3DRENDERSTATE_STIPPLEPATTERN18 82 + #define D3DRENDERSTATE_STIPPLEPATTERN19 83 + #define D3DRENDERSTATE_STIPPLEPATTERN20 84 + #define D3DRENDERSTATE_STIPPLEPATTERN21 85 + #define D3DRENDERSTATE_STIPPLEPATTERN22 86 + #define D3DRENDERSTATE_STIPPLEPATTERN23 87 + #define D3DRENDERSTATE_STIPPLEPATTERN24 88 + #define D3DRENDERSTATE_STIPPLEPATTERN25 89 + #define D3DRENDERSTATE_STIPPLEPATTERN26 90 + #define D3DRENDERSTATE_STIPPLEPATTERN27 91 + #define D3DRENDERSTATE_STIPPLEPATTERN28 92 + #define D3DRENDERSTATE_STIPPLEPATTERN29 93 + #define D3DRENDERSTATE_STIPPLEPATTERN30 94 + #define D3DRENDERSTATE_STIPPLEPATTERN31 95 + #define D3DRENDERSTATE_FOGTABLESTART 36 + #define D3DRENDERSTATE_FOGTABLEEND 37 + #define D3DRENDERSTATE_FOGTABLEDENSITY 38 + #define D3DRENDERSTATE_FORCE_DWORD 0x7fffffff +}; + + + +// +// Structs +// + +typedef struct _D3DLINEPATTERN +{ + WORD wRepeatFactor; + WORD wLinePattern; +} D3DLINEPATTERN; + +typedef struct _D3DPICKRECORD +{ + BYTE bOpcode; + BYTE bPad; + DWORD dwOffset; + D3DVALUE dvZ; +} D3DPICKRECORD, *LPD3DPICKRECORD; + +typedef struct _D3DMATERIAL7 +{ + D3DCOLORVALUE dcvDiffuse; + D3DCOLORVALUE dcvAmbient; + D3DCOLORVALUE dcvSpecular; + D3DCOLORVALUE dcvEmissive; + D3DVALUE dvPower; +} D3DMATERIAL7, *LPD3DMATERIAL7; + +typedef struct _D3DLINE +{ + WORD wV1; + WORD wV2; +} D3DLINE, *LPD3DLINE; + +typedef struct _D3DPOINT +{ + WORD wCount; /* number of points */ + WORD wFirst; /* index to first vertex */ +} D3DPOINT, *LPD3DPOINT; + +typedef struct _D3DCLIPSTATUS +{ + DWORD dwFlags; /* Do we set 2d extents, 3D extents or status */ + DWORD dwStatus; /* Clip status */ + float minx; + float maxx; /* X extents */ + float miny; + float maxy; /* Y extents */ + float minz; + float maxz; /* Z extents */ +} D3DCLIPSTATUS, *LPD3DCLIPSTATUS; + +typedef struct _D3DBRANCH +{ + DWORD dwMask; /* Bitmask against D3D status */ + DWORD dwValue; + BOOL bNegate; /* TRUE to negate comparison */ + DWORD dwOffset; /* How far to branch forward (0 for exit)*/ +} D3DBRANCH, *LPD3DBRANCH; + +typedef struct _D3DDP_PTRSTRIDE +{ + LPVOID lpvData; + DWORD dwStride; +} D3DDP_PTRSTRIDE; + +typedef struct _D3DDRAWPRIMITIVESTRIDEDDATA +{ + D3DDP_PTRSTRIDE position; + D3DDP_PTRSTRIDE normal; + D3DDP_PTRSTRIDE diffuse; + D3DDP_PTRSTRIDE specular; + D3DDP_PTRSTRIDE textureCoords[8]; +} D3DDRAWPRIMITIVESTRIDEDDATA, *LPD3DDRAWPRIMITIVESTRIDEDDATA; + +typedef struct _D3DSPAN +{ + WORD wCount; /* Number of spans */ + WORD wFirst; /* Index to first vertex */ +} D3DSPAN, *LPD3DSPAN; + +typedef struct _D3DLIGHTINGELEMENT +{ + D3DVECTOR dvPosition; /* Lightable point in model space */ + D3DVECTOR dvNormal; /* Normalised unit vector */ +} D3DLIGHTINGELEMENT, *LPD3DLIGHTINGELEMENT; + +typedef struct _D3DTRIANGLE +{ + WORD wV1; + WORD wV2; + WORD wV3; + WORD wFlags; /* Edge (and other) flags */ +} D3DTRIANGLE, *LPD3DTRIANGLE; + +typedef struct _D3DVERTEX +{ + D3DVALUE dvX; + D3DVALUE dvY; + D3DVALUE dvZ; + D3DVALUE dvNX; + D3DVALUE dvNY; + D3DVALUE dvNZ; + D3DVALUE dvTU; + D3DVALUE dvTV; +} D3DVERTEX, *LPD3DVERTEX; + +typedef struct _D3DSTATE +{ + DWORD dwStateType; + D3DVALUE dvArg[1]; +} D3DSTATE, *LPD3DSTATE; + +typedef struct _D3DSTATUS +{ + DWORD dwFlags; /* Do we set extents or status */ + DWORD dwStatus; /* D3D status */ + D3DRECT drExtent; +} D3DSTATUS, *LPD3DSTATUS; + +typedef struct _D3DPROCESSVERTICES +{ + DWORD dwFlags; /* Do we transform or light or just copy? */ + WORD wStart; /* Index to first vertex in source */ + WORD wDest; /* Index to first vertex in local buffer */ + DWORD dwCount; /* Number of vertices to be processed */ + DWORD dwReserved; /* Must be zero */ +} D3DPROCESSVERTICES, *LPD3DPROCESSVERTICES; + +typedef struct _D3DHVERTEX +{ + DWORD dwFlags; /* Homogeneous clipping flags */ + D3DVALUE dvHX; + D3DVALUE dvHY; + D3DVALUE dvHZ; +} D3DHVERTEX, *LPD3DHVERTEX; + +typedef struct _D3DLVERTEX +{ + D3DVALUE dvX; + D3DVALUE dvY; + D3DVALUE dvZ; + DWORD dwReserved; + D3DCOLOR dcColor; + D3DCOLOR dcSpecular; + D3DVALUE dvTU; + D3DVALUE dvTV; +} D3DLVERTEX, *LPD3DLVERTEX; + +typedef struct _D3DLIGHT2 +{ + DWORD dwSize; + D3DLIGHTTYPE dltType; /* Type of light source */ + D3DCOLORVALUE dcvColor; /* Color of light */ + D3DVECTOR dvPosition; /* Position in world space */ + D3DVECTOR dvDirection; /* Direction in world space */ + D3DVALUE dvRange; /* Cutoff range */ + D3DVALUE dvFalloff; /* Falloff */ + D3DVALUE dvAttenuation0; /* Constant attenuation */ + D3DVALUE dvAttenuation1; /* Linear attenuation */ + D3DVALUE dvAttenuation2; /* Quadratic attenuation */ + D3DVALUE dvTheta; /* Inner angle of spotlight cone */ + D3DVALUE dvPhi; /* Outer angle of spotlight cone */ + DWORD dwFlags; +} D3DLIGHT2, *LPD3DLIGHT2; + +typedef struct _D3DEXECUTEDATA +{ + DWORD dwSize; + DWORD dwVertexOffset; + DWORD dwVertexCount; + DWORD dwInstructionOffset; + DWORD dwInstructionLength; + DWORD dwHVertexOffset; + D3DSTATUS dsStatus; /* Status after execute */ +} D3DEXECUTEDATA, *LPD3DEXECUTEDATA; + +typedef struct _D3DSTATS +{ + DWORD dwSize; + DWORD dwTrianglesDrawn; + DWORD dwLinesDrawn; + DWORD dwPointsDrawn; + DWORD dwSpansDrawn; + DWORD dwVerticesProcessed; +} D3DSTATS, *LPD3DSTATS; + +typedef struct _D3DMATERIAL +{ + DWORD dwSize; + D3DCOLORVALUE dcvDiffuse; + D3DCOLORVALUE dcvAmbient; + D3DCOLORVALUE dcvSpecular; + D3DCOLORVALUE dcvEmissive; + D3DVALUE dvPower; + D3DTEXTUREHANDLE hTexture; /* Handle to texture map */ + DWORD dwRampSize; +} D3DMATERIAL, *LPD3DMATERIAL; + +typedef struct _D3DLIGHT7 +{ + D3DLIGHTTYPE dltType; /* Type of light source */ + D3DCOLORVALUE dcvDiffuse; /* Diffuse color of light */ + D3DCOLORVALUE dcvSpecular; /* Specular color of light */ + D3DCOLORVALUE dcvAmbient; /* Ambient color of light */ + D3DVECTOR dvPosition; /* Position in world space */ + D3DVECTOR dvDirection; /* Direction in world space */ + D3DVALUE dvRange; /* Cutoff range */ + D3DVALUE dvFalloff; /* Falloff */ + D3DVALUE dvAttenuation0; /* Constant attenuation */ + D3DVALUE dvAttenuation1; /* Linear attenuation */ + D3DVALUE dvAttenuation2; /* Quadratic attenuation */ + D3DVALUE dvTheta; /* Inner angle of spotlight cone */ + D3DVALUE dvPhi; /* Outer angle of spotlight cone */ +} D3DLIGHT7, *LPD3DLIGHT7; + +typedef struct _D3DTEXTURELOAD +{ + D3DTEXTUREHANDLE hDestTexture; + D3DTEXTUREHANDLE hSrcTexture; +} D3DTEXTURELOAD, *LPD3DTEXTURELOAD; + +typedef struct _D3DVIEWPORT2 +{ + DWORD dwSize; + DWORD dwX; + DWORD dwY; /* Viewport Top left */ + DWORD dwWidth; + DWORD dwHeight; /* Viewport Dimensions */ + D3DVALUE dvClipX; /* Top left of clip volume */ + D3DVALUE dvClipY; + D3DVALUE dvClipWidth; /* Clip Volume Dimensions */ + D3DVALUE dvClipHeight; + D3DVALUE dvMinZ; /* Min/max of clip Volume */ + D3DVALUE dvMaxZ; +} D3DVIEWPORT2, *LPD3DVIEWPORT2; + +typedef struct _D3DMATRIX +{ + D3DVALUE _11; + D3DVALUE _12; + D3DVALUE _13; + D3DVALUE _14; + D3DVALUE _21; + D3DVALUE _22; + D3DVALUE _23; + D3DVALUE _24; + D3DVALUE _31; + D3DVALUE _32; + D3DVALUE _33; + D3DVALUE _34; + D3DVALUE _41; + D3DVALUE _42; + D3DVALUE _43; + D3DVALUE _44; +} D3DMATRIX, *LPD3DMATRIX; + +typedef struct _D3DMATRIXLOAD +{ + D3DMATRIXHANDLE hDestMatrix; /* Destination matrix */ + D3DMATRIXHANDLE hSrcMatrix; /* Source matrix */ +} D3DMATRIXLOAD, *LPD3DMATRIXLOAD; + +typedef struct _D3DMATRIXMULTIPLY +{ + D3DMATRIXHANDLE hDestMatrix; /* Destination matrix */ + D3DMATRIXHANDLE hSrcMatrix1; /* First source matrix */ + D3DMATRIXHANDLE hSrcMatrix2; /* Second source matrix */ +} D3DMATRIXMULTIPLY, *LPD3DMATRIXMULTIPLY; + +typedef struct _D3DTRANSFORMDATA +{ + DWORD dwSize; + LPVOID lpIn; /* Input vertices */ + DWORD dwInSize; /* Stride of input vertices */ + LPVOID lpOut; /* Output vertices */ + DWORD dwOutSize; /* Stride of output vertices */ + LPD3DHVERTEX lpHOut; /* Output homogeneous vertices */ + DWORD dwClip; /* Clipping hint */ + DWORD dwClipIntersection; + DWORD dwClipUnion; /* Union of all clip flags */ + D3DRECT drExtent; /* Extent of transformed vertices */ +} D3DTRANSFORMDATA, *LPD3DTRANSFORMDATA; + +typedef struct _D3DVERTEXBUFFERDESC +{ + DWORD dwSize; + DWORD dwCaps; + DWORD dwFVF; + DWORD dwNumVertices; +} D3DVERTEXBUFFERDESC, *LPD3DVERTEXBUFFERDESC; + +typedef struct _D3DVIEWPORT7 +{ + DWORD dwX; + DWORD dwY; /* Viewport Top left */ + DWORD dwWidth; + DWORD dwHeight; /* Viewport Dimensions */ + D3DVALUE dvMinZ; /* Min/max of clip Volume */ + D3DVALUE dvMaxZ; +} D3DVIEWPORT7, *LPD3DVIEWPORT7; + +typedef struct _D3DLIGHT +{ + DWORD dwSize; + D3DLIGHTTYPE dltType; /* Type of light source */ + D3DCOLORVALUE dcvColor; /* Color of light */ + D3DVECTOR dvPosition; /* Position in world space */ + D3DVECTOR dvDirection; /* Direction in world space */ + D3DVALUE dvRange; /* Cutoff range */ + D3DVALUE dvFalloff; /* Falloff */ + D3DVALUE dvAttenuation0; /* Constant attenuation */ + D3DVALUE dvAttenuation1; /* Linear attenuation */ + D3DVALUE dvAttenuation2; /* Quadratic attenuation */ + D3DVALUE dvTheta; /* Inner angle of spotlight cone */ + D3DVALUE dvPhi; /* Outer angle of spotlight cone */ +} D3DLIGHT, *LPD3DLIGHT; + +typedef struct _D3DTLVERTEX +{ + D3DVALUE dvSX; + D3DVALUE dvSY; + D3DVALUE dvSZ; + D3DVALUE dvRHW; + D3DCOLOR dcColor; + D3DCOLOR dcSpecular; + D3DVALUE dvTU; + D3DVALUE dvTV; +} D3DTLVERTEX, *LPD3DTLVERTEX; + +typedef struct _D3DVIEWPORT +{ + DWORD dwSize; + DWORD dwX; + DWORD dwY; /* Top left */ + DWORD dwWidth; + DWORD dwHeight; /* Dimensions */ + D3DVALUE dvScaleX; /* Scale homogeneous to screen */ + D3DVALUE dvScaleY; /* Scale homogeneous to screen */ + D3DVALUE dvMaxX; /* Min/max homogeneous x coord */ + D3DVALUE dvMaxY; /* Min/max homogeneous y coord */ + D3DVALUE dvMinZ; + D3DVALUE dvMaxZ; /* Min/max homogeneous z coord */ +} D3DVIEWPORT, *LPD3DVIEWPORT; + +typedef struct _D3DINSTRUCTION +{ + BYTE bOpcode; /* Instruction opcode */ + BYTE bSize; /* Size of each instruction data unit */ + WORD wCount; /* Count of instruction data units to follow */ +} D3DINSTRUCTION, *LPD3DINSTRUCTION; + +typedef struct _D3DLIGHTDATA +{ + DWORD dwSize; + LPD3DLIGHTINGELEMENT lpIn; /* Input positions and normals */ + DWORD dwInSize; /* Stride of input elements */ + LPD3DTLVERTEX lpOut; /* Output colors */ + DWORD dwOutSize; /* Stride of output colors */ +} D3DLIGHTDATA, *LPD3DLIGHTDATA; + + + +// +// Interfaces +// diff --git a/tools/Debugging Tools for Windows/winext/manifest/ddraw.h b/tools/Debugging Tools for Windows/winext/manifest/ddraw.h new file mode 100644 index 0000000000..54fb50db87 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/ddraw.h @@ -0,0 +1,4453 @@ +module DDRAW.DLL: +category DirectDraw: + + class __declspec(uuid("D7B70EE0-4340-11CF-B063-0020AFC2CD35")) DirectDraw; + class __declspec(uuid("3c305196-50db-11d3-9cfe-00c04fd930c5")) DirectDraw7; + class __declspec(uuid("593817A0-7DB3-11CF-A2DE-00AA00b93356")) DirectDrawClipper; +struct __declspec(uuid("6C14DB80-A733-11CE-A521-0020AF0BE560")) IDirectDraw ; +struct __declspec(uuid("B3A6F3E0-2B43-11CF-A2DE-00AA00B93356")) IDirectDraw2 ; +struct __declspec(uuid("9c59509a-39bd-11d1-8c4a-00c04fd930c5")) IDirectDraw4 ; +struct __declspec(uuid("15e65ec0-3b9c-11d2-b92f-00609797ea5b")) IDirectDraw7 ; +struct __declspec(uuid("6c0f8a6c-2f3a-11d3-8f01-0000f8757fbc")) IDirectDrawRM ; +struct __declspec(uuid("6C14DB81-A733-11CE-A521-0020AF0BE560")) IDirectDrawSurface ; +struct __declspec(uuid("57805885-6eec-11cf-9441-a82303c10e27")) IDirectDrawSurface2 ; +struct __declspec(uuid("DA044E00-69B2-11D0-A1D5-00AA00B8DFBB")) IDirectDrawSurface3 ; +struct __declspec(uuid("0B2B8630-AD35-11D0-8EA6-00609797EA5B")) IDirectDrawSurface4 ; +struct __declspec(uuid("06675a80-3b9b-11d2-b92f-00609797ea5b")) IDirectDrawSurface7 ; +struct __declspec(uuid("6C14DB84-A733-11CE-A521-0020AF0BE560")) IDirectDrawPalette ; +struct __declspec(uuid("6C14DB85-A733-11CE-A521-0020AF0BE560")) IDirectDrawClipper ; +struct __declspec(uuid("4B9F0EE0-0D7E-11D0-9B06-00A0C903A3B8")) IDirectDrawColorControl; +struct __declspec(uuid("69C11C3E-B46B-11D1-AD7A-00C04FC29B4E")) IDirectDrawGammaControl; + +typedef IDirectDraw *LPDIRECTDRAW; +typedef IDirectDraw2 *LPDIRECTDRAW2; +typedef IDirectDraw4 *LPDIRECTDRAW4; +typedef IDirectDraw7 *LPDIRECTDRAW7; +typedef IDirectDrawSurface *LPDIRECTDRAWSURFACE; +typedef IDirectDrawSurface2 *LPDIRECTDRAWSURFACE2; +typedef IDirectDrawSurface3 *LPDIRECTDRAWSURFACE3; +typedef IDirectDrawSurface4 *LPDIRECTDRAWSURFACE4; +typedef IDirectDrawSurface7 *LPDIRECTDRAWSURFACE7; +typedef IDirectDrawPalette *LPDIRECTDRAWPALETTE; +typedef IDirectDrawClipper *LPDIRECTDRAWCLIPPER; +typedef IDirectDrawColorControl *LPDIRECTDRAWCOLORCONTROL; +typedef IDirectDrawGammaControl *LPDIRECTDRAWGAMMACONTROL; + +typedef DDSURFACEDESC *LPDDSURFACEDESC; +typedef DDSURFACEDESC2 *LPDDSURFACEDESC2; +typedef DDCOLORCONTROL *LPDDCOLORCONTROL; + +typedef LPVOID LPDDENUMCALLBACKW; +typedef LPVOID LPDDENUMCALLBACKA; +typedef LPVOID LPDDENUMCALLBACKEXW; +typedef LPVOID LPDDENUMCALLBACKEXA; + +typedef LPVOID LPDDENUMMODESCALLBACK; +typedef LPVOID LPDDENUMMODESCALLBACK2; + +typedef LPVOID LPDDENUMSURFACESCALLBACK; +typedef LPVOID LPDDENUMSURFACESCALLBACK2; +typedef LPVOID LPDDENUMSURFACESCALLBACK7; + +mask DWORD DirectDrawOptSurfaceDescFlags +{ +/* + * guid field is valid. + */ +#define DDOSD_GUID 0x00000001l + +/* + * dwCompressionRatio field is valid. + */ +#define DDOSD_COMPRESSION_RATIO 0x00000002l + +/* + * ddSCaps field is valid. + */ +#define DDOSD_SCAPS 0x00000004l + +/* + * ddOSCaps field is valid. + */ +#define DDOSD_OSCAPS 0x00000008l + +/* + * All input fields are valid. + */ +#define DDOSD_ALL 0x0000000fl +}; + +mask DWORD DirectDrawOptSurfaceDescCapsFlags +{ +/* + * The surface's optimized pixelformat is compressed + */ +#define DDOSDCAPS_OPTCOMPRESSED 0x00000001l + +/* + * The surface's optimized pixelformat is reordered + */ +#define DDOSDCAPS_OPTREORDERED 0x00000002l + +/* + * The opt surface is a monolithic mipmap + */ +#define DDOSDCAPS_MONOLITHICMIPMAP 0x00000004l +}; + +mask DWORD DirectDrawGetDeviceIdentifierFlags +{ +/* + * This flag causes GetDeviceIdentifier to return information about the host (typically 2D) adapter in a system equipped + * with a stacked secondary 3D adapter. Such an adapter appears to the application as if it were part of the + * host adapter, but is typically physcially located on a separate card. The stacked secondary's information is + * returned when GetDeviceIdentifier's dwFlags field is zero, since this most accurately reflects the qualities + * of the DirectDraw object involved. + */ +#define DDGDI_GETHOSTIDENTIFIER 0x00000001L +}; + + + +mask DWORD DirectDrawSurfaceDescFlags +{ +/* + * ddsCaps field is valid. + */ +#define DDSD_CAPS 0x00000001l // default + +/* + * dwHeight field is valid. + */ +#define DDSD_HEIGHT 0x00000002l + +/* + * dwWidth field is valid. + */ +#define DDSD_WIDTH 0x00000004l + +/* + * lPitch is valid. + */ +#define DDSD_PITCH 0x00000008l + +/* + * dwBackBufferCount is valid. + */ +#define DDSD_BACKBUFFERCOUNT 0x00000020l + +/* + * dwZBufferBitDepth is valid. (shouldnt be used in DDSURFACEDESC2) + */ +#define DDSD_ZBUFFERBITDEPTH 0x00000040l + +/* + * dwAlphaBitDepth is valid. + */ +#define DDSD_ALPHABITDEPTH 0x00000080l + + +/* + * lpSurface is valid. + */ +#define DDSD_LPSURFACE 0x00000800l + +/* + * ddpfPixelFormat is valid. + */ +#define DDSD_PIXELFORMAT 0x00001000l + +/* + * ddckCKDestOverlay is valid. + */ +#define DDSD_CKDESTOVERLAY 0x00002000l + +/* + * ddckCKDestBlt is valid. + */ +#define DDSD_CKDESTBLT 0x00004000l + +/* + * ddckCKSrcOverlay is valid. + */ +#define DDSD_CKSRCOVERLAY 0x00008000l + +/* + * ddckCKSrcBlt is valid. + */ +#define DDSD_CKSRCBLT 0x00010000l + +/* + * dwMipMapCount is valid. + */ +#define DDSD_MIPMAPCOUNT 0x00020000l + + /* + * dwRefreshRate is valid + */ +#define DDSD_REFRESHRATE 0x00040000l + +/* + * dwLinearSize is valid + */ +#define DDSD_LINEARSIZE 0x00080000l + +/* + * dwTextureStage is valid + */ +#define DDSD_TEXTURESTAGE 0x00100000l +/* + * dwFVF is valid + */ +#define DDSD_FVF 0x00200000l +/* + * dwSrcVBHandle is valid + */ +#define DDSD_SRCVBHANDLE 0x00400000l +/* + * All input fields are valid. + */ +#define DDSD_ALL 0x007ff9eel +}; + +mask DWORD DirectDrawEnumerateExFlags +{ +/* + * Flags for DirectDrawEnumerateEx + * DirectDrawEnumerateEx supercedes DirectDrawEnumerate. You must use GetProcAddress to + * obtain a function pointer (of type LPDIRECTDRAWENUMERATEEX) to DirectDrawEnumerateEx. + * By default, only the primary display device is enumerated. + * DirectDrawEnumerate is equivalent to DirectDrawEnumerate(,,DDENUM_NONDISPLAYDEVICES) + */ + +/* + * This flag causes enumeration of any GDI display devices which are part of + * the Windows Desktop + */ +#define DDENUM_ATTACHEDSECONDARYDEVICES 0x00000001L + +/* + * This flag causes enumeration of any GDI display devices which are not + * part of the Windows Desktop + */ +#define DDENUM_DETACHEDSECONDARYDEVICES 0x00000002L + +/* + * This flag causes enumeration of non-display devices + */ +#define DDENUM_NONDISPLAYDEVICES 0x00000004L +}; + +value DWORD DirectDrawCreateFlags +{ +#define DDCREATE_HARDWAREONLY 0x00000001L +#define DDCREATE_EMULATIONONLY 0x00000002L +}; + +mask DWORD DirectDrawColorControlFlags +{ +/* + * lBrightness field is valid. + */ +#define DDCOLOR_BRIGHTNESS 0x00000001l + +/* + * lContrast field is valid. + */ +#define DDCOLOR_CONTRAST 0x00000002l + +/* + * lHue field is valid. + */ +#define DDCOLOR_HUE 0x00000004l + +/* + * lSaturation field is valid. + */ +#define DDCOLOR_SATURATION 0x00000008l + +/* + * lSharpness field is valid. + */ +#define DDCOLOR_SHARPNESS 0x00000010l + +/* + * lGamma field is valid. + */ +#define DDCOLOR_GAMMA 0x00000020l + +/* + * lColorEnable field is valid. + */ +#define DDCOLOR_COLORENABLE 0x00000040l +}; + +mask DWORD DirectDrawCapsFlags +{ +/*============================================================================ + * + * Direct Draw Capability Flags + * + * These flags are used to describe the capabilities of a given Surface. + * All flags are bit flags. + * + *==========================================================================*/ + +/**************************************************************************** + * + * DIRECTDRAWSURFACE CAPABILITY FLAGS + * + ****************************************************************************/ + +/* + * This bit is reserved. It should not be specified. + */ +#define DDSCAPS_RESERVED1 0x00000001l + +/* + * Indicates that this surface contains alpha-only information. + * (To determine if a surface is RGBA/YUVA, the pixel format must be + * interrogated.) + */ +#define DDSCAPS_ALPHA 0x00000002l + +/* + * Indicates that this surface is a backbuffer. It is generally + * set by CreateSurface when the DDSCAPS_FLIP capability bit is set. + * It indicates that this surface is THE back buffer of a surface + * flipping structure. DirectDraw supports N surfaces in a + * surface flipping structure. Only the surface that immediately + * precedeces the DDSCAPS_FRONTBUFFER has this capability bit set. + * The other surfaces are identified as back buffers by the presence + * of the DDSCAPS_FLIP capability, their attachment order, and the + * absence of the DDSCAPS_FRONTBUFFER and DDSCAPS_BACKBUFFER + * capabilities. The bit is sent to CreateSurface when a standalone + * back buffer is being created. This surface could be attached to + * a front buffer and/or back buffers to form a flipping surface + * structure after the CreateSurface call. See AddAttachments for + * a detailed description of the behaviors in this case. + */ +#define DDSCAPS_BACKBUFFER 0x00000004l + +/* + * Indicates a complex surface structure is being described. A + * complex surface structure results in the creation of more than + * one surface. The additional surfaces are attached to the root + * surface. The complex structure can only be destroyed by + * destroying the root. + */ +#define DDSCAPS_COMPLEX 0x00000008l + +/* + * Indicates that this surface is a part of a surface flipping structure. + * When it is passed to CreateSurface the DDSCAPS_FRONTBUFFER and + * DDSCAP_BACKBUFFER bits are not set. They are set by CreateSurface + * on the resulting creations. The dwBackBufferCount field in the + * DDSURFACEDESC structure must be set to at least 1 in order for + * the CreateSurface call to succeed. The DDSCAPS_COMPLEX capability + * must always be set with creating multiple surfaces through CreateSurface. + */ +#define DDSCAPS_FLIP 0x00000010l + +/* + * Indicates that this surface is THE front buffer of a surface flipping + * structure. It is generally set by CreateSurface when the DDSCAPS_FLIP + * capability bit is set. + * If this capability is sent to CreateSurface then a standalonw front buffer + * is created. This surface will not have the DDSCAPS_FLIP capability. + * It can be attached to other back buffers to form a flipping structure. + * See AddAttachments for a detailed description of the behaviors in this + * case. + */ +#define DDSCAPS_FRONTBUFFER 0x00000020l + +/* + * Indicates that this surface is any offscreen surface that is not an overlay, + * texture, zbuffer, front buffer, back buffer, or alpha surface. It is used + * to identify plain vanilla surfaces. + */ +#define DDSCAPS_OFFSCREENPLAIN 0x00000040l + +/* + * Indicates that this surface is an overlay. It may or may not be directly visible + * depending on whether or not it is currently being overlayed onto the primary + * surface. DDSCAPS_VISIBLE can be used to determine whether or not it is being + * overlayed at the moment. + */ +#define DDSCAPS_OVERLAY 0x00000080l + +/* + * Indicates that unique DirectDrawPalette objects can be created and + * attached to this surface. + */ +#define DDSCAPS_PALETTE 0x00000100l + +/* + * Indicates that this surface is the primary surface. The primary + * surface represents what the user is seeing at the moment. + */ +#define DDSCAPS_PRIMARYSURFACE 0x00000200l + + +/* + * This flag used to be DDSCAPS_PRIMARYSURFACELEFT, which is now + * obsolete. + */ +#define DDSCAPS_RESERVED3 0x00000400l + +/* + * Indicates that this surface memory was allocated in system memory + */ +#define DDSCAPS_SYSTEMMEMORY 0x00000800l + +/* + * Indicates that this surface can be used as a 3D texture. It does not + * indicate whether or not the surface is being used for that purpose. + */ +#define DDSCAPS_TEXTURE 0x00001000l + +/* + * Indicates that a surface may be a destination for 3D rendering. This + * bit must be set in order to query for a Direct3D Device Interface + * from this surface. + */ +#define DDSCAPS_3DDEVICE 0x00002000l + +/* + * Indicates that this surface exists in video memory. + */ +#define DDSCAPS_VIDEOMEMORY 0x00004000l + +/* + * Indicates that changes made to this surface are immediately visible. + * It is always set for the primary surface and is set for overlays while + * they are being overlayed and texture maps while they are being textured. + */ +#define DDSCAPS_VISIBLE 0x00008000l + +/* + * Indicates that only writes are permitted to the surface. Read accesses + * from the surface may or may not generate a protection fault, but the + * results of a read from this surface will not be meaningful. READ ONLY. + */ +#define DDSCAPS_WRITEONLY 0x00010000l + +/* + * Indicates that this surface is a z buffer. A z buffer does not contain + * displayable information. Instead it contains bit depth information that is + * used to determine which pixels are visible and which are obscured. + */ +#define DDSCAPS_ZBUFFER 0x00020000l + +/* + * Indicates surface will have a DC associated long term + */ +#define DDSCAPS_OWNDC 0x00040000l + +/* + * Indicates surface should be able to receive live video + */ +#define DDSCAPS_LIVEVIDEO 0x00080000l + +/* + * Indicates surface should be able to have a stream decompressed + * to it by the hardware. + */ +#define DDSCAPS_HWCODEC 0x00100000l + +/* + * Surface is a ModeX surface. + * + */ +#define DDSCAPS_MODEX 0x00200000l + +/* + * Indicates surface is one level of a mip-map. This surface will + * be attached to other DDSCAPS_MIPMAP surfaces to form the mip-map. + * This can be done explicitly, by creating a number of surfaces and + * attaching them with AddAttachedSurface or by implicitly by CreateSurface. + * If this bit is set then DDSCAPS_TEXTURE must also be set. + */ +#define DDSCAPS_MIPMAP 0x00400000l + +/* + * This bit is reserved. It should not be specified. + */ +#define DDSCAPS_RESERVED2 0x00800000l + + +/* + * Indicates that memory for the surface is not allocated until the surface + * is loaded (via the Direct3D texture Load() function). + */ +#define DDSCAPS_ALLOCONLOAD 0x04000000l + +/* + * Indicates that the surface will recieve data from a video port. + */ +#define DDSCAPS_VIDEOPORT 0x08000000l + +/* + * Indicates that a video memory surface is resident in true, local video + * memory rather than non-local video memory. If this flag is specified then + * so must DDSCAPS_VIDEOMEMORY. This flag is mutually exclusive with + * DDSCAPS_NONLOCALVIDMEM. + */ +#define DDSCAPS_LOCALVIDMEM 0x10000000l + +/* + * Indicates that a video memory surface is resident in non-local video + * memory rather than true, local video memory. If this flag is specified + * then so must DDSCAPS_VIDEOMEMORY. This flag is mutually exclusive with + * DDSCAPS_LOCALVIDMEM. + */ +#define DDSCAPS_NONLOCALVIDMEM 0x20000000l + +/* + * Indicates that this surface is a standard VGA mode surface, and not a + * ModeX surface. (This flag will never be set in combination with the + * DDSCAPS_MODEX flag). + */ +#define DDSCAPS_STANDARDVGAMODE 0x40000000l + +/* + * Indicates that this surface will be an optimized surface. This flag is + * currently only valid in conjunction with the DDSCAPS_TEXTURE flag. The surface + * will be created without any underlying video memory until loaded. + */ +#define DDSCAPS_OPTIMIZED 0x80000000l +}; + + + +mask DWORD DirectDrawCapabilityFlags2 +{ +/* + * Indicates that this surface will receive data from a video port using + * the de-interlacing hardware. This allows the driver to allocate memory + * for any extra buffers that may be required. The DDSCAPS_VIDEOPORT and + * DDSCAPS_OVERLAY flags must also be set. + */ +#define DDSCAPS2_HARDWAREDEINTERLACE 0x00000002L + +/* + * Indicates to the driver that this surface will be locked very frequently + * (for procedural textures, dynamic lightmaps, etc). Surfaces with this cap + * set must also have DDSCAPS_TEXTURE. This cap cannot be used with + * DDSCAPS2_HINTSTATIC and DDSCAPS2_OPAQUE. + */ +#define DDSCAPS2_HINTDYNAMIC 0x00000004L + +/* + * Indicates to the driver that this surface can be re-ordered/retiled on + * load. This operation will not change the size of the texture. It is + * relatively fast and symmetrical, since the application may lock these + * bits (although it will take a performance hit when doing so). Surfaces + * with this cap set must also have DDSCAPS_TEXTURE. This cap cannot be + * used with DDSCAPS2_HINTDYNAMIC and DDSCAPS2_OPAQUE. + */ +#define DDSCAPS2_HINTSTATIC 0x00000008L + +/* + * Indicates that the client would like this texture surface to be managed by the + * DirectDraw/Direct3D runtime. Surfaces with this cap set must also have + * DDSCAPS_TEXTURE set. + */ +#define DDSCAPS2_TEXTUREMANAGE 0x00000010L + +/* + * These bits are reserved for internal use */ +#define DDSCAPS2_RESERVED1 0x00000020L +#define DDSCAPS2_RESERVED2 0x00000040L + +/* + * Indicates to the driver that this surface will never be locked again. + * The driver is free to optimize this surface via retiling and actual compression. + * All calls to Lock() or Blts from this surface will fail. Surfaces with this + * cap set must also have DDSCAPS_TEXTURE. This cap cannot be used with + * DDSCAPS2_HINTDYNAMIC and DDSCAPS2_HINTSTATIC. + */ +#define DDSCAPS2_OPAQUE 0x00000080L + +/* + * Applications should set this bit at CreateSurface time to indicate that they + * intend to use antialiasing. Only valid if DDSCAPS_3DDEVICE is also set. + */ +#define DDSCAPS2_HINTANTIALIASING 0x00000100L + + +/* + * This flag is used at CreateSurface time to indicate that this set of + * surfaces is a cubic environment map + */ +#define DDSCAPS2_CUBEMAP 0x00000200L + +/* + * These flags preform two functions: + * - At CreateSurface time, they define which of the six cube faces are + * required by the application. + * - After creation, each face in the cubemap will have exactly one of these + * bits set. + */ +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400L +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800L +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000L +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000L +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000L +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000L + +/* + * This flag is an additional flag which is present on mipmap sublevels from DX7 onwards + * It enables easier use of GetAttachedSurface rather than EnumAttachedSurfaces for surface + * constructs such as Cube Maps, wherein there are more than one mipmap surface attached + * to the root surface. + * This caps bit is ignored by CreateSurface + */ +#define DDSCAPS2_MIPMAPSUBLEVEL 0x00010000L + +/* This flag indicates that the texture should be managed by D3D only */ +#define DDSCAPS2_D3DTEXTUREMANAGE 0x00020000L + +/* This flag indicates that the managed surface can be safely lost */ +#define DDSCAPS2_DONOTPERSIST 0x00040000L + +/* indicates that this surface is part of a stereo flipping chain */ +#define DDSCAPS2_STEREOSURFACELEFT 0x00080000L +}; + +mask DWORD DirectDrawDriverCapsFlags +{ + /**************************************************************************** + * + * DIRECTDRAW DRIVER CAPABILITY FLAGS + * + ****************************************************************************/ + +/* + * Display hardware has 3D acceleration. + */ +#define DDCAPS_3D 0x00000001l + +/* + * Indicates that DirectDraw will support only dest rectangles that are aligned + * on DIRECTDRAWCAPS.dwAlignBoundaryDest boundaries of the surface, respectively. + * READ ONLY. + */ +#define DDCAPS_ALIGNBOUNDARYDEST 0x00000002l + +/* + * Indicates that DirectDraw will support only source rectangles whose sizes in + * BYTEs are DIRECTDRAWCAPS.dwAlignSizeDest multiples, respectively. READ ONLY. + */ +#define DDCAPS_ALIGNSIZEDEST 0x00000004l +/* + * Indicates that DirectDraw will support only source rectangles that are aligned + * on DIRECTDRAWCAPS.dwAlignBoundarySrc boundaries of the surface, respectively. + * READ ONLY. + */ +#define DDCAPS_ALIGNBOUNDARYSRC 0x00000008l + +/* + * Indicates that DirectDraw will support only source rectangles whose sizes in + * BYTEs are DIRECTDRAWCAPS.dwAlignSizeSrc multiples, respectively. READ ONLY. + */ +#define DDCAPS_ALIGNSIZESRC 0x00000010l + +/* + * Indicates that DirectDraw will create video memory surfaces that have a stride + * alignment equal to DIRECTDRAWCAPS.dwAlignStride. READ ONLY. + */ +#define DDCAPS_ALIGNSTRIDE 0x00000020l + +/* + * Display hardware is capable of blt operations. + */ +#define DDCAPS_BLT 0x00000040l + +/* + * Display hardware is capable of asynchronous blt operations. + */ +#define DDCAPS_BLTQUEUE 0x00000080l + +/* + * Display hardware is capable of color space conversions during the blt operation. + */ +#define DDCAPS_BLTFOURCC 0x00000100l + +/* + * Display hardware is capable of stretching during blt operations. + */ +#define DDCAPS_BLTSTRETCH 0x00000200l + +/* + * Display hardware is shared with GDI. + */ +#define DDCAPS_GDI 0x00000400l + +/* + * Display hardware can overlay. + */ +#define DDCAPS_OVERLAY 0x00000800l + +/* + * Set if display hardware supports overlays but can not clip them. + */ +#define DDCAPS_OVERLAYCANTCLIP 0x00001000l + +/* + * Indicates that overlay hardware is capable of color space conversions during + * the overlay operation. + */ +#define DDCAPS_OVERLAYFOURCC 0x00002000l + +/* + * Indicates that stretching can be done by the overlay hardware. + */ +#define DDCAPS_OVERLAYSTRETCH 0x00004000l + +/* + * Indicates that unique DirectDrawPalettes can be created for DirectDrawSurfaces + * other than the primary surface. + */ +#define DDCAPS_PALETTE 0x00008000l + +/* + * Indicates that palette changes can be syncd with the veritcal refresh. + */ +#define DDCAPS_PALETTEVSYNC 0x00010000l + +/* + * Display hardware can return the current scan line. + */ +#define DDCAPS_READSCANLINE 0x00020000l + + +/* + * This flag used to bo DDCAPS_STEREOVIEW, which is now obsolete + */ +#define DDCAPS_RESERVED1 0x00040000l + +/* + * Display hardware is capable of generating a vertical blank interrupt. + */ +#define DDCAPS_VBI 0x00080000l + +/* + * Supports the use of z buffers with blt operations. + */ +#define DDCAPS_ZBLTS 0x00100000l + +/* + * Supports Z Ordering of overlays. + */ +#define DDCAPS_ZOVERLAYS 0x00200000l + +/* + * Supports color key + */ +#define DDCAPS_COLORKEY 0x00400000l + +/* + * Supports alpha surfaces + */ +#define DDCAPS_ALPHA 0x00800000l + +/* + * colorkey is hardware assisted(DDCAPS_COLORKEY will also be set) + */ +#define DDCAPS_COLORKEYHWASSIST 0x01000000l + +/* + * no hardware support at all + */ +#define DDCAPS_NOHARDWARE 0x02000000l + +/* + * Display hardware is capable of color fill with bltter + */ +#define DDCAPS_BLTCOLORFILL 0x04000000l + +/* + * Display hardware is bank switched, and potentially very slow at + * random access to VRAM. + */ +#define DDCAPS_BANKSWITCHED 0x08000000l + +/* + * Display hardware is capable of depth filling Z-buffers with bltter + */ +#define DDCAPS_BLTDEPTHFILL 0x10000000l + +/* + * Display hardware is capable of clipping while bltting. + */ +#define DDCAPS_CANCLIP 0x20000000l + +/* + * Display hardware is capable of clipping while stretch bltting. + */ +#define DDCAPS_CANCLIPSTRETCHED 0x40000000l + +/* + * Display hardware is capable of bltting to or from system memory + */ +#define DDCAPS_CANBLTSYSMEM 0x80000000l +}; + + +mask DWORD DirectDrawDriverCapsFlags2 +{ + /**************************************************************************** + * + * MORE DIRECTDRAW DRIVER CAPABILITY FLAGS (dwCaps2) + * + ****************************************************************************/ + +/* + * Display hardware is certified + */ +#define DDCAPS2_CERTIFIED 0x00000001l + +/* + * Driver cannot interleave 2D operations (lock and blt) to surfaces with + * Direct3D rendering operations between calls to BeginScene() and EndScene() + */ +#define DDCAPS2_NO2DDURING3DSCENE 0x00000002l + +/* + * Display hardware contains a video port + */ +#define DDCAPS2_VIDEOPORT 0x00000004l + +/* + * The overlay can be automatically flipped according to the video port + * VSYNCs, providing automatic doubled buffered display of video port + * data using an overlay + */ +#define DDCAPS2_AUTOFLIPOVERLAY 0x00000008l + +/* + * Overlay can display each field of interlaced data individually while + * it is interleaved in memory without causing jittery artifacts. + */ +#define DDCAPS2_CANBOBINTERLEAVED 0x00000010l + +/* + * Overlay can display each field of interlaced data individually while + * it is not interleaved in memory without causing jittery artifacts. + */ +#define DDCAPS2_CANBOBNONINTERLEAVED 0x00000020l + +/* + * The overlay surface contains color controls (brightness, sharpness, etc.) + */ +#define DDCAPS2_COLORCONTROLOVERLAY 0x00000040l + +/* + * The primary surface contains color controls (gamma, etc.) + */ +#define DDCAPS2_COLORCONTROLPRIMARY 0x00000080l + +/* + * RGBZ -> RGB supported for 16:16 RGB:Z + */ +#define DDCAPS2_CANDROPZ16BIT 0x00000100l + +/* + * Driver supports non-local video memory. + */ +#define DDCAPS2_NONLOCALVIDMEM 0x00000200l + +/* + * Dirver supports non-local video memory but has different capabilities for + * non-local video memory surfaces. If this bit is set then so must + * DDCAPS2_NONLOCALVIDMEM. + */ +#define DDCAPS2_NONLOCALVIDMEMCAPS 0x00000400l + +/* + * Driver neither requires nor prefers surfaces to be pagelocked when performing + * blts involving system memory surfaces + */ +#define DDCAPS2_NOPAGELOCKREQUIRED 0x00000800l + +/* + * Driver can create surfaces which are wider than the primary surface + */ +#define DDCAPS2_WIDESURFACES 0x00001000l + +/* + * Driver supports bob without using a video port by handling the + * DDFLIP_ODD and DDFLIP_EVEN flags specified in Flip. + */ +#define DDCAPS2_CANFLIPODDEVEN 0x00002000l + +/* + * Driver supports bob using hardware + */ +#define DDCAPS2_CANBOBHARDWARE 0x00004000l + +/* + * Driver supports bltting any FOURCC surface to another surface of the same FOURCC + */ +#define DDCAPS2_COPYFOURCC 0x00008000l + + +/* + * Driver supports loadable gamma ramps for the primary surface + */ +#define DDCAPS2_PRIMARYGAMMA 0x00020000l + +/* + * Driver can render in windowed mode. + */ +#define DDCAPS2_CANRENDERWINDOWED 0x00080000l + +/* + * A calibrator is available to adjust the gamma ramp according to the + * physical display properties so that the result will be identical on + * all calibrated systems. + */ +#define DDCAPS2_CANCALIBRATEGAMMA 0x00100000l + +/* + * Indicates that the driver will respond to DDFLIP_INTERVALn flags + */ +#define DDCAPS2_FLIPINTERVAL 0x00200000l + +/* + * Indicates that the driver will respond to DDFLIP_NOVSYNC + */ +#define DDCAPS2_FLIPNOVSYNC 0x00400000l + +/* + * Driver supports management of video memory, if this flag is ON, + * driver manages the texture if requested with DDSCAPS2_TEXTUREMANAGE on + * DirectX manages the texture if this flag is OFF and surface has DDSCAPS2_TEXTUREMANAGE on + */ +#define DDCAPS2_CANMANAGETEXTURE 0x00800000l + +/* + * The Direct3D texture manager uses this cap to decide whether to put managed + * surfaces in non-local video memory. If the cap is set, the texture manager will + * put managed surfaces in non-local vidmem. Drivers that cannot texture from + * local vidmem SHOULD NOT set this cap. + */ +#define DDCAPS2_TEXMANINNONLOCALVIDMEM 0x01000000l + +/* + * Indicates that the driver supports DX7 type of stereo in at least one mode (which may + * not necessarily be the current mode). Applications should use IDirectDraw7 (or higher) + * ::EnumDisplayModes and check the DDSURFACEDESC.ddsCaps.dwCaps2 field for the presence of + * DDSCAPS2_STEREOSURFACELEFT to check if a particular mode supports stereo. The application + * can also use IDirectDraw7(or higher)::GetDisplayMode to check the current mode. + */ +#define DDCAPS2_STEREO 0x02000000L + +/* + * This caps bit is intended for internal DirectDraw use. + * -It is only valid if DDCAPS2_NONLOCALVIDMEMCAPS is set. + * -If this bit is set, then DDCAPS_CANBLTSYSMEM MUST be set by the driver (and + * all the assoicated system memory blt caps must be correct). + * -It implies that the system->video blt caps in DDCAPS also apply to system to + * nonlocal blts. I.e. the dwSVBCaps, dwSVBCKeyCaps, dwSVBFXCaps and dwSVBRops + * members of DDCAPS (DDCORECAPS) are filled in correctly. + * -Any blt from system to nonlocal memory that matches these caps bits will + * be passed to the driver. + * + * NOTE: This is intended to enable the driver itself to do efficient reordering + * of textures. This is NOT meant to imply that hardware can write into AGP memory. + * This operation is not currently supported. + */ +#define DDCAPS2_SYSTONONLOCAL_AS_SYSTOLOCAL 0x04000000L +}; + + +mask DWORD DirectDrawFxAlphaCapsFlags +{ +/**************************************************************************** + * + * DIRECTDRAW FX ALPHA CAPABILITY FLAGS + * + ****************************************************************************/ + +/* + * Supports alpha blending around the edge of a source color keyed surface. + * For Blt. + */ +#define DDFXALPHACAPS_BLTALPHAEDGEBLEND 0x00000001l + +/* + * Supports alpha information in the pixel format. The bit depth of alpha + * information in the pixel format can be 1,2,4, or 8. The alpha value becomes + * more opaque as the alpha value increases. (0 is transparent.) + * For Blt. + */ +#define DDFXALPHACAPS_BLTALPHAPIXELS 0x00000002l + +/* + * Supports alpha information in the pixel format. The bit depth of alpha + * information in the pixel format can be 1,2,4, or 8. The alpha value + * becomes more transparent as the alpha value increases. (0 is opaque.) + * This flag can only be set if DDCAPS_ALPHA is set. + * For Blt. + */ +#define DDFXALPHACAPS_BLTALPHAPIXELSNEG 0x00000004l + +/* + * Supports alpha only surfaces. The bit depth of an alpha only surface can be + * 1,2,4, or 8. The alpha value becomes more opaque as the alpha value increases. + * (0 is transparent.) + * For Blt. + */ +#define DDFXALPHACAPS_BLTALPHASURFACES 0x00000008l + +/* + * The depth of the alpha channel data can range can be 1,2,4, or 8. + * The NEG suffix indicates that this alpha channel becomes more transparent + * as the alpha value increases. (0 is opaque.) This flag can only be set if + * DDCAPS_ALPHA is set. + * For Blt. + */ +#define DDFXALPHACAPS_BLTALPHASURFACESNEG 0x00000010l + +/* + * Supports alpha blending around the edge of a source color keyed surface. + * For Overlays. + */ +#define DDFXALPHACAPS_OVERLAYALPHAEDGEBLEND 0x00000020l + +/* + * Supports alpha information in the pixel format. The bit depth of alpha + * information in the pixel format can be 1,2,4, or 8. The alpha value becomes + * more opaque as the alpha value increases. (0 is transparent.) + * For Overlays. + */ +#define DDFXALPHACAPS_OVERLAYALPHAPIXELS 0x00000040l + +/* + * Supports alpha information in the pixel format. The bit depth of alpha + * information in the pixel format can be 1,2,4, or 8. The alpha value + * becomes more transparent as the alpha value increases. (0 is opaque.) + * This flag can only be set if DDCAPS_ALPHA is set. + * For Overlays. + */ +#define DDFXALPHACAPS_OVERLAYALPHAPIXELSNEG 0x00000080l + +/* + * Supports alpha only surfaces. The bit depth of an alpha only surface can be + * 1,2,4, or 8. The alpha value becomes more opaque as the alpha value increases. + * (0 is transparent.) + * For Overlays. + */ +#define DDFXALPHACAPS_OVERLAYALPHASURFACES 0x00000100l + +/* + * The depth of the alpha channel data can range can be 1,2,4, or 8. + * The NEG suffix indicates that this alpha channel becomes more transparent + * as the alpha value increases. (0 is opaque.) This flag can only be set if + * DDCAPS_ALPHA is set. + * For Overlays. + */ +#define DDFXALPHACAPS_OVERLAYALPHASURFACESNEG 0x00000200l +}; + +mask DWORD DirectDrawFxCapsFlags +{ +/**************************************************************************** + * + * DIRECTDRAW FX CAPABILITY FLAGS + * + ****************************************************************************/ + +/* + * Uses arithmetic operations to stretch and shrink surfaces during blt + * rather than pixel doubling techniques. Along the Y axis. + */ +#define DDFXCAPS_BLTARITHSTRETCHY 0x00000020l + +/* + * Uses arithmetic operations to stretch during blt + * rather than pixel doubling techniques. Along the Y axis. Only + * works for x1, x2, etc. + */ +#define DDFXCAPS_BLTARITHSTRETCHYN 0x00000010l + +/* + * Supports mirroring left to right in blt. + */ +#define DDFXCAPS_BLTMIRRORLEFTRIGHT 0x00000040l + +/* + * Supports mirroring top to bottom in blt. + */ +#define DDFXCAPS_BLTMIRRORUPDOWN 0x00000080l + +/* + * Supports arbitrary rotation for blts. + */ +#define DDFXCAPS_BLTROTATION 0x00000100l + +/* + * Supports 90 degree rotations for blts. + */ +#define DDFXCAPS_BLTROTATION90 0x00000200l + +/* + * DirectDraw supports arbitrary shrinking of a surface along the + * x axis (horizontal direction) for blts. + */ +#define DDFXCAPS_BLTSHRINKX 0x00000400l + +/* + * DirectDraw supports integer shrinking (1x,2x,) of a surface + * along the x axis (horizontal direction) for blts. + */ +#define DDFXCAPS_BLTSHRINKXN 0x00000800l + +/* + * DirectDraw supports arbitrary shrinking of a surface along the + * y axis (horizontal direction) for blts. + */ +#define DDFXCAPS_BLTSHRINKY 0x00001000l + +/* + * DirectDraw supports integer shrinking (1x,2x,) of a surface + * along the y axis (vertical direction) for blts. + */ +#define DDFXCAPS_BLTSHRINKYN 0x00002000l + +/* + * DirectDraw supports arbitrary stretching of a surface along the + * x axis (horizontal direction) for blts. + */ +#define DDFXCAPS_BLTSTRETCHX 0x00004000l + +/* + * DirectDraw supports integer stretching (1x,2x,) of a surface + * along the x axis (horizontal direction) for blts. + */ +#define DDFXCAPS_BLTSTRETCHXN 0x00008000l + +/* + * DirectDraw supports arbitrary stretching of a surface along the + * y axis (horizontal direction) for blts. + */ +#define DDFXCAPS_BLTSTRETCHY 0x00010000l + +/* + * DirectDraw supports integer stretching (1x,2x,) of a surface + * along the y axis (vertical direction) for blts. + */ +#define DDFXCAPS_BLTSTRETCHYN 0x00020000l + +/* + * Uses arithmetic operations to stretch and shrink surfaces during + * overlay rather than pixel doubling techniques. Along the Y axis + * for overlays. + */ +#define DDFXCAPS_OVERLAYARITHSTRETCHY 0x00040000l + +/* + * Uses arithmetic operations to stretch surfaces during + * overlay rather than pixel doubling techniques. Along the Y axis + * for overlays. Only works for x1, x2, etc. + */ +#define DDFXCAPS_OVERLAYARITHSTRETCHYN 0x00000008l + +/* + * DirectDraw supports arbitrary shrinking of a surface along the + * x axis (horizontal direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSHRINKX 0x00080000l + +/* + * DirectDraw supports integer shrinking (1x,2x,) of a surface + * along the x axis (horizontal direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSHRINKXN 0x00100000l + +/* + * DirectDraw supports arbitrary shrinking of a surface along the + * y axis (horizontal direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSHRINKY 0x00200000l + +/* + * DirectDraw supports integer shrinking (1x,2x,) of a surface + * along the y axis (vertical direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSHRINKYN 0x00400000l + +/* + * DirectDraw supports arbitrary stretching of a surface along the + * x axis (horizontal direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSTRETCHX 0x00800000l + +/* + * DirectDraw supports integer stretching (1x,2x,) of a surface + * along the x axis (horizontal direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSTRETCHXN 0x01000000l + +/* + * DirectDraw supports arbitrary stretching of a surface along the + * y axis (horizontal direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSTRETCHY 0x02000000l + +/* + * DirectDraw supports integer stretching (1x,2x,) of a surface + * along the y axis (vertical direction) for overlays. + */ +#define DDFXCAPS_OVERLAYSTRETCHYN 0x04000000l + +/* + * DirectDraw supports mirroring of overlays across the vertical axis + */ +#define DDFXCAPS_OVERLAYMIRRORLEFTRIGHT 0x08000000l + +/* + * DirectDraw supports mirroring of overlays across the horizontal axis + */ +#define DDFXCAPS_OVERLAYMIRRORUPDOWN 0x10000000l + +/* + * Driver can do alpha blending for blits. + */ +#define DDFXCAPS_BLTALPHA 0x00000001l + + +/* + * Driver can do alpha blending for overlays. + */ +#define DDFXCAPS_OVERLAYALPHA 0x00000004l + +}; + +mask DWORD DirectDrawStereoViewCapsFlags +{ +/**************************************************************************** + * + * DIRECTDRAW STEREO VIEW CAPABILITIES + * + ****************************************************************************/ + +/* + * This flag used to be DDSVCAPS_ENIGMA, which is now obsolete + */ + +#define DDSVCAPS_RESERVED1 0x00000001l + +/* + * This flag used to be DDSVCAPS_FLICKER, which is now obsolete + */ +#define DDSVCAPS_RESERVED2 0x00000002l + +/* + * This flag used to be DDSVCAPS_REDBLUE, which is now obsolete + */ +#define DDSVCAPS_RESERVED3 0x00000004l + +/* + * This flag used to be DDSVCAPS_SPLIT, which is now obsolete + */ +#define DDSVCAPS_RESERVED4 0x00000008l + +/* + * The stereo view is accomplished with switching technology + */ + +#define DDSVCAPS_STEREOSEQUENTIAL 0x00000010L + +}; + + +mask DWORD DirectDrawPaletteCapsFlags +{ +/**************************************************************************** + * + * DIRECTDRAWPALETTE CAPABILITIES + * + ****************************************************************************/ + +/* + * Index is 4 bits. There are sixteen color entries in the palette table. + */ +#define DDPCAPS_4BIT 0x00000001l + +/* + * Index is onto a 8 bit color index. This field is only valid with the + * DDPCAPS_1BIT, DDPCAPS_2BIT or DDPCAPS_4BIT capability and the target + * surface is in 8bpp. Each color entry is one byte long and is an index + * into destination surface's 8bpp palette. + */ +#define DDPCAPS_8BITENTRIES 0x00000002l + +/* + * Index is 8 bits. There are 256 color entries in the palette table. + */ +#define DDPCAPS_8BIT 0x00000004l + +/* + * Indicates that this DIRECTDRAWPALETTE should use the palette color array + * passed into the lpDDColorArray parameter to initialize the DIRECTDRAWPALETTE + * object. + * This flag is obsolete. DirectDraw always initializes the color array from + * the lpDDColorArray parameter. The definition remains for source-level + * compatibility. + */ +#define DDPCAPS_INITIALIZE 0x00000000l + +/* + * This palette is the one attached to the primary surface. Changing this + * table has immediate effect on the display unless DDPSETPAL_VSYNC is specified + * and supported. + */ +#define DDPCAPS_PRIMARYSURFACE 0x00000010l + +/* + * This palette is the one attached to the primary surface left. Changing + * this table has immediate effect on the display for the left eye unless + * DDPSETPAL_VSYNC is specified and supported. + */ +#define DDPCAPS_PRIMARYSURFACELEFT 0x00000020l + +/* + * This palette can have all 256 entries defined + */ +#define DDPCAPS_ALLOW256 0x00000040l + +/* + * This palette can have modifications to it synced with the monitors + * refresh rate. + */ +#define DDPCAPS_VSYNC 0x00000080l + +/* + * Index is 1 bit. There are two color entries in the palette table. + */ +#define DDPCAPS_1BIT 0x00000100l + +/* + * Index is 2 bit. There are four color entries in the palette table. + */ +#define DDPCAPS_2BIT 0x00000200l + +/* + * The peFlags member of PALETTEENTRY denotes an 8 bit alpha value + */ +#define DDPCAPS_ALPHA 0x00000400l +}; + +/**************************************************************************** + * + * DIRECTDRAWPALETTE SETENTRY CONSTANTS + * + ****************************************************************************/ + + +/**************************************************************************** + * + * DIRECTDRAWPALETTE GETENTRY CONSTANTS + * + ****************************************************************************/ + +/* 0 is the only legal value */ + +/**************************************************************************** + * + * DIRECTDRAWSURFACE SETPRIVATEDATA CONSTANTS + * + ****************************************************************************/ + +value DWORD DirectDrawSurfaceSetPrivateDataConstants +{ +/* + * The passed pointer is an IUnknown ptr. The cbData argument to SetPrivateData + * must be set to sizeof(IUnknown*). DirectDraw will call AddRef through this + * pointer and Release when the private data is destroyed. This includes when + * the surface or palette is destroyed before such priovate data is destroyed. + */ +#define DDSPD_IUNKNOWNPOINTER 0x00000001L + +/* + * Private data is only valid for the current state of the object, + * as determined by the uniqueness value. + */ +#define DDSPD_VOLATILE 0x00000002L +}; + + +value DWORD DirectDrawSurfaceSetPaletteConstants +{ +/**************************************************************************** + * + * DIRECTDRAWSURFACE SETPALETTE CONSTANTS + * + ****************************************************************************/ + + +/**************************************************************************** + * + * DIRECTDRAW BITDEPTH CONSTANTS + * + * NOTE: These are only used to indicate supported bit depths. These + * are flags only, they are not to be used as an actual bit depth. The + * absolute numbers 1, 2, 4, 8, 16, 24 and 32 are used to indicate actual + * bit depths in a surface or for changing the display mode. + * + ****************************************************************************/ + +/* + * 1 bit per pixel. + */ +#define DDBD_1 0x00004000l + +/* + * 2 bits per pixel. + */ +#define DDBD_2 0x00002000l + +/* + * 4 bits per pixel. + */ +#define DDBD_4 0x00001000l + +/* + * 8 bits per pixel. + */ +#define DDBD_8 0x00000800l + +/* + * 16 bits per pixel. + */ +#define DDBD_16 0x00000400l + +/* + * 24 bits per pixel. + */ +#define DDBD_24 0X00000200l + +/* + * 32 bits per pixel. + */ +#define DDBD_32 0x00000100l +}; + +mask DWORD DirectDrawSurfaceSetGetColorKeyFlags +{ +/**************************************************************************** + * + * DIRECTDRAWSURFACE SET/GET COLOR KEY FLAGS + * + ****************************************************************************/ + +/* + * Set if the structure contains a color space. Not set if the structure + * contains a single color key. + */ +#define DDCKEY_COLORSPACE 0x00000001l + +/* + * Set if the structure specifies a color key or color space which is to be + * used as a destination color key for blt operations. + */ +#define DDCKEY_DESTBLT 0x00000002l + +/* + * Set if the structure specifies a color key or color space which is to be + * used as a destination color key for overlay operations. + */ +#define DDCKEY_DESTOVERLAY 0x00000004l + +/* + * Set if the structure specifies a color key or color space which is to be + * used as a source color key for blt operations. + */ +#define DDCKEY_SRCBLT 0x00000008l + +/* + * Set if the structure specifies a color key or color space which is to be + * used as a source color key for overlay operations. + */ +#define DDCKEY_SRCOVERLAY 0x00000010l +}; + +mask DWORD DirectDrawColorKeyCapsFlags +{ +/**************************************************************************** + * + * DIRECTDRAW COLOR KEY CAPABILITY FLAGS + * + ****************************************************************************/ + +/* + * Supports transparent blting using a color key to identify the replaceable + * bits of the destination surface for RGB colors. + */ +#define DDCKEYCAPS_DESTBLT 0x00000001l + +/* + * Supports transparent blting using a color space to identify the replaceable + * bits of the destination surface for RGB colors. + */ +#define DDCKEYCAPS_DESTBLTCLRSPACE 0x00000002l + +/* + * Supports transparent blting using a color space to identify the replaceable + * bits of the destination surface for YUV colors. + */ +#define DDCKEYCAPS_DESTBLTCLRSPACEYUV 0x00000004l + +/* + * Supports transparent blting using a color key to identify the replaceable + * bits of the destination surface for YUV colors. + */ +#define DDCKEYCAPS_DESTBLTYUV 0x00000008l + +/* + * Supports overlaying using colorkeying of the replaceable bits of the surface + * being overlayed for RGB colors. + */ +#define DDCKEYCAPS_DESTOVERLAY 0x00000010l + +/* + * Supports a color space as the color key for the destination for RGB colors. + */ +#define DDCKEYCAPS_DESTOVERLAYCLRSPACE 0x00000020l + +/* + * Supports a color space as the color key for the destination for YUV colors. + */ +#define DDCKEYCAPS_DESTOVERLAYCLRSPACEYUV 0x00000040l + +/* + * Supports only one active destination color key value for visible overlay + * surfaces. + */ +#define DDCKEYCAPS_DESTOVERLAYONEACTIVE 0x00000080l + +/* + * Supports overlaying using colorkeying of the replaceable bits of the + * surface being overlayed for YUV colors. + */ +#define DDCKEYCAPS_DESTOVERLAYYUV 0x00000100l + +/* + * Supports transparent blting using the color key for the source with + * this surface for RGB colors. + */ +#define DDCKEYCAPS_SRCBLT 0x00000200l + +/* + * Supports transparent blting using a color space for the source with + * this surface for RGB colors. + */ +#define DDCKEYCAPS_SRCBLTCLRSPACE 0x00000400l + +/* + * Supports transparent blting using a color space for the source with + * this surface for YUV colors. + */ +#define DDCKEYCAPS_SRCBLTCLRSPACEYUV 0x00000800l + +/* + * Supports transparent blting using the color key for the source with + * this surface for YUV colors. + */ +#define DDCKEYCAPS_SRCBLTYUV 0x00001000l + +/* + * Supports overlays using the color key for the source with this + * overlay surface for RGB colors. + */ +#define DDCKEYCAPS_SRCOVERLAY 0x00002000l + +/* + * Supports overlays using a color space as the source color key for + * the overlay surface for RGB colors. + */ +#define DDCKEYCAPS_SRCOVERLAYCLRSPACE 0x00004000l + +/* + * Supports overlays using a color space as the source color key for + * the overlay surface for YUV colors. + */ +#define DDCKEYCAPS_SRCOVERLAYCLRSPACEYUV 0x00008000l + +/* + * Supports only one active source color key value for visible + * overlay surfaces. + */ +#define DDCKEYCAPS_SRCOVERLAYONEACTIVE 0x00010000l + +/* + * Supports overlays using the color key for the source with this + * overlay surface for YUV colors. + */ +#define DDCKEYCAPS_SRCOVERLAYYUV 0x00020000l + +/* + * there are no bandwidth trade-offs for using colorkey with an overlay + */ +#define DDCKEYCAPS_NOCOSTOVERLAY 0x00040000l +}; + +mask DWORD DirectDrawPixelFormatFlags +{ +/**************************************************************************** + * + * DIRECTDRAW PIXELFORMAT FLAGS + * + ****************************************************************************/ + +/* + * The surface has alpha channel information in the pixel format. + */ +#define DDPF_ALPHAPIXELS 0x00000001l + +/* + * The pixel format contains alpha only information + */ +#define DDPF_ALPHA 0x00000002l + +/* + * The FourCC code is valid. + */ +#define DDPF_FOURCC 0x00000004l + +/* + * The surface is 4-bit color indexed. + */ +#define DDPF_PALETTEINDEXED4 0x00000008l + +/* + * The surface is indexed into a palette which stores indices + * into the destination surface's 8-bit palette. + */ +#define DDPF_PALETTEINDEXEDTO8 0x00000010l + +/* + * The surface is 8-bit color indexed. + */ +#define DDPF_PALETTEINDEXED8 0x00000020l + +/* + * The RGB data in the pixel format structure is valid. + */ +#define DDPF_RGB 0x00000040l + +/* + * The surface will accept pixel data in the format specified + * and compress it during the write. + */ +#define DDPF_COMPRESSED 0x00000080l + +/* + * The surface will accept RGB data and translate it during + * the write to YUV data. The format of the data to be written + * will be contained in the pixel format structure. The DDPF_RGB + * flag will be set. + */ +#define DDPF_RGBTOYUV 0x00000100l + +/* + * pixel format is YUV - YUV data in pixel format struct is valid + */ +#define DDPF_YUV 0x00000200l + +/* + * pixel format is a z buffer only surface + */ +#define DDPF_ZBUFFER 0x00000400l + +/* + * The surface is 1-bit color indexed. + */ +#define DDPF_PALETTEINDEXED1 0x00000800l + +/* + * The surface is 2-bit color indexed. + */ +#define DDPF_PALETTEINDEXED2 0x00001000l + +/* + * The surface contains Z information in the pixels + */ +#define DDPF_ZPIXELS 0x00002000l + +/* + * The surface contains stencil information along with Z + */ +#define DDPF_STENCILBUFFER 0x00004000l + +/* + * Premultiplied alpha format -- the color components have been + * premultiplied by the alpha component. + */ +#define DDPF_ALPHAPREMULT 0x00008000l + + +/* + * Luminance data in the pixel format is valid. + * Use this flag for luminance-only or luminance+alpha surfaces, + * the bit depth is then ddpf.dwLuminanceBitCount. + */ +#define DDPF_LUMINANCE 0x00020000l + +/* + * Luminance data in the pixel format is valid. + * Use this flag when hanging luminance off bumpmap surfaces, + * the bit mask for the luminance portion of the pixel is then + * ddpf.dwBumpLuminanceBitMask + */ +#define DDPF_BUMPLUMINANCE 0x00040000l + +/* + * Bump map dUdV data in the pixel format is valid. + */ +#define DDPF_BUMPDUDV 0x00080000l +}; + +/*=========================================================================== + * + * + * DIRECTDRAW CALLBACK FLAGS + * + * + *==========================================================================*/ + +mask DWORD DirectDrawEnumSurfacesFlags +{ +/**************************************************************************** + * + * DIRECTDRAW ENUMSURFACES FLAGS + * + ****************************************************************************/ + +/* + * Enumerate all of the surfaces that meet the search criterion. + */ +#define DDENUMSURFACES_ALL 0x00000001l + +/* + * A search hit is a surface that matches the surface description. + */ +#define DDENUMSURFACES_MATCH 0x00000002l + +/* + * A search hit is a surface that does not match the surface description. + */ +#define DDENUMSURFACES_NOMATCH 0x00000004l + +/* + * Enumerate the first surface that can be created which meets the search criterion. + */ +#define DDENUMSURFACES_CANBECREATED 0x00000008l + +/* + * Enumerate the surfaces that already exist that meet the search criterion. + */ +#define DDENUMSURFACES_DOESEXIST 0x00000010l +}; + +mask DWORD DirectDrawSetDisplayModeFlags +{ +/**************************************************************************** + * + * DIRECTDRAW SETDISPLAYMODE FLAGS + * + ****************************************************************************/ + +/* + * The desired mode is a standard VGA mode + */ +#define DDSDM_STANDARDVGAMODE 0x00000001l +}; + +mask DWORD DirectDrawEnumDisplayModesFlags +{ +/**************************************************************************** + * + * DIRECTDRAW ENUMDISPLAYMODES FLAGS + * + ****************************************************************************/ + +/* + * Enumerate Modes with different refresh rates. EnumDisplayModes guarantees + * that a particular mode will be enumerated only once. This flag specifies whether + * the refresh rate is taken into account when determining if a mode is unique. + */ +#define DDEDM_REFRESHRATES 0x00000001l + +/* + * Enumerate VGA modes. Specify this flag if you wish to enumerate supported VGA + * modes such as mode 0x13 in addition to the usual ModeX modes (which are always + * enumerated if the application has previously called SetCooperativeLevel with the + * DDSCL_ALLOWMODEX flag set). + */ +#define DDEDM_STANDARDVGAMODES 0x00000002L + +}; + +mask DWORD DirectDrawSetCooperativeLevelFlags +{ + +/**************************************************************************** + * + * DIRECTDRAW SETCOOPERATIVELEVEL FLAGS + * + ****************************************************************************/ + +/* + * Exclusive mode owner will be responsible for the entire primary surface. + * GDI can be ignored. used with DD + */ +#define DDSCL_FULLSCREEN 0x00000001l + +/* + * allow CTRL_ALT_DEL to work while in fullscreen exclusive mode + */ +#define DDSCL_ALLOWREBOOT 0x00000002l + +/* + * prevents DDRAW from modifying the application window. + * prevents DDRAW from minimize/restore the application window on activation. + */ +#define DDSCL_NOWINDOWCHANGES 0x00000004l + +/* + * app wants to work as a regular Windows application + */ +#define DDSCL_NORMAL 0x00000008l + +/* + * app wants exclusive access + */ +#define DDSCL_EXCLUSIVE 0x00000010l + + +/* + * app can deal with non-windows display modes + */ +#define DDSCL_ALLOWMODEX 0x00000040l + +/* + * this window will receive the focus messages + */ +#define DDSCL_SETFOCUSWINDOW 0x00000080l + +/* + * this window is associated with the DDRAW object and will + * cover the screen in fullscreen mode + */ +#define DDSCL_SETDEVICEWINDOW 0x00000100l + +/* + * app wants DDRAW to create a window to be associated with the + * DDRAW object + */ +#define DDSCL_CREATEDEVICEWINDOW 0x00000200l + +/* + * App explicitly asks DDRAW/D3D to be multithread safe. This makes D3D + * take the global crtisec more frequently. + */ +#define DDSCL_MULTITHREADED 0x00000400l + +/* + * App specifies that it would like to keep the FPU set up for optimal Direct3D + * performance (single precision and exceptions disabled) so Direct3D + * does not need to explicitly set the FPU each time. This is assumed by + * default in DirectX 7. See also DDSCL_FPUPRESERVE + */ +#define DDSCL_FPUSETUP 0x00000800l + +/* + * App specifies that it needs either double precision FPU or FPU exceptions + * enabled. This makes Direct3D explicitly set the FPU state eah time it is + * called. Setting the flag will reduce Direct3D performance. The flag is + * assumed by default in DirectX 6 and earlier. See also DDSCL_FPUSETUP + */ +#define DDSCL_FPUPRESERVE 0x00001000l +}; + +mask DWORD DirectDrawBltFlags +{ +/**************************************************************************** + * + * DIRECTDRAW BLT FLAGS + * + ****************************************************************************/ + +/* + * Use the alpha information in the pixel format or the alpha channel surface + * attached to the destination surface as the alpha channel for this blt. + */ +#define DDBLT_ALPHADEST 0x00000001l + +/* + * Use the dwConstAlphaDest field in the DDBLTFX structure as the alpha channel + * for the destination surface for this blt. + */ +#define DDBLT_ALPHADESTCONSTOVERRIDE 0x00000002l + +/* + * The NEG suffix indicates that the destination surface becomes more + * transparent as the alpha value increases. (0 is opaque) + */ +#define DDBLT_ALPHADESTNEG 0x00000004l + +/* + * Use the lpDDSAlphaDest field in the DDBLTFX structure as the alpha + * channel for the destination for this blt. + */ +#define DDBLT_ALPHADESTSURFACEOVERRIDE 0x00000008l + +/* + * Use the dwAlphaEdgeBlend field in the DDBLTFX structure as the alpha channel + * for the edges of the image that border the color key colors. + */ +#define DDBLT_ALPHAEDGEBLEND 0x00000010l + +/* + * Use the alpha information in the pixel format or the alpha channel surface + * attached to the source surface as the alpha channel for this blt. + */ +#define DDBLT_ALPHASRC 0x00000020l + +/* + * Use the dwConstAlphaSrc field in the DDBLTFX structure as the alpha channel + * for the source for this blt. + */ +#define DDBLT_ALPHASRCCONSTOVERRIDE 0x00000040l + +/* + * The NEG suffix indicates that the source surface becomes more transparent + * as the alpha value increases. (0 is opaque) + */ +#define DDBLT_ALPHASRCNEG 0x00000080l + +/* + * Use the lpDDSAlphaSrc field in the DDBLTFX structure as the alpha channel + * for the source for this blt. + */ +#define DDBLT_ALPHASRCSURFACEOVERRIDE 0x00000100l + +/* + * Do this blt asynchronously through the FIFO in the order received. If + * there is no room in the hardware FIFO fail the call. + */ +#define DDBLT_ASYNC 0x00000200l + +/* + * Uses the dwFillColor field in the DDBLTFX structure as the RGB color + * to fill the destination rectangle on the destination surface with. + */ +#define DDBLT_COLORFILL 0x00000400l + +/* + * Uses the dwDDFX field in the DDBLTFX structure to specify the effects + * to use for the blt. + */ +#define DDBLT_DDFX 0x00000800l + +/* + * Uses the dwDDROPS field in the DDBLTFX structure to specify the ROPS + * that are not part of the Win32 API. + */ +#define DDBLT_DDROPS 0x00001000l + +/* + * Use the color key associated with the destination surface. + */ +#define DDBLT_KEYDEST 0x00002000l + +/* + * Use the dckDestColorkey field in the DDBLTFX structure as the color key + * for the destination surface. + */ +#define DDBLT_KEYDESTOVERRIDE 0x00004000l + +/* + * Use the color key associated with the source surface. + */ +#define DDBLT_KEYSRC 0x00008000l + +/* + * Use the dckSrcColorkey field in the DDBLTFX structure as the color key + * for the source surface. + */ +#define DDBLT_KEYSRCOVERRIDE 0x00010000l + +/* + * Use the dwROP field in the DDBLTFX structure for the raster operation + * for this blt. These ROPs are the same as the ones defined in the Win32 API. + */ +#define DDBLT_ROP 0x00020000l + +/* + * Use the dwRotationAngle field in the DDBLTFX structure as the angle + * (specified in 1/100th of a degree) to rotate the surface. + */ +#define DDBLT_ROTATIONANGLE 0x00040000l + +/* + * Z-buffered blt using the z-buffers attached to the source and destination + * surfaces and the dwZBufferOpCode field in the DDBLTFX structure as the + * z-buffer opcode. + */ +#define DDBLT_ZBUFFER 0x00080000l + +/* + * Z-buffered blt using the dwConstDest Zfield and the dwZBufferOpCode field + * in the DDBLTFX structure as the z-buffer and z-buffer opcode respectively + * for the destination. + */ +#define DDBLT_ZBUFFERDESTCONSTOVERRIDE 0x00100000l + +/* + * Z-buffered blt using the lpDDSDestZBuffer field and the dwZBufferOpCode + * field in the DDBLTFX structure as the z-buffer and z-buffer opcode + * respectively for the destination. + */ +#define DDBLT_ZBUFFERDESTOVERRIDE 0x00200000l + +/* + * Z-buffered blt using the dwConstSrcZ field and the dwZBufferOpCode field + * in the DDBLTFX structure as the z-buffer and z-buffer opcode respectively + * for the source. + */ +#define DDBLT_ZBUFFERSRCCONSTOVERRIDE 0x00400000l + +/* + * Z-buffered blt using the lpDDSSrcZBuffer field and the dwZBufferOpCode + * field in the DDBLTFX structure as the z-buffer and z-buffer opcode + * respectively for the source. + */ +#define DDBLT_ZBUFFERSRCOVERRIDE 0x00800000l + +/* + * wait until the device is ready to handle the blt + * this will cause blt to not return DDERR_WASSTILLDRAWING + */ +#define DDBLT_WAIT 0x01000000l + +/* + * Uses the dwFillDepth field in the DDBLTFX structure as the depth value + * to fill the destination rectangle on the destination Z-buffer surface + * with. + */ +#define DDBLT_DEPTHFILL 0x02000000l + + +/* + * wait until the device is ready to handle the blt + * this will cause blt to not return DDERR_WASSTILLDRAWING + */ +#define DDBLT_DONOTWAIT 0x08000000l +}; + + +mask DWORD DirectDrawBltFastFlags +{ +/**************************************************************************** + * + * BLTFAST FLAGS + * + ****************************************************************************/ + +#define DDBLTFAST_NOCOLORKEY 0x00000000 +#define DDBLTFAST_SRCCOLORKEY 0x00000001 +#define DDBLTFAST_DESTCOLORKEY 0x00000002 +#define DDBLTFAST_WAIT 0x00000010 +#define DDBLTFAST_DONOTWAIT 0x00000020 +}; + +mask DWORD DirectDrawFlipFlags +{ +/**************************************************************************** + * + * FLIP FLAGS + * + ****************************************************************************/ + +#define DDFLIP_WAIT 0x00000001L + +/* + * Indicates that the target surface contains the even field of video data. + * This flag is only valid with an overlay surface. + */ +#define DDFLIP_EVEN 0x00000002L + +/* + * Indicates that the target surface contains the odd field of video data. + * This flag is only valid with an overlay surface. + */ +#define DDFLIP_ODD 0x00000004L + +/* + * Causes DirectDraw to perform the physical flip immediately and return + * to the application. Typically, what was the front buffer but is now the back + * buffer will still be visible (depending on timing) until the next vertical + * retrace. Subsequent operations involving the two flipped surfaces will + * not check to see if the physical flip has finished (i.e. will not return + * DDERR_WASSTILLDRAWING for that reason (but may for other reasons)). + * This allows an application to perform Flips at a higher frequency than the + * monitor refresh rate, but may introduce visible artifacts. + * Only effective if DDCAPS2_FLIPNOVSYNC is set. If that bit is not set, + * DDFLIP_NOVSYNC has no effect. + */ +#define DDFLIP_NOVSYNC 0x00000008L + + +/* + * Flip Interval Flags. These flags indicate how many vertical retraces to wait between + * each flip. The default is one. DirectDraw will return DDERR_WASSTILLDRAWING for each + * surface involved in the flip until the specified number of vertical retraces has + * ocurred. Only effective if DDCAPS2_FLIPINTERVAL is set. If that bit is not set, + * DDFLIP_INTERVALn has no effect. + */ + +/* + * DirectDraw will flip on every other vertical sync + */ +#define DDFLIP_INTERVAL2 0x02000000L + + +/* + * DirectDraw will flip on every third vertical sync + */ +#define DDFLIP_INTERVAL3 0x03000000L + + +/* + * DirectDraw will flip on every fourth vertical sync + */ +#define DDFLIP_INTERVAL4 0x04000000L + +/* + * DirectDraw will flip and display a main stereo surface + */ +#define DDFLIP_STEREO 0x00000010L + +/* + * On IDirectDrawSurface7 and higher interfaces, the default is DDFLIP_WAIT. If you wish + * to override the default and use time when the accelerator is busy (as denoted by + * the DDERR_WASSTILLDRAWING return code) then use DDFLIP_DONOTWAIT. + */ +#define DDFLIP_DONOTWAIT 0x00000020L + +}; + +mask DWORD DirectDrawSurfaceOverlayFlags +{ +/**************************************************************************** + * + * DIRECTDRAW SURFACE OVERLAY FLAGS + * + ****************************************************************************/ + +/* + * Use the alpha information in the pixel format or the alpha channel surface + * attached to the destination surface as the alpha channel for the + * destination overlay. + */ +#define DDOVER_ALPHADEST 0x00000001l + +/* + * Use the dwConstAlphaDest field in the DDOVERLAYFX structure as the + * destination alpha channel for this overlay. + */ +#define DDOVER_ALPHADESTCONSTOVERRIDE 0x00000002l + +/* + * The NEG suffix indicates that the destination surface becomes more + * transparent as the alpha value increases. + */ +#define DDOVER_ALPHADESTNEG 0x00000004l + +/* + * Use the lpDDSAlphaDest field in the DDOVERLAYFX structure as the alpha + * channel destination for this overlay. + */ +#define DDOVER_ALPHADESTSURFACEOVERRIDE 0x00000008l + +/* + * Use the dwAlphaEdgeBlend field in the DDOVERLAYFX structure as the alpha + * channel for the edges of the image that border the color key colors. + */ +#define DDOVER_ALPHAEDGEBLEND 0x00000010l + +/* + * Use the alpha information in the pixel format or the alpha channel surface + * attached to the source surface as the source alpha channel for this overlay. + */ +#define DDOVER_ALPHASRC 0x00000020l + +/* + * Use the dwConstAlphaSrc field in the DDOVERLAYFX structure as the source + * alpha channel for this overlay. + */ +#define DDOVER_ALPHASRCCONSTOVERRIDE 0x00000040l + +/* + * The NEG suffix indicates that the source surface becomes more transparent + * as the alpha value increases. + */ +#define DDOVER_ALPHASRCNEG 0x00000080l + +/* + * Use the lpDDSAlphaSrc field in the DDOVERLAYFX structure as the alpha channel + * source for this overlay. + */ +#define DDOVER_ALPHASRCSURFACEOVERRIDE 0x00000100l + +/* + * Turn this overlay off. + */ +#define DDOVER_HIDE 0x00000200l + +/* + * Use the color key associated with the destination surface. + */ +#define DDOVER_KEYDEST 0x00000400l + +/* + * Use the dckDestColorkey field in the DDOVERLAYFX structure as the color key + * for the destination surface + */ +#define DDOVER_KEYDESTOVERRIDE 0x00000800l + +/* + * Use the color key associated with the source surface. + */ +#define DDOVER_KEYSRC 0x00001000l + +/* + * Use the dckSrcColorkey field in the DDOVERLAYFX structure as the color key + * for the source surface. + */ +#define DDOVER_KEYSRCOVERRIDE 0x00002000l + +/* + * Turn this overlay on. + */ +#define DDOVER_SHOW 0x00004000l + +/* + * Add a dirty rect to an emulated overlayed surface. + */ +#define DDOVER_ADDDIRTYRECT 0x00008000l + +/* + * Redraw all dirty rects on an emulated overlayed surface. + */ +#define DDOVER_REFRESHDIRTYRECTS 0x00010000l + +/* + * Redraw the entire surface on an emulated overlayed surface. + */ +#define DDOVER_REFRESHALL 0x00020000l + + +/* + * Use the overlay FX flags to define special overlay FX + */ +#define DDOVER_DDFX 0x00080000l + +/* + * Autoflip the overlay when ever the video port autoflips + */ +#define DDOVER_AUTOFLIP 0x00100000l + +/* + * Display each field of video port data individually without + * causing any jittery artifacts + */ +#define DDOVER_BOB 0x00200000l + +/* + * Indicates that bob/weave decisions should not be overridden by other + * interfaces. + */ +#define DDOVER_OVERRIDEBOBWEAVE 0x00400000l + +/* + * Indicates that the surface memory is composed of interleaved fields. + */ +#define DDOVER_INTERLEAVED 0x00800000l + +/* + * Indicates that bob will be performed using hardware rather than + * software or emulated. + */ +#define DDOVER_BOBHARDWARE 0x01000000l + +/* + * Indicates that overlay FX structure contains valid ARGB scaling factors. + */ +#define DDOVER_ARGBSCALEFACTORS 0x02000000l + +/* + * Indicates that ARGB scaling factors can be degraded to fit driver capabilities. + */ +#define DDOVER_DEGRADEARGBSCALING 0x04000000l +}; + + +mask DWORD DirectDrawSurfaceLockFlags +{ +/**************************************************************************** + * + * DIRECTDRAWSURFACE LOCK FLAGS + * + ****************************************************************************/ + +/* + * The default. Set to indicate that Lock should return a valid memory pointer + * to the top of the specified rectangle. If no rectangle is specified then a + * pointer to the top of the surface is returned. + */ +#define DDLOCK_SURFACEMEMORYPTR 0x00000000L // default + +/* + * Set to indicate that Lock should wait until it can obtain a valid memory + * pointer before returning. If this bit is set, Lock will never return + * DDERR_WASSTILLDRAWING. + */ +#define DDLOCK_WAIT 0x00000001L + +/* + * Set if an event handle is being passed to Lock. Lock will trigger the event + * when it can return the surface memory pointer requested. + */ +#define DDLOCK_EVENT 0x00000002L + +/* + * Indicates that the surface being locked will only be read from. + */ +#define DDLOCK_READONLY 0x00000010L + +/* + * Indicates that the surface being locked will only be written to + */ +#define DDLOCK_WRITEONLY 0x00000020L + + +/* + * Indicates that a system wide lock should not be taken when this surface + * is locked. This has several advantages (cursor responsiveness, ability + * to call more Windows functions, easier debugging) when locking video + * memory surfaces. However, an application specifying this flag must + * comply with a number of conditions documented in the help file. + * Furthermore, this flag cannot be specified when locking the primary. + */ +#define DDLOCK_NOSYSLOCK 0x00000800L + +/* + * Used only with Direct3D Vertex Buffer Locks. Indicates that no vertices + * that were referred to in Draw*PrimtiveVB calls since the start of the + * frame (or the last lock without this flag) will be modified during the + * lock. This can be useful when one is only appending data to the vertex + * buffer + */ +#define DDLOCK_NOOVERWRITE 0x00001000L + +/* + * Indicates that no assumptions will be made about the contents of the + * surface or vertex buffer during this lock. + * This enables two things: + * - Direct3D or the driver may provide an alternative memory + * area as the vertex buffer. This is useful when one plans to clear the + * contents of the vertex buffer and fill in new data. + * - Drivers sometimes store surface data in a re-ordered format. + * When the application locks the surface, the driver is forced to un-re-order + * the surface data before allowing the application to see the surface contents. + * This flag is a hint to the driver that it can skip the un-re-ordering process + * since the application plans to overwrite every single pixel in the surface + * or locked rectangle (and so erase any un-re-ordered pixels anyway). + * Applications should always set this flag when they intend to overwrite the entire + * surface or locked rectangle. + */ +#define DDLOCK_DISCARDCONTENTS 0x00002000L + /* + * DDLOCK_OKTOSWAP is an older, less informative name for DDLOCK_DISCARDCONTENTS + */ +#define DDLOCK_OKTOSWAP 0x00002000L + +/* + * On IDirectDrawSurface7 and higher interfaces, the default is DDLOCK_WAIT. If you wish + * to override the default and use time when the accelerator is busy (as denoted by + * the DDERR_WASSTILLDRAWING return code) then use DDLOCK_DONOTWAIT. + */ +#define DDLOCK_DONOTWAIT 0x00004000L + +}; + +/**************************************************************************** + * + * DIRECTDRAWSURFACE PAGELOCK FLAGS + * + ****************************************************************************/ + +/* + * No flags defined at present + */ + + +/**************************************************************************** + * + * DIRECTDRAWSURFACE PAGEUNLOCK FLAGS + * + ****************************************************************************/ + +/* + * No flags defined at present + */ + + +/**************************************************************************** + * + * DIRECTDRAWSURFACE BLT FX FLAGS + * + ****************************************************************************/ + +mask DWORD DirectDrawSurfaceBltFxFlags +{ +/* + * If stretching, use arithmetic stretching along the Y axis for this blt. + */ +#define DDBLTFX_ARITHSTRETCHY 0x00000001l + +/* + * Do this blt mirroring the surface left to right. Spin the + * surface around its y-axis. + */ +#define DDBLTFX_MIRRORLEFTRIGHT 0x00000002l + +/* + * Do this blt mirroring the surface up and down. Spin the surface + * around its x-axis. + */ +#define DDBLTFX_MIRRORUPDOWN 0x00000004l + +/* + * Schedule this blt to avoid tearing. + */ +#define DDBLTFX_NOTEARING 0x00000008l + +/* + * Do this blt rotating the surface one hundred and eighty degrees. + */ +#define DDBLTFX_ROTATE180 0x00000010l + +/* + * Do this blt rotating the surface two hundred and seventy degrees. + */ +#define DDBLTFX_ROTATE270 0x00000020l + +/* + * Do this blt rotating the surface ninety degrees. + */ +#define DDBLTFX_ROTATE90 0x00000040l + +/* + * Do this z blt using dwZBufferLow and dwZBufferHigh as range values + * specified to limit the bits copied from the source surface. + */ +#define DDBLTFX_ZBUFFERRANGE 0x00000080l + +/* + * Do this z blt adding the dwZBufferBaseDest to each of the sources z values + * before comparing it with the desting z values. + */ +#define DDBLTFX_ZBUFFERBASEDEST 0x00000100l +}; + +mask DWORD DirectDrawOverlayFxFlags +{ +/**************************************************************************** + * + * DIRECTDRAWSURFACE OVERLAY FX FLAGS + * + ****************************************************************************/ + +/* + * If stretching, use arithmetic stretching along the Y axis for this overlay. + */ +#define DDOVERFX_ARITHSTRETCHY 0x00000001l + +/* + * Mirror the overlay across the vertical axis + */ +#define DDOVERFX_MIRRORLEFTRIGHT 0x00000002l + +/* + * Mirror the overlay across the horizontal axis + */ +#define DDOVERFX_MIRRORUPDOWN 0x00000004l + +}; + +mask DWORD DirectDrawWaitForVerticalBlankFlags +{ + +/**************************************************************************** + * + * DIRECTDRAW WAITFORVERTICALBLANK FLAGS + * + ****************************************************************************/ + +/* + * return when the vertical blank interval begins + */ +#define DDWAITVB_BLOCKBEGIN 0x00000001l + +/* + * set up an event to trigger when the vertical blank begins + */ +#define DDWAITVB_BLOCKBEGINEVENT 0x00000002l + +/* + * return when the vertical blank interval ends and display begins + */ +#define DDWAITVB_BLOCKEND 0x00000004l + + }; + +mask DWORD DirectDrawGetFlipStatusFlags +{ + /**************************************************************************** + * + * DIRECTDRAW GETFLIPSTATUS FLAGS + * + ****************************************************************************/ + +/* + * is it OK to flip now? + */ +#define DDGFS_CANFLIP 0x00000001l + +/* + * is the last flip finished? + */ +#define DDGFS_ISFLIPDONE 0x00000002l +}; + +mask DWORD DirectDrawGetBltStatusFlags +{ +/**************************************************************************** + * + * DIRECTDRAW GETBLTSTATUS FLAGS + * + ****************************************************************************/ + +/* + * is it OK to blt now? + */ +#define DDGBS_CANBLT 0x00000001l + +/* + * is the blt to the surface finished? + */ +#define DDGBS_ISBLTDONE 0x00000002l + +}; + +mask DWORD DirectDrawEnumOverlayZOrderFlags +{ + +/**************************************************************************** + * + * DIRECTDRAW ENUMOVERLAYZORDER FLAGS + * + ****************************************************************************/ + +/* + * Enumerate overlays back to front. + */ +#define DDENUMOVERLAYZ_BACKTOFRONT 0x00000000l + +/* + * Enumerate overlays front to back + */ +#define DDENUMOVERLAYZ_FRONTTOBACK 0x00000001l + +}; + +mask DWORD DirectDrawUpdateOverlayZOrderFlags +{ +/**************************************************************************** + * + * DIRECTDRAW UPDATEOVERLAYZORDER FLAGS + * + ****************************************************************************/ + +/* + * Send overlay to front + */ +#define DDOVERZ_SENDTOFRONT 0x00000000l + +/* + * Send overlay to back + */ +#define DDOVERZ_SENDTOBACK 0x00000001l + +/* + * Move Overlay forward + */ +#define DDOVERZ_MOVEFORWARD 0x00000002l + +/* + * Move Overlay backward + */ +#define DDOVERZ_MOVEBACKWARD 0x00000003l + +/* + * Move Overlay in front of relative surface + */ +#define DDOVERZ_INSERTINFRONTOF 0x00000004l + +/* + * Move Overlay in back of relative surface + */ +#define DDOVERZ_INSERTINBACKOF 0x00000005l +}; + +mask DWORD DirectDrawSetGammaRampFlags +{ +/**************************************************************************** + * + * DIRECTDRAW SETGAMMARAMP FLAGS + * + ****************************************************************************/ + +/* + * Request calibrator to adjust the gamma ramp according to the physical + * properties of the display so that the result should appear identical + * on all systems. + */ +#define DDSGR_CALIBRATE 0x00000001L + +}; + +mask DWORD DirectDrawStartModeTestFlags +{ + +/**************************************************************************** + * + * DIRECTDRAW STARTMODETEST FLAGS + * + ****************************************************************************/ + +/* + * Indicates that the mode being tested has passed + */ +#define DDSMT_ISTESTREQUIRED 0x00000001L + +}; + +mask DWORD DirectDrawEvaluateModeFlags +{ + +/**************************************************************************** + * + * DIRECTDRAW EVALUATEMODE FLAGS + * + ****************************************************************************/ + +/* + * Indicates that the mode being tested has passed + */ +#define DDEM_MODEPASSED 0x00000001L + +/* + * Indicates that the mode being tested has failed + */ +#define DDEM_MODEFAILED 0x00000002L + +}; + +value DWORD DDRESULT +{ +/*=========================================================================== + * + * + * DIRECTDRAW RETURN CODES + * + * The return values from DirectDraw Commands and Surface that return an HRESULT + * are codes from DirectDraw concerning the results of the action + * requested by DirectDraw. + * + *==========================================================================*/ + +/* + * Status is OK + * + * Issued by: DirectDraw Commands and all callbacks + */ +#define DD_OK 0 +#define DD_FALSE 1 + +/**************************************************************************** + * + * DIRECTDRAW ERRORS + * + * Errors are represented by negative values and cannot be combined. + * + ****************************************************************************/ + +/* + * This object is already initialized + */ +#define DDERR_ALREADYINITIALIZED 0x88760005 [fail] + +/* + * This surface can not be attached to the requested surface. + */ +#define DDERR_CANNOTATTACHSURFACE 0x8876000A [fail] + +/* + * This surface can not be detached from the requested surface. + */ +#define DDERR_CANNOTDETACHSURFACE 0x88760014 [fail] + +/* + * Support is currently not available. + */ +#define DDERR_CURRENTLYNOTAVAIL 0x88760028 [fail] + +/* + * An exception was encountered while performing the requested operation + */ +#define DDERR_EXCEPTION 0x88760037 [fail] + +/* + * Generic failure. + */ +#define DDERR_GENERIC 0x80004005L [fail] + +/* + * Height of rectangle provided is not a multiple of reqd alignment + */ +#define DDERR_HEIGHTALIGN 0x8876005A [fail] + +/* + * Unable to match primary surface creation request with existing + * primary surface. + */ +#define DDERR_INCOMPATIBLEPRIMARY 0x8876005F [fail] + +/* + * One or more of the caps bits passed to the callback are incorrect. + */ +#define DDERR_INVALIDCAPS 0x88760064 [fail] + +/* + * DirectDraw does not support provided Cliplist. + */ +#define DDERR_INVALIDCLIPLIST 0x8876006E [fail] + +/* + * DirectDraw does not support the requested mode + */ +#define DDERR_INVALIDMODE 0x88760078 [fail] + +/* + * DirectDraw received a pointer that was an invalid DIRECTDRAW object. + */ +#define DDERR_INVALIDOBJECT 0x88760082 [fail] + +/* + * One or more of the parameters passed to the callback function are + * incorrect. + */ +#define DDERR_INVALIDPARAMS 0x80070057L [fail] + +/* + * pixel format was invalid as specified + */ +#define DDERR_INVALIDPIXELFORMAT 0x88760091 [fail] + +/* + * Rectangle provided was invalid. + */ +#define DDERR_INVALIDRECT 0x88760096 [fail] + +/* + * Operation could not be carried out because one or more surfaces are locked + */ +#define DDERR_LOCKEDSURFACES 0x887600A0 [fail] + +/* + * There is no 3D present. + */ +#define DDERR_NO3D 0x887600AA [fail] + +/* + * Operation could not be carried out because there is no alpha accleration + * hardware present or available. + */ +#define DDERR_NOALPHAHW 0x887600B4 [fail] + +/* + * Operation could not be carried out because there is no stereo + * hardware present or available. + */ +#define DDERR_NOSTEREOHARDWARE 0x887600B5 [fail] + +/* + * Operation could not be carried out because there is no hardware + * present which supports stereo surfaces + */ +#define DDERR_NOSURFACELEFT 0x887600B6 [fail] + + + +/* + * no clip list available + */ +#define DDERR_NOCLIPLIST 0x887600CD [fail] + +/* + * Operation could not be carried out because there is no color conversion + * hardware present or available. + */ +#define DDERR_NOCOLORCONVHW 0x887600D2 [fail] + +/* + * Create function called without DirectDraw object method SetCooperativeLevel + * being called. + */ +#define DDERR_NOCOOPERATIVELEVELSET 0x887600D4 [fail] + +/* + * Surface doesn't currently have a color key + */ +#define DDERR_NOCOLORKEY 0x887600D7 [fail] + +/* + * Operation could not be carried out because there is no hardware support + * of the dest color key. + */ +#define DDERR_NOCOLORKEYHW 0x887600DC [fail] + +/* + * No DirectDraw support possible with current display driver + */ +#define DDERR_NODIRECTDRAWSUPPORT 0x887600DE [fail] + +/* + * Operation requires the application to have exclusive mode but the + * application does not have exclusive mode. + */ +#define DDERR_NOEXCLUSIVEMODE 0x887600E1 [fail] + +/* + * Flipping visible surfaces is not supported. + */ +#define DDERR_NOFLIPHW 0x887600E6 [fail] + +/* + * There is no GDI present. + */ +#define DDERR_NOGDI 0x887600F0 [fail] + +/* + * Operation could not be carried out because there is no hardware present + * or available. + */ +#define DDERR_NOMIRRORHW 0x887600FA [fail] + +/* + * Requested item was not found + */ +#define DDERR_NOTFOUND 0x887600FF [fail] + +/* + * Operation could not be carried out because there is no overlay hardware + * present or available. + */ +#define DDERR_NOOVERLAYHW 0x88760104 [fail] + +/* + * Operation could not be carried out because the source and destination + * rectangles are on the same surface and overlap each other. + */ +#define DDERR_OVERLAPPINGRECTS 0x8876010E [fail] + +/* + * Operation could not be carried out because there is no appropriate raster + * op hardware present or available. + */ +#define DDERR_NORASTEROPHW 0x88760118 [fail] + +/* + * Operation could not be carried out because there is no rotation hardware + * present or available. + */ +#define DDERR_NOROTATIONHW 0x88760122 [fail] + +/* + * Operation could not be carried out because there is no hardware support + * for stretching + */ +#define DDERR_NOSTRETCHHW 0x88760136 [fail] + +/* + * DirectDrawSurface is not in 4 bit color palette and the requested operation + * requires 4 bit color palette. + */ +#define DDERR_NOT4BITCOLOR 0x8876013C [fail] + +/* + * DirectDrawSurface is not in 4 bit color index palette and the requested + * operation requires 4 bit color index palette. + */ +#define DDERR_NOT4BITCOLORINDEX 0x8876013D [fail] + +/* + * DirectDraw Surface is not in 8 bit color mode and the requested operation + * requires 8 bit color. + */ +#define DDERR_NOT8BITCOLOR 0x88760140 [fail] + +/* + * Operation could not be carried out because there is no texture mapping + * hardware present or available. + */ +#define DDERR_NOTEXTUREHW 0x8876014A [fail] + +/* + * Operation could not be carried out because there is no hardware support + * for vertical blank synchronized operations. + */ +#define DDERR_NOVSYNCHW 0x8876014F [fail] + +/* + * Operation could not be carried out because there is no hardware support + * for zbuffer blting. + */ +#define DDERR_NOZBUFFERHW 0x88760154 [fail] + +/* + * Overlay surfaces could not be z layered based on their BltOrder because + * the hardware does not support z layering of overlays. + */ +#define DDERR_NOZOVERLAYHW 0x8876015E [fail] + +/* + * The hardware needed for the requested operation has already been + * allocated. + */ +#define DDERR_OUTOFCAPS 0x88760168 [fail] + +/* + * DirectDraw does not have enough memory to perform the operation. + */ +#define DDERR_OUTOFMEMORY 0x8007000EL [fail] + +/* + * DirectDraw does not have enough memory to perform the operation. + */ +#define DDERR_OUTOFVIDEOMEMORY 0x8876017C [fail] + +/* + * hardware does not support clipped overlays + */ +#define DDERR_OVERLAYCANTCLIP 0x8876017E [fail] + +/* + * Can only have ony color key active at one time for overlays + */ +#define DDERR_OVERLAYCOLORKEYONLYONEACTIVE 0x88760180 [fail] + +/* + * Access to this palette is being refused because the palette is already + * locked by another thread. + */ +#define DDERR_PALETTEBUSY 0x88760183 [fail] + +/* + * No src color key specified for this operation. + */ +#define DDERR_COLORKEYNOTSET 0x88760190 [fail] + +/* + * This surface is already attached to the surface it is being attached to. + */ +#define DDERR_SURFACEALREADYATTACHED 0x8876019A [fail] + +/* + * This surface is already a dependency of the surface it is being made a + * dependency of. + */ +#define DDERR_SURFACEALREADYDEPENDENT 0x887601A4 [fail] + +/* + * Access to this surface is being refused because the surface is already + * locked by another thread. + */ +#define DDERR_SURFACEBUSY 0x887601AE [fail] + +/* + * Access to this surface is being refused because no driver exists + * which can supply a pointer to the surface. + * This is most likely to happen when attempting to lock the primary + * surface when no DCI provider is present. + * Will also happen on attempts to lock an optimized surface. + */ +#define DDERR_CANTLOCKSURFACE 0x887601B3 [fail] + +/* + * Access to Surface refused because Surface is obscured. + */ +#define DDERR_SURFACEISOBSCURED 0x887601B8 [fail] + +/* + * Access to this surface is being refused because the surface is gone. + * The DIRECTDRAWSURFACE object representing this surface should + * have Restore called on it. + */ +#define DDERR_SURFACELOST 0x887601C2 [fail] + +/* + * The requested surface is not attached. + */ +#define DDERR_SURFACENOTATTACHED 0x887601CC [fail] + +/* + * Height requested by DirectDraw is too large. + */ +#define DDERR_TOOBIGHEIGHT 0x887601D6 [fail] + +/* + * Size requested by DirectDraw is too large -- The individual height and + * width are OK. + */ +#define DDERR_TOOBIGSIZE 0x887601E0 [fail] + +/* + * Width requested by DirectDraw is too large. + */ +#define DDERR_TOOBIGWIDTH 0x887601EA [fail] + +/* + * Action not supported. + */ +#define DDERR_UNSUPPORTED 0x80004001L [fail] + +/* + * Pixel format requested is unsupported by DirectDraw + */ +#define DDERR_UNSUPPORTEDFORMAT 0x887601FE [fail] + +/* + * Bitmask in the pixel format requested is unsupported by DirectDraw + */ +#define DDERR_UNSUPPORTEDMASK 0x88760208 [fail] + +/* + * The specified stream contains invalid data + */ +#define DDERR_INVALIDSTREAM 0x88760209 [fail] + +/* + * vertical blank is in progress + */ +#define DDERR_VERTICALBLANKINPROGRESS 0x88760219 [fail] + +/* + * Informs DirectDraw that the previous Blt which is transfering information + * to or from this Surface is incomplete. + */ +#define DDERR_WASSTILLDRAWING 0x8876021C [fail] + + +/* + * The specified surface type requires specification of the COMPLEX flag + */ +#define DDERR_DDSCAPSCOMPLEXREQUIRED 0x8876021E [fail] + + +/* + * Rectangle provided was not horizontally aligned on reqd. boundary + */ +#define DDERR_XALIGN 0x88760230 [fail] + +/* + * The GUID passed to DirectDrawCreate is not a valid DirectDraw driver + * identifier. + */ +#define DDERR_INVALIDDIRECTDRAWGUID 0x88760231 [fail] + +/* + * A DirectDraw object representing this driver has already been created + * for this process. + */ +#define DDERR_DIRECTDRAWALREADYCREATED 0x88760232 [fail] + +/* + * A hardware only DirectDraw object creation was attempted but the driver + * did not support any hardware. + */ +#define DDERR_NODIRECTDRAWHW 0x88760233 [fail] + +/* + * this process already has created a primary surface + */ +#define DDERR_PRIMARYSURFACEALREADYEXISTS 0x88760234 [fail] + +/* + * software emulation not available. + */ +#define DDERR_NOEMULATION 0x88760235 [fail] + +/* + * region passed to Clipper::GetClipList is too small. + */ +#define DDERR_REGIONTOOSMALL 0x88760236 [fail] + +/* + * an attempt was made to set a clip list for a clipper objec that + * is already monitoring an hwnd. + */ +#define DDERR_CLIPPERISUSINGHWND 0x88760237 [fail] + +/* + * No clipper object attached to surface object + */ +#define DDERR_NOCLIPPERATTACHED 0x88760238 [fail] + +/* + * Clipper notification requires an HWND or + * no HWND has previously been set as the CooperativeLevel HWND. + */ +#define DDERR_NOHWND 0x88760239 [fail] + +/* + * HWND used by DirectDraw CooperativeLevel has been subclassed, + * this prevents DirectDraw from restoring state. + */ +#define DDERR_HWNDSUBCLASSED 0x8876023A [fail] + +/* + * The CooperativeLevel HWND has already been set. + * It can not be reset while the process has surfaces or palettes created. + */ +#define DDERR_HWNDALREADYSET 0x8876023B [fail] + +/* + * No palette object attached to this surface. + */ +#define DDERR_NOPALETTEATTACHED 0x8876023C [fail] + +/* + * No hardware support for 16 or 256 color palettes. + */ +#define DDERR_NOPALETTEHW 0x8876023D [fail] + +/* + * If a clipper object is attached to the source surface passed into a + * BltFast call. + */ +#define DDERR_BLTFASTCANTCLIP 0x8876023E [fail] + +/* + * No blter. + */ +#define DDERR_NOBLTHW 0x8876023F [fail] + +/* + * No DirectDraw ROP hardware. + */ +#define DDERR_NODDROPSHW 0x88760240 [fail] + +/* + * returned when GetOverlayPosition is called on a hidden overlay + */ +#define DDERR_OVERLAYNOTVISIBLE 0x88760241 [fail] + +/* + * returned when GetOverlayPosition is called on a overlay that UpdateOverlay + * has never been called on to establish a destionation. + */ +#define DDERR_NOOVERLAYDEST 0x88760242 [fail] + +/* + * returned when the position of the overlay on the destionation is no longer + * legal for that destionation. + */ +#define DDERR_INVALIDPOSITION 0x88760243 [fail] + +/* + * returned when an overlay member is called for a non-overlay surface + */ +#define DDERR_NOTAOVERLAYSURFACE 0x88760244 [fail] + +/* + * An attempt was made to set the cooperative level when it was already + * set to exclusive. + */ +#define DDERR_EXCLUSIVEMODEALREADYSET 0x88760245 [fail] + +/* + * An attempt has been made to flip a surface that is not flippable. + */ +#define DDERR_NOTFLIPPABLE 0x88760246 [fail] + +/* + * Can't duplicate primary & 3D surfaces, or surfaces that are implicitly + * created. + */ +#define DDERR_CANTDUPLICATE 0x88760247 [fail] + +/* + * Surface was not locked. An attempt to unlock a surface that was not + * locked at all, or by this process, has been attempted. + */ +#define DDERR_NOTLOCKED 0x88760248 [fail] + +/* + * Windows can not create any more DCs, or a DC was requested for a paltte-indexed + * surface when the surface had no palette AND the display mode was not palette-indexed + * (in this case DirectDraw cannot select a proper palette into the DC) + */ +#define DDERR_CANTCREATEDC 0x88760249 [fail] + +/* + * No DC was ever created for this surface. + */ +#define DDERR_NODC 0x8876024A [fail] + +/* + * This surface can not be restored because it was created in a different + * mode. + */ +#define DDERR_WRONGMODE 0x8876024B [fail] + +/* + * This surface can not be restored because it is an implicitly created + * surface. + */ +#define DDERR_IMPLICITLYCREATED 0x8876024C [fail] + +/* + * The surface being used is not a palette-based surface + */ +#define DDERR_NOTPALETTIZED 0x8876024D [fail] + + +/* + * The display is currently in an unsupported mode + */ +#define DDERR_UNSUPPORTEDMODE 0x8876024E [fail] + +/* + * Operation could not be carried out because there is no mip-map + * texture mapping hardware present or available. + */ +#define DDERR_NOMIPMAPHW 0x8876024F [fail] + +/* + * The requested action could not be performed because the surface was of + * the wrong type. + */ +#define DDERR_INVALIDSURFACETYPE 0x88760250 [fail] + + +/* + * Device does not support optimized surfaces, therefore no video memory optimized surfaces + */ +#define DDERR_NOOPTIMIZEHW 0x88760258 [fail] + +/* + * Surface is an optimized surface, but has not yet been allocated any memory + */ +#define DDERR_NOTLOADED 0x88760259 [fail] + +/* + * Attempt was made to create or set a device window without first setting + * the focus window + */ +#define DDERR_NOFOCUSWINDOW 0x8876025A [fail] + +/* + * Attempt was made to set a palette on a mipmap sublevel + */ +#define DDERR_NOTONMIPMAPSUBLEVEL 0x8876025B [fail] + +/* + * A DC has already been returned for this surface. Only one DC can be + * retrieved per surface. + */ +#define DDERR_DCALREADYCREATED 0x8876026C [fail] + +/* + * An attempt was made to allocate non-local video memory from a device + * that does not support non-local video memory. + */ +#define DDERR_NONONLOCALVIDMEM 0x88760276 [fail] + +/* + * The attempt to page lock a surface failed. + */ +#define DDERR_CANTPAGELOCK 0x88760280 [fail] + + +/* + * The attempt to page unlock a surface failed. + */ +#define DDERR_CANTPAGEUNLOCK 0x88760294 [fail] + +/* + * An attempt was made to page unlock a surface with no outstanding page locks. + */ +#define DDERR_NOTPAGELOCKED 0x887602A8 [fail] + +/* + * There is more data available than the specified buffer size could hold + */ +#define DDERR_MOREDATA 0x887602B2 [fail] + +/* + * The data has expired and is therefore no longer valid. + */ +#define DDERR_EXPIRED 0x887602B3 [fail] + +/* + * The mode test has finished executing. + */ +#define DDERR_TESTFINISHED 0x887602B4 [fail] + +/* + * The mode test has switched to a new mode. + */ +#define DDERR_NEWMODE 0x887602B5 [fail] + +/* + * D3D has not yet been initialized. + */ +#define DDERR_D3DNOTINITIALIZED 0x887602B6 [fail] + +/* + * The video port is not active + */ +#define DDERR_VIDEONOTACTIVE 0x887602B7 [fail] + +/* + * The monitor does not have EDID data. + */ +#define DDERR_NOMONITORINFORMATION 0x887602B8 [fail] + +/* + * The driver does not enumerate display mode refresh rates. + */ +#define DDERR_NODRIVERSUPPORT 0x887602B9 [fail] + +/* + * Surfaces created by one direct draw device cannot be used directly by + * another direct draw device. + */ +#define DDERR_DEVICEDOESNTOWNSURFACE 0x887602BB [fail] + + + +/* + * An attempt was made to invoke an interface member of a DirectDraw object + * created by CoCreateInstance() before it was initialized. + */ +#define DDERR_NOTINITIALIZED 0x800401F0L [fail] + +}; + +typedef struct _DDARGB +{ + BYTE blue; + BYTE green; + BYTE red; + BYTE alpha; +} DDARGB; + +typedef DDARGB *LPDDARGB; + +/* + * This version of the structure remains for backwards source compatibility. + * The DDARGB structure is the one that should be used for all DirectDraw APIs. + */ +typedef struct _DDRGBA +{ + BYTE red; + BYTE green; + BYTE blue; + BYTE alpha; +} DDRGBA; + +typedef DDRGBA *LPDDRGBA; + + +/* + * DDCOLORKEY + */ +typedef struct _DDCOLORKEY +{ + DWORD dwColorSpaceLowValue; // low boundary of color space that is to + // be treated as Color Key, inclusive + DWORD dwColorSpaceHighValue; // high boundary of color space that is + // to be treated as Color Key, inclusive +} DDCOLORKEY; + +typedef DDCOLORKEY * LPDDCOLORKEY; + +/* + * DDBLTFX + * Used to pass override information to the DIRECTDRAWSURFACE callback Blt. + */ +typedef struct _DDBLTFX +{ + DWORD dwSize; // size of structure + DWORD dwDDFX; // FX operations + DWORD dwROP; // Win32 raster operations + DWORD dwDDROP; // Raster operations new for DirectDraw + DWORD dwRotationAngle; // Rotation angle for blt + DWORD dwZBufferOpCode; // ZBuffer compares + DWORD dwZBufferLow; // Low limit of Z buffer + DWORD dwZBufferHigh; // High limit of Z buffer + DWORD dwZBufferBaseDest; // Destination base value + DWORD dwZDestConstBitDepth; // Bit depth used to specify Z constant for destination + DWORD dwZDestConst; // Constant to use as Z buffer for dest + DWORD dwZSrcConstBitDepth; // Bit depth used to specify Z constant for source + DWORD dwZSrcConst; // Constant to use as Z buffer for src + DWORD dwAlphaEdgeBlendBitDepth; // Bit depth used to specify constant for alpha edge blend + DWORD dwAlphaEdgeBlend; // Alpha for edge blending + DWORD dwReserved; + DWORD dwAlphaDestConstBitDepth; // Bit depth used to specify alpha constant for destination + DWORD dwAlphaDestConst; // Constant to use as Alpha Channel + DWORD dwAlphaSrcConstBitDepth; // Bit depth used to specify alpha constant for source + DWORD dwAlphaSrcConst; // Constant to use as Alpha Channel + DWORD dwFillColorDepthPixel; // color in RGB or Palettized + DDCOLORKEY ddckDestColorkey; // DestColorkey override + DDCOLORKEY ddckSrcColorkey; // SrcColorkey override +} DDBLTFX; + +typedef DDBLTFX * LPDDBLTFX; + +typedef LPVOID LPDDCAPS; + +/* + * DDSCAPS + */ +typedef struct _DDSCAPS +{ + DWORD dwCaps; // capabilities of surface wanted +} DDSCAPS; + +typedef DDSCAPS * LPDDSCAPS; + + +/* + * DDOSCAPS + */ +typedef struct _DDOSCAPS +{ + DWORD dwCaps; // capabilities of surface wanted +} DDOSCAPS; + +typedef DDOSCAPS * LPDDOSCAPS; + +/* + * This structure is used internally by DirectDraw. + */ +typedef struct _DDSCAPSEX +{ + DWORD dwCaps2; + DWORD dwCaps3; + DWORD dwCaps4; +} DDSCAPSEX, * LPDDSCAPSEX; + +/* + * DDSCAPS2 + */ +typedef struct _DDSCAPS2 +{ + DWORD dwCaps; // capabilities of surface wanted + DWORD dwCaps2; + DWORD dwCaps3; + DWORD dwCaps4; +} DDSCAPS2; + +typedef DDSCAPS2 * LPDDSCAPS2; + +typedef struct _DDCAPS_DX1 +{ + DWORD dwSize; // size of the DDDRIVERCAPS structure + DWORD dwCaps; // driver specific capabilities + DWORD dwCaps2; // more driver specific capabilites + DWORD dwCKeyCaps; // color key capabilities of the surface + DWORD dwFXCaps; // driver specific stretching and effects capabilites + DWORD dwFXAlphaCaps; // alpha driver specific capabilities + DWORD dwPalCaps; // palette capabilities + DWORD dwSVCaps; // stereo vision capabilities + DWORD dwAlphaBltConstBitDepths; // DDBD_2,4,8 + DWORD dwAlphaBltPixelBitDepths; // DDBD_1,2,4,8 + DWORD dwAlphaBltSurfaceBitDepths; // DDBD_1,2,4,8 + DWORD dwAlphaOverlayConstBitDepths; // DDBD_2,4,8 + DWORD dwAlphaOverlayPixelBitDepths; // DDBD_1,2,4,8 + DWORD dwAlphaOverlaySurfaceBitDepths; // DDBD_1,2,4,8 + DWORD dwZBufferBitDepths; // DDBD_8,16,24,32 + DWORD dwVidMemTotal; // total amount of video memory + DWORD dwVidMemFree; // amount of free video memory + DWORD dwMaxVisibleOverlays; // maximum number of visible overlays + DWORD dwCurrVisibleOverlays; // current number of visible overlays + DWORD dwNumFourCCCodes; // number of four cc codes + DWORD dwAlignBoundarySrc; // source rectangle alignment + DWORD dwAlignSizeSrc; // source rectangle byte size + DWORD dwAlignBoundaryDest; // dest rectangle alignment + DWORD dwAlignSizeDest; // dest rectangle byte size + DWORD dwAlignStrideAlign; // stride alignment + DWORD dwRops[8]; // ROPS supported + DDSCAPS ddsCaps; // DDSCAPS structure has all the general capabilities + DWORD dwMinOverlayStretch; // minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMaxOverlayStretch; // maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMinLiveVideoStretch; // OBSOLETE! This field remains for compatability reasons only + DWORD dwMaxLiveVideoStretch; // OBSOLETE! This field remains for compatability reasons only + DWORD dwMinHwCodecStretch; // OBSOLETE! This field remains for compatability reasons only + DWORD dwMaxHwCodecStretch; // OBSOLETE! This field remains for compatability reasons only + DWORD dwReserved1; // reserved + DWORD dwReserved2; // reserved + DWORD dwReserved3; // reserved +} DDCAPS_DX1, *LPDDCAPS_DX1; + +/* + * This structure is the DDCAPS structure as it was in version 2 and 3 of Direct X. + * It is present for back compatability. + */ +typedef struct _DDCAPS_DX3 +{ + DWORD dwSize; // size of the DDDRIVERCAPS structure + DWORD dwCaps; // driver specific capabilities + DWORD dwCaps2; // more driver specific capabilites + DWORD dwCKeyCaps; // color key capabilities of the surface + DWORD dwFXCaps; // driver specific stretching and effects capabilites + DWORD dwFXAlphaCaps; // alpha driver specific capabilities + DWORD dwPalCaps; // palette capabilities + DWORD dwSVCaps; // stereo vision capabilities + DWORD dwAlphaBltConstBitDepths; // DDBD_2,4,8 + DWORD dwAlphaBltPixelBitDepths; // DDBD_1,2,4,8 + DWORD dwAlphaBltSurfaceBitDepths; // DDBD_1,2,4,8 + DWORD dwAlphaOverlayConstBitDepths; // DDBD_2,4,8 + DWORD dwAlphaOverlayPixelBitDepths; // DDBD_1,2,4,8 + DWORD dwAlphaOverlaySurfaceBitDepths; // DDBD_1,2,4,8 + DWORD dwZBufferBitDepths; // DDBD_8,16,24,32 + DWORD dwVidMemTotal; // total amount of video memory + DWORD dwVidMemFree; // amount of free video memory + DWORD dwMaxVisibleOverlays; // maximum number of visible overlays + DWORD dwCurrVisibleOverlays; // current number of visible overlays + DWORD dwNumFourCCCodes; // number of four cc codes + DWORD dwAlignBoundarySrc; // source rectangle alignment + DWORD dwAlignSizeSrc; // source rectangle byte size + DWORD dwAlignBoundaryDest; // dest rectangle alignment + DWORD dwAlignSizeDest; // dest rectangle byte size + DWORD dwAlignStrideAlign; // stride alignment + DWORD dwRops[8]; // ROPS supported + DDSCAPS ddsCaps; // DDSCAPS structure has all the general capabilities + DWORD dwMinOverlayStretch; // minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMaxOverlayStretch; // maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMinLiveVideoStretch; // minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMaxLiveVideoStretch; // maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMinHwCodecStretch; // minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwMaxHwCodecStretch; // maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 + DWORD dwReserved1; // reserved + DWORD dwReserved2; // reserved + DWORD dwReserved3; // reserved + DWORD dwSVBCaps; // driver specific capabilities for System->Vmem blts + DWORD dwSVBCKeyCaps; // driver color key capabilities for System->Vmem blts + DWORD dwSVBFXCaps; // driver FX capabilities for System->Vmem blts + DWORD dwSVBRops[8];// ROPS supported for System->Vmem blts + DWORD dwVSBCaps; // driver specific capabilities for Vmem->System blts + DWORD dwVSBCKeyCaps; // driver color key capabilities for Vmem->System blts + DWORD dwVSBFXCaps; // driver FX capabilities for Vmem->System blts + DWORD dwVSBRops[8];// ROPS supported for Vmem->System blts + DWORD dwSSBCaps; // driver specific capabilities for System->System blts + DWORD dwSSBCKeyCaps; // driver color key capabilities for System->System blts + DWORD dwSSBFXCaps; // driver FX capabilities for System->System blts + DWORD dwSSBRops[8];// ROPS supported for System->System blts + DWORD dwReserved4; // reserved + DWORD dwReserved5; // reserved + DWORD dwReserved6; // reserved +} DDCAPS_DX3, *LPDDCAPS_DX3; + +/* + * This structure is the DDCAPS structure as it was in version 5 of Direct X. + * It is present for back compatability. + */ +typedef struct _DDCAPS_DX5 +{ +/* 0*/ DWORD dwSize; // size of the DDDRIVERCAPS structure +/* 4*/ DWORD dwCaps; // driver specific capabilities +/* 8*/ DWORD dwCaps2; // more driver specific capabilites +/* c*/ DWORD dwCKeyCaps; // color key capabilities of the surface +/* 10*/ DWORD dwFXCaps; // driver specific stretching and effects capabilites +/* 14*/ DWORD dwFXAlphaCaps; // alpha driver specific capabilities +/* 18*/ DWORD dwPalCaps; // palette capabilities +/* 1c*/ DWORD dwSVCaps; // stereo vision capabilities +/* 20*/ DWORD dwAlphaBltConstBitDepths; // DDBD_2,4,8 +/* 24*/ DWORD dwAlphaBltPixelBitDepths; // DDBD_1,2,4,8 +/* 28*/ DWORD dwAlphaBltSurfaceBitDepths; // DDBD_1,2,4,8 +/* 2c*/ DWORD dwAlphaOverlayConstBitDepths; // DDBD_2,4,8 +/* 30*/ DWORD dwAlphaOverlayPixelBitDepths; // DDBD_1,2,4,8 +/* 34*/ DWORD dwAlphaOverlaySurfaceBitDepths; // DDBD_1,2,4,8 +/* 38*/ DWORD dwZBufferBitDepths; // DDBD_8,16,24,32 +/* 3c*/ DWORD dwVidMemTotal; // total amount of video memory +/* 40*/ DWORD dwVidMemFree; // amount of free video memory +/* 44*/ DWORD dwMaxVisibleOverlays; // maximum number of visible overlays +/* 48*/ DWORD dwCurrVisibleOverlays; // current number of visible overlays +/* 4c*/ DWORD dwNumFourCCCodes; // number of four cc codes +/* 50*/ DWORD dwAlignBoundarySrc; // source rectangle alignment +/* 54*/ DWORD dwAlignSizeSrc; // source rectangle byte size +/* 58*/ DWORD dwAlignBoundaryDest; // dest rectangle alignment +/* 5c*/ DWORD dwAlignSizeDest; // dest rectangle byte size +/* 60*/ DWORD dwAlignStrideAlign; // stride alignment +/* 64*/ DWORD dwRops[8]; // ROPS supported +/* 84*/ DDSCAPS ddsCaps; // DDSCAPS structure has all the general capabilities +/* 88*/ DWORD dwMinOverlayStretch; // minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 8c*/ DWORD dwMaxOverlayStretch; // maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 90*/ DWORD dwMinLiveVideoStretch; // minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 94*/ DWORD dwMaxLiveVideoStretch; // maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 98*/ DWORD dwMinHwCodecStretch; // minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 9c*/ DWORD dwMaxHwCodecStretch; // maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* a0*/ DWORD dwReserved1; // reserved +/* a4*/ DWORD dwReserved2; // reserved +/* a8*/ DWORD dwReserved3; // reserved +/* ac*/ DWORD dwSVBCaps; // driver specific capabilities for System->Vmem blts +/* b0*/ DWORD dwSVBCKeyCaps; // driver color key capabilities for System->Vmem blts +/* b4*/ DWORD dwSVBFXCaps; // driver FX capabilities for System->Vmem blts +/* b8*/ DWORD dwSVBRops[8];// ROPS supported for System->Vmem blts +/* d8*/ DWORD dwVSBCaps; // driver specific capabilities for Vmem->System blts +/* dc*/ DWORD dwVSBCKeyCaps; // driver color key capabilities for Vmem->System blts +/* e0*/ DWORD dwVSBFXCaps; // driver FX capabilities for Vmem->System blts +/* e4*/ DWORD dwVSBRops[8];// ROPS supported for Vmem->System blts +/*104*/ DWORD dwSSBCaps; // driver specific capabilities for System->System blts +/*108*/ DWORD dwSSBCKeyCaps; // driver color key capabilities for System->System blts +/*10c*/ DWORD dwSSBFXCaps; // driver FX capabilities for System->System blts +/*110*/ DWORD dwSSBRops[8];// ROPS supported for System->System blts +// Members added for DX5: +/*130*/ DWORD dwMaxVideoPorts; // maximum number of usable video ports +/*134*/ DWORD dwCurrVideoPorts; // current number of video ports used +/*138*/ DWORD dwSVBCaps2; // more driver specific capabilities for System->Vmem blts +/*13c*/ DWORD dwNLVBCaps; // driver specific capabilities for non-local->local vidmem blts +/*140*/ DWORD dwNLVBCaps2; // more driver specific capabilities non-local->local vidmem blts +/*144*/ DWORD dwNLVBCKeyCaps; // driver color key capabilities for non-local->local vidmem blts +/*148*/ DWORD dwNLVBFXCaps; // driver FX capabilities for non-local->local blts +/*14c*/ DWORD dwNLVBRops[8]; // ROPS supported for non-local->local blts +} DDCAPS_DX5, *LPDDCAPS_DX5; + +typedef struct _DDCAPS_DX6 +{ +/* 0*/ DWORD dwSize; // size of the DDDRIVERCAPS structure +/* 4*/ DWORD dwCaps; // driver specific capabilities +/* 8*/ DWORD dwCaps2; // more driver specific capabilites +/* c*/ DWORD dwCKeyCaps; // color key capabilities of the surface +/* 10*/ DWORD dwFXCaps; // driver specific stretching and effects capabilites +/* 14*/ DWORD dwFXAlphaCaps; // alpha caps +/* 18*/ DWORD dwPalCaps; // palette capabilities +/* 1c*/ DWORD dwSVCaps; // stereo vision capabilities +/* 20*/ DWORD dwAlphaBltConstBitDepths; // DDBD_2,4,8 +/* 24*/ DWORD dwAlphaBltPixelBitDepths; // DDBD_1,2,4,8 +/* 28*/ DWORD dwAlphaBltSurfaceBitDepths; // DDBD_1,2,4,8 +/* 2c*/ DWORD dwAlphaOverlayConstBitDepths; // DDBD_2,4,8 +/* 30*/ DWORD dwAlphaOverlayPixelBitDepths; // DDBD_1,2,4,8 +/* 34*/ DWORD dwAlphaOverlaySurfaceBitDepths; // DDBD_1,2,4,8 +/* 38*/ DWORD dwZBufferBitDepths; // DDBD_8,16,24,32 +/* 3c*/ DWORD dwVidMemTotal; // total amount of video memory +/* 40*/ DWORD dwVidMemFree; // amount of free video memory +/* 44*/ DWORD dwMaxVisibleOverlays; // maximum number of visible overlays +/* 48*/ DWORD dwCurrVisibleOverlays; // current number of visible overlays +/* 4c*/ DWORD dwNumFourCCCodes; // number of four cc codes +/* 50*/ DWORD dwAlignBoundarySrc; // source rectangle alignment +/* 54*/ DWORD dwAlignSizeSrc; // source rectangle byte size +/* 58*/ DWORD dwAlignBoundaryDest; // dest rectangle alignment +/* 5c*/ DWORD dwAlignSizeDest; // dest rectangle byte size +/* 60*/ DWORD dwAlignStrideAlign; // stride alignment +/* 64*/ DWORD dwRops[8]; // ROPS supported +/* 84*/ DDSCAPS ddsOldCaps; // Was DDSCAPS ddsCaps. ddsCaps is of type DDSCAPS2 for DX6 +/* 88*/ DWORD dwMinOverlayStretch; // minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 8c*/ DWORD dwMaxOverlayStretch; // maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 90*/ DWORD dwMinLiveVideoStretch; // minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 94*/ DWORD dwMaxLiveVideoStretch; // maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 98*/ DWORD dwMinHwCodecStretch; // minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 9c*/ DWORD dwMaxHwCodecStretch; // maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* a0*/ DWORD dwReserved1; // reserved +/* a4*/ DWORD dwReserved2; // reserved +/* a8*/ DWORD dwReserved3; // reserved +/* ac*/ DWORD dwSVBCaps; // driver specific capabilities for System->Vmem blts +/* b0*/ DWORD dwSVBCKeyCaps; // driver color key capabilities for System->Vmem blts +/* b4*/ DWORD dwSVBFXCaps; // driver FX capabilities for System->Vmem blts +/* b8*/ DWORD dwSVBRops[8];// ROPS supported for System->Vmem blts +/* d8*/ DWORD dwVSBCaps; // driver specific capabilities for Vmem->System blts +/* dc*/ DWORD dwVSBCKeyCaps; // driver color key capabilities for Vmem->System blts +/* e0*/ DWORD dwVSBFXCaps; // driver FX capabilities for Vmem->System blts +/* e4*/ DWORD dwVSBRops[8];// ROPS supported for Vmem->System blts +/*104*/ DWORD dwSSBCaps; // driver specific capabilities for System->System blts +/*108*/ DWORD dwSSBCKeyCaps; // driver color key capabilities for System->System blts +/*10c*/ DWORD dwSSBFXCaps; // driver FX capabilities for System->System blts +/*110*/ DWORD dwSSBRops[8];// ROPS supported for System->System blts +/*130*/ DWORD dwMaxVideoPorts; // maximum number of usable video ports +/*134*/ DWORD dwCurrVideoPorts; // current number of video ports used +/*138*/ DWORD dwSVBCaps2; // more driver specific capabilities for System->Vmem blts +/*13c*/ DWORD dwNLVBCaps; // driver specific capabilities for non-local->local vidmem blts +/*140*/ DWORD dwNLVBCaps2; // more driver specific capabilities non-local->local vidmem blts +/*144*/ DWORD dwNLVBCKeyCaps; // driver color key capabilities for non-local->local vidmem blts +/*148*/ DWORD dwNLVBFXCaps; // driver FX capabilities for non-local->local blts +/*14c*/ DWORD dwNLVBRops[8]; // ROPS supported for non-local->local blts +// Members added for DX6 release +/*16c*/ DDSCAPS2 ddsCaps; // Surface Caps +} DDCAPS_DX6, * LPDDCAPS_DX6; + +typedef struct _DDCAPS_DX7 +{ +/* 0*/ DWORD dwSize; // size of the DDDRIVERCAPS structure +/* 4*/ DWORD dwCaps; // driver specific capabilities +/* 8*/ DWORD dwCaps2; // more driver specific capabilites +/* c*/ DWORD dwCKeyCaps; // color key capabilities of the surface +/* 10*/ DWORD dwFXCaps; // driver specific stretching and effects capabilites +/* 14*/ DWORD dwFXAlphaCaps; // alpha driver specific capabilities +/* 18*/ DWORD dwPalCaps; // palette capabilities +/* 1c*/ DWORD dwSVCaps; // stereo vision capabilities +/* 20*/ DWORD dwAlphaBltConstBitDepths; // DDBD_2,4,8 +/* 24*/ DWORD dwAlphaBltPixelBitDepths; // DDBD_1,2,4,8 +/* 28*/ DWORD dwAlphaBltSurfaceBitDepths; // DDBD_1,2,4,8 +/* 2c*/ DWORD dwAlphaOverlayConstBitDepths; // DDBD_2,4,8 +/* 30*/ DWORD dwAlphaOverlayPixelBitDepths; // DDBD_1,2,4,8 +/* 34*/ DWORD dwAlphaOverlaySurfaceBitDepths; // DDBD_1,2,4,8 +/* 38*/ DWORD dwZBufferBitDepths; // DDBD_8,16,24,32 +/* 3c*/ DWORD dwVidMemTotal; // total amount of video memory +/* 40*/ DWORD dwVidMemFree; // amount of free video memory +/* 44*/ DWORD dwMaxVisibleOverlays; // maximum number of visible overlays +/* 48*/ DWORD dwCurrVisibleOverlays; // current number of visible overlays +/* 4c*/ DWORD dwNumFourCCCodes; // number of four cc codes +/* 50*/ DWORD dwAlignBoundarySrc; // source rectangle alignment +/* 54*/ DWORD dwAlignSizeSrc; // source rectangle byte size +/* 58*/ DWORD dwAlignBoundaryDest; // dest rectangle alignment +/* 5c*/ DWORD dwAlignSizeDest; // dest rectangle byte size +/* 60*/ DWORD dwAlignStrideAlign; // stride alignment +/* 64*/ DWORD dwRops[8]; // ROPS supported +/* 84*/ DDSCAPS ddsOldCaps; // Was DDSCAPS ddsCaps. ddsCaps is of type DDSCAPS2 for DX6 +/* 88*/ DWORD dwMinOverlayStretch; // minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 8c*/ DWORD dwMaxOverlayStretch; // maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 90*/ DWORD dwMinLiveVideoStretch; // minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 94*/ DWORD dwMaxLiveVideoStretch; // maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 98*/ DWORD dwMinHwCodecStretch; // minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* 9c*/ DWORD dwMaxHwCodecStretch; // maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 +/* a0*/ DWORD dwReserved1; // reserved +/* a4*/ DWORD dwReserved2; // reserved +/* a8*/ DWORD dwReserved3; // reserved +/* ac*/ DWORD dwSVBCaps; // driver specific capabilities for System->Vmem blts +/* b0*/ DWORD dwSVBCKeyCaps; // driver color key capabilities for System->Vmem blts +/* b4*/ DWORD dwSVBFXCaps; // driver FX capabilities for System->Vmem blts +/* b8*/ DWORD dwSVBRops[8];// ROPS supported for System->Vmem blts +/* d8*/ DWORD dwVSBCaps; // driver specific capabilities for Vmem->System blts +/* dc*/ DWORD dwVSBCKeyCaps; // driver color key capabilities for Vmem->System blts +/* e0*/ DWORD dwVSBFXCaps; // driver FX capabilities for Vmem->System blts +/* e4*/ DWORD dwVSBRops[8];// ROPS supported for Vmem->System blts +/*104*/ DWORD dwSSBCaps; // driver specific capabilities for System->System blts +/*108*/ DWORD dwSSBCKeyCaps; // driver color key capabilities for System->System blts +/*10c*/ DWORD dwSSBFXCaps; // driver FX capabilities for System->System blts +/*110*/ DWORD dwSSBRops[8];// ROPS supported for System->System blts +/*130*/ DWORD dwMaxVideoPorts; // maximum number of usable video ports +/*134*/ DWORD dwCurrVideoPorts; // current number of video ports used +/*138*/ DWORD dwSVBCaps2; // more driver specific capabilities for System->Vmem blts +/*13c*/ DWORD dwNLVBCaps; // driver specific capabilities for non-local->local vidmem blts +/*140*/ DWORD dwNLVBCaps2; // more driver specific capabilities non-local->local vidmem blts +/*144*/ DWORD dwNLVBCKeyCaps; // driver color key capabilities for non-local->local vidmem blts +/*148*/ DWORD dwNLVBFXCaps; // driver FX capabilities for non-local->local blts +/*14c*/ DWORD dwNLVBRops[8]; // ROPS supported for non-local->local blts +// Members added for DX6 release +/*16c*/ DDSCAPS2 ddsCaps; // Surface Caps +} DDCAPS_DX7, * LPDDCAPS_DX7; + +/* + * DDPIXELFORMAT + */ +typedef struct _DDPIXELFORMAT +{ + DWORD dwSize; // size of structure + DWORD dwFlags; // pixel format flags + DWORD dwFourCC; // (FOURCC code) + DWORD dwRGBBitCountOrDepth; // how many bits per pixel + DWORD dwRBitMask; // mask for red bit + DWORD dwGBitMask; // mask for green bits + DWORD dwBBitMask; // mask for blue bits + DWORD dwRGBAlphaBitMask; // mask for alpha channel +} DDPIXELFORMAT; + +typedef DDPIXELFORMAT * LPDDPIXELFORMAT; + +/* + * DDOVERLAYFX + */ +typedef struct _DDOVERLAYFX +{ + DWORD dwSize; // size of structure + DWORD dwAlphaEdgeBlendBitDepth; // Bit depth used to specify constant for alpha edge blend + DWORD dwAlphaEdgeBlend; // Constant to use as alpha for edge blend + DWORD dwReserved; + DWORD dwAlphaDestConstBitDepth; // Bit depth used to specify alpha constant for destination + DWORD dwAlphaDestConst; // Constant to use as alpha channel for dest + DWORD dwAlphaSrcConstBitDepth; // Bit depth used to specify alpha constant for source + DWORD dwAlphaSrcConst; // Constant to use as alpha channel for src + DDCOLORKEY dckDestColorkey; // DestColorkey override + DDCOLORKEY dckSrcColorkey; // DestColorkey override + DWORD dwDDFX; // Overlay FX + DWORD dwFlags; // flags +} DDOVERLAYFX; + +typedef DDOVERLAYFX *LPDDOVERLAYFX; + + +/* + * DDBLTBATCH: BltBatch entry structure + */ +typedef struct _DDBLTBATCH +{ + LPRECT lprDest; + LPDIRECTDRAWSURFACE lpDDSSrc; + LPRECT lprSrc; + DWORD dwFlags; + LPDDBLTFX lpDDBltFx; +} DDBLTBATCH; + +typedef DDBLTBATCH * LPDDBLTBATCH; + + +/* + * DDGAMMARAMP + */ +typedef struct _DDGAMMARAMP +{ + WORD red[256]; + WORD green[256]; + WORD blue[256]; +} DDGAMMARAMP; +typedef DDGAMMARAMP * LPDDGAMMARAMP; + +/* + * This is the structure within which DirectDraw returns data about the current graphics driver and chipset + */ + +typedef struct tagDDDEVICEIDENTIFIER +{ + /* + * These elements are for presentation to the user only. They should not be used to identify particular + * drivers, since this is unreliable and many different strings may be associated with the same + * device, and the same driver from different vendors. + */ + char szDriver[512]; + char szDescription[512]; + + /* + * This element is the version of the DirectDraw/3D driver. It is legal to do <, > comparisons + * on the whole 64 bits. Caution should be exercised if you use this element to identify problematic + * drivers. It is recommended that guidDeviceIdentifier is used for this purpose. + * + * This version has the form: + * wProduct = HIWORD(liDriverVersion.HighPart) + * wVersion = LOWORD(liDriverVersion.HighPart) + * wSubVersion = HIWORD(liDriverVersion.LowPart) + * wBuild = LOWORD(liDriverVersion.LowPart) + */ + LARGE_INTEGER liDriverVersion; /* Defined for applications and other 32 bit components */ + + + /* + * These elements can be used to identify particular chipsets. Use with extreme caution. + * dwVendorId Identifies the manufacturer. May be zero if unknown. + * dwDeviceId Identifies the type of chipset. May be zero if unknown. + * dwSubSysId Identifies the subsystem, typically this means the particular board. May be zero if unknown. + * dwRevision Identifies the revision level of the chipset. May be zero if unknown. + */ + DWORD dwVendorId; + DWORD dwDeviceId; + DWORD dwSubSysId; + DWORD dwRevision; + + /* + * This element can be used to check changes in driver/chipset. This GUID is a unique identifier for the + * driver/chipset pair. Use this element if you wish to track changes to the driver/chipset in order to + * reprofile the graphics subsystem. + * This element can also be used to identify particular problematic drivers. + */ + GUID guidDeviceIdentifier; +} DDDEVICEIDENTIFIER, * LPDDDEVICEIDENTIFIER; + +typedef struct tagDDDEVICEIDENTIFIER2 +{ + /* + * These elements are for presentation to the user only. They should not be used to identify particular + * drivers, since this is unreliable and many different strings may be associated with the same + * device, and the same driver from different vendors. + */ + char szDriver[512]; + char szDescription[512]; + + /* + * This element is the version of the DirectDraw/3D driver. It is legal to do <, > comparisons + * on the whole 64 bits. Caution should be exercised if you use this element to identify problematic + * drivers. It is recommended that guidDeviceIdentifier is used for this purpose. + * + * This version has the form: + * wProduct = HIWORD(liDriverVersion.HighPart) + * wVersion = LOWORD(liDriverVersion.HighPart) + * wSubVersion = HIWORD(liDriverVersion.LowPart) + * wBuild = LOWORD(liDriverVersion.LowPart) + */ + LARGE_INTEGER liDriverVersion; /* Defined for applications and other 32 bit components */ + + + /* + * These elements can be used to identify particular chipsets. Use with extreme caution. + * dwVendorId Identifies the manufacturer. May be zero if unknown. + * dwDeviceId Identifies the type of chipset. May be zero if unknown. + * dwSubSysId Identifies the subsystem, typically this means the particular board. May be zero if unknown. + * dwRevision Identifies the revision level of the chipset. May be zero if unknown. + */ + DWORD dwVendorId; + DWORD dwDeviceId; + DWORD dwSubSysId; + DWORD dwRevision; + + /* + * This element can be used to check changes in driver/chipset. This GUID is a unique identifier for the + * driver/chipset pair. Use this element if you wish to track changes to the driver/chipset in order to + * reprofile the graphics subsystem. + * This element can also be used to identify particular problematic drivers. + */ + GUID guidDeviceIdentifier; + + /* + * This element is used to determine the Windows Hardware Quality Lab (WHQL) + * certification level for this driver/device pair. + */ + DWORD dwWHQLLevel; + +} DDDEVICEIDENTIFIER2, * LPDDDEVICEIDENTIFIER2; + +/* + * Flags for the IDirectDraw4::GetDeviceIdentifier method + */ + +/* + * DDSURFACEDESC + */ +typedef struct _DDSURFACEDESC +{ + DWORD dwSize; // size of the DDSURFACEDESC structure + DWORD dwFlags; // determines what fields are valid + DWORD dwHeight; // height of surface to be created + DWORD dwWidth; // width of input surface + LONG lPitch; // distance to start of next line (return value only) + DWORD dwBackBufferCount; // number of back buffers requested + DWORD dwMipMapCount; // number of mip-map levels requested + DWORD dwAlphaBitDepth; // depth of alpha buffer requested + DWORD dwReserved; // reserved + LPVOID lpSurface; // pointer to the associated surface memory + DDCOLORKEY ddckCKDestOverlay; // color key for destination overlay use + DDCOLORKEY ddckCKDestBlt; // color key for destination blt use + DDCOLORKEY ddckCKSrcOverlay; // color key for source overlay use + DDCOLORKEY ddckCKSrcBlt; // color key for source blt use + DDPIXELFORMAT ddpfPixelFormat; // pixel format description of the surface + DDSCAPS ddsCaps; // direct draw surface capabilities +} DDSURFACEDESC; + +/* + * DDSURFACEDESC2 + */ +typedef struct _DDSURFACEDESC2 +{ + DWORD dwSize; // size of the DDSURFACEDESC structure + DWORD dwFlags; // determines what fields are valid + DWORD dwHeight; // height of surface to be created + DWORD dwWidth; // width of input surface + LONG lPitch; // distance to start of next line (return value only) + DWORD dwBackBufferCount; // number of back buffers requested + DWORD dwMipMapCount; // number of mip-map levels requestde + DWORD dwAlphaBitDepth; // depth of alpha buffer requested + DWORD dwReserved; // reserved + LPVOID lpSurface; // pointer to the associated surface memory + DDCOLORKEY ddckCKDestOverlay; // color key for destination overlay use + DDCOLORKEY ddckCKDestBlt; // color key for destination blt use + DDCOLORKEY ddckCKSrcOverlay; // color key for source overlay use + DDCOLORKEY ddckCKSrcBlt; // color key for source blt use + DDPIXELFORMAT ddpfPixelFormat; // pixel format description of the surface + DDSCAPS2 ddsCaps; // direct draw surface capabilities + DWORD dwTextureStage; // stage in multitexture cascade +} DDSURFACEDESC2; + + +/* + * DDOPTSURFACEDESC + */ +typedef struct _DDOPTSURFACEDESC +{ + DWORD dwSize; // size of the DDOPTSURFACEDESC structure + DirectDrawOptSurfaceDescFlags dwFlags; // determines what fields are valid + DDSCAPS2 ddSCaps; // Common caps like: Memory type + DDOSCAPS ddOSCaps; // Common caps like: Memory type + GUID guid; // Compression technique GUID + DWORD dwCompressionRatio; // Compression ratio +} DDOPTSURFACEDESC; + +/* + * DDCOLORCONTROL + */ +typedef struct _DDCOLORCONTROL +{ + DWORD dwSize; + DirectDrawColorControlFlags dwFlags; + LONG lBrightness; + LONG lContrast; + LONG lHue; + LONG lSaturation; + LONG lSharpness; + LONG lGamma; + LONG lColorEnable; + DWORD dwReserved1; +} DDCOLORCONTROL; + +/* + * INTERACES FOLLOW: + * IDirectDraw + * IDirectDrawClipper + * IDirectDrawPalette + * IDirectDrawSurface + */ + +/* + * IDirectDraw + */ +interface IDirectDraw : IUnknown +{ + /*** IDirectDraw methods ***/ + DDRESULT Compact(); + DDRESULT CreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER * lplpDDClipper, IUnknown * pUnkOuter ); + DDRESULT CreatePalette( DirectDrawPaletteCapsFlags dwFlags, LPPALETTEENTRY lpDDColorArray, [out] LPDIRECTDRAWPALETTE * lplpDDPalette, IUnknown * pUnkOuter); + DDRESULT CreateSurface( LPDDSURFACEDESC lpDDSurfaceDesc, [out] LPDIRECTDRAWSURFACE * lplpDDSurface, IUnknown * pUnkOuter); + DDRESULT DuplicateSurface( LPDIRECTDRAWSURFACE lpDDSurface, [out] LPDIRECTDRAWSURFACE * lplpDupDDSurface ); + DDRESULT EnumDisplayModes( DirectDrawEnumDisplayModesFlags dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback ); + DDRESULT EnumSurfaces( DirectDrawEnumSurfacesFlags dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback ); + DDRESULT FlipToGDISurface(); + DDRESULT GetCaps( [out] LPDDCAPS lpDDDriverCaps, [out] LPDDCAPS lpDDHELCaps); + DDRESULT GetDisplayMode( [out] LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT GetFourCCCodes( [out] LPDWORD lpNumCodes, LPDWORD lpCodes ); + DDRESULT GetGDISurface( [out] LPDIRECTDRAWSURFACE * lplpGDIDDSSurface ); + DDRESULT GetMonitorFrequency( [out] LPDWORD lpdwFrequency ); + DDRESULT GetScanLine( [out] LPDWORD lpdwScanLine ); + DDRESULT GetVerticalBlankStatus( [out] LPBOOL lpbIsInVB ); + DDRESULT Initialize( GUID * lpGUID ); + DDRESULT RestoreDisplayMode(); + DDRESULT SetCooperativeLevel( HWND hWnd, DirectDrawSetCooperativeLevelFlags dwFlags ); + DDRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP ); + DDRESULT WaitForVerticalBlank( DirectDrawWaitForVerticalBlankFlags dwFlags, HANDLE hEvent ); +}; + +interface IDirectDraw2 : IUnknown +{ + /*** IDirectDraw methods ***/ + DDRESULT Compact(); + DDRESULT CreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER * lplpDDClipper, IUnknown * pUnkOuter ); + DDRESULT CreatePalette( DirectDrawPaletteCapsFlags dwFlags, LPPALETTEENTRY lpDDColorArray, [out] LPDIRECTDRAWPALETTE * lplpDDPalette, IUnknown * pUnkOuter); + DDRESULT CreateSurface( LPDDSURFACEDESC lpDDSurfaceDesc, [out] LPDIRECTDRAWSURFACE * lplpDDSurface, IUnknown * pUnkOuter); + DDRESULT DuplicateSurface( LPDIRECTDRAWSURFACE lpDDSurface, [out] LPDIRECTDRAWSURFACE * lplpDupDDSurface ); + DDRESULT EnumDisplayModes( DirectDrawEnumDisplayModesFlags dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback ); + DDRESULT EnumSurfaces( DirectDrawEnumSurfacesFlags dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback ); + DDRESULT FlipToGDISurface(); + DDRESULT GetCaps( [out] LPDDCAPS lpDDDriverCaps, [out] LPDDCAPS lpDDHELCaps); + DDRESULT GetDisplayMode( [out] LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT GetFourCCCodes( [out] LPDWORD lpNumCodes, LPDWORD lpCodes ); + DDRESULT GetGDISurface( [out] LPDIRECTDRAWSURFACE * lplpGDIDDSSurface ); + DDRESULT GetMonitorFrequency( [out] LPDWORD lpdwFrequency ); + DDRESULT GetScanLine( [out] LPDWORD lpdwScanLine ); + DDRESULT GetVerticalBlankStatus( [out] LPBOOL lpbIsInVB ); + DDRESULT Initialize( GUID * lpGUID ); + DDRESULT RestoreDisplayMode(); + DDRESULT SetCooperativeLevel( HWND hWnd, DirectDrawSetCooperativeLevelFlags dwFlags ); + DDRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DirectDrawSetDisplayModeFlags dwFlags ); + DDRESULT WaitForVerticalBlank( DirectDrawWaitForVerticalBlankFlags dwFlags, HANDLE hEvent ); + /*** Added in the v2 interface ***/ + DDRESULT GetAvailableVidMem( LPDDSCAPS lpDDSCaps, [out] LPDWORD lpdwTotal, [out] LPDWORD lpdwFree ); +}; + +interface IDirectDraw4 : IUnknown +{ + /*** IDirectDraw methods ***/ + DDRESULT Compact(); + DDRESULT CreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER * lplpDDClipper, IUnknown * pUnkOuter ); + DDRESULT CreatePalette( DirectDrawPaletteCapsFlags dwFlags, LPPALETTEENTRY lpDDColorArray, [out] LPDIRECTDRAWPALETTE * lplpDDPalette, IUnknown * pUnkOuter); + DDRESULT CreateSurface( LPDDSURFACEDESC2 lpDDSurfaceDesc, [out] LPDIRECTDRAWSURFACE4 * lplpDDSurface, IUnknown * pUnkOuter); + DDRESULT DuplicateSurface( LPDIRECTDRAWSURFACE4 lpDDSurface, [out] LPDIRECTDRAWSURFACE4 * lplpDupDDSurface ); + DDRESULT EnumDisplayModes( DirectDrawEnumDisplayModesFlags dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback ); + DDRESULT EnumSurfaces( DirectDrawEnumSurfacesFlags dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback ); + DDRESULT FlipToGDISurface(); + DDRESULT GetCaps( [out] LPDDCAPS lpDDDriverCaps, [out] LPDDCAPS lpDDHELCaps); + DDRESULT GetDisplayMode( [out] LPDDSURFACEDESC2 lpDDSurfaceDesc ); + DDRESULT GetFourCCCodes( [out] LPDWORD lpNumCodes, LPDWORD lpCodes ); + DDRESULT GetGDISurface( [out] LPDIRECTDRAWSURFACE4 * lplpGDIDDSSurface ); + DDRESULT GetMonitorFrequency( [out] LPDWORD lpdwFrequency ); + DDRESULT GetScanLine( [out] LPDWORD lpdwScanLine ); + DDRESULT GetVerticalBlankStatus( [out] LPBOOL lpbIsInVB ); + DDRESULT Initialize( GUID * lpGUID ); + DDRESULT RestoreDisplayMode(); + DDRESULT SetCooperativeLevel( HWND hWnd, DirectDrawSetCooperativeLevelFlags dwFlags ); + DDRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DirectDrawSetDisplayModeFlags dwFlags ); + DDRESULT WaitForVerticalBlank( DirectDrawWaitForVerticalBlankFlags dwFlags, HANDLE hEvent ); + /*** Added in the v2 interface ***/ + DDRESULT GetAvailableVidMem( LPDDSCAPS2 lpDDSCaps, [out] LPDWORD lpdwTotal, [out] LPDWORD lpdwFree ); + /*** Added in the V4 Interface ***/ + DDRESULT GetSurfaceFromDC( HDC hdc, [out] LPDIRECTDRAWSURFACE4 * lpDDSurface ); + DDRESULT RestoreAllSurfaces(); + DDRESULT TestCooperativeLevel(); + DDRESULT GetDeviceIdentifier( LPDDDEVICEIDENTIFIER lpDDDI, DirectDrawGetDeviceIdentifierFlags dwFlags ); +}; + +interface IDirectDraw7 : IUnknown +{ + /*** IDirectDraw methods ***/ + DDRESULT Compact(); + DDRESULT CreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER * lplpDDClipper, IUnknown * pUnkOuter ); + DDRESULT CreatePalette( DirectDrawPaletteCapsFlags dwFlags, LPPALETTEENTRY lpDDColorArray, [out] LPDIRECTDRAWPALETTE * lplpDDPalette, IUnknown * pUnkOuter); + DDRESULT CreateSurface( LPDDSURFACEDESC2 lpDDSurfaceDesc, [out] LPDIRECTDRAWSURFACE7 * lplpDDSurface, IUnknown * pUnkOuter); + DDRESULT DuplicateSurface( LPDIRECTDRAWSURFACE4 lpDDSurface, [out] LPDIRECTDRAWSURFACE7 * lplpDupDDSurface ); + DDRESULT EnumDisplayModes( DirectDrawEnumDisplayModesFlags dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback ); + DDRESULT EnumSurfaces( DirectDrawEnumSurfacesFlags dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback ); + DDRESULT FlipToGDISurface(); + DDRESULT GetCaps( [out] LPDDCAPS lpDDDriverCaps, [out] LPDDCAPS lpDDHELCaps); + DDRESULT GetDisplayMode( [out] LPDDSURFACEDESC2 lpDDSurfaceDesc ); + DDRESULT GetFourCCCodes( [out] LPDWORD lpNumCodes, LPDWORD lpCodes ); + DDRESULT GetGDISurface( [out] LPDIRECTDRAWSURFACE7 * lplpGDIDDSSurface ); + DDRESULT GetMonitorFrequency( [out] LPDWORD lpdwFrequency ); + DDRESULT GetScanLine( [out] LPDWORD lpdwScanLine ); + DDRESULT GetVerticalBlankStatus( [out] LPBOOL lpbIsInVB ); + DDRESULT Initialize( GUID * lpGUID ); + DDRESULT RestoreDisplayMode(); + DDRESULT SetCooperativeLevel( HWND hWnd, DirectDrawSetCooperativeLevelFlags dwFlags ); + DDRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DirectDrawSetDisplayModeFlags dwFlags ); + DDRESULT WaitForVerticalBlank( DirectDrawWaitForVerticalBlankFlags dwFlags, HANDLE hEvent ); + /*** Added in the v2 interface ***/ + DDRESULT GetAvailableVidMem( LPDDSCAPS2 lpDDSCaps, [out] LPDWORD lpdwTotal, [out] LPDWORD lpdwFree ); + /*** Added in the V4 Interface ***/ + DDRESULT GetSurfaceFromDC( HDC hdc, [out] LPDIRECTDRAWSURFACE7 * lpDDSurface ); + DDRESULT RestoreAllSurfaces(); + DDRESULT TestCooperativeLevel(); + DDRESULT GetDeviceIdentifier( LPDDDEVICEIDENTIFIER2 lpDDDI, DirectDrawGetDeviceIdentifierFlags dwFlags ); + /*** Added in the V7 Interface ***/ + DDRESULT StartModeTest( LPSIZE lpModesToTest, DWORD dwNumEntries, DirectDrawStartModeTestFlags dwFlags); + DDRESULT EvaluateMode( DirectDrawEvaluateModeFlags dwFlags, [out] DWORD * pSecondsUntilTimeout ); +}; + +interface IDirectDrawPalette : IUnknown +{ + /*** IDirectDrawPalette methods ***/ + DDRESULT GetCaps( [out] DirectDrawPaletteCapsFlags* lpdwCaps); + DDRESULT GetEntries( DWORD dwFlags, DWORD dwBase, DWORD dwNumEntries, LPPALETTEENTRY lpEntries ); + DDRESULT Initialize( LPDIRECTDRAW lpDD, DWORD dwFlags, LPPALETTEENTRY lpDDColorTable ); + DDRESULT SetEntries( DWORD dwFlags, DWORD dwStartingEntry, DWORD dwCount, LPPALETTEENTRY lpEntries ); +}; + +/* + * IDirectDrawClipper + */ +interface IDirectDrawClipper : IUnknown +{ + /*** IDirectDrawClipper methods ***/ + DDRESULT GetClipList( LPRECT lpRect, LPRGNDATA lpClipList, [out] LPDWORD lpdwSize ); + DDRESULT GetHWnd( [out] HWND * hWnd); + DDRESULT Initialize( LPDIRECTDRAW lpDD, DWORD dwFlags ); + DDRESULT IsClipListChanged( [out] BOOL * lpbChanged ); + DDRESULT SetClipList( LPRGNDATA lpClipList, DWORD dwFlags ); + DDRESULT SetHWnd( DWORD dwFlags, HWND hWnd ); +}; + +/* + * IDirectDrawSurface and related interfaces + */ +interface IDirectDrawSurface : IUnknown +{ + /*** IDirectDrawSurface methods ***/ + DDRESULT AddAttachedSurface( LPDIRECTDRAWSURFACE lpDDSAttachedSurface ); + DDRESULT AddOverlayDirtyRect( LPRECT lpRect ); + DDRESULT Blt( LPRECT lpDestRect, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DirectDrawBltFlags dwFlags, LPDDBLTFX lpDDBltFx ); + DDRESULT BltBatch( LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags ); + DDRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans ); + DDRESULT DeleteAttachedSurface( DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSurface ); + DDRESULT EnumAttachedSurfaces( LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback ); + DDRESULT EnumOverlayZOrders( DirectDrawEnumOverlayZOrderFlags dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback ); + DDRESULT Flip( LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DirectDrawFlipFlags dwFlags ); + DDRESULT GetAttachedSurface( LPDDSCAPS lpDDSCaps, [out] LPDIRECTDRAWSURFACE * lplpDDAttachedSurface); + DDRESULT GetBltStatus( DirectDrawGetBltStatusFlags dwFlags ); + DDRESULT GetCaps( [out] LPDDSCAPS lpDDSCaps ); + DDRESULT GetClipper( [out] LPDIRECTDRAWCLIPPER * lplpDDClipper ); + DDRESULT GetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT GetDC( [out] HDC * phDC ); + DDRESULT GetFlipStatus( DWORD dwFlags ); + DDRESULT GetOverlayPosition( [out] LPLONG lplX, [out] LPLONG lplY ); + DDRESULT GetPalette( [out] LPDIRECTDRAWPALETTE * lplpDDPalette ); + DDRESULT GetPixelFormat( [out] LPDDPIXELFORMAT lpDDPixelFormat ); + DDRESULT GetSurfaceDesc( [out] LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT Initialize( LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT IsLost(); + DDRESULT Lock( LPRECT lpDestRect,LPDDSURFACEDESC lpDDSurfaceDesc, DirectDrawSurfaceLockFlags dwFlags, HANDLE hEvent ); + DDRESULT ReleaseDC( HDC hDC ); + DDRESULT Restore(); + DDRESULT SetClipper( LPDIRECTDRAWCLIPPER lpDDClipper ); + DDRESULT SetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT SetOverlayPosition( LONG lX, LONG lY ); + DDRESULT SetPalette( LPDIRECTDRAWPALETTE lpDDPalette ); + DDRESULT Unlock( LPVOID lp ); + DDRESULT UpdateOverlay( LPRECT lpSrcRect, LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, DirectDrawSurfaceOverlayFlags dwFlags, LPDDOVERLAYFX lpDDOverlayFx ); + DDRESULT UpdateOverlayDisplay( DWORD dwFlags ); + DDRESULT UpdateOverlayZOrder( DirectDrawUpdateOverlayZOrderFlags dwFlags, LPDIRECTDRAWSURFACE lpDDSReference ); +}; + +/* + * IDirectDrawSurface2 and related interfaces + */ +interface IDirectDrawSurface2 : IUnknown +{ + /*** IDirectDrawSurface methods ***/ + DDRESULT AddAttachedSurface( LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface ); + DDRESULT AddOverlayDirtyRect( LPRECT lpRect ); + DDRESULT Blt( LPRECT lpDestRect, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DirectDrawBltFlags dwFlags, LPDDBLTFX lpDDBltFx ); + DDRESULT BltBatch( LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags ); + DDRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans ); + DDRESULT DeleteAttachedSurface( DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSurface ); + DDRESULT EnumAttachedSurfaces( LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback ); + DDRESULT EnumOverlayZOrders( DirectDrawEnumOverlayZOrderFlags dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback ); + DDRESULT Flip( LPDIRECTDRAWSURFACE2 lpDDSurfaceTargetOverride, DirectDrawFlipFlags dwFlags ); + DDRESULT GetAttachedSurface( LPDDSCAPS lpDDSCaps, [out] LPDIRECTDRAWSURFACE2 * lplpDDAttachedSurface); + DDRESULT GetBltStatus( DirectDrawGetBltStatusFlags dwFlags ); + DDRESULT GetCaps( [out] LPDDSCAPS lpDDSCaps ); + DDRESULT GetClipper( [out] LPDIRECTDRAWCLIPPER * lplpDDClipper ); + DDRESULT GetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT GetDC( [out] HDC * phDC ); + DDRESULT GetFlipStatus( DWORD dwFlags ); + DDRESULT GetOverlayPosition( [out] LPLONG lplX, [out] LPLONG lplY ); + DDRESULT GetPalette( [out] LPDIRECTDRAWPALETTE * lplpDDPalette ); + DDRESULT GetPixelFormat( [out] LPDDPIXELFORMAT lpDDPixelFormat ); + DDRESULT GetSurfaceDesc( [out] LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT Initialize( LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT IsLost(); + DDRESULT Lock( LPRECT lpDestRect,LPDDSURFACEDESC lpDDSurfaceDesc, DirectDrawSurfaceLockFlags dwFlags, HANDLE hEvent ); + DDRESULT ReleaseDC( HDC hDC ); + DDRESULT Restore(); + DDRESULT SetClipper( LPDIRECTDRAWCLIPPER lpDDClipper ); + DDRESULT SetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT SetOverlayPosition( LONG lX, LONG lY ); + DDRESULT SetPalette( LPDIRECTDRAWPALETTE lpDDPalette ); + DDRESULT Unlock( LPVOID lp ); + DDRESULT UpdateOverlay( LPRECT lpSrcRect, LPDIRECTDRAWSURFACE2 lpDDDestSurface, LPRECT lpDestRect, DirectDrawSurfaceOverlayFlags dwFlags, LPDDOVERLAYFX lpDDOverlayFx ); + DDRESULT UpdateOverlayDisplay( DWORD dwFlags ); + DDRESULT UpdateOverlayZOrder( DirectDrawUpdateOverlayZOrderFlags dwFlags, LPDIRECTDRAWSURFACE2 lpDDSReference ); + /*** Added in the v2 interface ***/ + DDRESULT GetDDInterface( [out] IUnknown* lplpDD ); + DDRESULT PageLock( DWORD dwFlags ); + DDRESULT PageUnlock( DWORD dwFlags ); +}; + +/* + * IDirectDrawSurface3 and related interfaces + */ +interface IDirectDrawSurface3 : IUnknown +{ + /*** IDirectDrawSurface methods ***/ + DDRESULT AddAttachedSurface( LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface ); + DDRESULT AddOverlayDirtyRect( LPRECT lpRect ); + DDRESULT Blt( LPRECT lpDestRect, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DirectDrawBltFlags dwFlags, LPDDBLTFX lpDDBltFx ); + DDRESULT BltBatch( LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags ); + DDRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans ); + DDRESULT DeleteAttachedSurface( DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSurface ); + DDRESULT EnumAttachedSurfaces( LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback ); + DDRESULT EnumOverlayZOrders( DirectDrawEnumOverlayZOrderFlags dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback ); + DDRESULT Flip( LPDIRECTDRAWSURFACE3 lpDDSurfaceTargetOverride, DirectDrawFlipFlags dwFlags ); + DDRESULT GetAttachedSurface( LPDDSCAPS lpDDSCaps, [out] LPDIRECTDRAWSURFACE3 * lplpDDAttachedSurface); + DDRESULT GetBltStatus( DirectDrawGetBltStatusFlags dwFlags ); + DDRESULT GetCaps( [out] LPDDSCAPS lpDDSCaps ); + DDRESULT GetClipper( [out] LPDIRECTDRAWCLIPPER * lplpDDClipper ); + DDRESULT GetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT GetDC( [out] HDC * phDC ); + DDRESULT GetFlipStatus( DWORD dwFlags ); + DDRESULT GetOverlayPosition( [out] LPLONG lplX, [out] LPLONG lplY ); + DDRESULT GetPalette( [out] LPDIRECTDRAWPALETTE * lplpDDPalette ); + DDRESULT GetPixelFormat( [out] LPDDPIXELFORMAT lpDDPixelFormat ); + DDRESULT GetSurfaceDesc( [out] LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT Initialize( LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc ); + DDRESULT IsLost(); + DDRESULT Lock( LPRECT lpDestRect,LPDDSURFACEDESC lpDDSurfaceDesc, DirectDrawSurfaceLockFlags dwFlags, HANDLE hEvent ); + DDRESULT ReleaseDC( HDC hDC ); + DDRESULT Restore(); + DDRESULT SetClipper( LPDIRECTDRAWCLIPPER lpDDClipper ); + DDRESULT SetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT SetOverlayPosition( LONG lX, LONG lY ); + DDRESULT SetPalette( LPDIRECTDRAWPALETTE lpDDPalette ); + DDRESULT Unlock( LPVOID lp ); + DDRESULT UpdateOverlay( LPRECT lpSrcRect, LPDIRECTDRAWSURFACE3 lpDDDestSurface, LPRECT lpDestRect, DirectDrawSurfaceOverlayFlags dwFlags, LPDDOVERLAYFX lpDDOverlayFx ); + DDRESULT UpdateOverlayDisplay( DWORD dwFlags ); + DDRESULT UpdateOverlayZOrder( DirectDrawUpdateOverlayZOrderFlags dwFlags, LPDIRECTDRAWSURFACE3 lpDDSReference ); + /*** Added in the v2 interface ***/ + DDRESULT GetDDInterface( [out] IUnknown* lplpDD ); + DDRESULT PageLock( DWORD dwFlags ); + DDRESULT PageUnlock( DWORD dwFlags ); + /*** Added in the V3 interface ***/ + DDRESULT SetSurfaceDesc( LPDDSURFACEDESC lpDDSD, DWORD dwFlags ); +}; + + +/* + * IDirectDrawSurface4 and related interfaces + */ +interface IDirectDrawSurface4 : IUnknown +{ + /*** IDirectDrawSurface methods ***/ + DDRESULT AddAttachedSurface( LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface ); + DDRESULT AddOverlayDirtyRect( LPRECT lpRect ); + DDRESULT Blt( LPRECT lpDestRect, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DirectDrawBltFlags dwFlags, LPDDBLTFX lpDDBltFx ); + DDRESULT BltBatch( LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags ); + DDRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans ); + DDRESULT DeleteAttachedSurface( DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSurface ); + DDRESULT EnumAttachedSurfaces( LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback ); + DDRESULT EnumOverlayZOrders( DirectDrawEnumOverlayZOrderFlags dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpfnCallback ); + DDRESULT Flip( LPDIRECTDRAWSURFACE4 lpDDSurfaceTargetOverride, DirectDrawFlipFlags dwFlags ); + DDRESULT GetAttachedSurface( LPDDSCAPS lpDDSCaps, [out] LPDIRECTDRAWSURFACE4 * lplpDDAttachedSurface); + DDRESULT GetBltStatus( DirectDrawGetBltStatusFlags dwFlags ); + DDRESULT GetCaps( [out] LPDDSCAPS lpDDSCaps ); + DDRESULT GetClipper( [out] LPDIRECTDRAWCLIPPER * lplpDDClipper ); + DDRESULT GetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT GetDC( [out] HDC * phDC ); + DDRESULT GetFlipStatus( DWORD dwFlags ); + DDRESULT GetOverlayPosition( [out] LPLONG lplX, [out] LPLONG lplY ); + DDRESULT GetPalette( [out] LPDIRECTDRAWPALETTE * lplpDDPalette ); + DDRESULT GetPixelFormat( [out] LPDDPIXELFORMAT lpDDPixelFormat ); + DDRESULT GetSurfaceDesc( [out] LPDDSURFACEDESC2 lpDDSurfaceDesc ); + DDRESULT Initialize( LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc ); + DDRESULT IsLost(); + DDRESULT Lock( LPRECT lpDestRect,LPDDSURFACEDESC2 lpDDSurfaceDesc, DirectDrawSurfaceLockFlags dwFlags, HANDLE hEvent ); + DDRESULT ReleaseDC( HDC hDC ); + DDRESULT Restore(); + DDRESULT SetClipper( LPDIRECTDRAWCLIPPER lpDDClipper ); + DDRESULT SetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT SetOverlayPosition( LONG lX, LONG lY ); + DDRESULT SetPalette( LPDIRECTDRAWPALETTE lpDDPalette ); + DDRESULT Unlock( LPVOID lp ); + DDRESULT UpdateOverlay( LPRECT lpSrcRect, LPDIRECTDRAWSURFACE4 lpDDDestSurface, LPRECT lpDestRect, DirectDrawSurfaceOverlayFlags dwFlags, LPDDOVERLAYFX lpDDOverlayFx ); + DDRESULT UpdateOverlayDisplay( DWORD dwFlags ); + DDRESULT UpdateOverlayZOrder( DirectDrawUpdateOverlayZOrderFlags dwFlags, LPDIRECTDRAWSURFACE4 lpDDSReference ); + /*** Added in the v2 interface ***/ + DDRESULT GetDDInterface( [out] IUnknown* lplpDD ); + DDRESULT PageLock( DWORD dwFlags ); + DDRESULT PageUnlock( DWORD dwFlags ); + /*** Added in the V3 interface ***/ + DDRESULT SetSurfaceDesc( LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags ); + /*** Added in the v4 interface ***/ + DDRESULT SetPrivateData( REFGUID guidTag, LPVOID lpData, DWORD cbSize, DWORD dwFlags ); + DDRESULT GetPrivateData( REFGUID guidTag, LPVOID lpData, [out] LPDWORD lpcbBufferSize ); + DDRESULT FreePrivateData( REFGUID guidTag ); + DDRESULT GetUniquenessValue( [out] LPDWORD lpValue ); + DDRESULT ChangeUniquenessValue(); +}; + +/* + * IDirectDrawSurface7 and related interfaces + */ +interface IDirectDrawSurface7 : IUnknown +{ + /*** IDirectDrawSurface methods ***/ + DDRESULT AddAttachedSurface( LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface ); + DDRESULT AddOverlayDirtyRect( LPRECT lpRect ); + DDRESULT Blt( LPRECT lpDestRect, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DirectDrawBltFlags dwFlags, LPDDBLTFX lpDDBltFx ); + DDRESULT BltBatch( LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags ); + DDRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans ); + DDRESULT DeleteAttachedSurface( DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSurface ); + DDRESULT EnumAttachedSurfaces( LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback ); + DDRESULT EnumOverlayZOrders( DirectDrawEnumOverlayZOrderFlags dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpfnCallback ); + DDRESULT Flip( LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride, DirectDrawFlipFlags dwFlags ); + DDRESULT GetAttachedSurface( LPDDSCAPS2 lpDDSCaps, [out] LPDIRECTDRAWSURFACE7 * lplpDDAttachedSurface); + DDRESULT GetBltStatus( DirectDrawGetBltStatusFlags dwFlags ); + DDRESULT GetCaps( [out] LPDDSCAPS2 lpDDSCaps ); + DDRESULT GetClipper( [out] LPDIRECTDRAWCLIPPER * lplpDDClipper ); + DDRESULT GetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT GetDC( [out] HDC * phDC ); + DDRESULT GetFlipStatus( DWORD dwFlags ); + DDRESULT GetOverlayPosition( [out] LPLONG lplX, [out] LPLONG lplY ); + DDRESULT GetPalette( [out] LPDIRECTDRAWPALETTE * lplpDDPalette ); + DDRESULT GetPixelFormat( [out] LPDDPIXELFORMAT lpDDPixelFormat ); + DDRESULT GetSurfaceDesc( [out] LPDDSURFACEDESC2 lpDDSurfaceDesc ); + DDRESULT Initialize( LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc ); + DDRESULT IsLost(); + DDRESULT Lock( LPRECT lpDestRect,LPDDSURFACEDESC2 lpDDSurfaceDesc, DirectDrawSurfaceLockFlags dwFlags, HANDLE hEvent ); + DDRESULT ReleaseDC( HDC hDC ); + DDRESULT Restore(); + DDRESULT SetClipper( LPDIRECTDRAWCLIPPER lpDDClipper ); + DDRESULT SetColorKey( DirectDrawSurfaceSetGetColorKeyFlags dwFlags, [out] LPDDCOLORKEY lpDDColorKey ); + DDRESULT SetOverlayPosition( LONG lX, LONG lY ); + DDRESULT SetPalette( LPDIRECTDRAWPALETTE lpDDPalette ); + DDRESULT Unlock( LPVOID lp ); + DDRESULT UpdateOverlay( LPRECT lpSrcRect, LPDIRECTDRAWSURFACE7 lpDDDestSurface, LPRECT lpDestRect, DirectDrawSurfaceOverlayFlags dwFlags, LPDDOVERLAYFX lpDDOverlayFx ); + DDRESULT UpdateOverlayDisplay( DWORD dwFlags ); + DDRESULT UpdateOverlayZOrder( DirectDrawUpdateOverlayZOrderFlags dwFlags, LPDIRECTDRAWSURFACE7 lpDDSReference ); + /*** Added in the v2 interface ***/ + DDRESULT GetDDInterface( [out] IUnknown* lplpDD ); + DDRESULT PageLock( DWORD dwFlags ); + DDRESULT PageUnlock( DWORD dwFlags ); + /*** Added in the V3 interface ***/ + DDRESULT SetSurfaceDesc( LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags ); + /*** Added in the v4 interface ***/ + DDRESULT SetPrivateData( REFGUID guidTag, LPVOID lpData, DWORD cbSize, DWORD dwFlags ); + DDRESULT GetPrivateData( REFGUID guidTag, LPVOID lpData, [out] LPDWORD lpcbBufferSize ); + DDRESULT FreePrivateData( REFGUID guidTag ); + DDRESULT GetUniquenessValue( [out] LPDWORD lpValue ); + DDRESULT ChangeUniquenessValue(); + /*** Moved Texture7 methods here ***/ + DDRESULT SetPriority( DWORD dwPriority ); + DDRESULT GetPriority( [out] LPDWORD lpdwPriority ); + DDRESULT SetLOD( DWORD dwMaxLOD ); + DDRESULT GetLOD( [out] LPDWORD lpdwMaxLOD ); +}; + +/* + * IDirectDrawColorControl + */ +interface IDirectDrawColorControl : IUnknown +{ + /*** IDirectDrawColorControl methods ***/ + DDRESULT GetColorControls( LPDDCOLORCONTROL lpColorControl ); + DDRESULT SetColorControls( LPDDCOLORCONTROL lpColorControl ); +}; + +/* + * IDirectDrawGammaControl + */ +interface IDirectDrawGammaControl : IUnknown +{ + /*** IDirectDrawGammaControl methods ***/ + DDRESULT GetGammaRamp( DWORD dwFlags, LPDDGAMMARAMP lpRampData ); + DDRESULT SetGammaRamp( DirectDrawSetGammaRampFlags dwFlags, LPDDGAMMARAMP lpRampData ); +}; + +DDRESULT DirectDrawEnumerateW( LPDDENUMCALLBACKW lpCallback, LPVOID lpContext ); +DDRESULT DirectDrawEnumerateA( LPDDENUMCALLBACKA lpCallback, LPVOID lpContext ); +DDRESULT DirectDrawEnumerateExW( LPDDENUMCALLBACKEXW lpCallback, LPVOID lpContext, DirectDrawEnumerateExFlags dwFlags); +DDRESULT DirectDrawEnumerateExA( LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DirectDrawEnumerateExFlags dwFlags); + +DDRESULT DirectDrawCreate( DirectDrawCreateFlags lpGUID, [out] LPDIRECTDRAW *lplpDD, IUnknown *pUnkOuter ); +DDRESULT DirectDrawCreateEx( DirectDrawCreateFlags lpGuid, [out] COM_INTERFACE_PTR *lplpDD, [iid] REFIID iid, IUnknown *pUnkOuter ); +DDRESULT DirectDrawCreateClipper( DWORD dwFlags, [out] LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter ); + +HRESULT DllGetClassObject( + REFCLSID rclsid, //CLSID for the class object + [iid] REFIID riid, //Reference to the identifier of the interface + // that communicates with the class object + [out] COM_INTERFACE_PTR * ppv //Address of output variable that receives the + // interface pointer requested in riid +); diff --git a/tools/Debugging Tools for Windows/winext/manifest/debugging.h b/tools/Debugging Tools for Windows/winext/manifest/debugging.h new file mode 100644 index 0000000000..6ea76b837a --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/debugging.h @@ -0,0 +1,397 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Debugging Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +module KERNEL32.DLL: +category DebuggingAndErrorHandling: + +typedef LPVOID LPDEBUG_EVENT; + +FailOnFalse [gle] ContinueDebugEvent( + DWORD dwProcessId, + DWORD dwThreadId, + DWORD dwContinueStatus + ); + +FailOnFalse [gle] DebugActiveProcess( + DWORD dwProcessId + ); + +VOID DebugBreak(); + +VOID FatalExit( + INT ExitCode + ); + +FailOnFalse [gle] FlushInstructionCache( + HANDLE hProcess, + LPCVOID lpBaseAddress, + DWORD dwSize + ); + +FailOnFalse [gle] GetThreadContext( + HANDLE hThread, + LPCONTEXT lpContext + ); + +FailOnFalse [gle] GetThreadSelectorEntry( + HANDLE hThread, + DWORD dwSelector, + [out] LPVOID lpSelectorEntry + ); + +BOOL IsDebuggerPresent(); + +VOID OutputDebugStringA( + LPCSTR lpOutputString + ); + +VOID OutputDebugStringW( + LPCWSTR lpOutputString + ); + +FailOnFalse [gle] SetThreadContext( + HANDLE hThread, + LPCONTEXT lpContext + ); + +FailOnFalse [gle] WaitForDebugEvent( + LPDEBUG_EVENT lpDebugEvent, + DWORD dwMilliseconds + ); + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Error Handling Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +FailOnFalse [gle] Beep( + DWORD dwFreq, + DWORD dwDuration + ); + +VOID FatalAppExitA( + UINT uAction, + LPCSTR lpMessageText + ); + +VOID FatalAppExitW( + UINT uAction, + LPCWSTR lpMessageText + ); + +module KERNEL32.DLL: + +mask DWORD FormatMessageFlags +{ +#define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 +#define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 +#define FORMAT_MESSAGE_FROM_STRING 0x00000400 +#define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 +#define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 +#define FORMAT_MESSAGE_ARGUMENT_ARRAY 0x00002000 +#define FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF +}; + +DwordFailIfZero [gle] FormatMessageA( + FormatMessageFlags dwFlags, + LPCVOID lpSource, + DWORD dwMessageId, + DWORD dwLanguageId, + [out] LPSTR lpBuffer, + DWORD nSize, + PVOID Arguments + ); + +DwordFailIfZero [gle] FormatMessageW( + FormatMessageFlags dwFlags, + LPCVOID lpSource, + DWORD dwMessageId, + DWORD dwLanguageId, + [out] LPWSTR lpBuffer, + DWORD nSize, + PVOID Arguments + ); + + +DWORD GetLastError(); + + +value UINT MessageBeepType +{ +#define MB_OK 0x00000000L +#define MB_ICONHAND 0x00000010L +#define MB_ICONQUESTION 0x00000020L +#define MB_ICONEXCLAMATION 0x00000030L +#define MB_ICONASTERISK 0x00000040L +}; + +module USER32.DLL: +FailOnFalse [gle] MessageBeep( + MessageBeepType uType + ); + +value UINT SetErrorModeType +{ +#define SEM_FAILCRITICALERRORS 0x0001 +#define SEM_NOGPFAULTERRORBOX 0x0002 +#define SEM_NOALIGNMENTFAULTEXCEPT 0x0004 +#define SEM_NOOPENFILEERRORBOX 0x8000 +}; + +module KERNEL32.DLL: +UINT SetErrorMode( + SetErrorModeType uMode + ); + +VOID SetLastError( + DWORD dwErrCode + ); + +module USER32.DLL: +VOID SetLastErrorEx( + DWORD dwErrCode, + DWORD dwType + ); + + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Toolhelp32 Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +module KERNEL32.DLL: +mask DWORD CreateToolhelp32SnapshotFlags +{ +#define TH32CS_SNAPHEAPLIST 0x00000001 +#define TH32CS_SNAPPROCESS 0x00000002 +#define TH32CS_SNAPTHREAD 0x00000004 +#define TH32CS_SNAPMODULE 0x00000008 +#define TH32CS_INHERIT 0x80000000 +}; + +value DWORD HeapList32Type +{ +#define HF32_DEFAULT 1 // process's default heap +#define HF32_SHARED 2 // is shared heap +}; + +typedef struct tagHEAPLIST32 +{ + SIZE_T dwSize; + DWORD th32ProcessID; // owning process + ULONG_PTR th32HeapID; // heap (in owning process's context!) + HeapList32Type dwFlags; +} HEAPLIST32; +typedef HEAPLIST32 * PHEAPLIST32; +typedef HEAPLIST32 * LPHEAPLIST32; + +mask DWORD HeapEntry32Flags +{ +#define LF32_FIXED 0x00000001 +#define LF32_FREE 0x00000002 +#define LF32_MOVEABLE 0x00000004 +}; + +typedef struct tagHEAPENTRY32 +{ + SIZE_T dwSize; + HANDLE hHandle; // Handle of this heap block + ULONG_PTR dwAddress; // Linear address of start of block + SIZE_T dwBlockSize; // Size of block in bytes + HeapEntry32Flags dwFlags; + DWORD dwLockCount; + DWORD dwResvd; + DWORD th32ProcessID; // owning process + ULONG_PTR th32HeapID; // heap block is in +} HEAPENTRY32; +typedef HEAPENTRY32 * PHEAPENTRY32; +typedef HEAPENTRY32 * LPHEAPENTRY32; + +HANDLE [gle] +CreateToolhelp32Snapshot( + CreateToolhelp32SnapshotFlags dwFlags, + DWORD th32ProcessID + ); + + +FailOnFalse [gle] +Heap32ListFirst( + HANDLE hSnapshot, + [out] LPHEAPLIST32 lphl + ); + +FailOnFalse [gle] +Heap32ListNext( + HANDLE hSnapshot, + [out] LPHEAPLIST32 lphl + ); + +FailOnFalse [gle] +Heap32First( + [out] LPHEAPENTRY32 lphe, + DWORD th32ProcessID, + ULONG_PTR th32HeapID + ); + +FailOnFalse [gle] +Heap32Next( + [out] LPHEAPENTRY32 lphe + ); + +FailOnFalse [gle] +Toolhelp32ReadProcessMemory( + DWORD th32ProcessID, + LPCVOID lpBaseAddress, + LPVOID lpBuffer, + SIZE_T cbRead, + [out] SIZE_T *lpNumberOfBytesRead + ); + +typedef struct tagPROCESSENTRY32W +{ + DWORD dwSize; + DWORD cntUsage; + DWORD th32ProcessID; // this process + ULONG_PTR th32DefaultHeapID; + DWORD th32ModuleID; // associated exe + DWORD cntThreads; + DWORD th32ParentProcessID; // this process's parent process + LONG pcPriClassBase; // Base priority of process's threads + DWORD dwFlags; + WCHAR szExeFile[260]; // Path +} PROCESSENTRY32W; +typedef PROCESSENTRY32W * PPROCESSENTRY32W; +typedef PROCESSENTRY32W * LPPROCESSENTRY32W; + +typedef struct tagPROCESSENTRY32 +{ + DWORD dwSize; + DWORD cntUsage; + DWORD th32ProcessID; // this process + ULONG_PTR th32DefaultHeapID; + DWORD th32ModuleID; // associated exe + DWORD cntThreads; + DWORD th32ParentProcessID; // this process's parent process + LONG pcPriClassBase; // Base priority of process's threads + DWORD dwFlags; + CHAR szExeFile[260]; // Path +} PROCESSENTRY32; +typedef PROCESSENTRY32 * PPROCESSENTRY32; +typedef PROCESSENTRY32 * LPPROCESSENTRY32; + +FailOnFalse [gle] +Process32FirstW( + HANDLE hSnapshot, + [out] LPPROCESSENTRY32W lppe + ); + +FailOnFalse [gle] +Process32NextW( + HANDLE hSnapshot, + [out] LPPROCESSENTRY32W lppe + ); + +FailOnFalse [gle] +Process32First( + HANDLE hSnapshot, + [out] LPPROCESSENTRY32 lppe + ); + +FailOnFalse [gle] +Process32Next( + HANDLE hSnapshot, + [out] LPPROCESSENTRY32 lppe + ); + +typedef struct tagTHREADENTRY32 +{ + DWORD dwSize; + DWORD cntUsage; + DWORD th32ThreadID; // this thread + DWORD th32OwnerProcessID; // Process this thread is associated with + LONG tpBasePri; + LONG tpDeltaPri; + DWORD dwFlags; +} THREADENTRY32; +typedef THREADENTRY32 * PTHREADENTRY32; +typedef THREADENTRY32 * LPTHREADENTRY32; + +FailOnFalse [gle] +Thread32First( + HANDLE hSnapshot, + [out] LPTHREADENTRY32 lpte + ); + +FailOnFalse [gle] +Thread32Next( + HANDLE hSnapshot, + [out] LPTHREADENTRY32 lpte + ); + +typedef struct tagMODULEENTRY32W +{ + DWORD dwSize; + DWORD th32ModuleID; // This module + DWORD th32ProcessID; // owning process + DWORD GlblcntUsage; // Global usage count on the module + DWORD ProccntUsage; // Module usage count in th32ProcessID's context + BYTE * modBaseAddr; // Base address of module in th32ProcessID's context + DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr + HMODULE hModule; // The hModule of this module in th32ProcessID's context + WCHAR szModule[256]; + WCHAR szExePath[260]; +} MODULEENTRY32W; +typedef MODULEENTRY32W * PMODULEENTRY32W; +typedef MODULEENTRY32W * LPMODULEENTRY32W; + +FailOnFalse [gle] +Module32FirstW( + HANDLE hSnapshot, + [out] LPMODULEENTRY32W lpme + ); + +FailOnFalse [gle] +Module32NextW( + HANDLE hSnapshot, + [out] LPMODULEENTRY32W lpme + ); + + +typedef struct tagMODULEENTRY32 +{ + DWORD dwSize; + DWORD th32ModuleID; // This module + DWORD th32ProcessID; // owning process + DWORD GlblcntUsage; // Global usage count on the module + DWORD ProccntUsage; // Module usage count in th32ProcessID's context + BYTE * modBaseAddr; // Base address of module in th32ProcessID's context + DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr + HMODULE hModule; // The hModule of this module in th32ProcessID's context + char szModule[256]; + char szExePath[260]; +} MODULEENTRY32; +typedef MODULEENTRY32 * PMODULEENTRY32; +typedef MODULEENTRY32 * LPMODULEENTRY32; + +// +// NOTE CAREFULLY that the modBaseAddr and hModule fields are valid ONLY +// in th32ProcessID's process context. +// + +FailOnFalse [gle] +Module32First( + HANDLE hSnapshot, + [out] LPMODULEENTRY32 lpme + ); + +FailOnFalse [gle] +Module32Next( + HANDLE hSnapshot, + [out] LPMODULEENTRY32 lpme + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/dplay.h b/tools/Debugging Tools for Windows/winext/manifest/dplay.h new file mode 100644 index 0000000000..e13716dc16 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/dplay.h @@ -0,0 +1,1396 @@ +module DPLAYX.DLL: +category DirectPlay: + +/* + * GUIDS used by DirectPlay objects + */ + class __declspec(uuid("D1EB6D20-8923-11d0-9D97-00A0C90A43CB")) DirectPlay; + class __declspec(uuid("2b74f7c0-9154-11cf-a9cd-00aa006886e3")) IDirectPlay2; + class __declspec(uuid("9d460580-a822-11cf-960c-0080c7534e82")) IDirectPlay2A; + class __declspec(uuid("133efe40-32dc-11d0-9cfb-00a0c90a43cb")) IDirectPlay3; + class __declspec(uuid("133efe41-32dc-11d0-9cfb-00a0c90a43cb")) IDirectPlay3A; + class __declspec(uuid("0ab1c530-4745-11d1-a7a1-0000f803abfc")) IDirectPlay4; + class __declspec(uuid("0ab1c531-4745-11d1-a7a1-0000f803abfc")) IDirectPlay4A; + +struct __declspec(uuid("685BC400-9D2C-11cf-A9CD-00AA006886E3")) DPSPGUID_IPX; +struct __declspec(uuid("36E95EE0-8577-11cf-960C-0080C7534E82")) DPSPGUID_TCPIP; +struct __declspec(uuid("0F1D6860-88D9-11cf-9C4E-00A0C905425E")) DPSPGUID_SERIAL; +struct __declspec(uuid("44EAA760-CB68-11cf-9C4E-00A0C905425E")) DPSPGUID_MODEM; + +typedef IUnknown *LPDIRECTPLAY; +typedef IDirectPlay2 *LPDIRECTPLAY2; +typedef IDirectPlay2 *LPDIRECTPLAY2A; +typedef IDirectPlay2 IDirectPlay2A; + +typedef IDirectPlay3 *LPDIRECTPLAY3; +typedef IDirectPlay3 *LPDIRECTPLAY3A; +typedef IDirectPlay3 IDirectPlay3A; + +typedef IDirectPlay4 *LPDIRECTPLAY4; +typedef IDirectPlay4 *LPDIRECTPLAY4A; +typedef IDirectPlay4 IDirectPlay4A; + +/* + * DPID + * DirectPlay player and group ID + */ +typedef DWORD DPID; +typedef DWORD *LPDPID; + +/* +#define DPID_SYSMSG 0 +#define DPID_ALLPLAYERS 0 +#define DPID_SERVERPLAYER 1 +#define DPID_UNKNOWN 0xFFFFFFFF +*/ + +mask DWORD DPlayObjectFlags +{ +/* + * This DirectPlay object is the session host. If the host exits the + * session, another application will become the host and receive a + * DPSYS_HOST system message. + */ +#define DPCAPS_ISHOST 0x00000002 + +/* + * The service provider bound to this DirectPlay object can optimize + * group messaging. + */ +#define DPCAPS_GROUPOPTIMIZED 0x00000008 + +/* + * The service provider bound to this DirectPlay object can optimize + * keep alives (see DPSESSION_KEEPALIVE) + */ +#define DPCAPS_KEEPALIVEOPTIMIZED 0x00000010 + +/* + * The service provider bound to this DirectPlay object can optimize + * guaranteed message delivery. + */ +#define DPCAPS_GUARANTEEDOPTIMIZED 0x00000020 + +/* + * This DirectPlay object supports guaranteed message delivery. + */ +#define DPCAPS_GUARANTEEDSUPPORTED 0x00000040 + +/* + * This DirectPlay object supports digital signing of messages. + */ +#define DPCAPS_SIGNINGSUPPORTED 0x00000080 + +/* + * This DirectPlay object supports encryption of messages. + */ +#define DPCAPS_ENCRYPTIONSUPPORTED 0x00000100 + +}; + + +/* + * DPCAPS + * Used to obtain the capabilities of a DirectPlay object + */ +typedef struct _DPCAPS +{ + DWORD dwSize; // Size of structure, in bytes + DPlayObjectFlags dwFlags; // DPCAPS_xxx flags + DWORD dwMaxBufferSize; // Maximum message size, in bytes, for this service provider + DWORD dwMaxQueueSize; // Obsolete. + DWORD dwMaxPlayers; // Maximum players/groups (local + remote) + DWORD dwHundredBaud; // Bandwidth in 100 bits per second units; + // i.e. 24 is 2400, 96 is 9600, etc. + DWORD dwLatency; // Estimated latency; 0 = unknown + DWORD dwMaxLocalPlayers; // Maximum # of locally created players allowed + DWORD dwHeaderLength; // Maximum header length, in bytes, on messages + // added by the service provider + DWORD dwTimeout; // Service provider's suggested timeout value + // This is how long DirectPlay will wait for + // responses to system messages +} DPCAPS; +typedef DPCAPS *LPDPCAPS; + +/* + * LPCDPSESSIONDESC2 + * A constant pointer to DPSESSIONDESC2 + */ +typedef DPSESSIONDESC2 *LPCDPSESSIONDESC2; + +mask DWORD DPSESSION_Flags +{ +/* + * Applications cannot create new players in this session. + */ +#define DPSESSION_NEWPLAYERSDISABLED 0x00000001 + +/* + * If the DirectPlay object that created the session, the host, + * quits, then the host will attempt to migrate to another + * DirectPlay object so that new players can continue to be created + * and new applications can join the session. + */ +#define DPSESSION_MIGRATEHOST 0x00000004 + +/* + * This flag tells DirectPlay not to set the idPlayerTo and idPlayerFrom + * fields in player messages. This cuts two DWORD's off the message + * overhead. + */ +#define DPSESSION_NOMESSAGEID 0x00000008 + + +/* + * This flag tells DirectPlay to not allow any new applications to + * join the session. Applications already in the session can still + * create new players. + */ +#define DPSESSION_JOINDISABLED 0x00000020 + +/* + * This flag tells DirectPlay to detect when remote players + * exit abnormally (e.g. their computer or modem gets unplugged) + */ +#define DPSESSION_KEEPALIVE 0x00000040 + +/* + * This flag tells DirectPlay not to send a message to all players + * when a players remote data changes + */ +#define DPSESSION_NODATAMESSAGES 0x00000080 + +/* + * This flag indicates that the session belongs to a secure server + * and needs user authentication + */ +#define DPSESSION_SECURESERVER 0x00000100 + +/* + * This flag indicates that the session is private and requirs a password + * for EnumSessions as well as Open. + */ +#define DPSESSION_PRIVATE 0x00000200 + +/* + * This flag indicates that the session requires a password for joining. + */ +#define DPSESSION_PASSWORDREQUIRED 0x00000400 + +/* + * This flag tells DirectPlay to route all messages through the server + */ +#define DPSESSION_MULTICASTSERVER 0x00000800 + +/* + * This flag tells DirectPlay to only download information about the + * DPPLAYER_SERVERPLAYER. + */ +#define DPSESSION_CLIENTSERVER 0x00001000 + +/* + * This flag tells DirectPlay to use the protocol built into dplay + * for reliability and statistics all the time. When this bit is + * set, only other sessions with this bit set can join or be joined. + */ +#define DPSESSION_DIRECTPLAYPROTOCOL 0x00002000 + +/* + * This flag tells DirectPlay that preserving order of received + * packets is not important, when using reliable delivery. This + * will allow messages to be indicated out of order if preceding + * messages have not yet arrived. Otherwise DPLAY will wait for + * earlier messages before delivering later reliable messages. + */ +#define DPSESSION_NOPRESERVEORDER 0x00004000 + +}; + + +/* + * DPSESSIONDESC2 + * Used to describe the properties of a DirectPlay + * session instance + */ +typedef struct _DPSESSIONDESC2 +{ + DWORD dwSize; // Size of structure + DWORD dwFlags; // DPSESSION_xxx flags + GUID guidInstance; // ID for the session instance + GUID guidApplication; // GUID of the DirectPlay application. + // GUID_NULL for all applications. + DWORD dwMaxPlayers; // Maximum # players allowed in session + DWORD dwCurrentPlayers; // Current # players in session (read only) +// union +// { // Name of the session +// LPWSTR lpszSessionName; // Unicode + LPSTR lpszSessionNameA; // ANSI +// }; +// union +// { // Password of the session (optional) +// LPWSTR lpszPassword; // Unicode + LPSTR lpszPasswordA; // ANSI +// }; + DWORD dwReserved1; // Reserved for future MS use. + DWORD dwReserved2; + DWORD dwUser1; // For use by the application + DWORD dwUser2; + DWORD dwUser3; + DWORD dwUser4; +} DPSESSIONDESC2; +typedef DPSESSIONDESC2 *LPDPSESSIONDESC2; + +/* + * DPNAME + * Used to hold the name of a DirectPlay entity + * like a player or a group + */ +typedef struct _DPNAME +{ + DWORD dwSize; // Size of structure + DWORD dwFlags; // Not used. Must be zero. +// union +// { // The short or friendly name +// LPWSTR lpszShortName; // Unicode + LPSTR lpszShortNameA; // ANSI +// }; +// union +// { // The long or formal name +// LPWSTR lpszLongName; // Unicode + LPSTR lpszLongNameA; // ANSI +// }; + +} DPNAME; +typedef DPNAME *LPDPNAME; + +/* + * LPCDPNAME + * A constant pointer to DPNAME + */ +typedef DPNAME *LPCDPNAME; + +/* + * DPCREDENTIALS + * Used to hold the user name and password of a DirectPlay user + */ +typedef struct _DPCREDENTIALS +{ + DWORD dwSize; // Size of structure + DWORD dwFlags; // Not used. Must be zero. +// union +// { // User name of the account +// LPWSTR lpszUsername; // Unicode + LPSTR lpszUsernameA; // ANSI +// }; +// union +// { // Password of the account +// LPWSTR lpszPassword; // Unicode + LPSTR lpszPasswordA; // ANSI +// }; +// union +// { // Domain name of the account +// LPWSTR lpszDomain; // Unicode + LPSTR lpszDomainA; // ANSI +// }; +} DPCREDENTIALS; +typedef DPCREDENTIALS *LPDPCREDENTIALS; + +typedef DPCREDENTIALS *LPCDPCREDENTIALS; + +/* + * DPSECURITYDESC + * Used to describe the security properties of a DirectPlay + * session instance + */ +typedef struct _DPSECURITYDESC +{ + DWORD dwSize; // Size of structure + DWORD dwFlags; // Not used. Must be zero. +// union +// { // SSPI provider name +// LPWSTR lpszSSPIProvider; // Unicode + LPSTR lpszSSPIProviderA; // ANSI +// }; +// union +// { // CAPI provider name +// LPWSTR lpszCAPIProvider; // Unicode + LPSTR lpszCAPIProviderA; // ANSI +// }; + DWORD dwCAPIProviderType; // Crypto Service Provider type + DWORD dwEncryptionAlgorithm; // Encryption Algorithm type +} DPSECURITYDESC; +typedef DPSECURITYDESC *LPDPSECURITYDESC; + +typedef DPSECURITYDESC *LPCDPSECURITYDESC; + +/* + * DPACCOUNTDESC + * Used to describe a user membership account + */ +typedef struct _DPACCOUNTDESC +{ + DWORD dwSize; // Size of structure + DWORD dwFlags; // Not used. Must be zero. +// union +// { // Account identifier +// LPWSTR lpszAccountID; // Unicode + LPSTR lpszAccountIDA; // ANSI +// }; +} DPACCOUNTDESC; +typedef DPACCOUNTDESC *LPDPACCOUNTDESC; + +typedef DPACCOUNTDESC *LPCDPACCOUNTDESC; + +/* + * LPCGUID + * A constant pointer to a guid + */ +typedef GUID *LPCGUID; + +/**************************************************************************** + * + * DPLCONNECTION flags + * + ****************************************************************************/ +mask DWORD DPLCONNECTIONFlags +{ + +/* + * This application should create a new session as + * described by the DPSESIONDESC structure + */ +#define DPLCONNECTION_CREATESESSION 0x00000002 + +/* + * This application should join the session described by + * the DPSESIONDESC structure with the lpAddress data + */ +#define DPLCONNECTION_JOINSESSION 0x00000001 + +}; + +/* + * DPLCONNECTION + * Used to hold all in the informaion needed to connect + * an application to a session or create a session + */ +typedef struct _DPLCONNECTION +{ + DWORD dwSize; // Size of this structure + DPLCONNECTIONFlags dwFlags; // Flags specific to this structure + LPDPSESSIONDESC2 lpSessionDesc; // Pointer to session desc to use on connect + LPDPNAME lpPlayerName; // Pointer to Player name structure + GUID guidSP; // GUID of the DPlay SP to use + LPVOID lpAddress; // Address for service provider + DWORD dwAddressSize; // Size of address data +} DPLCONNECTION; +typedef DPLCONNECTION *LPDPLCONNECTION; + +/* + * LPCDPLCONNECTION + * A constant pointer to DPLCONNECTION + */ +typedef DPLCONNECTION *LPCDPLCONNECTION; + +/* + * DPCHAT + * Used to hold the a DirectPlay chat message + */ +typedef struct _DPCHAT +{ + DWORD dwSize; + DWORD dwFlags; +// union +// { // Message string +// LPWSTR lpszMessage; // Unicode + LPSTR lpszMessageA; // ANSI +// }; +} DPCHAT; +typedef DPCHAT * LPDPCHAT; + +/* + * SGBUFFER + * Scatter Gather Buffer used for SendEx + */ +typedef struct _SGBUFFER +{ + UINT len; // length of buffer data + //PUCHAR pData; // pointer to buffer data + CHAR * pData; // pointer to buffer data +} SGBUFFER; +typedef SGBUFFER *PSGBUFFER; +typedef SGBUFFER *LPSGBUFFER; + + +value DWORD DPRESULT +{ + +/**************************************************************************** + * + * DIRECTPLAY ERRORS + * + * Errors are represented by negative values and cannot be combined. + * + ****************************************************************************/ +#define DP_OK 0 +#define DPERR_ALREADYINITIALIZED 0x88770005L [fail] +#define DPERR_ACCESSDENIED 0x8877000AL [fail] +#define DPERR_ACTIVEPLAYERS 0x88770014L [fail] +#define DPERR_BUFFERTOOSMALL 0x8877001EL [fail] +#define DPERR_CANTADDPLAYER 0x88770028L [fail] +#define DPERR_CANTCREATEGROUP 0x88770032L [fail] +#define DPERR_CANTCREATEPLAYER 0x8877003CL [fail] +#define DPERR_CANTCREATESESSION 0x88770046L [fail] +#define DPERR_CAPSNOTAVAILABLEYET 0x88770050L [fail] +#define DPERR_EXCEPTION 0x8877005AL [fail] +#define DPERR_GENERIC 0x80004005L [fail] +#define DPERR_INVALIDFLAGS 0x88770078L [fail] +#define DPERR_INVALIDOBJECT 0x88770082L [fail] +#define DPERR_INVALIDPARAM 0x80070057L [fail] +#define DPERR_INVALIDPARAMS 0x80070057L [fail] +#define DPERR_INVALIDPLAYER 0x88770096L [fail] +#define DPERR_INVALIDGROUP 0x8877009BL [fail] +#define DPERR_NOCAPS 0x887700A0L [fail] +#define DPERR_NOCONNECTION 0x887700AAL [fail] +#define DPERR_NOMEMORY 0x8007000EL [fail] +#define DPERR_OUTOFMEMORY 0x8007000EL [fail] +#define DPERR_NOMESSAGES 0x887700BEL [fail] +#define DPERR_NONAMESERVERFOUND 0x887700C8L [fail] +#define DPERR_NOPLAYERS 0x887700D2L [fail] +#define DPERR_NOSESSIONS 0x887700DCL [fail] +#define DPERR_PENDING 0x8000000AL [fail] +#define DPERR_SENDTOOBIG 0x887700E6L [fail] +#define DPERR_TIMEOUT 0x887700F0L [fail] +#define DPERR_UNAVAILABLE 0x887700FAL [fail] +#define DPERR_UNSUPPORTED 0x80004001L [fail] +#define DPERR_BUSY 0x8877010EL [fail] +#define DPERR_USERCANCEL 0x88770118L [fail] +#define DPERR_NOINTERFACE 0x80004002L [fail] +#define DPERR_CANNOTCREATESERVER 0x88770122L [fail] +#define DPERR_PLAYERLOST 0x8877012CL [fail] +#define DPERR_SESSIONLOST 0x88770136L [fail] +#define DPERR_UNINITIALIZED 0x88770140L [fail] +#define DPERR_NONEWPLAYERS 0x8877013AL [fail] +#define DPERR_INVALIDPASSWORD 0x88770154L [fail] +#define DPERR_CONNECTING 0x8877015EL [fail] +#define DPERR_CONNECTIONLOST 0x88770168L [fail] +#define DPERR_UNKNOWNMESSAGE 0x88770172L [fail] +#define DPERR_CANCELFAILED 0x8877017CL [fail] +#define DPERR_INVALIDPRIORITY 0x88770186L [fail] +#define DPERR_NOTHANDLED 0x88770190L [fail] +#define DPERR_CANCELLED 0x8877019AL [fail] +#define DPERR_ABORTED 0x887701A4L [fail] + + +#define DPERR_BUFFERTOOLARGE 0x887703E8L [fail] +#define DPERR_CANTCREATEPROCESS 0x887703F2L [fail] +#define DPERR_APPNOTSTARTED 0x887703FCL [fail] +#define DPERR_INVALIDINTERFACE 0x88770406L [fail] +#define DPERR_NOSERVICEPROVIDER 0x88770410L [fail] +#define DPERR_UNKNOWNAPPLICATION 0x8877041AL [fail] +#define DPERR_NOTLOBBIED 0x8877042EL [fail] +#define DPERR_SERVICEPROVIDERLOADED 0x88770438L [fail] +#define DPERR_ALREADYREGISTERED 0x88770442L [fail] +#define DPERR_NOTREGISTERED 0x8877044CL [fail] + +// +// Security related errors +// +#define DPERR_AUTHENTICATIONFAILED 0x887707D0L [fail] +#define DPERR_CANTLOADSSPI 0x887707DAL [fail] +#define DPERR_ENCRYPTIONFAILED 0x887707E4L [fail] +#define DPERR_SIGNFAILED 0x887707EEL [fail] +#define DPERR_CANTLOADSECURITYPACKAGE 0x887707F8L [fail] +#define DPERR_ENCRYPTIONNOTSUPPORTED 0x88770802L [fail] +#define DPERR_CANTLOADCAPI 0x8877080CL [fail] +#define DPERR_NOTLOGGEDIN 0x88770816L [fail] +#define DPERR_LOGONDENIED 0x88770820L [fail] + +}; + +/**************************************************************************** + * + * Prototypes for DirectPlay callback functions + * + ****************************************************************************/ + +typedef LPVOID LPDPENUMSESSIONSCALLBACK2; +typedef LPVOID LPDPENUMPLAYERSCALLBACK2; +typedef LPVOID LPDPENUMDPCALLBACK; +typedef LPVOID LPDPENUMDPCALLBACKA; +typedef LPVOID LPDPENUMCONNECTIONSCALLBACK; + + +/**************************************************************************** + * + * EnumConnections API flags + * + ****************************************************************************/ +mask DWORD EnumConnectionsFlags +{ +/* + * Enumerate Service Providers + */ +#define DPCONNECTION_DIRECTPLAY 0x00000001 + +/* + * Enumerate Lobby Providers + */ +#define DPCONNECTION_DIRECTPLAYLOBBY 0x00000002 + +}; + +/**************************************************************************** + * + * EnumPlayers API flags + * + ****************************************************************************/ +mask DWORD EnumPlayersMask +{ +/* + * Enumerate all players in the current session + */ +#define DPENUMPLAYERS_ALL 0x00000000 +//#define DPENUMGROUPS_ALL DPENUMPLAYERS_ALL + + +/* + * Enumerate only local (created by this application) players + * or groups + */ +#define DPENUMPLAYERS_LOCAL 0x00000008 +//#define DPENUMGROUPS_LOCAL DPENUMPLAYERS_LOCAL + +/* + * Enumerate only remote (non-local) players + * or groups + */ +#define DPENUMPLAYERS_REMOTE 0x00000010 +//#define DPENUMGROUPS_REMOTE DPENUMPLAYERS_REMOTE + +/* + * Enumerate groups along with the players + */ +#define DPENUMPLAYERS_GROUP 0x00000020 + +/* + * Enumerate players or groups in another session + * (must supply lpguidInstance) + */ +#define DPENUMPLAYERS_SESSION 0x00000080 +//#define DPENUMGROUPS_SESSION DPENUMPLAYERS_SESSION + +/* + * Enumerate server players + */ +#define DPENUMPLAYERS_SERVERPLAYER 0x00000100 + +/* + * Enumerate spectator players + */ +#define DPENUMPLAYERS_SPECTATOR 0x00000200 + +/* + * Enumerate shortcut groups + */ +#define DPENUMGROUPS_SHORTCUT 0x00000400 + +/* + * Enumerate staging area groups + */ +#define DPENUMGROUPS_STAGINGAREA 0x00000800 +/* + * Enumerate hidden groups + */ +#define DPENUMGROUPS_HIDDEN 0x00001000 + +/* + * Enumerate the group's owner + */ +#define DPENUMPLAYERS_OWNER 0x00002000 + +}; + +/**************************************************************************** + * + * CreatePlayer API flags + * + ****************************************************************************/ + +mask DWORD CreatePlayerFlags +{ +/* + * This flag indicates that this player should be designated + * the server player. The app should specify this at CreatePlayer. + */ +#define DPPLAYER_SERVERPLAYER 0x00000100 + +/* + * This flag indicates that this player should be designated + * a spectator. The app should specify this at CreatePlayer. + */ +#define DPPLAYER_SPECTATOR 0x00000200 + +/* + * This flag indicates that this player was created locally. + * (returned from GetPlayerFlags) + */ +#define DPPLAYER_LOCAL 0x00000008 + +/* + * This flag indicates that this player is the group's owner + * (Only returned in EnumGroupPlayers) + */ +#define DPPLAYER_OWNER 0x00002000 + +}; + +/**************************************************************************** + * + * CreateGroup API flags + * + ****************************************************************************/ + + +mask DWORD CreateGroupFlags +{ +/* + * This flag indicates that the StartSession can be called on the group. + * The app should specify this at CreateGroup, or CreateGroupInGroup. + */ +#define DPGROUP_STAGINGAREA 0x00000800 + +/* + * This flag indicates that this group was created locally. + * (returned from GetGroupFlags) + */ +#define DPGROUP_LOCAL 0x00000008 + +/* + * This flag indicates that this group was created hidden. + */ +#define DPGROUP_HIDDEN 0x00001000 +}; + +/**************************************************************************** + * + * EnumSessions API flags + * + ****************************************************************************/ + +mask DWORD EnumSessionsFlags +{ +/* + * Enumerate sessions which can be joined + */ +#define DPENUMSESSIONS_AVAILABLE 0x00000001 + +/* + * Enumerate all sessions even if they can't be joined. + */ +#define DPENUMSESSIONS_ALL 0x00000002 + + +/* + * Start an asynchronous enum sessions + */ + #define DPENUMSESSIONS_ASYNC 0x00000010 + +/* + * Stop an asynchronous enum sessions + */ + #define DPENUMSESSIONS_STOPASYNC 0x00000020 + +/* + * Enumerate sessions even if they require a password + */ + #define DPENUMSESSIONS_PASSWORDREQUIRED 0x00000040 + +/* + * Return status about progress of enumeration instead of + * showing any status dialogs. + */ + #define DPENUMSESSIONS_RETURNSTATUS 0x00000080 +}; + +/**************************************************************************** + * + * GetCaps and GetPlayerCaps API flags + * + ****************************************************************************/ +mask DWORD GetCapsFlags +{ +/* + * The latency returned should be for guaranteed message sending. + * Default is non-guaranteed messaging. + */ +#define DPGETCAPS_GUARANTEED 0x00000001 + +}; + +/**************************************************************************** + * + * GetGroupData, GetPlayerData API flags + * Remote and local Group/Player data is maintained separately. + * Default is DPGET_REMOTE. + * + ****************************************************************************/ + +mask DWORD GetDataFlags +{ +/* + * Get the remote data (set by any DirectPlay object in + * the session using DPSET_REMOTE) + */ +#define DPGET_REMOTE 0x00000000 + +/* + * Get the local data (set by this DirectPlay object + * using DPSET_LOCAL) + */ +#define DPGET_LOCAL 0x00000001 + +}; + +/**************************************************************************** + * + * Open API flags + * + ****************************************************************************/ + +mask DWORD OpenFlags +{ +/* + * Join the session that is described by the DPSESSIONDESC2 structure + */ +#define DPOPEN_JOIN 0x00000001 + +/* + * Create a new session as described by the DPSESSIONDESC2 structure + */ +#define DPOPEN_CREATE 0x00000002 + +/* + * Return status about progress of open instead of showing + * any status dialogs. + */ +#define DPOPEN_RETURNSTATUS 0x00000080L + +}; + +/**************************************************************************** + * + * Receive API flags + * Default is DPRECEIVE_ALL + * + ****************************************************************************/ +mask DWORD ReceiveFlags +{ +/* + * Get the first message in the queue + */ +#define DPRECEIVE_ALL 0x00000001 + +/* + * Get the first message in the queue directed to a specific player + */ +#define DPRECEIVE_TOPLAYER 0x00000002 + +/* + * Get the first message in the queue from a specific player + */ +#define DPRECEIVE_FROMPLAYER 0x00000004 + +/* + * Get the message but don't remove it from the queue + */ +#define DPRECEIVE_PEEK 0x00000008 + +}; + +/**************************************************************************** + * + * Send API flags + * + ****************************************************************************/ +mask DWORD SendFlags +{ + +/* + * Send the message using a guaranteed send method. + * Default is non-guaranteed. + */ +#define DPSEND_GUARANTEED 0x00000001 + + +/* + * This flag is obsolete. It is ignored by DirectPlay + */ +#define DPSEND_HIGHPRIORITY 0x00000002 + +/* + * This flag is obsolete. It is ignored by DirectPlay + */ +#define DPSEND_OPENSTREAM 0x00000008 + +/* + * This flag is obsolete. It is ignored by DirectPlay + */ +#define DPSEND_CLOSESTREAM 0x00000010 + +/* + * Send the message digitally signed to ensure authenticity. + */ +#define DPSEND_SIGNED 0x00000020 + +/* + * Send the message with encryption to ensure privacy. + */ +#define DPSEND_ENCRYPTED 0x00000040 + +/* + * The message is a lobby system message + */ +#define DPSEND_LOBBYSYSTEMMESSAGE 0x00000080 + + +/* + * Send message asynchronously, must check caps + * before using this flag. It is always provided + * if the protocol flag is set. + */ +#define DPSEND_ASYNC 0x00000200 + +/* + * When an message is completed, don't tell me. + * by default the application is notified with a system message. + */ +#define DPSEND_NOSENDCOMPLETEMSG 0x00000400 + + +/* + * Maximum priority for sends available to applications + */ +#define DPSEND_MAX_PRIORITY 0x0000FFFF + +}; + +/**************************************************************************** + * + * SetGroupData, SetGroupName, SetPlayerData, SetPlayerName, + * SetSessionDesc API flags. + * Default is DPSET_REMOTE. + * + ****************************************************************************/ + +mask DWORD SetDataFlags +{ +/* + * Propagate the data to all players in the session + */ +#define DPSET_REMOTE 0x00000000 + +/* + * Do not propagate the data to other players + */ +#define DPSET_LOCAL 0x00000001 + +/* + * Used with DPSET_REMOTE, use guaranteed message send to + * propagate the data + */ +#define DPSET_GUARANTEED 0x00000002 + +}; + +/**************************************************************************** + * + * GetMessageQueue API flags. + * Default is DPMESSAGEQUEUE_SEND + * + ****************************************************************************/ +mask DWORD GetMessageQueueFlags +{ +/* + * Get Send Queue - requires Service Provider Support + */ +#define DPMESSAGEQUEUE_SEND 0x00000001 + +/* + * Get Receive Queue + */ +#define DPMESSAGEQUEUE_RECEIVE 0x00000002 + +}; + +/**************************************************************************** + * + * Connect API flags + * + ****************************************************************************/ + + +/* + * Start an asynchronous connect which returns status codes + */ +//#define DPCONNECT_RETURNSTATUS 0x00000080 + + +/**************************************************************************** + * + * DirectPlay system messages and message data structures + * + * All system message come 'From' player DPID_SYSMSG. To determine what type + * of message it is, cast the lpData from Receive to DPMSG_GENERIC and check + * the dwType member against one of the following DPSYS_xxx constants. Once + * a match is found, cast the lpData to the corresponding of the DPMSG_xxx + * structures to access the data of the message. + * + ****************************************************************************/ + +value DWORD DirectPlayMessages +{ +/* + * A new player or group has been created in the session + * Use DPMSG_CREATEPLAYERORGROUP. Check dwPlayerType to see if it + * is a player or a group. + */ +#define DPSYS_CREATEPLAYERORGROUP 0x0003 + +/* + * A player has been deleted from the session + * Use DPMSG_DESTROYPLAYERORGROUP + */ +#define DPSYS_DESTROYPLAYERORGROUP 0x0005 + +/* + * A player has been added to a group + * Use DPMSG_ADDPLAYERTOGROUP + */ +#define DPSYS_ADDPLAYERTOGROUP 0x0007 + +/* + * A player has been removed from a group + * Use DPMSG_DELETEPLAYERFROMGROUP + */ +#define DPSYS_DELETEPLAYERFROMGROUP 0x0021 + +/* + * This DirectPlay object lost its connection with all the + * other players in the session. + * Use DPMSG_SESSIONLOST. + */ +#define DPSYS_SESSIONLOST 0x0031 + +/* + * The current host has left the session. + * This DirectPlay object is now the host. + * Use DPMSG_HOST. + */ +#define DPSYS_HOST 0x0101 + +/* + * The remote data associated with a player or + * group has changed. Check dwPlayerType to see + * if it is a player or a group + * Use DPMSG_SETPLAYERORGROUPDATA + */ +#define DPSYS_SETPLAYERORGROUPDATA 0x0102 + +/* + * The name of a player or group has changed. + * Check dwPlayerType to see if it is a player + * or a group. + * Use DPMSG_SETPLAYERORGROUPNAME + */ +#define DPSYS_SETPLAYERORGROUPNAME 0x0103 + +/* + * The session description has changed. + * Use DPMSG_SETSESSIONDESC + */ +#define DPSYS_SETSESSIONDESC 0x0104 + +/* + * A group has been added to a group + * Use DPMSG_ADDGROUPTOGROUP + */ +#define DPSYS_ADDGROUPTOGROUP 0x0105 + +/* + * A group has been removed from a group + * Use DPMSG_DELETEGROUPFROMGROUP + */ +#define DPSYS_DELETEGROUPFROMGROUP 0x0106 + +/* + * A secure player-player message has arrived. + * Use DPMSG_SECUREMESSAGE + */ +#define DPSYS_SECUREMESSAGE 0x0107 + +/* + * Start a new session. + * Use DPMSG_STARTSESSION + */ +#define DPSYS_STARTSESSION 0x0108 + +/* + * A chat message has arrived + * Use DPMSG_CHAT + */ +#define DPSYS_CHAT 0x0109 + +/* + * The owner of a group has changed + * Use DPMSG_SETGROUPOWNER + */ +#define DPSYS_SETGROUPOWNER 0x010A + +/* + * An async send has finished, failed or been cancelled + * Use DPMSG_SENDCOMPLETE + */ +#define DPSYS_SENDCOMPLETE 0x010d + +}; + + +value DWORD PlayerTypeValue +{ +/* + * Used in the dwPlayerType field to indicate if it applies to a group + * or a player + */ +#define DPPLAYERTYPE_GROUP 0x00000000 +#define DPPLAYERTYPE_PLAYER 0x00000001 + +}; + + +/* + * DPMSG_GENERIC + * Generic message structure used to identify the message type. + */ +typedef struct _DPMSG_GENERIC +{ + DirectPlayMessages dwType; // Message type +} DPMSG_GENERIC; +typedef DPMSG_GENERIC *LPDPMSG_GENERIC; + +/* + * DPMSG_CREATEPLAYERORGROUP + * System message generated when a new player or group + * created in the session with information about it. + */ +typedef struct _DPMSG_CREATEPLAYERORGROUP +{ + DWORD dwType; // Message type + PlayerTypeValue dwPlayerType; // Is it a player or group + DPID dpId; // ID of the player or group + DWORD dwCurrentPlayers; // current # players & groups in session + LPVOID lpData; // pointer to remote data + DWORD dwDataSize; // size of remote data + DPNAME dpnName; // structure with name info + // the following fields are only available when using + // the IDirectPlay3 interface or greater + DPID dpIdParent; // id of parent group + DWORD dwFlags; // player or group flags +} DPMSG_CREATEPLAYERORGROUP; +typedef DPMSG_CREATEPLAYERORGROUP *LPDPMSG_CREATEPLAYERORGROUP; + +/* + * DPMSG_DESTROYPLAYERORGROUP + * System message generated when a player or group is being + * destroyed in the session with information about it. + */ +typedef struct _DPMSG_DESTROYPLAYERORGROUP +{ + DWORD dwType; // Message type + DWORD dwPlayerType; // Is it a player or group + DPID dpId; // player ID being deleted + LPVOID lpLocalData; // copy of players local data + DWORD dwLocalDataSize; // sizeof local data + LPVOID lpRemoteData; // copy of players remote data + DWORD dwRemoteDataSize; // sizeof remote data + // the following fields are only available when using + // the IDirectPlay3 interface or greater + DPNAME dpnName; // structure with name info + DPID dpIdParent; // id of parent group + DWORD dwFlags; // player or group flags +} DPMSG_DESTROYPLAYERORGROUP; +typedef DPMSG_DESTROYPLAYERORGROUP *LPDPMSG_DESTROYPLAYERORGROUP; + +/* + * DPMSG_ADDPLAYERTOGROUP + * System message generated when a player is being added + * to a group. + */ +typedef struct _DPMSG_ADDPLAYERTOGROUP +{ + DWORD dwType; // Message type + DPID dpIdGroup; // group ID being added to + DPID dpIdPlayer; // player ID being added +} DPMSG_ADDPLAYERTOGROUP; +typedef DPMSG_ADDPLAYERTOGROUP *LPDPMSG_ADDPLAYERTOGROUP; + +/* + * DPMSG_DELETEPLAYERFROMGROUP + * System message generated when a player is being + * removed from a group + */ +typedef DPMSG_ADDPLAYERTOGROUP DPMSG_DELETEPLAYERFROMGROUP; +typedef DPMSG_DELETEPLAYERFROMGROUP *LPDPMSG_DELETEPLAYERFROMGROUP; + +/* + * DPMSG_ADDGROUPTOGROUP + * System message generated when a group is being added + * to a group. + */ +typedef struct _DPMSG_ADDGROUPTOGROUP +{ + DWORD dwType; // Message type + DPID dpIdParentGroup; // group ID being added to + DPID dpIdGroup; // group ID being added +} DPMSG_ADDGROUPTOGROUP; +typedef DPMSG_ADDGROUPTOGROUP *LPDPMSG_ADDGROUPTOGROUP; + +/* + * DPMSG_DELETEGROUPFROMGROUP + * System message generated when a GROUP is being + * removed from a group + */ +typedef DPMSG_ADDGROUPTOGROUP DPMSG_DELETEGROUPFROMGROUP; +typedef DPMSG_DELETEGROUPFROMGROUP *LPDPMSG_DELETEGROUPFROMGROUP; + +/* + * DPMSG_SETPLAYERORGROUPDATA + * System message generated when remote data for a player or + * group has changed. + */ +typedef struct _DPMSG_SETPLAYERORGROUPDATA +{ + DWORD dwType; // Message type + DWORD dwPlayerType; // Is it a player or group + DPID dpId; // ID of player or group + LPVOID lpData; // pointer to remote data + DWORD dwDataSize; // size of remote data +} DPMSG_SETPLAYERORGROUPDATA; +typedef DPMSG_SETPLAYERORGROUPDATA *LPDPMSG_SETPLAYERORGROUPDATA; + +/* + * DPMSG_SETPLAYERORGROUPNAME + * System message generated when the name of a player or + * group has changed. + */ +typedef struct _DPMSG_SETPLAYERORGROUPNAME +{ + DWORD dwType; // Message type + DWORD dwPlayerType; // Is it a player or group + DPID dpId; // ID of player or group + DPNAME dpnName; // structure with new name info +} DPMSG_SETPLAYERORGROUPNAME; +typedef DPMSG_SETPLAYERORGROUPNAME *LPDPMSG_SETPLAYERORGROUPNAME; + +/* + * DPMSG_SETSESSIONDESC + * System message generated when session desc has changed + */ +typedef struct _DPMSG_SETSESSIONDESC +{ + DWORD dwType; // Message type + DPSESSIONDESC2 dpDesc; // Session desc +} DPMSG_SETSESSIONDESC; +typedef DPMSG_SETSESSIONDESC *LPDPMSG_SETSESSIONDESC; + +/* + * DPMSG_HOST + * System message generated when the host has migrated to this + * DirectPlay object. + * + */ +typedef DPMSG_GENERIC DPMSG_HOST; +typedef DPMSG_HOST *LPDPMSG_HOST; + +/* + * DPMSG_SESSIONLOST + * System message generated when the connection to the session is lost. + * + */ +typedef DPMSG_GENERIC DPMSG_SESSIONLOST; +typedef DPMSG_SESSIONLOST *LPDPMSG_SESSIONLOST; + +/* + * DPMSG_SECUREMESSAGE + * System message generated when a player requests a secure send + */ +typedef struct _DPMSG_SECUREMESSAGE +{ + DWORD dwType; // Message Type + DWORD dwFlags; // Signed/Encrypted + DPID dpIdFrom; // ID of Sending Player + LPVOID lpData; // Player message + DWORD dwDataSize; // Size of player message +} DPMSG_SECUREMESSAGE; +typedef DPMSG_SECUREMESSAGE *LPDPMSG_SECUREMESSAGE; + +/* + * DPMSG_STARTSESSION + * System message containing all information required to + * start a new session + */ +typedef struct _DPMSG_STARTSESSION +{ + DWORD dwType; // Message type + LPDPLCONNECTION lpConn; // DPLCONNECTION structure +} DPMSG_STARTSESSION; +typedef DPMSG_STARTSESSION *LPDPMSG_STARTSESSION; + +/* + * DPMSG_CHAT + * System message containing a chat message + */ +typedef struct _DPMSG_CHAT +{ + DWORD dwType; // Message type + DWORD dwFlags; // Message flags + DPID idFromPlayer; // ID of the Sending Player + DPID idToPlayer; // ID of the To Player + DPID idToGroup; // ID of the To Group + LPDPCHAT lpChat; // Pointer to a structure containing the chat message +} DPMSG_CHAT; +typedef DPMSG_CHAT *LPDPMSG_CHAT; + +/* + * DPMSG_SETGROUPOWNER + * System message generated when the owner of a group has changed + */ +typedef struct _DPMSG_SETGROUPOWNER +{ + DWORD dwType; // Message type + DPID idGroup; // ID of the group + DPID idNewOwner; // ID of the player that is the new owner + DPID idOldOwner; // ID of the player that used to be the owner +} DPMSG_SETGROUPOWNER; +typedef DPMSG_SETGROUPOWNER *LPDPMSG_SETGROUPOWNER; + +/* + * DPMSG_SENDCOMPLETE + * System message generated when finished with an Async Send message + * + * NOTE SENDPARMS has an overlay for DPMSG_SENDCOMPLETE, don't + * change this message w/o changing SENDPARMS. + */ +typedef struct _DPMSG_SENDCOMPLETE +{ + DWORD dwType; + DPID idFrom; + DPID idTo; + DWORD dwFlags; + DWORD dwPriority; + DWORD dwTimeout; + LPVOID lpvContext; + DWORD dwMsgID; + DPRESULT hr; + DWORD dwSendTime; +} DPMSG_SENDCOMPLETE; +typedef DPMSG_SENDCOMPLETE *LPDPMSG_SENDCOMPLETE; + + + +/**************************************************************************** + * + * IDirectPlay2 (and IDirectPlay2A) Interface + * + ****************************************************************************/ + + +interface IDirectPlay2 : IUnknown +{ + /*** IDirectPlay2 methods ***/ + DPRESULT AddPlayerToGroup(DPID idGroup, DPID idPlayer ); + DPRESULT Close(); + DPRESULT CreateGroup( LPDPID lpidGroup, LPDPNAME lpGroupName, LPVOID lpData, DWORD dwDataSize, CreateGroupFlags dwFlags); + DPRESULT CreatePlayer( LPDPID lpidPlayer, LPDPNAME lpPlayerName, HANDLE hEvent, LPVOID lpData, DWORD dwDataSize, CreatePlayerFlags dwFlags); + DPRESULT DeletePlayerFromGroup(DPID idGroup, DPID idPlayer ); + DPRESULT DestroyGroup( DPID idGroup); + DPRESULT DestroyPlayer( DPID idPlayer); + DPRESULT EnumGroupPlayers( DPID idGroup, LPGUID lpguidInstance, LPDPENUMPLAYERSCALLBACK2 lpEnumPlayersCallback2, LPVOID lpContext, DWORD dwFlags); + DPRESULT EnumGroups( LPGUID lpguidInstance, LPDPENUMPLAYERSCALLBACK2 lpEnumPlayersCallback2, LPVOID lpContext, DWORD dwFlags ); + DPRESULT EnumPlayers(LPGUID lpguidInstance, LPDPENUMPLAYERSCALLBACK2 lpEnumPlayersCallback2, LPVOID lpContext, EnumPlayersMask dwFlags); + DPRESULT EnumSessions(LPDPSESSIONDESC2 lpsd, DWORD dwTimeout, LPDPENUMSESSIONSCALLBACK2 lpEnumSessionsCallback2, LPVOID lpContext, EnumSessionsFlags dwFlags ); + DPRESULT GetCaps( [out] LPDPCAPS lpDPCaps, GetCapsFlags dwFlags); + DPRESULT GetGroupData( DPID idGroup, [out] LPVOID lpData, [out] LPDWORD lpdwDataSize, GetDataFlags dwFlags); + DPRESULT GetGroupName( DPID idGroup, [out] LPVOID lpData, [out] LPDWORD lpdwDataSize); + DPRESULT GetMessageCount( DPID idPlayer, [out] LPDWORD lpdwCount ); + DPRESULT GetPlayerAddress( DPID idPlayer, [out] LPVOID lpData, [out] LPDWORD lpdwDataSize ); + DPRESULT GetPlayerCaps( DPID idPlayer, [out] LPDPCAPS lpPlayerCaps , GetCapsFlags dwFlags); + DPRESULT GetPlayerData( DPID idPlayerD, [out] LPVOID lpData, [out] LPDWORD lpdwDataSize, GetDataFlags dwFlags); + DPRESULT GetPlayerName( DPID idPlayerD, [out] LPVOID lpData, [out] LPDWORD lpdwDataSize ); + DPRESULT GetSessionDesc( [out] LPVOID lpData, [out] LPDWORD lpdwDataSize ); + DPRESULT Initialize( LPGUID lpGUID ); + DPRESULT Open( LPDPSESSIONDESC2 lpsd , OpenFlags dwFlags); + DPRESULT Receive( LPDPID lpidFrom, LPDPID lpidTo, ReceiveFlags dwFlags, [out] LPVOID lpData, [out] LPDWORD lpdwDataSize ); + DPRESULT Send( DPID idFrom, DPID idTo, SendFlags dwFlags, LPVOID lpData, DWORD dwDataSize); + DPRESULT SetGroupData( DPID idGroup, LPVOID lpData, DWORD dwDataSize, SetDataFlags dwFlags); + DPRESULT SetGroupName( DPID idGroup, LPDPNAME lpGroupName, SetDataFlags dwFlags); + DPRESULT SetPlayerData( DPID idPlayer, LPVOID lpData, DWORD dwDataSize, SetDataFlags dwFlags); + DPRESULT SetPlayerName( DPID idPlayer, LPDPNAME lpPlayerName, SetDataFlags dwFlags); + DPRESULT SetSessionDesc( LPDPSESSIONDESC2 lpSessDesc , SetDataFlags dwFlags); +}; + +/**************************************************************************** + * + * IDirectPlay3 (and IDirectPlay3A) Interface + * + ****************************************************************************/ + +interface IDirectPlay3 : IDirectPlay2 +{ + /*** IDirectPlay3 methods ***/ + DPRESULT AddGroupToGroup(DPID idParentGroup, DPID idGroup); + DPRESULT CreateGroupInGroup(DPID idParentGroup , LPDPID lpidGroup , LPDPNAME lpGroupName , LPVOID lpData, DWORD dwDataSize, CreateGroupFlags dwFlags); + DPRESULT DeleteGroupFromGroup(DPID idParentGroup, DPID idGroup); + DPRESULT EnumConnections( LPCGUID lpguidApplication, LPDPENUMCONNECTIONSCALLBACK lpEnumCallback, LPVOID lpContext, EnumConnectionsFlags dwFlags); + DPRESULT EnumGroupsInGroup( DPID idGroup, LPGUID lpguidInstance, LPDPENUMPLAYERSCALLBACK2 lpEnumPlayersCallback2, LPVOID lpContext, EnumPlayersMask dwFlags); + DPRESULT GetGroupConnectionSettings(DWORD dwFlags, DPID idGroup, [out] LPVOID lpData, [out] LPDWORD dwDataSize); + DPRESULT InitializeConnection(LPVOID lpData, DWORD dwDataSize); + DPRESULT SecureOpen(LPCDPSESSIONDESC2 lpsd, DWORD dwFlags, LPCDPSECURITYDESC lpSecurity, LPCDPCREDENTIALS lpCredentials); + DPRESULT SendChatMessage(DPID idFrom, DPID idTo, SendFlags dwFlags, LPDPCHAT lpChatMessage ); + DPRESULT SetGroupConnectionSettings(DWORD dwFlags, DPID idGroup, LPDPLCONNECTION lpConnection ); + DPRESULT StartSession(DWORD dwFlags, DPID idGroup ); + DPRESULT GetGroupFlags(DPID idGroup, [out] CreateGroupFlags * lpdwFlags ); + DPRESULT GetGroupParent(DPID idGroup, [out] LPDPID lpidParentGroup); + DPRESULT GetPlayerAccount(DPID idPlayer, DWORD dwFlags, [out] LPVOID lpData , [out] LPDWORD lpdwDataSize ); + DPRESULT GetPlayerFlags(DPID idPlayer, [out] LPDWORD lpdwDataSize ); +}; + + +interface IDirectPlay4 : IDirectPlay3 +{ + /*** IDirectPlay4 methods ***/ + DPRESULT GetGroupOwner( DPID idGroup, [out] LPDPID lpidOwner ); + DPRESULT SetGroupOwner(DPID idGroup, DPID idOwner); + DPRESULT SendEx( DPID idFrom, DPID idTo, SendFlags dwFlags, LPVOID lpData, DWORD dwDataSize, DWORD dwPriority, DWORD dwTimeout, LPVOID lpContext, LPDWORD lpdwMsgID ); + DPRESULT GetMessageQueue(DPID idFrom, DPID idTo, GetMessageQueueFlags dwFlags, [out] LPDWORD lpdwNumMsgs, [out] LPDWORD lpdwNumBytes ); + DPRESULT CancelMessage( DWORD dwMsgID, DWORD dwFlags ); + DPRESULT CancelPriority( DWORD dwMinPriority, DWORD dwMaxPriority, DWORD dwFlags ); +}; + + + +/* + * API's + */ + +DPRESULT DirectPlayEnumerateA( LPDPENUMDPCALLBACKA lpCallback, LPVOID lpContext ); +DPRESULT DirectPlayEnumerateW( LPDPENUMDPCALLBACK lpCallback, LPVOID lpContext); +DPRESULT DirectPlayCreate( LPGUID lpGUID, [out] LPDIRECTPLAY *lplpDP, IUnknown *pUnk); + +HRESULT DllGetClassObject( + REFCLSID rclsid, //CLSID for the class object + [iid] REFIID riid, //Reference to the identifier of the interface + // that communicates with the class object + [out] COM_INTERFACE_PTR * ppv //Address of output variable that receives the + // interface pointer requested in riid +); diff --git a/tools/Debugging Tools for Windows/winext/manifest/dsound.h b/tools/Debugging Tools for Windows/winext/manifest/dsound.h new file mode 100644 index 0000000000..3284db5000 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/dsound.h @@ -0,0 +1,937 @@ +module DSOUND.DLL: +category DirectSound: + + class __declspec(uuid("47d4d946-62e8-11cf-93bc-444553540000")) DirectSound; + class __declspec(uuid("3901cc3f-84b5-4fa4-ba35-aa8172b8a09b")) DirectSound8; + class __declspec(uuid("b0210780-89cd-11d0-af08-00a0c925cd16")) DirectSoundCapture; + class __declspec(uuid("e4bcac13-7f99-4908-9a8e-74e3bf24b6e1")) DirectSoundCapture8; + class __declspec(uuid("fea4300c-7959-4147-b26a-2377b9e7a91d")) DirectSoundFullDuplex; + class __declspec(uuid("b2f586d4-5558-49d1-a07b-3249dbbb33c2")) DirectSoundBufferConfig; + +struct __declspec(uuid("279AFA83-4981-11CE-A521-0020AF0BE560")) IDirectSound; +struct __declspec(uuid("279AFA85-4981-11CE-A521-0020AF0BE560")) IDirectSoundBuffer; +struct __declspec(uuid("279AFA84-4981-11CE-A521-0020AF0BE560")) IDirectSound3DListener; +struct __declspec(uuid("279AFA86-4981-11CE-A521-0020AF0BE560")) IDirectSound3DBuffer; +struct __declspec(uuid("b0210781-89cd-11d0-af08-00a0c925cd16")) IDirectSoundCapture; +struct __declspec(uuid("b0210782-89cd-11d0-af08-00a0c925cd16")) IDirectSoundCaptureBuffer; +struct __declspec(uuid("b30f3564-1698-45ba-9f75-fc3c6c3b2810")) IDirectSoundFXSend; +struct __declspec(uuid("C50A7E93-F395-4834-9EF6-7FA99DE50966")) IDirectSound8; +struct __declspec(uuid("6825a449-7524-4d82-920f-50e36ab3ab1e")) IDirectSoundBuffer8; +struct __declspec(uuid("00990df4-0dbb-4872-833e-6d303e80aeb6")) IDirectSoundCaptureBuffer8; + +struct __declspec(uuid("b0210783-89cd-11d0-af08-00a0c925cd16")) IDirectSoundNotify; +struct __declspec(uuid("31efac30-515c-11d0-a9aa-00aa0061be93")) IKsPropertySet; + +struct __declspec(uuid("def00000-9c6d-47ed-aaf1-4dda8f2b5c03")) IDefaultPlayback; +struct __declspec(uuid("def00001-9c6d-47ed-aaf1-4dda8f2b5c03")) IDefaultCapture; +struct __declspec(uuid("def00002-9c6d-47ed-aaf1-4dda8f2b5c03")) IDefaultVoicePlayback; +struct __declspec(uuid("def00003-9c6d-47ed-aaf1-4dda8f2b5c03")) IDefaultVoiceCapture; +struct __declspec(uuid("d616f352-d622-11ce-aac5-0020af0b99a3")) IDirectSoundFXGargle; +struct __declspec(uuid("880842e3-145f-43e6-a934-a71806e50547")) IDirectSoundFXChorus; +struct __declspec(uuid("903e9878-2c92-4072-9b2c-ea68f5396783")) IDirectSoundFXFlanger; +struct __declspec(uuid("8bd28edf-50db-4e92-a2bd-445488d1ed42")) IDirectSoundFXEcho; +struct __declspec(uuid("8ecf4326-455f-4d8b-bda9-8d5d3e9e3e0b")) IDirectSoundFXDistortion; +struct __declspec(uuid("4bbd1154-62f6-4e2c-a15c-d3b6c417f7a0")) IDirectSoundFXCompressor; +struct __declspec(uuid("c03ca9fe-fe90-4204-8078-82334cd177da")) IDirectSoundFXParamEq; +struct __declspec(uuid("4b166a6a-0d66-43f3-80e3-ee6280dee1a4")) IDirectSoundFXI3DL2Reverb; +struct __declspec(uuid("46858c3a-0dc6-45e3-b760-d4eef16cb325")) IDirectSoundFXWavesReverb; +struct __declspec(uuid("174d3eb9-6696-4fac-a46c-a0ac7bc9e20f")) IDirectSoundCaptureFXAec; +struct __declspec(uuid("ed311e41-fbae-4175-9625-cd0854f693ca")) IDirectSoundCaptureFXNoiseSuppress; +struct __declspec(uuid("edcb4c7a-daab-4216-a42e-6c50596ddc1d")) IDirectSoundFullDuplex; + + + + +typedef IDirectSound *LPDIRECTSOUND; +typedef IDirectSoundBuffer *LPDIRECTSOUNDBUFFER; +typedef IDirectSound3DListener *LPDIRECTSOUND3DLISTENER; +typedef IDirectSound3DBuffer *LPDIRECTSOUND3DBUFFER; +typedef IDirectSoundCapture *LPDIRECTSOUNDCAPTURE; +typedef IDirectSoundCaptureBuffer *LPDIRECTSOUNDCAPTUREBUFFER; +typedef IDirectSoundNotify *LPDIRECTSOUNDNOTIFY; + +typedef IDirectSoundFXSend *LPDIRECTSOUNDFXSEND; +typedef IDirectSoundFXGargle *LPDIRECTSOUNDFXGARGLE; +typedef IDirectSoundFXChorus *LPDIRECTSOUNDFXCHORUS; +typedef IDirectSoundFXFlanger *LPDIRECTSOUNDFXFLANGER; +typedef IDirectSoundFXEcho *LPDIRECTSOUNDFXECHO; +typedef IDirectSoundFXDistortion *LPDIRECTSOUNDFXDISTORTION; +typedef IDirectSoundFXCompressor *LPDIRECTSOUNDFXCOMPRESSOR; +typedef IDirectSoundFXParamEq *LPDIRECTSOUNDFXPARAMEQ; +typedef IDirectSoundFXWavesReverb *LPDIRECTSOUNDFXWAVESREVERB; +typedef IDirectSoundFXI3DL2Reverb *LPDIRECTSOUNDFXI3DL2REVERB; +typedef IDirectSoundCaptureFXAec *LPDIRECTSOUNDCAPTUREFXAEC; +typedef IDirectSoundCaptureFXNoiseSuppress *LPDIRECTSOUNDCAPTUREFXNOISESUPPRESS; +typedef IDirectSoundFullDuplex *LPDIRECTSOUNDFULLDUPLEX; + +typedef IDirectSound8 *LPDIRECTSOUND8; +typedef IDirectSoundBuffer8 *LPDIRECTSOUNDBUFFER8; +typedef IDirectSoundCaptureBuffer8 *LPDIRECTSOUNDCAPTUREBUFFER8; + +typedef LPDIRECTSOUND8 *LPLPDIRECTSOUND8; +typedef LPDIRECTSOUNDBUFFER8 *LPLPDIRECTSOUNDBUFFER8; +typedef LPDIRECTSOUNDCAPTUREBUFFER8 *LPLPDIRECTSOUNDCAPTUREBUFFER8; + +// +// Flags +// + +mask DWORD DSCAPS_MASK +{ +#define DSCAPS_PRIMARYMONO 0x00000001 +#define DSCAPS_PRIMARYSTEREO 0x00000002 +#define DSCAPS_PRIMARY8BIT 0x00000004 +#define DSCAPS_PRIMARY16BIT 0x00000008 +#define DSCAPS_CONTINUOUSRATE 0x00000010 +#define DSCAPS_EMULDRIVER 0x00000020 +#define DSCAPS_CERTIFIED 0x00000040 +#define DSCAPS_SECONDARYMONO 0x00000100 +#define DSCAPS_SECONDARYSTEREO 0x00000200 +#define DSCAPS_SECONDARY8BIT 0x00000400 +#define DSCAPS_SECONDARY16BIT 0x00000800 +}; + +mask DWORD DSBPLAY_MASK +{ +#define DSBPLAY_LOOPING 0x00000001 +#define DSBPLAY_LOCHARDWARE 0x00000002 +#define DSBPLAY_LOCSOFTWARE 0x00000004 +#define DSBPLAY_TERMINATEBY_TIME 0x00000008 +#define DSBPLAY_TERMINATEBY_DISTANCE 0x000000010 +#define DSBPLAY_TERMINATEBY_PRIORITY 0x000000020 +}; + +mask DWORD DSBSTATUS_MASK +{ +#define DSBSTATUS_PLAYING 0x00000001 +#define DSBSTATUS_BUFFERLOST 0x00000002 +#define DSBSTATUS_LOOPING 0x00000004 +#define DSBSTATUS_LOCHARDWARE 0x00000008 +#define DSBSTATUS_LOCSOFTWARE 0x00000010 +#define DSBSTATUS_TERMINATED 0x00000020 +}; + +value DWORD DSBLOCK_VALUE +{ +#define DSBLOCK_FROMWRITECURSOR 0x00000001 +#define DSBLOCK_ENTIREBUFFER 0x00000002 +}; + +value DWORD DSSCL_VALUE +{ +#define DSSCL_NORMAL 0x00000001 +#define DSSCL_PRIORITY 0x00000002 +#define DSSCL_EXCLUSIVE 0x00000003 +#define DSSCL_WRITEPRIMARY 0x00000004 +}; + +value DWORD DS3DMODE_VALUE +{ +#define DS3DMODE_NORMAL 0x00000000 +#define DS3DMODE_HEADRELATIVE 0x00000001 +#define DS3DMODE_DISABLE 0x00000002 +}; + +mask DWORD DSBCAPS_MASK +{ +#define DSBCAPS_PRIMARYBUFFER 0x00000001 +#define DSBCAPS_STATIC 0x00000002 +#define DSBCAPS_LOCHARDWARE 0x00000004 +#define DSBCAPS_LOCSOFTWARE 0x00000008 +#define DSBCAPS_CTRL3D 0x00000010 +#define DSBCAPS_CTRLFREQUENCY 0x00000020 +#define DSBCAPS_CTRLPAN 0x00000040 +#define DSBCAPS_CTRLVOLUME 0x00000080 +#define DSBCAPS_CTRLPOSITIONNOTIFY 0x00000100 +#define DSCBCAPS_CTRLFX 0x00000200 +//#define DSBCAPS_CTRLDEFAULT 0x000000E0 +//#define DSBCAPS_CTRLALL 0x000001F0 +#define DSBCAPS_STICKYFOCUS 0x00004000 +#define DSBCAPS_GLOBALFOCUS 0x00008000 +#define DSBCAPS_GETCURRENTPOSITION2 0x00010000 +#define DSBCAPS_MUTE3DATMAXDISTANCE 0x00020000 +#define DSBCAPS_LOCDEFER 0x00040000 + +#define DSCBCAPS_WAVEMAPPED 0x80000000 +}; + +value DWORD DSSPEAKER_VALUE +{ +#define DSSPEAKER_HEADPHONE 0x00000001 +#define DSSPEAKER_MONO 0x00000002 +#define DSSPEAKER_QUAD 0x00000003 +#define DSSPEAKER_STEREO 0x00000004 +#define DSSPEAKER_SURROUND 0x00000005 + +#define DSSPEAKER_GEOMETRY_MIN 0x00000005 // 5 degrees +#define DSSPEAKER_GEOMETRY_NARROW 0x0000000A // 10 degrees +#define DSSPEAKER_GEOMETRY_WIDE 0x00000014 // 20 degrees +#define DSSPEAKER_GEOMETRY_MAX 0x000000B4 // 180 degrees + +//#define DSSPEAKER_COMBINED(c, g) ((DWORD)(((BYTE)(c)) | ((DWORD)((BYTE)(g))) << 16)) +//#define DSSPEAKER_CONFIG(a) ((BYTE)(a)) +//#define DSSPEAKER_GEOMETRY(a) ((BYTE)(((DWORD)(a) >> 16) & 0x00FF)) +}; + +value DWORD DSBFREQUENCY_VALUE +{ +#define DSBFREQUENCY_MIN 100 +#define DSBFREQUENCY_MAX 100000 +#define DSBFREQUENCY_ORIGINAL 0 +}; + +value DWORD DSBPAN_VALUE +{ +#define DSBPAN_LEFT -10000 +#define DSBPAN_CENTER 0 +#define DSBPAN_RIGHT 10000 +}; + +value DWORD DSBVOLUME_VALUE +{ +#define DSBVOLUME_MIN -10000 +#define DSBVOLUME_MAX 0 +}; + +value DWORD DSBSIZE_VALUE +{ +#define DSBSIZE_MIN 4 +#define DSBSIZE_MAX 0x0FFFFFFF +}; + +value DWORD DS3D_VALUE +{ +#define DS3D_IMMEDIATE 0x00000000 +#define DS3D_DEFERRED 0x00000001 +}; + + +//#define DSCCAPS_EMULDRIVER 0x00000020 + +mask DWORD DSCBLOCK_MASK +{ +#define DSCBLOCK_ENTIREBUFFER 0x00000001 +}; + +mask DWORD DSCBSTATUS_MASK +{ +#define DSCBSTATUS_CAPTURING 0x00000001 +#define DSCBSTATUS_LOOPING 0x00000002 +}; +mask DWORD DSCBSTART_MASK +{ +#define DSCBSTART_LOOPING 0x00000001 +}; +// +//#define DSBPN_OFFSETSTOP 0xFFFFFFFF +// +mask DWORD DSFX_MASK +{ + #define DSFX_LOCHARDWARE 0x00000001 + #define DSFX_LOCSOFTWARE 0x00000002 +}; + +mask DWORD DSFXR_MASK +{ + #define DSCFXR_LOCHARDWARE 0x00000010 + #define DSCFXR_LOCSOFTWARE 0x00000020 + #define DSCFXR_UNALLOCATED 0x00000040 + #define DSCFXR_FAILED 0x00000080 + #define DSCFXR_UNKNOWN 0x00000100 +}; + +typedef struct _DSEFFECTDESC +{ + DWORD dwSize; + DWORD dwFlags; + GUID guidDSFXClass; + LPDIRECTSOUNDBUFFER lpSendBuffer; + DWORD dwReserved; +} DSEFFECTDESC; +typedef DSEFFECTDESC *LPDSEFFECTDESC; +typedef DSEFFECTDESC *LPCDSEFFECTDESC; + +value DWORD DSFXR_ENUM +{ +#define DSFXR_PRESENT 0 +#define DSFXR_LOCHARDWARE 1 +#define DSFXR_LOCSOFTWARE 2 +#define DSFXR_UNALLOCATED 3 +#define DSFXR_FAILED 4 +#define DSFXR_UNKNOWN 5 +#define DSFXR_SENDLOOP 6 +}; + +typedef struct _DSCEFFECTDESC +{ + DWORD dwSize; + DWORD dwFlags; + GUID guidDSCFXClass; + GUID guidDSCFXInstance; + DWORD dwReserved1; + DWORD dwReserved2; +} DSCEFFECTDESC; +typedef DSCEFFECTDESC *LPDSCEFFECTDESC; +typedef DSCEFFECTDESC *LPCDSCEFFECTDESC; + +typedef struct _DSCAPS +{ + DWORD dwSize; + DSCAPS_MASK dwFlags; + DWORD dwMinSecondarySampleRate; + DWORD dwMaxSecondarySampleRate; + DWORD dwPrimaryBuffers; + DWORD dwMaxHwMixingAllBuffers; + DWORD dwMaxHwMixingStaticBuffers; + DWORD dwMaxHwMixingStreamingBuffers; + DWORD dwFreeHwMixingAllBuffers; + DWORD dwFreeHwMixingStaticBuffers; + DWORD dwFreeHwMixingStreamingBuffers; + DWORD dwMaxHw3DAllBuffers; + DWORD dwMaxHw3DStaticBuffers; + DWORD dwMaxHw3DStreamingBuffers; + DWORD dwFreeHw3DAllBuffers; + DWORD dwFreeHw3DStaticBuffers; + DWORD dwFreeHw3DStreamingBuffers; + DWORD dwTotalHwMemBytes; + DWORD dwFreeHwMemBytes; + DWORD dwMaxContigFreeHwMemBytes; + DWORD dwUnlockTransferRateHwBuffers; + DWORD dwPlayCpuOverheadSwBuffers; + DWORD dwReserved1; + DWORD dwReserved2; +} DSCAPS, *LPDSCAPS; + +typedef DSCAPS *LPCDSCAPS; + +typedef struct _DSBCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwUnlockTransferRate; + DWORD dwPlayCpuOverhead; +} DSBCAPS, *LPDSBCAPS; + +typedef DSBCAPS *LPCDSBCAPS; + +typedef struct _DSBUFFERDESC +{ + DWORD dwSize; + DSBCAPS_MASK dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + LPWAVEFORMATEX lpwfxFormat; +} DSBUFFERDESC, *LPDSBUFFERDESC; + +typedef DSBUFFERDESC *LPCDSBUFFERDESC; + +typedef struct _DS3DBUFFER +{ + DWORD dwSize; + D3DVECTOR vPosition; + D3DVECTOR vVelocity; + DWORD dwInsideConeAngle; + DWORD dwOutsideConeAngle; + D3DVECTOR vConeOrientation; + LONG lConeOutsideVolume; + D3DVALUE flMinDistance; + D3DVALUE flMaxDistance; + DWORD dwMode; +} DS3DBUFFER, *LPDS3DBUFFER; + +typedef DS3DBUFFER *LPCDS3DBUFFER; + +typedef struct _DS3DLISTENER +{ + DWORD dwSize; + D3DVECTOR vPosition; + D3DVECTOR vVelocity; + D3DVECTOR vOrientFront; + D3DVECTOR vOrientTop; + D3DVALUE flDistanceFactor; + D3DVALUE flRolloffFactor; + D3DVALUE flDopplerFactor; +} DS3DLISTENER, *LPDS3DLISTENER; + +typedef DS3DLISTENER *LPCDS3DLISTENER; + +typedef struct _DSCCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFormats; + DWORD dwChannels; +} DSCCAPS, *LPDSCCAPS; + +typedef DSCCAPS *LPCDSCCAPS; + +typedef struct _DSCBUFFERDESC +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + LPWAVEFORMATEX lpwfxFormat; + DWORD dwFXCount; + LPDSCEFFECTDESC lpDSCFXDesc; +} DSCBUFFERDESC, *LPDSCBUFFERDESC; + +typedef DSCBUFFERDESC *LPCDSCBUFFERDESC; + +typedef struct _DSCBCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; +} DSCBCAPS, *LPDSCBCAPS; + +typedef DSCBCAPS *LPCDSCBCAPS; + +typedef struct _DSBPOSITIONNOTIFY +{ + DWORD dwOffset; + HANDLE hEventNotify; +} DSBPOSITIONNOTIFY, *LPDSBPOSITIONNOTIFY; + +typedef DSBPOSITIONNOTIFY *LPCDSBPOSITIONNOTIFY; + +// +// Compatibility typedefs +// + +typedef LPDIRECTSOUND *LPLPDIRECTSOUND; +typedef LPDIRECTSOUNDBUFFER *LPLPDIRECTSOUNDBUFFER; +typedef LPDIRECTSOUND3DLISTENER *LPLPDIRECTSOUND3DLISTENER; +typedef LPDIRECTSOUND3DBUFFER *LPLPDIRECTSOUND3DBUFFER; +typedef LPDIRECTSOUNDCAPTURE *LPLPDIRECTSOUNDCAPTURE; +typedef LPDIRECTSOUNDCAPTUREBUFFER *LPLPDIRECTSOUNDCAPTUREBUFFER; +typedef LPDIRECTSOUNDNOTIFY *LPLPDIRECTSOUNDNOTIFY; +typedef LPVOID *LPLPVOID; +//typedef WAVEFORMATEX *LPCWAVEFORMATEX; + +value DWORD DSRESULT +{ +// +// Return Codes +// + +#define DS_OK 0 + +// The call failed because resources (such as a priority level) +// were already being used by another caller. +#define DSERR_ALLOCATED 0x8878000A [fail] + +// The control (vol,pan,etc.) requested by the caller is not available. +#define DSERR_CONTROLUNAVAIL 0x8878001E [fail] + +// An invalid parameter was passed to the returning function +#define DSERR_INVALIDPARAM 0x80070057 [fail] + +// This call is not valid for the current state of this object +#define DSERR_INVALIDCALL 0x88780032 [fail] + +// An undetermined error occured inside the DirectSound subsystem +#define DSERR_GENERIC 0x80004005 [fail] + +// The caller does not have the priority level required for the function to +// succeed. +#define DSERR_PRIOLEVELNEEDED 0x88780046 [fail] + +// Not enough free memory is available to complete the operation +#define DSERR_OUTOFMEMORY 0x8007000E [fail] + +// The specified WAVE format is not supported +#define DSERR_BADFORMAT 0x88780064 [fail] + +// The function called is not supported at this time +#define DSERR_UNSUPPORTED 0x80004001 [fail] + +// No sound driver is available for use +#define DSERR_NODRIVER 0x88780078 [fail] + +// This object is already initialized +#define DSERR_ALREADYINITIALIZED 0x88780082 [fail] + +// This object does not support aggregation +#define DSERR_NOAGGREGATION 0x80040110 [fail] + +// The buffer memory has been lost, and must be restored. +#define DSERR_BUFFERLOST 0x88780096 [fail] + +// Another app has a higher priority level, preventing this call from +// succeeding. +#define DSERR_OTHERAPPHASPRIO 0x887800A0 [fail] + +// This object has not been initialized +#define DSERR_UNINITIALIZED 0x887800AA [fail] + +// The requested COM interface is not available +#define DSERR_NOINTERFACE 0x80000004 [fail] + +// Access is denied +#define DSERR_ACCESSDENIED 0x80070005 [fail] + +// Tried to create a DSBCAPS_CTRLFX buffer shorter than DSBSIZE_FX_MIN milliseconds +#define DSERR_BUFFERTOOSMALL 0x887800B4 [fail] + +// Attempt to use DirectSound 8 functionality on an older DirectSound object +#define DSERR_DS8_REQUIRED 0x887800BE [fail] + +// A circular loop of send effects was detected +#define DSERR_SENDLOOP 0x887800C8 [fail] + +// The GUID specified in an audiopath file does not match a valid MIXIN buffer +#define DSERR_BADSENDBUFFERGUID 0x887800D2 [fail] + +// The object requested was not found (numerically equal to DMUS_E_NOT_FOUND) +#define DSERR_OBJECTNOTFOUND 0x88781193 [fail] + +}; + + +// +// IDirectSound +// + + +interface IDirectSound: IUnknown +{ + // IDirectSound methods + DSRESULT CreateSoundBuffer ([in] LPCDSBUFFERDESC lpcDSBufferDesc, [out] LPLPDIRECTSOUNDBUFFER lplpDirectSoundBuffer, [in] IUnknown * pUnkOuter); + DSRESULT GetCaps ([out] LPDSCAPS lpDSCaps) ; + DSRESULT DuplicateSoundBuffer ([in] LPDIRECTSOUNDBUFFER lpDsbOriginal, [out] LPLPDIRECTSOUNDBUFFER lplpDsbDuplicate) ; + DSRESULT SetCooperativeLevel (HWND hwnd, DSSCL_VALUE dwLevel) ; + DSRESULT Compact () ; + DSRESULT GetSpeakerConfig ([out] DSSPEAKER_VALUE * lpdwSpeakerConfig) ; + DSRESULT SetSpeakerConfig (DSSPEAKER_VALUE dwSpeakerConfig) ; + DSRESULT Initialize ([in] LPCGUID lpcGuid) ; +}; +interface IDirectSound8 : IDirectSound +{ + // IDirectSound8 methods + DSRESULT VerifyCertification (LPDWORD pdwCertified) ; +}; + +// +// IDirectSoundBuffer +// + + +interface IDirectSoundBuffer: IUnknown +{ + // IDirectSoundBuffer methods + DSRESULT GetCaps ([out] LPDSCAPS lpDSCaps ) ; + DSRESULT GetCurrentPosition ([out] LPDWORD lpdwCurrentPlayCursor, [out] LPDWORD lpdwCurrentWriteCursor ); + DSRESULT GetFormat ([out] LPWAVEFORMATEX lpwfxFormat, DWORD dwSizeAllocated, [out] LPDWORD lpdwSizeWritten ); + DSRESULT GetVolume ([out] LPLONG lplVolume ); + DSRESULT GetPan ([out] LPLONG lplPan); + DSRESULT GetFrequency ([out] LPDWORD lpdwFrequency ); + DSRESULT GetStatus ([out] LPDWORD lpdwStatus ); + DSRESULT Initialize ([in] LPDIRECTSOUND lpDirectSound, [in] LPCDSBUFFERDESC lpcDSBufferDesc ); + DSRESULT Lock (DWORD dwWriteCursor, DWORD dwWriteBytes, [out] LPVOID lplpvAudioPtr1, [out] LPDWORD lpdwAudioBytes1, [out] LPVOID lplpvAudioPtr2, [out] LPDWORD lpdwAudioBytes2, DSBLOCK_VALUE dwFlags); + DSRESULT Play (DWORD dwReserved1, DWORD dwPriority, DSBPLAY_MASK dwFlags ); + DSRESULT SetCurrentPosition (DWORD dwNewPosition); + DSRESULT SetFormat ([in] LPCWAVEFORMATEX lpcfxFormat ); + DSRESULT SetVolume (LONG lVolume ); + DSRESULT SetPan (LONG lPan ); + DSRESULT SetFrequency (DWORD dwFrequency ); + DSRESULT Stop ( ); + DSRESULT Unlock ([in] LPVOID lpvAudioPtr1, DWORD dwAudioBytes1, [in] LPVOID lpvAudioPtr2, DWORD dwAudioBytes2 ); + DSRESULT Restore ( ); +}; + +interface IDirectSoundBuffer8: IDirectSoundBuffer +{ + // IDirectSoundBuffer8 methods + DSRESULT SetFX (DWORD dwEffectsCount, [in] LPDSEFFECTDESC pDSFXDesc, [out] LPDWORD pdwResultCodes) ; + DSRESULT AcquireResources (DWORD dwFlags, DWORD dwEffectsCount, [out] LPDWORD pdwResultCodes) ; + DSRESULT GetObjectInPath (REFGUID rguidObject, DWORD dwIndex, REFGUID rguidInterface, [out] LPVOID *ppObject) ; +}; + + +// +// IDirectSound3DListener +// + + +interface IDirectSound3DListener: IUnknown +{ + // IDirectSound3D methods + DSRESULT GetAllParameters ([out] LPDS3DLISTENER lpListener ); + DSRESULT GetDistanceFactor ([out] LPD3DVALUE lpflDistanceFactor ); + DSRESULT GetDopplerFactor ([out] LPD3DVALUE lpflDopplerFactor ); + DSRESULT GetOrientation ([out] LPD3DVECTOR lpvOrientFront, [out] LPD3DVECTOR lpvOrientTop ); + DSRESULT GetPosition ([out] LPD3DVECTOR lpvPosition); + DSRESULT GetRolloffFactor ([out] LPD3DVALUE lpflRolloffFactor ); + DSRESULT GetVelocity ([out] LPD3DVECTOR lpvVelocity); + DSRESULT SetAllParameters ([in] LPCDS3DLISTENER lpcListener, DS3D_VALUE dwApply ); + DSRESULT SetDistanceFactor (D3DVALUE flDistanceFactor, DS3D_VALUE dwApply ); + DSRESULT SetDopplerFactor (D3DVALUE flDopplerFactor, DS3D_VALUE dwApply ); + DSRESULT SetOrientation (D3DVALUE xFront, D3DVALUE yFront, D3DVALUE zFront, D3DVALUE xTop, D3DVALUE yTop, D3DVALUE zTop, DS3D_VALUE dwApply); + DSRESULT SetPosition (D3DVALUE x, D3DVALUE y, D3DVALUE z, DS3D_VALUE dwApply ); + DSRESULT SetRolloffFactor (D3DVALUE flRolloffFactor, DS3D_VALUE dwApply); + DSRESULT SetVelocity (D3DVALUE x, D3DVALUE y, D3DVALUE z, DS3D_VALUE dwApply ); + DSRESULT CommitDeferredSettings ( ); +}; + +// +// IDirectSound3DBuffer +// + + +interface IDirectSound3DBuffer: IUnknown +{ + // IDirectSoundBuffer3D methods + DSRESULT GetAllParameters ([out] LPDS3DBUFFER lpDs3dBuffer ); + DSRESULT GetConeAngles ([out] LPDWORD lpdwInsideConeAngle, [out] LPDWORD lpdwOutsideConeAngle ); + DSRESULT GetConeOrientation ([out] LPD3DVECTOR lpvOrientation ); + DSRESULT GetConeOutsideVolume ([out] LPLONG lplConeOutsideVolume ); + DSRESULT GetMaxDistance ([out] LPD3DVALUE lpflMaxDistance ); + DSRESULT GetMinDistance ([out] LPD3DVALUE lpflMinDistance ); + DSRESULT GetMode ([out] LPDWORD lpdwMode ); + DSRESULT GetPosition ([out] LPD3DVECTOR lpvPosition ); + DSRESULT GetVelocity ([out] LPD3DVECTOR lpvVelocity ); + DSRESULT SetAllParameters ([in] LPCDS3DBUFFER lpcDs3dBuffer, DS3D_VALUE dwApply ); + DSRESULT SetConeAngles (DWORD dwInsideConeAngle, DWORD dwOutsideConeAngle, DS3D_VALUE dwApply ); + DSRESULT SetConeOrientation (D3DVALUE x, D3DVALUE y, D3DVALUE z, DS3D_VALUE dwApply ); + DSRESULT SetConeOutsideVolume (LONG lConeOutsideVolume, DS3D_VALUE dwApply ); + DSRESULT SetMaxDistance (D3DVALUE flMaxDistance, DS3D_VALUE dwApply ); + DSRESULT SetMinDistance (D3DVALUE flMinDistance, DS3D_VALUE dwApply ); + DSRESULT SetMode (DS3DMODE_VALUE dwMode, DS3D_VALUE dwApply ); + DSRESULT SetPosition (D3DVALUE x, D3DVALUE y, D3DVALUE z, DS3D_VALUE dwApply ); + DSRESULT SetVelocity (D3DVALUE x, D3DVALUE y, D3DVALUE z, DS3D_VALUE dwApply ); +}; + + +// +// IDirectSoundCapture +// + + +interface IDirectSoundCapture: IUnknown +{ + // IDirectSoundCapture methods + DSRESULT CreateCaptureBuffer ([in] LPDSCBUFFERDESC lpDSCBufferDesc, [out] LPLPDIRECTSOUNDCAPTUREBUFFER lplpDirectSoundCaptureBuffer, [in] LPUNKNOWN pUnkOuter ); + DSRESULT GetCaps ([out] LPDSCAPS lpDSCaps ) ; + DSRESULT Initialize ([in] LPCGUID lpcGuid ); +}; + +interface IDirectSoundCaptureBuffer8 : IDirectSoundCapture +{ + // IDirectSoundCaptureBuffer8 methods + DSRESULT GetObjectInPath (REFGUID rguidObject, DWORD dwIndex, REFGUID rguidInterface, [out] LPVOID *ppObject) ; + DSRESULT GetFXStatus (DWORD dwFXCount, [out] LPDWORD pdwFXStatus) ; +}; + +// +// IDirectSoundCaptureBuffer +// + + +interface IDirectSoundCaptureBuffer: IUnknown +{ + // IDirectSoundCaptureBuffer methods + DSRESULT GetCaps ([out] LPDSCAPS lpDSCaps ) ; + DSRESULT GetCurrentPosition ([out] LPDWORD lpdwCapturePosition, [out] LPDWORD lpdwReadPosition ); + DSRESULT GetFormat ([out] LPWAVEFORMATEX lpwfxFormat, DWORD dwSizeAllocated, [out] LPDWORD lpdwSizeWritten ); + DSRESULT GetStatus ([out] DWORD *lpdwStatus ); + DSRESULT Initialize (LPDIRECTSOUNDCAPTURE lpDirectSoundCapture, [in] LPCDSCBUFFERDESC lpcDSCBufferDesc ); + DSRESULT Lock (DWORD dwReadCursor, DWORD dwReadBytes, [out] LPVOID *lplpvAudioPtr1, [out] LPDWORD lpdwAudioBytes1, [out] LPVOID *lplpvAudioPtr2, [out] LPDWORD lpdwAudioBytes2, DSCBLOCK_MASK dwFlags); + DSRESULT Start (DSCBSTART_MASK dwFlags ); + DSRESULT Stop ( ); + DSRESULT Unlock ([in] LPVOID lpvAudioPtr1,DWORD dwAudioBytes1, [in] LPVOID lpvAudioPtr2,DWORD dwAudioBytes2 ); +}; + + +// +// IDirectSoundNotify +// + + +interface IDirectSoundNotify: IUnknown +{ + // IDirectSoundNotify methods + DSRESULT SetNotificationPositions ( ); +}; + +typedef struct _DSFXSend +{ + LONG lSendLevel; +} DSFXSend; +typedef DSFXSend *LPDSFXSend; +typedef DSFXSend *LPCDSFXSend; + + +interface IDirectSoundFXSend: IUnknown +{ + // IDirectSoundFXSend methods + DSRESULT SetAllParameters ([in] LPCDSFXSend pcDsFxSend) ; + DSRESULT GetAllParameters ([out] LPDSFXSend pDsFxSend) ; +}; + + +typedef struct _DSFXGargle +{ + DWORD dwRateHz; // Rate of modulation in hz + DWORD dwWaveShape; // DSFXGARGLE_WAVE_xxx +} DSFXGargle; +typedef DSFXGargle *LPDSFXGargle; +typedef DSFXGargle *LPCDSFXGargle; + +value DWORD DSFXGARGLE_VALUE +{ +#define DSFXGARGLE_WAVE_TRIANGLE 0 +#define DSFXGARGLE_WAVE_SQUARE 1 +}; + + + +interface IDirectSoundFXGargle: IUnknown +{ + // IDirectSoundFXGargle methods + DSRESULT SetAllParameters ([in] LPCDSFXGargle pcDsFxGargle) ; + DSRESULT GetAllParameters ([out] LPDSFXGargle pDsFxGargle) ; +}; + +typedef struct _DSFXChorus +{ + FLOAT fWetDryMix; + FLOAT fDepth; + FLOAT fFeedback; + FLOAT fFrequency; + LONG lWaveform; // LFO shape; DSFXCHORUS_WAVE_xxx + FLOAT fDelay; + LONG lPhase; +} DSFXChorus; +typedef DSFXChorus *LPDSFXChorus; +typedef DSFXChorus *LPCDSFXChorus; + +interface IDirectSoundFXChorus: IUnknown +{ + // IDirectSoundFXChorus methods + DSRESULT SetAllParameters ([in] LPCDSFXChorus pcDsFxChorus) ; + DSRESULT GetAllParameters ([out] LPDSFXChorus pDsFxChorus) ; +}; + + +typedef struct _DSFXFlanger +{ + FLOAT fWetDryMix; + FLOAT fDepth; + FLOAT fFeedback; + FLOAT fFrequency; + LONG lWaveform; + FLOAT fDelay; + LONG lPhase; +} DSFXFlanger; +typedef DSFXFlanger *LPDSFXFlanger; +typedef DSFXFlanger *LPCDSFXFlanger; + +interface IDirectSoundFXFlanger: IUnknown +{ + // IDirectSoundFXFlanger methods + DSRESULT SetAllParameters ([in] LPCDSFXFlanger pcDsFxFlanger) ; + DSRESULT GetAllParameters ([out] LPDSFXFlanger pDsFxFlanger) ; +}; + +typedef struct _DSFXEcho +{ + FLOAT fWetDryMix; + FLOAT fFeedback; + FLOAT fLeftDelay; + FLOAT fRightDelay; + LONG lPanDelay; +} DSFXEcho; +typedef DSFXEcho *LPDSFXEcho; +typedef DSFXEcho *LPCDSFXEcho; + +interface IDirectSoundFXEcho: IUnknown +{ + // IDirectSoundFXEcho methods + DSRESULT SetAllParameters ([in] LPCDSFXEcho pcDsFxEcho) ; + DSRESULT GetAllParameters ([out] LPDSFXEcho pDsFxEcho) ; +}; +typedef struct _DSFXDistortion +{ + FLOAT fGain; + FLOAT fEdge; + FLOAT fPostEQCenterFrequency; + FLOAT fPostEQBandwidth; + FLOAT fPreLowpassCutoff; +} DSFXDistortion; +typedef DSFXDistortion *LPDSFXDistortion; +typedef DSFXDistortion *LPCDSFXDistortion; + +interface IDirectSoundFXDistortion: IUnknown +{ + // IDirectSoundFXDistortion methods + DSRESULT SetAllParameters ([in] LPCDSFXDistortion pcDsFxDistortion) ; + DSRESULT GetAllParameters ([out] LPDSFXDistortion pDsFxDistortion) ; +}; + +typedef struct _DSFXCompressor +{ + FLOAT fGain; + FLOAT fAttack; + FLOAT fRelease; + FLOAT fThreshold; + FLOAT fRatio; + FLOAT fPredelay; +} DSFXCompressor; +typedef DSFXCompressor *LPDSFXCompressor; +typedef DSFXCompressor *LPCDSFXCompressor; + +interface IDirectSoundFXCompressor: IUnknown +{ + // IDirectSoundFXCompressor methods + DSRESULT SetAllParameters ([in] LPCDSFXCompressor pcDsFxCompressor) ; + DSRESULT GetAllParameters ([out] LPDSFXCompressor pDsFxCompressor) ; +}; + + +typedef struct _DSFXParamEq +{ + FLOAT fCenter; + FLOAT fBandwidth; + FLOAT fGain; +} DSFXParamEq; +typedef DSFXParamEq *LPDSFXParamEq; +typedef DSFXParamEq *LPCDSFXParamEq; + +interface IDirectSoundFXParamEq: IUnknown +{ + // IDirectSoundFXParamEq methods + DSRESULT SetAllParameters ([in] LPCDSFXParamEq pcDsFxParamEq) ; + DSRESULT GetAllParameters ([out] LPDSFXParamEq pDsFxParamEq) ; +}; + + +typedef struct _DSFXI3DL2Reverb +{ + LONG lRoom; // [-10000, 0] default: -1000 mB + LONG lRoomHF; // [-10000, 0] default: 0 mB + FLOAT flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 + FLOAT flDecayTime; // [0.1, 20.0] default: 1.49s + FLOAT flDecayHFRatio; // [0.1, 2.0] default: 0.83 + LONG lReflections; // [-10000, 1000] default: -2602 mB + FLOAT flReflectionsDelay; // [0.0, 0.3] default: 0.007 s + LONG lReverb; // [-10000, 2000] default: 200 mB + FLOAT flReverbDelay; // [0.0, 0.1] default: 0.011 s + FLOAT flDiffusion; // [0.0, 100.0] default: 100.0 % + FLOAT flDensity; // [0.0, 100.0] default: 100.0 % + FLOAT flHFReference; // [20.0, 20000.0] default: 5000.0 Hz +} DSFXI3DL2Reverb; +typedef DSFXI3DL2Reverb *LPDSFXI3DL2Reverb; +typedef DSFXI3DL2Reverb *LPCDSFXI3DL2Reverb; + + +interface IDirectSoundFXI3DL2Reverb: IUnknown +{ + // IDirectSoundFXI3DL2Reverb methods + DSRESULT SetAllParameters ([in] LPCDSFXI3DL2Reverb pcDsFxI3DL2Reverb) ; + DSRESULT GetAllParameters ([out] LPDSFXI3DL2Reverb pDsFxI3DL2Reverb) ; + DSRESULT SetPreset (DWORD dwPreset) ; + DSRESULT GetPreset ([out] LPDWORD pdwPreset) ; + DSRESULT SetQuality (LONG lQuality) ; + DSRESULT GetQuality ([out] LONG *plQuality) ; +}; + +typedef struct _DSFXWavesReverb +{ + FLOAT fInGain; // [-96.0,0.0] default: 0.0 dB + FLOAT fReverbMix; // [-96.0,0.0] default: 0.0 db + FLOAT fReverbTime; // [0.001,3000.0] default: 1000.0 ms + FLOAT fHighFreqRTRatio; // [0.001,0.999] default: 0.001 +} DSFXWavesReverb; +typedef DSFXWavesReverb *LPDSFXWavesReverb; +typedef DSFXWavesReverb *LPCDSFXWavesReverb; + +interface IDirectSoundFXWavesReverb: IUnknown +{ + // IDirectSoundFXWavesReverb methods + DSRESULT SetAllParameters ([in] LPCDSFXWavesReverb pcDsFxWavesReverb) ; + DSRESULT GetAllParameters ([out] LPDSFXWavesReverb pDsFxWavesReverb) ; +}; + +typedef struct _DSCFXAec +{ + BOOL fEnable; + BOOL fReset; +} DSCFXAec; +typedef DSCFXAec *LPDSCFXAec; +typedef DSCFXAec *LPCDSCFXAec; + +interface IDirectSoundCaptureFXAec: IUnknown +{ + // IDirectSoundCaptureFXAec methods + DSRESULT SetAllParameters ([in] LPCDSCFXAec pDscFxAec) ; + DSRESULT GetAllParameters ([out] LPDSCFXAec pDscFxAec) ; +}; + +typedef struct _DSCFXNoiseSuppress +{ + BOOL fEnable; + BOOL fReset; +} DSCFXNoiseSuppress; +typedef DSCFXNoiseSuppress *LPDSCFXNoiseSuppress; +typedef DSCFXNoiseSuppress *LPCDSCFXNoiseSuppress; + +interface IDirectSoundCaptureFXNoiseSuppress: IUnknown +{ + // IDirectSoundCaptureFXNoiseSuppress methods + DSRESULT SetAllParameters ([in] LPCDSCFXNoiseSuppress pcDscFxNoiseSuppress) ; + DSRESULT GetAllParameters ([out] LPDSCFXNoiseSuppress pDscFxNoiseSuppress) ; +}; + + +interface IDirectSoundFullDuplex: IUnknown +{ + // IDirectSoundFullDuplex methods + DSRESULT Initialize ([in] LPCGUID pCaptureGuid, [in] LPCGUID pRenderGuid, [in] LPCDSCBUFFERDESC lpDscBufferDesc, [in] LPCDSBUFFERDESC lpDsBufferDesc, HWND hWnd, DWORD dwLevel, [out] LPLPDIRECTSOUNDCAPTUREBUFFER8 lplpDirectSoundCaptureBuffer8, [out] LPLPDIRECTSOUNDBUFFER8 lplpDirectSoundBuffer8) ; +}; + + + + + +// +// IKsPropertySet +// + + +mask DWORD KSPROPERTY_SUPPORT_MASK +{ +#define KSPROPERTY_SUPPORT_GET 0x00000001 +#define KSPROPERTY_SUPPORT_SET 0x00000002 +}; + +interface IKsPropertySet: IUnknown +{ + // IKsPropertySet methods + DSRESULT Get (REFGUID rguidPropSet, ULONG ulId, LPVOID pInstanceData, ULONG ulInstanceLength, [out] LPVOID pPropertyData, ULONG ulDataLength, [out] ULONG * pulBytesReturned ); + DSRESULT Set (REFGUID rguidPropSet, ULONG ulId, LPVOID pInstanceData, ULONG ulInstanceLength, LPVOID pPropertyData, ULONG ulDataLength ); + DSRESULT QuerySupport (REFGUID rguidPropSet, ULONG ulId, ULONG* pulTypeSupport ); +}; + +typedef IKsPropertySet *LPKSPROPERTYSET; + +// +// DirectSound API +// + +typedef LPVOID LPDSENUMCALLBACKW; +typedef LPVOID LPDSENUMCALLBACKA; + +DSRESULT DirectSoundCreate( [in] LPCGUID lpcGuid, [out] LPDIRECTSOUND * ppDS, [in] LPUNKNOWN pUnkOuter ); +DSRESULT DirectSoundEnumerateA( LPDSENUMCALLBACKA lpDSEnumCallback, LPVOID lpContext ); +DSRESULT DirectSoundEnumerateW( LPDSENUMCALLBACKW lpDSEnumCallback, LPVOID lpContext ); +DSRESULT DirectSoundCaptureCreate( [in] LPCGUID lpcGUID, [out] LPDIRECTSOUNDCAPTURE *lplpDSC, [in] LPUNKNOWN pUnkOuter ); +DSRESULT DirectSoundCaptureEnumerateA( [in] LPDSENUMCALLBACKA lpDSEnumCallback, [in] LPVOID lpContext ); +DSRESULT DirectSoundCaptureEnumerateW( [in] LPDSENUMCALLBACKW lpDSEnumCallback, [in] LPVOID lpContext ); + +DSRESULT DirectSoundCreate8([in] LPCGUID pcGuidDevice, [out] LPDIRECTSOUND8 *ppDS8, [in] LPUNKNOWN pUnkOuter); +DSRESULT DirectSoundCaptureCreate8([in] LPCGUID pcGuidDevice, [out] LPDIRECTSOUNDCAPTURE *ppDSC8, [in] LPUNKNOWN pUnkOuter); +DSRESULT DirectSoundFullDuplexCreate([in] LPCGUID pcGuidCaptureDevice, [in] LPCGUID pcGuidRenderDevice, [in] LPCDSCBUFFERDESC pcDSCBufferDesc, [in] LPCDSBUFFERDESC pcDSBufferDesc, HWND hWnd, DWORD dwLevel, [out] LPDIRECTSOUNDFULLDUPLEX* ppDSFD, [out] LPDIRECTSOUNDCAPTUREBUFFER8 *ppDSCBuffer8, [out] LPDIRECTSOUNDBUFFER8 *ppDSBuffer8, [in] LPUNKNOWN pUnkOuter); +DSRESULT GetDeviceID([in] LPCGUID pGuidSrc, [out] LPGUID pGuidDest); diff --git a/tools/Debugging Tools for Windows/winext/manifest/fileio.h b/tools/Debugging Tools for Windows/winext/manifest/fileio.h new file mode 100644 index 0000000000..ec363ff431 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/fileio.h @@ -0,0 +1,2110 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// File I/O Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category IOFunctions: +module KERNEL32.DLL: + +value long FileErrorReturnValue +{ +#define HFILE_ERROR -1 +}; + +value int _lcreatFileAttributes +{ +#define Normal 0 +#define Read_Only 1 +#define Hidden 2 +#define System 3 +}; + +mask DWORD CopyFlags +{ +#define COPY_FILE_FAIL_IF_EXISTS 0x00000001 +#define COPY_FILE_RESTARTABLE 0x00000002 +#define COPY_FILE_OPEN_SOURCE_FOR_WRITE 0x00000004 +}; + +value DWORD CallBackReasons +{ +#define CALLBACK_CHUNK_FINISHED 0x00000000 +#define CALLBACK_STREAM_SWITCH 0x00000001 +}; + +value DWORD CopyRoutineReturns +{ +#define PROGRESS_CONTINUE 0 +#define PROGRESS_CANCEL 1 +#define PROGRESS_STOP 2 +#define PROGRESS_QUIET 3 +}; + +mask DWORD DefineDosDeviceFlags +{ +#define DDD_RAW_TARGET_PATH 0x00000001 +#define DDD_REMOVE_DEFINITION 0x00000002 +#define DDD_EXACT_MATCH_ON_REMOVE 0x00000004 +#define DDD_NO_BROADCAST_SYSTEM 0x00000008 +}; + +mask DWORD ChangeNotifications +{ +#define FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001 +#define FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002 +#define FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004 +#define FILE_NOTIFY_CHANGE_SIZE 0x00000008 +#define FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010 +#define FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020 +#define FILE_NOTIFY_CHANGE_CREATION 0x00000040 +#define FILE_NOTIFY_CHANGE_SECURITY 0x00000100 +}; + +value LONG _FILEEX_INFO_LEVELS +{ +#define FindExInfoStandard 0 +#define FindExInfoMaxInfoLevel 1 +}; + +value LONG OpenFileStyle +{ +#define OF_READ 0 +#define OF_WRITE 1 +#define OF_READ_WRITE 2 +}; + + +value LONG FINDEX_INFO_LEVELS +{ +#define FindExSearchNameMatch 0 +#define FindExSearchLimitToDirectories 1 +#define FindExSearchLimitToDevices 2 +}; + +value LONG FINDEX_SEARCH_OPS +{ +#define FindExSearchNameMatch 0 +#define FindExSearchLimitToDirectories 1 +#define FindExSearchLimitToDevices 2 +}; + +value LPDWORD GetBinaryTypeValues +{ +#define SCS_32BIT_BINARY 0 +#define SCS_DOS_BINARY 1 +#define SCS_WOW_BINARY 2 +#define SCS_PIF_BINARY 3 +#define SCS_POSIX_BINARY 4 +#define SCS_OS216_BINARY 5 +}; + +value UINT DriveTypes +{ +#define DRIVE_UNKNOWN 0 +#define DRIVE_NO_ROOT_DIR 1 +#define DRIVE_REMOVABLE 2 +#define DRIVE_FIXED 3 +#define DRIVE_REMOTE 4 +#define DRIVE_CDROM 5 +#define DRIVE_RAMDISK 6 +}; + +value DWORD SPDataFlags +{ +#define SPINT_ACTIVE 0x00000001 +#define SPINT_DEFAULT 0x00000002 +#define SPINT_REMOVED 0x00000004 +}; + +typedef struct _SP_DEVICE_INTERFACE_DATA { + DWORD cbSize; + GUID InterfaceClassGuid; + SPDataFlags Flags; + ULONG_PTR Reserved; +} SP_DEVICE_INTERFACE_DATA, *PSP_DEVICE_INTERFACE_DATA; + +typedef struct _SP_DEVINFO_LIST_DETAIL_DATA_A { + DWORD cbSize; + GUID ClassGuid; + HANDLE RemoteMachineHandle; + CHAR RemoteMachineName[263]; +} SP_DEVINFO_LIST_DETAIL_DATA_A, *PSP_DEVINFO_LIST_DETAIL_DATA_A; + +typedef struct _SP_DEVINFO_LIST_DETAIL_DATA_W { + DWORD cbSize; + GUID ClassGuid; + HANDLE RemoteMachineHandle; + WCHAR RemoteMachineName[263]; +} SP_DEVINFO_LIST_DETAIL_DATA_W, *PSP_DEVINFO_LIST_DETAIL_DATA_W; + +typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA_A { + DWORD cbSize; + CHAR DevicePath[1]; +} SP_DEVICE_INTERFACE_DETAIL_DATA_A, *PSP_DEVICE_INTERFACE_DETAIL_DATA_A; + +typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA_W { + DWORD cbSize; + WCHAR DevicePath[1]; +} SP_DEVICE_INTERFACE_DETAIL_DATA_W, *PSP_DEVICE_INTERFACE_DETAIL_DATA_W; + +value LONG GET_FILEEX_INFO_LEVELS +{ +#define GetFileExInfoStandard 0 +}; + +typedef struct _BY_HANDLE_FILE_INFORMATION { + FileFlagsAndAttributes dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD dwVolumeSerialNumber; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD nNumberOfLinks; + DWORD nFileIndexHigh; + DWORD nFileIndexLow; +} BY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION; + + +typedef struct _OFSTRUCT { + BYTE cBytes; + BYTE fFixedDisk; + WORD nErrCode; + WORD Reserved1; + WORD Reserved2; + CHAR szPathName[128]; +} OFSTRUCT, *LPOFSTRUCT, *POFSTRUCT; + +value DWORD GetFileTypeReturnValue +{ +#define FILE_TYPE_UNKNOWN 0x0000 +#define FILE_TYPE_DISK 0x0001 +#define FILE_TYPE_CHAR 0x0002 +#define FILE_TYPE_PIPE 0x0003 +#define FILE_TYPE_REMOTE 0x8000 +}; + +value DWORD LockOptions +{ +#define LOCKFILE_FAIL_IMMEDIATELY 0x00000001 +#define LOCKFILE_EXCLUSIVE_LOCK 0x00000002 +}; + +mask DWORD MoveFilePossibilities +{ +#define MOVEFILE_REPLACE_EXISTING 0x00000001 +#define MOVEFILE_COPY_ALLOWED 0x00000002 +#define MOVEFILE_DELAY_UNTIL_REBOOT 0x00000004 +#define MOVEFILE_WRITE_THROUGH 0x00000008 +#define MOVEFILE_CREATE_HARDLINK 0x00000010 +#define MOVEFILE_FAIL_IF_NOT_TRACKABLE 0x00000020 +}; + +value UINT OpenFileActions +{ +#define OF_READ 0x00000000 +#define OF_WRITE 0x00000001 +#define OF_READWRITE 0x00000002 +#define OF_SHARE_COMPAT 0x00000000 +#define OF_SHARE_EXCLUSIVE 0x00000010 +#define OF_SHARE_DENY_WRITE 0x00000020 +#define OF_SHARE_DENY_READ 0x00000030 +#define OF_SHARE_DENY_NONE 0x00000040 +#define OF_PARSE 0x00000100 +#define OF_DELETE 0x00000200 +#define OF_VERIFY 0x00000400 +#define OF_CANCEL 0x00000800 +#define OF_CREATE 0x00001000 +#define OF_PROMPT 0x00002000 +#define OF_EXIST 0x00004000 +#define OF_REOPEN 0x00008000 +}; + + +value DWORD FileChangeType +{ +#define FILE_ACTION_ADDED 0x00000001 +#define FILE_ACTION_REMOVED 0x00000002 +#define FILE_ACTION_MODIFIED 0x00000003 +#define FILE_ACTION_RENAMED_OLD_NAME 0x00000004 +#define FILE_ACTION_RENAMED_NEW_NAME 0x00000005 +}; + +typedef struct _FILE_NOTIFY_INFORMATION { + DWORD NextEntryOffset; + FileChangeType Action; + DWORD FileNameLength; + WCHAR FileName[1]; +} FILE_NOTIFY_INFORMATION, *LPFILE_NOTIFY_INFORMATION; + +typedef DWORD NumberOfClusters; +alias NumberOfClusters; + +typedef DWORD SectorsPerCluster; +alias SectorsPerCluster; + + +value DWORD SetFilePointerReturn +{ +#define INVALID_SET_POINTER -1 [fail] +}; + +FileErrorReturnValue [gle] _hread( + HFILE hFile, + [out] LPVOID lpBuffer, + long lBytes + ); + +FileErrorReturnValue [gle] _hwrite( + HFILE hFile, + LPCSTR lpBuffer, + long lBytes + ); + +FileErrorReturnValue [gle] _lclose( + [out] HFILE hFile + ); + +FileErrorReturnValue [gle] _lcreat( + LPCSTR lpPathName, + _lcreatFileAttributes iAttribute + ); + +FileErrorReturnValue [gle] _llseek( + HFILE hFile, + LONG lOffset, + FilePointerStartingPosition iOrigin + ); + +FileErrorReturnValue [gle] _lopen( + LPCSTR lpPathName, + OpenFileStyle iReadWrite + ); + +FileErrorReturnValue [gle] _lread( + HFILE hFile, + [out] LPVOID lpBuffer, + UINT uBytes + ); + +FileErrorReturnValue [gle] _lwrite( + HFILE hFile, + LPCSTR lpBuffer, + UINT uBytes + ); + +FailOnFalse AreFileApisANSI(); + +FailOnFalse [gle] CancelIo( + HANDLE hFile + ); + +FailOnFalse [gle] CopyFileA( + LPCSTR lpExistingFileName, + LPCSTR lpNewFileName, + BOOL bFailIfExists + ); + +FailOnFalse [gle] CopyFileW( + LPCWSTR lpExistingFileName, + LPCWSTR lpNewFileName, + BOOL bFailIfExists + ); + +FailOnFalse [gle] CopyFileExA( + LPCSTR lpExistingFileName, + LPCSTR lpNewFileName, + LPVOID lpProgressRoutine, + LPVOID lpData, + LPBOOL pbCancel, + CopyFlags dwCopyFlags + ); + +FailOnFalse [gle] CopyFileExW( + LPCWSTR lpExistingFileName, + LPCWSTR lpNewFileName, + LPVOID lpProgressRoutine, + LPVOID lpData, + LPBOOL pbCancel, + CopyFlags dwCopyFlags + ); + +FailOnFalse [gle] CreateDirectoryA( + LPCSTR lpPathName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +FailOnFalse [gle] CreateDirectoryW( + LPCWSTR lpPathName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +FailOnFalse [gle] CreateDirectoryExA( + LPCSTR lpTemplateDirectory, + LPCSTR lpNewDirectory, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +FailOnFalse [gle] CreateDirectoryExW( + LPCWSTR lpTemplateDirectory, + LPCWSTR lpNewDirectory, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +HANDLE [gle] CreateFileA( + LPCSTR lpFileName, + DWORD dwDesiredAccess, + GenericAccessRights dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + CreationActions dwCreationDisposition, + FileFlagsAndAttributes dwFlagsAndAttributes, + HANDLE hTemplateFile + ); + +HANDLE [gle] CreateFileW( + LPCWSTR lpFileName, + GenericAccessRights dwDesiredAccess, + ShareRights dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + CreationActions dwCreationDisposition, + FileFlagsAndAttributes dwFlagsAndAttributes, + HANDLE hTemplateFile + ); + +HANDLE [gle] CreateIoCompletionPort ( + HANDLE FileHandle, + HANDLE ExistingCompletionPort, + ULONG_PTR CompletionKey, + DWORD NumberOfConcurrentThreads + ); + +FailOnFalse [gle] DefineDosDeviceA( + DefineDosDeviceFlags dwFlags, + LPCSTR lpDeviceName, + LPCSTR lpTargetPath + ); + +FailOnFalse [gle] DefineDosDeviceW( + DefineDosDeviceFlags dwFlags, + LPCWSTR lpDeviceName, + LPCWSTR lpTargetPath + ); + +FailOnFalse [gle] DeleteFileA( + LPCSTR lpFileName + ); + +FailOnFalse [gle] DeleteFileW( + LPCWSTR lpFileName + ); + +FailOnFalse [gle] FindClose( + [out] HANDLE hFindFile + ); + +FailOnFalse [gle] FindCloseChangeNotification( + HANDLE hChangeHandle + ); + +HANDLE [gle] FindFirstChangeNotificationA( + LPCSTR lpPathName, + BOOL bWatchSubtree, + DWORD dwNotifyFilter + ); + +HANDLE [gle] FindFirstChangeNotificationW( + LPCWSTR lpPathName, + BOOL bWatchSubtree, + DWORD ChangeNotifications + ); + +HANDLE [gle] FindFirstFileA( + LPCSTR lpFileName, + [out] LPWIN32_FIND_DATAA lpFindFileData + ); + +HANDLE [gle] FindFirstFileW( + LPCWSTR lpFileName, + [out] LPWIN32_FIND_DATAW lpFindFileData + ); + +HANDLE [gle] FindFirstFileExA( + LPCSTR lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + [out] LPWIN32_FIND_DATAA lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + LPVOID lpSearchFilter, + DWORD dwAdditionalFlags + ); + +HANDLE [gle] FindFirstFileExW( + LPCWSTR lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + [out] LPWIN32_FIND_DATAW lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + LPVOID lpSearchFilter, + DWORD dwAdditionalFlags + ); + +FailOnFalse [gle] FindNextChangeNotification( + HANDLE hChangeHandle + ); + +FailOnFalse [gle] FindNextFileA( + HANDLE hFindFile, + [out] LPWIN32_FIND_DATAA lpFindFileData + ); + +FailOnFalse [gle] FindNextFileW( + HANDLE hFindFile, + [out] LPWIN32_FIND_DATAW lpFindFileData + ); + +FailOnFalse [gle] FlushFileBuffers( + HANDLE hFile + ); + +FailOnFalse [gle] GetBinaryTypeA( + LPCSTR lpApplicationName, + [out] GetBinaryTypeValues lpBinaryType + ); + +FailOnFalse [gle] GetBinaryTypeW( + LPCWSTR lpApplicationName, + [out] GetBinaryTypeValues lpBinaryType + ); + +DwordFailIfZero [gle] GetCurrentDirectoryA( + DWORD nBufferLength, + [out] LPSTR lpBuffer + ); + +DwordFailIfZero [gle] GetCurrentDirectoryW( + DWORD nBufferLength, + [out] LPWSTR lpBuffer + ); + +DwordFailIfZero [gle] GetWindowsDirectoryA( + [out] LPSTR lpBuffer, + UINT uSize + ); + +DwordFailIfZero [gle] GetWindowsDirectoryW( + [out] LPWSTR lpBuffer, + UINT uSize + ); + +DwordFailIfZero [gle] GetSystemDirectoryA( + [out] LPSTR lpBuffer, + UINT uSize + ); + +DwordFailIfZero [gle] GetSystemDirectoryW( + [out] LPWSTR lpBuffer, + UINT uSize + ); + +DwordFailIfZero [gle] GetSystemWindowsDirectoryA( + [out] LPSTR lpBuffer, + UINT uSize + ); + +DwordFailIfZero [gle] GetSystemWindowsDirectoryW( + [out] LPWSTR lpBuffer, + UINT uSize + ); + +FailOnFalse [gle] GetDiskFreeSpaceA( + LPCSTR lpRootPathName, + [out] SectorsPerCluster* lpSectorsPerCluster, + [out] LPDWORD lpBytesPerSector, + [out] NumberOfClusters* lpNumberOfFreeClusters, + [out] NumberOfClusters* lpTotalNumberOfClusters + ); + +FailOnFalse [gle] GetDiskFreeSpaceW( + LPCWSTR lpRootPathName, + [out] SectorsPerCluster* lpSectorsPerCluster, + [out] LPDWORD lpBytesPerSector, + [out] NumberOfClusters* lpNumberOfFreeClusters, + [out] NumberOfClusters* lpTotalNumberOfClusters + ); + +typedef DWORD DiskBytesDWORD; +alias DiskBytesDWORD; + +typedef struct _DiskBytes +{ + DiskBytesDWORD Low; + DiskBytesDWORD High; +} DiskBytes,*PDiskBytes; + + +FailOnFalse [gle] GetDiskFreeSpaceExA( + LPCSTR lpDirectoryName, + [out] PDiskBytes lpFreeBytesAvailable, + [out] PDiskBytes lpTotalNumberOfBytes, + [out] PDiskBytes lpTotalNumberOfFreeBytes + ); + +FailOnFalse [gle] GetDiskFreeSpaceExW( + LPCWSTR lpDirectoryName, + [out] PDiskBytes lpFreeBytesAvailableToCaller, + [out] PDiskBytes lpTotalNumberOfBytes, + [out] PDiskBytes lpTotalNumberOfFreeBytes + ); + +DriveTypes GetDriveTypeA( + LPCSTR lpRootPathName + ); + +DriveTypes GetDriveTypeW( + LPCWSTR lpRootPathName + ); + +FileFlagsAndAttributes [gle] GetFileAttributesA( + LPCSTR lpFileName + ); + +FileFlagsAndAttributes [gle] GetFileAttributesW( + LPCWSTR lpFileName + ); + +FailOnFalse [gle] GetFileAttributesExA( + LPCSTR lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + [out] LPVOID lpFileInformation + ); + +FailOnFalse [gle] GetFileAttributesExW( + LPCWSTR lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + [out] LPVOID lpFileInformation + ); + +FailOnFalse [gle] GetFileInformationByHandle( + HANDLE hFile, + [out] LPBY_HANDLE_FILE_INFORMATION lpFileInformation + ); + +DwordFailIfNeg1 [gle] GetFileSize( + HANDLE hFile, + [out] LPDWORD lpFileSizeHigh + ); + +FailOnFalse [gle] GetFileSizeEx( + HANDLE hFile, + [out] PLARGE_INTEGER lpFileSize + ); + +GetFileTypeReturnValue GetFileType( + HANDLE hFile + ); + +DwordFailIfZero [gle] GetFullPathNameA( + LPCSTR lpFileName, + DWORD nBufferLength, + [out] LPSTR lpBuffer, + [out] LPSTR *lpFilePart + ); + +DwordFailIfZero [gle] GetFullPathNameW( + LPCWSTR lpFileName, + DWORD nBufferLength, + [out] LPWSTR lpBuffer, + [out] LPWSTR *lpFilePart + ); + +DwordFailIfZero [gle] GetLogicalDrives(); + +DwordFailIfZero [gle] GetLogicalDriveStringsA( + DWORD nBufferLength, + [out] LPSTR lpBuffer + ); + +DwordFailIfZero [gle] GetLogicalDriveStringsW( + DWORD nBufferLength, + [out] LPWSTR lpBuffer + ); + +DwordFailIfZero [gle] GetLongPathNameA( + LPCSTR lpszShortPath, + [out] LPSTR lpszLongPath, + DWORD cchBuffer + ); + +DwordFailIfZero [gle] GetLongPathNameW( + LPCWSTR lpszShortPath, + [out] LPWSTR lpszLongPath, + DWORD cchBuffer + ); + +FailOnFalse [gle] GetQueuedCompletionStatus( + HANDLE CompletionPort, + [out] LPDWORD lpNumberOfBytes, + [out] PULONG_PTR lpCompletionKey, + [out] LPOVERLAPPED *lpOverlapped, + DWORD dwMilliseconds + ); + +DwordFailIfZero [gle] GetShortPathNameA( + LPCSTR lpszLongPath, + [out] LPSTR lpszShortPath, + DWORD cchBuffer + ); + +DwordFailIfZero [gle] GetShortPathNameW( + LPCWSTR lpszLongPath, + [out] LPWSTR lpszShortPath, + DWORD cchBuffer + ); + +UintFailIfZero [gle] GetTempFileNameA( + LPCSTR lpPathName, + LPCSTR lpPrefixString, + UINT uUnique, + [out] LPSTR lpTempFileName + ); + +UintFailIfZero [gle] GetTempFileNameW( + LPCWSTR lpPathName, + LPCWSTR lpPrefixString, + UINT uUnique, + [out] LPWSTR lpTempFileName + ); + +DwordFailIfZero [gle] GetTempPathA( + DWORD nBufferLength, + [out] LPSTR lpBuffer + ); + +DwordFailIfZero [gle] GetTempPathW( + DWORD nBufferLength, + [out] LPWSTR lpBuffer + ); + +FailOnFalse [gle] LockFile( + HANDLE hFile, + DWORD dwFileOffsetLow, + DWORD dwFileOffsetHigh, + DWORD nNumberOfBytesToLockLow, + DWORD nNumberOfBytesToLockHigh + ); + +FailOnFalse [gle] LockFileEx( + HANDLE hFile, + LockOptions dwFlags, + DWORD dwReserved, + DWORD nNumberOfBytesToLockLow, + DWORD nNumberOfBytesToLockHigh, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] MoveFileA( + LPCSTR lpExistingFileName, + LPCSTR lpNewFileName + ); + +FailOnFalse [gle] MoveFileW( + LPCWSTR lpExistingFileName, + LPCWSTR lpNewFileName + ); + +FailOnFalse [gle] MoveFileExA( + LPCSTR lpExistingFileName, + LPCSTR lpNewFileName, + MoveFilePossibilities dwFlags + ); + +FailOnFalse [gle] MoveFileExW( + LPCWSTR lpExistingFileName, + LPCWSTR lpNewFileName, + MoveFilePossibilities dwFlags + ); + +FailOnFalse [gle] MoveFileWithProgressA( + LPCSTR lpExistingFileName, + LPCSTR lpNewFileName, + LPVOID lpProgressRoutine , + LPVOID lpData , + DWORD dwFlags + ); + +FailOnFalse [gle] MoveFileWithProgressW( + LPCWSTR lpExistingFileName, + LPCWSTR lpNewFileName, + LPVOID lpProgressRoutine , + LPVOID lpData , + MoveFilePossibilities dwFlags + ); + +IntFailIfNeg1 MulDiv( + int nNumber, + int nNumerator, + int nDenominator + ); + +HFILE OpenFile( + LPCSTR lpFileName, + [out] LPOFSTRUCT lpReOpenBuff, + OpenFileActions uStyle + ); + +FailOnFalse [gle] PostQueuedCompletionStatus( + HANDLE CompletionPort, + DWORD dwNumberOfBytesTransferred, + ULONG_PTR dwCompletionKey, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] PrivCopyFileExW( + LPCWSTR lpExistingFileName, + LPCWSTR lpNewFileName, + LPVOID lpProgressRoutine, + LPVOID lpData, + LPBOOL pbCancel, + CopyFlags dwCopyFlags + ); + +DwordFailIfZero [gle] QueryDosDeviceA( + LPCSTR lpDeviceName, + [out] LPSTR lpTargetPath, + DWORD ucchMax + ); + +DwordFailIfZero [gle] QueryDosDeviceW( + LPCWSTR lpDeviceName, + [out] LPWSTR lpTargetPath, + DWORD ucchMax + ); + +FailOnFalse [gle] ReadDirectoryChangesW( + HANDLE hDirectory, + [out] LPVOID lpBuffer, + DWORD nBufferLength, + BOOL bWatchSubtree, + DWORD dwNotifyFilter, + LPDWORD lpBytesReturned, + LPOVERLAPPED lpOverlapped, + LPVOID lpCompletionRoutine + ); + +FailOnFalse [gle] ReadFile( + HANDLE hFile, + [out] LPVOID lpBuffer, + DWORD nNumberOfBytesToRead, + LPDWORD lpNumberOfBytesRead, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] ReadFileEx( + HANDLE hFile, + [out] LPVOID lpBuffer, + DWORD nNumberOfBytesToRead, + LPOVERLAPPED lpOverlapped, + LPVOID lpCompletionRoutine + ); + +// cjc ULONGLONGFILE_SEGMENT_ELEMENT aSegmentArray[ ], +FailOnFalse [gle] ReadFileScatter( + HANDLE hFile, + ULONGLONG aSegmentArray, + DWORD nNumberOfBytesToRead, + LPDWORD lpReserved, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] RemoveDirectoryA( + LPCSTR lpPathName + ); + +FailOnFalse [gle] RemoveDirectoryW( + LPCWSTR lpPathName + ); + +FailOnFalse [gle] ReplaceFile( + LPCSTR lpReplacedFileName, + LPCSTR lpReplacementFileName, + LPCSTR lpBackupFileName, + DWORD dwReplaceFlags, + LPVOID lpExclude, + LPVOID lpReserved + ); + +FailOnFalse [gle] ReplaceFileW( + LPCWSTR lpReplacedFileName, + LPCWSTR lpReplacementFileName, + LPCWSTR lpBackupFileName, + DWORD dwReplaceFlags, + LPVOID lpExclude, + LPVOID lpReserved + ); + +DwordFailIfZero [gle] SearchPathA( + LPCSTR lpPath, + LPCSTR lpFileName, + LPCSTR lpExtension, + DWORD nBufferLength, + [out] LPSTR lpBuffer, + [out] LPSTR *lpFilePart + ); + +DwordFailIfZero [gle] SearchPathW( + LPCWSTR lpPath, + LPCWSTR lpFileName, + LPCWSTR lpExtension, + DWORD nBufferLength, + [out] LPWSTR lpBuffer, + [out] LPWSTR *lpFilePart + ); + +FailOnFalse [gle] SetCurrentDirectoryA( + LPCSTR lpPathName + ); + +FailOnFalse [gle] SetCurrentDirectoryW( + LPCWSTR lpPathName + ); + +FailOnFalse [gle] SetEndOfFile( + HANDLE hFile + ); + +VOID SetFileApisToANSI(); + +VOID SetFileApisToOEM(); + +FailOnFalse [gle] SetFileAttributesA( + LPCSTR lpFileName, + FileFlagsAndAttributes dwFileAttributes + ); + +FailOnFalse [gle] SetFileAttributesW( + LPCWSTR lpFileName, + FileFlagsAndAttributes dwFileAttributes + ); + +SetFilePointerReturn [gle] SetFilePointer( + HANDLE hFile, + LONG lDistanceToMove, + PLONG lpDistanceToMoveHigh, + FilePointerStartingPosition dwMoveMethod + ); + +FailOnFalse [gle] SetFilePointerEx( + HANDLE hFile, + LARGE_INTEGER liDistanceToMove, + PLARGE_INTEGER lpNewFilePointer, + FilePointerStartingPosition dwMoveMethod + ); + +UINT SetHandleCount( + UINT uNumber + ); + +FailOnFalse [gle] SetVolumeLabelA( + LPCSTR lpRootPathName, + LPCSTR lpVolumeName + ); + +FailOnFalse [gle] SetVolumeLabelW( + LPCWSTR lpRootPathName, + LPCWSTR lpVolumeName + ); + +FailOnFalse [gle] UnlockFile( + HANDLE hFile, + DWORD dwFileOffsetLow, + DWORD dwFileOffsetHigh, + DWORD nNumberOfBytesToUnlockLow, + DWORD nNumberOfBytesToUnlockHigh + ); + +FailOnFalse [gle] UnlockFileEx( + HANDLE hFile, + DWORD dwReserved, + DWORD nNumberOfBytesToUnlockLow, + DWORD nNumberOfBytesToUnlockHigh, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] WriteFile( + HANDLE hFile, + LPCVOID lpBuffer, + DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] WriteFileEx( + HANDLE hFile, + LPCVOID lpBuffer, + DWORD nNumberOfBytesToWrite, + LPOVERLAPPED lpOverlapped, + LPVOID lpCompletionRoutine + ); + +// cjc[out] FILE_SEGMENT_ELEMENT aSegmentArray[ ], +FailOnFalse [gle] WriteFileGather( + HANDLE hFile, + [out] ULONGLONG aSegmentArray, + DWORD nNumberOfBytesToWrite, + LPDWORD lpReserved, + LPOVERLAPPED lpOverlapped + ); + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// File System Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +mask LPDWORD FileSystemFlags +{ +#define FS_CASE_SENSITIVE 0x00000001 +#define FS_CASE_IS_PRESERVED 0x00000002 +#define FS_UNICODE_STORED_ON_DISK 0x00000004 +#define FS_PERSISTENT_ACLS 0x00000008 +#define FS_VOL_IS_COMPRESSED 0x00008000 +#define FS_FILE_COMPRESSION 0x00000010 +#define FILE_SUPPORTS_OBJECT_IDS 0x00010000 +#define FILE_SUPPORTS_ENCRYPTION 0x00020000 +#define FILE_SUPPORTS_REPARSE_POINTS 0x00000080 +#define FILE_SUPPORTS_REMOTE_STORAGE 0x00000100 +}; + +typedef struct _EFS_HASH_BLOB { + DWORD cbData; + PBYTE pbData; +} EFS_HASH_BLOB, *LPEFS_HASH_BLOB, *PEFS_HASH_BLOB; + +typedef struct _CERTIFICATE_BLOB { + DWORD dwCertEncodingType; + DWORD cbData; + PBYTE pbData; +} EFS_CERTIFICATE_BLOB, *PEFS_CERTIFICATE_BLOB; + +//cjc SID *pUserSid; +typedef struct _ENCRYPTION_CERTIFICATE_HASH { + DWORD cbTotalLength; + VOID *pUserSid; + PEFS_HASH_BLOB pHash; + LPWSTR lpDisplayInformation; +} ENCRYPTION_CERTIFICATE_HASH, *LPENCRYPTION_CERTIFICATE_HASH, *PENCRYPTION_CERTIFICATE_HASH; + +typedef struct _ENCRYPTION_CERTIFICATE_HASH_LIST { + DWORD nCert_Hash; + PENCRYPTION_CERTIFICATE_HASH *pUsers; +} ENCRYPTION_CERTIFICATE_HASH_LIST, *LPENCRYPTION_CERTIFICATE_HASH_LIST, *PENCRYPTION_CERTIFICATE_HASH_LIST; + + +//cjc SID *pUserSid; +typedef struct _ENCRYPTION_CERTIFICATE { + DWORD cbTotalLength; + VOID *pUserSid; + PEFS_CERTIFICATE_BLOB pCertBlob; +} ENCRYPTION_CERTIFICATE, *LPENCRYPTION_CERTIFICATE, *PENCRYPTION_CERTIFICATE; + +typedef struct _ENCRYPTION_CERTIFICATE_LIST { + DWORD nUsers; + PENCRYPTION_CERTIFICATE * pUsers; +} ENCRYPTION_CERTIFICATE_LIST, *LPENCRYPTION_CERTIFICATE_LIST, *PENCRYPTION_CERTIFICATE_LIST; + +FailOnFalse [gle] CreateHardLinkA( + LPCSTR lpFileName, + LPCSTR lpExistingFileName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +FailOnFalse [gle] CreateHardLinkW( + LPCWSTR lpFileName, + LPCWSTR lpExistingFileName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +module ADVAPI32.DLL: +WinError AddUsersToEncryptedFile( + LPCWSTR lpFileName, + PENCRYPTION_CERTIFICATE_LIST pUsers + ); + +FailOnFalse [gle] DecryptFileA( + LPCSTR lpFileName, + DWORD dwReserved + ); + +FailOnFalse [gle] DecryptFileW( + LPCWSTR lpFileName, + DWORD dwReserved + ); + +module KERNEL32.DLL: +FailOnFalse [gle] DeleteVolumeMountPointA( + LPCSTR lpszVolumeMountPoint + ); + +FailOnFalse [gle] DeleteVolumeMountPointW( + LPCWSTR lpszVolumeMountPoint + ); + + +module ADVAPI32.DLL: +FailOnFalse [gle] EncryptFileA( + LPCSTR lpFileName + ); + +FailOnFalse [gle] EncryptFileW( + LPCWSTR lpFileName + ); + +FailOnFalse [gle] EncryptionDisable( + LPCWSTR DirPath, + BOOL Disable + ); + +FailOnFalse [gle] FileEncryptionStatusA( + LPCSTR lpFileName, + LPDWORD lpStatus + ); + +FailOnFalse [gle] FileEncryptionStatusW( + LPCWSTR lpFileName, + LPDWORD lpStatus + ); + +module KERNEL32.DLL: + +HANDLE FindFirstVolumeA( + [out] LPCSTR lpszVolumeName, + DWORD cchBufferLength + ); + +HANDLE FindFirstVolumeW( + [out] LPCWSTR lpszVolumeName, + DWORD cchBufferLength + ); + +HANDLE FindFirstVolumeMountPointA( + LPSTR lpszRootPathName, + [out] LPSTR lpszVolumeMountPoint, + DWORD cchBufferLength + ); + + +HANDLE FindFirstVolumeMountPointW( + LPWSTR lpszRootPathName, + [out] LPWSTR lpszVolumeMountPoint, + DWORD cchBufferLength + ); + +FailOnFalse [gle] FindNextVolumeA( + HANDLE hFindVolume, + [out] LPSTR lpszVolumeName, + DWORD cchBufferLength + ); + +FailOnFalse [gle] FindNextVolumeW( + HANDLE hFindVolume, + [out] LPWSTR lpszVolumeName, + DWORD cchBufferLength + ); + + +FailOnFalse [gle] FindNextVolumeMountPointA( + HANDLE hFindVolumeMountPoint, + [out] LPSTR lpszVolumeMountPoint, + DWORD cchBufferLength + ); + +FailOnFalse [gle] FindNextVolumeMountPointW( + HANDLE hFindVolumeMountPoint, + [out] LPWSTR lpszVolumeMountPoint, + DWORD cchBufferLength + ); + + +FailOnFalse [gle] FindVolumeClose( + HANDLE hFindVolume + ); + +FailOnFalse [gle] FindVolumeMountPointClose( + HANDLE hFindVolumeMountPoint + ); + +module ADVAPI32.DLL: +VOID FreeEncryptionCertificateHashList( + PENCRYPTION_CERTIFICATE_HASH_LIST pHashes + ); + +module KERNEL32.DLL: + +DwordFailIfNeg1 [gle] GetCompressedFileSizeA( + LPCSTR lpFileName, + [out] LPDWORD lpFileSizeHigh + ); + +DwordFailIfNeg1 [gle] GetCompressedFileSizeW( + LPCWSTR lpFileName, + [out] LPDWORD lpFileSizeHigh + ); + +FailOnFalse [gle] GetVolumeInformationA( + LPCSTR lpRootPathName, + LPSTR lpVolumeNameBuffer, + DWORD nVolumeNameSize, + LPDWORD lpVolumeSerialNumber, + LPDWORD lpMaximumComponentLength, + LPDWORD lpFileSystemFlags, + LPSTR lpFileSystemNameBuffer, + DWORD nFileSystemNameSize + ); + +FailOnFalse [gle] GetVolumeInformationW( + LPCWSTR lpRootPathName, + [out] LPWSTR lpVolumeNameBuffer, + DWORD nVolumeNameSize, + [out] LPDWORD lpVolumeSerialNumber, + [out] LPDWORD lpMaximumComponentLength, + [out] FileSystemFlags lpFileSystemFlags, + [out] LPWSTR lpFileSystemNameBuffer, + DWORD nFileSystemNameSize + ); + +FailOnFalse [gle] GetVolumeNameForVolumeMountPointA( + LPCSTR lpszVolumeMountPoint, + [out] LPSTR lpszVolumeName, + DWORD cchBufferLength + ); + +FailOnFalse [gle] GetVolumeNameForVolumeMountPointW( + LPCWSTR lpszVolumeMountPoint, + [out] LPWSTR lpszVolumeName, + DWORD cchBufferLength + ); + + +FailOnFalse [gle] GetVolumePathNameA( + LPCSTR lpszFileName, + [out] LPSTR lpszVolumePathName, + DWORD cchBufferLength + ); + +FailOnFalse [gle] GetVolumePathNameW( + LPCWSTR lpszFileName, + [out] LPWSTR lpszVolumePathName, + DWORD cchBufferLength + ); + + +module ADVAPI32.DLL: +WinError QueryRecoveryAgentsOnEncryptedFile( + LPCWSTR lpFileName, + [out] LPENCRYPTION_CERTIFICATE_HASH_LIST *pRecoveryAgents + ); + +WinError QueryUsersOnEncryptedFile( + LPCWSTR lpFileName, + [out] LPENCRYPTION_CERTIFICATE_HASH_LIST *pUsers + ); + +WinError RemoveUsersFromEncryptedFile( + LPCWSTR lpFileName, + LPENCRYPTION_CERTIFICATE_HASH_LIST pHashes + ); + +DWORD SetUserFileEncryptionKey( + LPENCRYPTION_CERTIFICATE pEncryptionCertificate + ); + +module KERNEL32.DLL: +FailOnFalse [gle] SetVolumeMountPointA( + LPCSTR lpszVolumeMountPoint, + LPCSTR lpszVolumeName + ); + + +FailOnFalse [gle] SetVolumeMountPointW( + LPCWSTR lpszVolumeMountPoint, + LPCWSTR lpszVolumeName + ); + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Tape Backup Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category DeviceFunctions: + +value DWORD StreamIds +{ +#define BACKUP_DATA 0x00000001 +#define BACKUP_EA_DATA 0x00000002 +#define BACKUP_SECURITY_DATA 0x00000003 +#define BACKUP_ALTERNATE_DATA 0x00000004 +#define BACKUP_LINK 0x00000005 +#define BACKUP_PROPERTY_DATA 0x00000006 +#define BACKUP_OBJECT_ID 0x00000007 +#define BACKUP_REPARSE_DATA 0x00000008 +#define BACKUP_SPARSE_BLOCK 0x00000009 +}; + +value DWORD StreamAttributes +{ +#define STREAM_MODIFIED_WHEN_READ 0x00000001 +#define STREAM_CONTAINS_SECURITY 0x00000002 +}; + +typedef struct _WIN32_STREAM_ID { + StreamIds dwStreamId; + StreamAttributes dwStreamAttributes; + LARGE_INTEGER Size; + DWORD dwStreamNameSize; + WCHAR cStreamName[1]; +} WIN32_STREAM_ID, *LPWIN32_STREAM_ID; + +value DWORD TapeCreateDefinitions +{ +#define TAPE_FIXED_PARTITIONS 0L +#define TAPE_SELECT_PARTITIONS 1L +#define TAPE_INITIATOR_PARTITIONS 2L +}; + +value DWORD EraseTypeToPerform +{ +#define TAPE_ERASE_SHORT 0L +#define TAPE_ERASE_LONG 1L +}; + +value DWORD TypeOfInformation +{ +#define GET_TAPE_MEDIA_INFORMATION 0 +#define GET_TAPE_DRIVE_INFORMATION 1 +}; + +typedef struct _TAPE_GET_MEDIA_PARAMETERS { + LARGE_INTEGER Capacity; + LARGE_INTEGER Remaining; + DWORD BlockSize; + DWORD PartitionCount; + BOOLEAN WriteProtected; +} TAPE_GET_MEDIA_PARAMETERS, *LPTAPE_GET_MEDIA_PARAMETERS; + +typedef struct _TAPE_GET_DRIVE_PARAMETERS { + BOOLEAN ECC; + BOOLEAN Compression; + BOOLEAN DataPadding; + BOOLEAN ReportSetmarks; + DWORD DefaultBlockSize; + DWORD MaximumBlockSize; + DWORD MinimumBlockSize; + DWORD MaximumPartitionCount; + DWORD FeaturesLow; + DWORD FeaturesHigh; + DWORD EOTWarningZoneSize; +} TAPE_GET_DRIVE_PARAMETERS, *LPTAPE_GET_DRIVE_PARAMETERS; + +value DWORD TapePosition +{ +#define TAPE_ABSOLUTE_POSITION 0L +#define TAPE_LOGICAL_POSITION 1L +#define TAPE_PSEUDO_LOGICAL_POSITION 2L +}; + +value DWORD TapeDevicePreparation +{ +#define TAPE_LOAD 0L +#define TAPE_UNLOAD 1L +#define TAPE_TENSION 2L +#define TAPE_LOCK 3L +#define TAPE_UNLOCK 4L +#define TAPE_FORMAT 5L +}; + +value DWORD InformationToBeSet +{ +#define SET_TAPE_MEDIA_INFORMATION 0 +#define SET_TAPE_DRIVE_INFORMATION 1 +}; + +typedef struct _TAPE_SET_MEDIA_PARAMETERS { + DWORD BlockSize; +} TAPE_SET_MEDIA_PARAMETERS, *LPTAPE_SET_MEDIA_PARAMETERS; + +typedef struct _TAPE_SET_DRIVE_PARAMETERS { + BOOLEAN ECC; + BOOLEAN Compression; + BOOLEAN DataPadding; + BOOLEAN ReportSetmarks; + DWORD EOTWarningZoneSize; +} TAPE_SET_DRIVE_PARAMETERS, *LPTAPE_SET_DRIVE_PARAMETERS; + +value DWORD TypeOfPositioning +{ +#define TAPE_REWIND 0L +#define TAPE_ABSOLUTE_BLOCK 1L +#define TAPE_LOGICAL_BLOCK 2L +#define TAPE_PSEUDO_LOGICAL_BLOCK 3L +#define TAPE_SPACE_END_OF_DATA 4L +#define TAPE_SPACE_RELATIVE_BLOCKS 5L +#define TAPE_SPACE_FILEMARKS 6L +#define TAPE_SPACE_SEQUENTIAL_FMKS 7L +#define TAPE_SPACE_SETMARKS 8L +#define TAPE_SPACE_SEQUENTIAL_SMKS 9L +}; + +value DWORD TapeMarksToWrite +{ +#define TAPE_SETMARKS 0L +#define TAPE_FILEMARKS 1L +#define TAPE_SHORT_FILEMARKS 2L +#define TAPE_LONG_FILEMARKS 3L +}; + +FailOnFalse [gle] BackupRead( + HANDLE hFile, + [out] LPBYTE lpBuffer, + DWORD nNumberOfBytesToRead, + LPDWORD lpNumberOfBytesRead, + BOOL bAbort, + BOOL bProcessSecurity, + [out] LPVOID *lpContext + ); + +FailOnFalse [gle] BackupSeek( + HANDLE hFile, + DWORD dwLowBytesToSeek, + DWORD dwHighBytesToSeek, + [out] LPDWORD lpdwLowByteSeeked, + [out] LPDWORD lpdwHighByteSeeked, + LPVOID *lpContext + ); + +FailOnFalse [gle] BackupWrite( + HANDLE hFile, + LPBYTE lpBuffer, + DWORD nNumberOfBytesToWrite, + [out] LPDWORD lpNumberOfBytesWritten, + BOOL bAbort, + BOOL bProcessSecurity, + [out] LPVOID *lpContext + ); + +WinError CreateTapePartition( + HANDLE hDevice, + TapeCreateDefinitions dwPartitionMethod, + DWORD dwCount, + DWORD dwSize + ); + +WinError EraseTape( + HANDLE hDevice, + EraseTypeToPerform dwEraseType, + BOOL bImmediate + ); + +WinError GetTapeParameters( + HANDLE hDevice, + TypeOfInformation dwOperation, + [out] LPDWORD lpdwSize, + [out] LPVOID lpTapeInformation + ); + +WinError GetTapePosition( + HANDLE hDevice, + TapePosition dwPositionType, + [out] LPDWORD lpdwPartition, + [out] LPDWORD lpdwOffsetLow, + [out] LPDWORD lpdwOffsetHigh + ); + +WinError GetTapeStatus( + HANDLE hDevice + ); + +WinError PrepareTape( + HANDLE hDevice, + TapeDevicePreparation dwOperation, + BOOL bImmediate + ); + +WinError SetTapeParameters( + HANDLE hDevice, + InformationToBeSet dwOperation, + LPVOID lpTapeInformation + ); + +WinError SetTapePosition( + HANDLE hDevice, + TypeOfPositioning dwPositionMethod, + DWORD dwPartition, + DWORD dwOffsetLow, + DWORD dwOffsetHigh, + BOOL bImmediate + ); + +WinError WriteTapemark( + HANDLE hDevice, + TapeMarksToWrite dwTapemarkType, + DWORD dwTapemarkCount, + BOOL bImmediate + ); + + + + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Device Input and Output Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +FailOnFalse [gle] DeviceIoControl( + HANDLE hDevice, + DWORD dwIoControlCode, + LPVOID lpInBuffer, + DWORD nInBufferSize, + LPVOID lpOutBuffer, + DWORD nOutBufferSize, + [out] LPDWORD lpBytesReturned, + LPOVERLAPPED lpOverlapped + ); + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Device Management Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +typedef struct _DEV_BROADCAST_HDR { + DWORD dbch_size; + DWORD dbch_devicetype; + DWORD dbch_reserved; +} DEV_BROADCAST_HDR; +typedef DEV_BROADCAST_HDR *LPDEV_BROADCAST_HDR; + +value DWORD HandleType +{ +#define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000 +}; + +value HANDLE HdevnotifyFailNull +{ +#define NULL 0 [fail] +}; + +value HANDLE HdevinfoFailInvalid +{ +#define INVALID_HANDLE_VALUE -1 [fail] +}; + +value HKEY HkeyFailInvalid +{ +#define INVALID_HANDLE_VALUE -1 [fail] +}; + +mask DWORD DeviceInterfaceFlags +{ +#define SPINT_ACTIVE 0x00000001 +#define SPINT_DEFAULT 0x00000002 +#define SPINT_REMOVED 0x00000004 +}; + +typedef struct _SP_DEVINFO_DATA { + DWORD cbSize; + GUID ClassGuid; + DWORD DevInst; + ULONG_PTR Reserved; +} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA; + +value DWORD ControlOptionFlags +{ +#define DIGCF_DEFAULT 0x00000001 // only valid with DIGCF_DEVICEINTERFACE +#define DIGCF_PRESENT 0x00000002 +#define DIGCF_ALLCLASSES 0x00000004 +#define DIGCF_PROFILE 0x00000008 +#define DIGCF_DEVICEINTERFACE 0x00000010 +}; + +value DWORD SetupDiopenClassRegKeyExFlags +{ +#define DIOCR_INSTALLER 0x00000001 // class installer registry branch +#define DIOCR_INTERFACE 0x00000002 // interface class registry branch +}; + +module USER32.DLL: +HdevnotifyFailNull [gle] RegisterDeviceNotificationA( + HANDLE hRecipient, + LPVOID NotificationFilter, + HandleType Flags + ); + +HdevnotifyFailNull [gle] RegisterDeviceNotificationW( + HANDLE hRecipient, + LPVOID NotificationFilter, + HandleType Flags + ); + +FailOnFalse [gle] UnregisterDeviceNotification( + HDEVNOTIFY Handle + ); + +module SETUPAPI.DLL: +HdevinfoFailInvalid [gle] SetupDiCreateDeviceInfoList( + LPGUID ClassGuid, + HWND hwndParent + ); + +HdevinfoFailInvalid [gle] SetupDiCreateDeviceInfoListExA( + LPGUID ClassGuid, + HWND hwndParent, + LPCSTR MachineName, + PVOID Reserved + ); + +HdevinfoFailInvalid [gle] SetupDiCreateDeviceInfoListExW( + LPGUID ClassGuid, + HWND hwndParent, + LPCWSTR MachineName, + PVOID Reserved + ); + +HkeyFailInvalid [gle] SetupDiCreateDeviceInterfaceRegKeyA( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + DWORD Reserved, + DesiredSecurityAccess samDesired, + HINF InfHandle, + LPCSTR InfSectionName + ); + +HkeyFailInvalid [gle] SetupDiCreateDeviceInterfaceRegKeyW( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + DWORD Reserved, + DesiredSecurityAccess samDesired, + HINF InfHandle, + LPCWSTR InfSectionName + ); + +FailOnFalse [gle] SetupDiDeleteDeviceInterfaceData( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData + ); + +FailOnFalse [gle] SetupDiDeleteDeviceInterfaceRegKey( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + DWORD Reserved + ); + +FailOnFalse [gle] SetupDiDestroyDeviceInfoList( + HDEVINFO DeviceInfoSet + ); + +FailOnFalse [gle] SetupDiEnumDeviceInterfaces( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + LPGUID InterfaceClassGuid, + DWORD MemberIndex, + [out] PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData + ); + +HdevinfoFailInvalid [gle] SetupDiGetClassDevsA( + LPGUID ClassGuid, + LPCSTR Enumerator, + HWND hwndParent, + ControlOptionFlags Flags + ); + +HdevinfoFailInvalid [gle] SetupDiGetClassDevsW( + LPGUID ClassGuid, + LPCWSTR Enumerator, + HWND hwndParent, + ControlOptionFlags Flags + ); + +HdevinfoFailInvalid [gle] SetupDiGetClassDevsExA( + LPGUID ClassGuid, + LPCSTR Enumerator, + HWND hwndParent, + ControlOptionFlags Flags, + HDEVINFO DeviceInfoSet, + LPCSTR MachineName, + PVOID Reserved + ); + +HdevinfoFailInvalid [gle] SetupDiGetClassDevsExW( + LPGUID ClassGuid, + LPCWSTR Enumerator, + HWND hwndParent, + ControlOptionFlags Flags, + HDEVINFO DeviceInfoSet, + LPCWSTR MachineName, + PVOID Reserved + ); + +FailOnFalse [gle] SetupDiGetDeviceInterfaceAlias( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + LPGUID AliasInterfaceClassGuid, + [out] PSP_DEVICE_INTERFACE_DATA AliasDeviceInterfaceData + ); + +FailOnFalse [gle] SetupDiGetDeviceInterfaceDetailA( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + [out] PSP_DEVICE_INTERFACE_DETAIL_DATA_A DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + [out] PDWORD RequiredSize, + [out] PSP_DEVINFO_DATA DeviceInfoData + ); + +FailOnFalse [gle] SetupDiGetDeviceInterfaceDetailW( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + [out] PSP_DEVICE_INTERFACE_DETAIL_DATA_W DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + [out] PDWORD RequiredSize, + [out] PSP_DEVINFO_DATA DeviceInfoData + ); + +HkeyFailInvalid [gle] SetupDiOpenClassRegKeyExA( + LPGUID ClassGuid, + DesiredSecurityAccess samDesired, + SetupDiopenClassRegKeyExFlags Flags, + LPCSTR MachineName, + PVOID Reserved + ); + +HkeyFailInvalid [gle] SetupDiOpenClassRegKeyExW( + LPGUID ClassGuid, + DesiredSecurityAccess samDesired, + SetupDiopenClassRegKeyExFlags Flags, + LPCWSTR MachineName, + PVOID Reserved + ); + +FailOnFalse [gle] SetupDiOpenDeviceInterfaceA( + HDEVINFO DeviceInfoSet, + LPCSTR DevicePath, + DWORD OpenFlags, + [out] PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData + ); + +FailOnFalse [gle] SetupDiOpenDeviceInterfaceW( + HDEVINFO DeviceInfoSet, + LPCWSTR DevicePath, + DWORD OpenFlags, + [out] PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData + ); + +HkeyFailInvalid [gle] SetupDiOpenDeviceInterfaceRegKey( + HDEVINFO DeviceInfoSet, + PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, + DWORD Reserved, + DesiredSecurityAccess samDesired + ); +module KERNEL32.DLL: + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Power Management Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +value BYTE ACLineStatusValues +{ +#define Offline 0 +#define Online 1 +#define Unknown 255 +}; + +value BYTE BatteryFlags +{ +#define High 1 +#define Low 2 +#define Critical 4 +#define Charging 8 +#define No_system_battery 128 +#define Unknown_status 255 +}; + +typedef struct _SYSTEM_POWER_STATUS { + ACLineStatusValues ACLineStatus; + BatteryFlags BatteryFlag; + BYTE BatteryLifePercent; + BYTE Reserved1; + DWORD BatteryLifeTime; + DWORD BatteryFullLifeTime; +} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS; + +mask DWORD ExecutionRequirements +{ +#define ES_SYSTEM_REQUIRED 0x00000001 +#define ES_DISPLAY_REQUIRED 0x00000002 +#define ES_USER_PRESENT 0x00000004 +#define ES_CONTINUOUS 0x80000000 +}; + + +value DWORD LATENCY_TIME +{ +#define LT_DONT_CARE 0 +#define LT_LOWEST_LATENCY 1 +}; + +FailOnFalse GetDevicePowerState( + HANDLE hDevice, + [out] BOOL *pfOn + ); + +FailOnFalse [gle] GetSystemPowerStatus( + LPSYSTEM_POWER_STATUS lpSystemPowerStatus + ); + +FailOnFalse IsSystemResumeAutomatic(); + +FailOnFalse RequestWakeupLatency( + LATENCY_TIME latency + ); + +FailOnFalse [gle] SetSystemPowerState( + BOOL fSuspend, + BOOL fForce + ); + +ULONG SetThreadExecutionState( + ExecutionRequirements esFlags + ); + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Atom Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category AtomFunctions: + +value ATOM AtomFailIfZero +{ +#define ZERO 0 [fail] +}; + +AtomFailIfZero [gle] AddAtomA( + LPCSTR lpString + ); + +AtomFailIfZero [gle] AddAtomW( + LPCWSTR lpString + ); + +ATOM [gle] DeleteAtom( + ATOM nAtom + ); + +AtomFailIfZero [gle] FindAtomA( + LPCSTR lpString + ); + +AtomFailIfZero [gle] FindAtomW( + LPCWSTR lpString + ); + +UintFailIfZero [gle] GetAtomNameA( + ATOM nAtom, + [out] LPSTR lpBuffer, + int nSize + ); + +UintFailIfZero [gle] GetAtomNameW( + ATOM nAtom, + [out] LPWSTR lpBuffer, + int nSize + ); + + + +AtomFailIfZero [gle] GlobalAddAtomA( + LPCSTR lpString + ); + +AtomFailIfZero [gle] GlobalAddAtomW( + LPCWSTR lpString + ); + +ATOM [gle] GlobalDeleteAtom( + ATOM nAtom + ); + +AtomFailIfZero [gle] GlobalFindAtomA( + LPCSTR lpString + ); + +AtomFailIfZero [gle] GlobalFindAtomW( + LPCWSTR lpString + ); + +UintFailIfZero [gle] GlobalGetAtomNameA( + ATOM nAtom, + [out] LPSTR lpBuffer, + int nSize + ); + +UintFailIfZero [gle] GlobalGetAtomNameW( + ATOM nAtom, + [out] LPWSTR lpBuffer, + int nSize + ); + +FailOnFalse InitAtomTable( + DWORD nSize + ); + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Handle and Object Functions + +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category HandleAndObjectFunctions: + +mask DWORD OptionalActions +{ +#define DUPLICATE_CLOSE_SOURCE 0x00000001 +#define DUPLICATE_SAME_ACCESS 0x00000002 +}; + +mask LPDWORD HandleProperties +{ +#define HANDLE_FLAG_INHERIT 0x00000001 +#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 +}; + +FailOnFalse [gle] CloseHandle( + [out] HANDLE hObject + ); + +FailOnFalse [gle] DuplicateHandle( + HANDLE hSourceProcessHandle, + HANDLE hSourceHandle, + HANDLE hTargetProcessHandle, + [out] LPHANDLE lpTargetHandle, + DWORD dwDesiredAccess, + BOOL bInheritHandle, + OptionalActions dwOptions + ); + +FailOnFalse [gle] GetHandleInformation( + HANDLE hObject, + [out] HandleProperties lpdwFlags + ); + +FailOnFalse [gle] SetHandleInformation( + HANDLE hObject, + DWORD dwMask, + HandleProperties dwFlags + ); + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Mailslot Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category IOFunctions: + +value DWORD MessageWaitLength +{ +#define NoMessage 0 +#define MAILSLOT_WAIT_FOREVER -1 +}; + +value LPDWORD NextSizeValue +{ +#define MAILSLOT_NO_MESSAGE -1 +}; + +HANDLE [gle] CreateMailslotA( + LPCSTR lpName, + DWORD nMaxMessageSize, + MessageWaitLength lReadTimeout, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +HANDLE [gle] CreateMailslotW( + LPCWSTR lpName, + DWORD nMaxMessageSize, + MessageWaitLength lReadTimeout, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +FailOnFalse [gle] GetMailslotInfo( + HANDLE hMailslot, + LPDWORD lpMaxMessageSize, + NextSizeValue lpNextSize, + LPDWORD lpMessageCount, + LPDWORD lpReadTimeout + ); + +FailOnFalse [gle] SetMailslotInfo( + HANDLE hMailslot, + MessageWaitLength lReadTimeout + ); + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Pipe Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category IOFunctions: + +value DWORD TimeOutValue +{ +#define NMPWAIT_WAIT_FOREVER 0xffffffff +#define NMPWAIT_NOWAIT 0x00000001 +#define NMPWAIT_USE_DEFAULT_WAIT 0x00000000 +}; + + +mask DWORD PipeSpecificModes +{ +#define PIPE_TYPE_BYTE 0x00000000 +#define PIPE_TYPE_MESSAGE 0x00000004 +#define PIPE_READMODE_MESSAGE 0x00000002 + // #define PIPE_READMODE_BYTE 0x00000000 + // #define PIPE_WAIT 0x00000000 +#define PIPE_NOWAIT 0x00000001 +}; + +value LPDWORD PipeType +{ +#define PIPE_CLIENT_END 0x00000000 +#define PIPE_SERVER_END 0x00000001 + // #define PIPE_TYPE_BYTE 0x00000000 +#define PIPE_TYPE_MESSAGE 0x00000004 +}; + + + + +FailOnFalse [gle] CallNamedPipeA( + LPCSTR lpNamedPipeName, + LPVOID lpInBuffer, + DWORD nInBufferSize, + [out] LPVOID lpOutBuffer, + DWORD nOutBufferSize, + [out] LPDWORD lpBytesRead, + TimeOutValue nTimeOut + ); + +FailOnFalse [gle] CallNamedPipeW( + LPCWSTR lpNamedPipeName, + LPVOID lpInBuffer, + DWORD nInBufferSize, + [out] LPVOID lpOutBuffer, + DWORD nOutBufferSize, + [out] LPDWORD lpBytesRead, + TimeOutValue nTimeOut + ); + +FailOnFalse [gle] ConnectNamedPipe( + HANDLE hNamedPipe, + LPOVERLAPPED lpOverlapped + ); + +HANDLE [gle] CreateNamedPipeA( + LPCSTR lpName, + AccessMode dwOpenMode, + PipeSpecificModes dwPipeMode, + DWORD nMaxInstances, + DWORD nOutBufferSize, + DWORD nInBufferSize, + DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +HANDLE [gle] CreateNamedPipeW( + LPCWSTR lpName, + AccessMode dwOpenMode, + PipeSpecificModes dwPipeMode, + DWORD nMaxInstances, + DWORD nOutBufferSize, + DWORD nInBufferSize, + DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes + ); + +FailOnFalse [gle] CreatePipe( + [out] PHANDLE hReadPipe, + [out] PHANDLE hWritePipe, + LPSECURITY_ATTRIBUTES lpPipeAttributes, + DWORD nSize + ); + +FailOnFalse [gle] DisconnectNamedPipe( + HANDLE hNamedPipe + ); + +FailOnFalse [gle] GetNamedPipeHandleStateA( + HANDLE hNamedPipe, + [out] PipeSpecificModes lpState, + [out] LPDWORD lpCurInstances, + [out] LPDWORD lpMaxCollectionCount, + [out] LPDWORD lpCollectDataTimeout, + [out] LPSTR lpUserName, + DWORD nMaxUserNameSize + ); + +FailOnFalse [gle] GetNamedPipeHandleStateW( + HANDLE hNamedPipe, + [out] PipeSpecificModes lpState, + [out] LPDWORD lpCurInstances, + [out] LPDWORD lpMaxCollectionCount, + [out] LPDWORD lpCollectDataTimeout, + [out] LPWSTR lpUserName, + DWORD nMaxUserNameSize + ); + +FailOnFalse [gle] GetNamedPipeInfo( + HANDLE hNamedPipe, + PipeType lpFlags, + [out] LPDWORD lpOutBufferSize, + [out] LPDWORD lpInBufferSize, + [out] LPDWORD lpMaxInstances + ); + +FailOnFalse [gle] PeekNamedPipe( + HANDLE hNamedPipe, + [out] LPVOID lpBuffer, + DWORD nBufferSize, + [out] LPDWORD lpBytesRead, + [out] LPDWORD lpTotalBytesAvail, + [out] LPDWORD lpBytesLeftThisMessage + ); + +FailOnFalse [gle] SetNamedPipeHandleState( + HANDLE hNamedPipe, + PipeSpecificModes lpMode, + LPDWORD lpMaxCollectionCount, + LPDWORD lpCollectDataTimeout + ); + +FailOnFalse [gle] TransactNamedPipe( + HANDLE hNamedPipe, + LPVOID lpInBuffer, + DWORD nInBufferSize, + LPVOID lpOutBuffer, + DWORD nOutBufferSize, + [out] LPDWORD lpBytesRead, + LPOVERLAPPED lpOverlapped + ); + +FailOnFalse [gle] WaitNamedPipeA( + LPCSTR lpNamedPipeName, + TimeOutValue nTimeOut + ); + +FailOnFalse [gle] WaitNamedPipeW( + LPCWSTR lpNamedPipeName, + TimeOutValue nTimeOut + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/gdi32.h b/tools/Debugging Tools for Windows/winext/manifest/gdi32.h new file mode 100644 index 0000000000..29fc6b9acc --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/gdi32.h @@ -0,0 +1,5565 @@ +module GDI32.DLL: +category GDI: + + +typedef HANDLE HGDIOBJ; +typedef HANDLE HFONT; +typedef HANDLE HPALETTE; +typedef HANDLE HBITMAP; +typedef HANDLE HBRUSH; +typedef HANDLE HPEN; +typedef HANDLE HENHMETAFILE; +typedef HANDLE HCOLORSPACE; +typedef HANDLE HGLRC; + +typedef DWORD COLORREF; +typedef DWORD *LPCOLORREF; + +value int _ODD_FAILURE +{ +#define ODD_FAILURE 0x80000000 [fail] +}; + +/* Binary raster ops */ +value DWORD _BinaryDrawMode +{ +#define R2_BLACK 1 /* 0 */ +#define R2_NOTMERGEPEN 2 /* DPon */ +#define R2_MASKNOTPEN 3 /* DPna */ +#define R2_NOTCOPYPEN 4 /* PN */ +#define R2_MASKPENNOT 5 /* PDna */ +#define R2_NOT 6 /* Dn */ +#define R2_XORPEN 7 /* DPx */ +#define R2_NOTMASKPEN 8 /* DPan */ +#define R2_MASKPEN 9 /* DPa */ +#define R2_NOTXORPEN 10 /* DPxn */ +#define R2_NOP 11 /* D */ +#define R2_MERGENOTPEN 12 /* DPno */ +#define R2_COPYPEN 13 /* P */ +#define R2_MERGEPENNOT 14 /* PDno */ +#define R2_MERGEPEN 15 /* DPo */ +#define R2_WHITE 16 /* 1 */ +#define R2_LAST 16 +}; + +value DWORD _TernaryDrawMode +{ +/* Ternary raster operations */ +#define SRCCOPY 0x00CC0020 /* dest = source */ +#define SRCPAINT 0x00EE0086 /* dest = source OR dest */ +#define SRCAND 0x008800C6 /* dest = source AND dest */ +#define SRCINVERT 0x00660046 /* dest = source XOR dest */ +#define SRCERASE 0x00440328 /* dest = source AND (NOT dest ) */ +#define NOTSRCCOPY 0x00330008 /* dest = (NOT source) */ +#define NOTSRCERASE 0x001100A6 /* dest = (NOT src) AND (NOT dest) */ +#define MERGECOPY 0x00C000CA /* dest = (source AND pattern) */ +#define MERGEPAINT 0x00BB0226 /* dest = (NOT source) OR dest */ +#define PATCOPY 0x00F00021 /* dest = pattern */ +#define PATPAINT 0x00FB0A09 /* dest = DPSnoo */ +#define PATINVERT 0x005A0049 /* dest = pattern XOR dest */ +#define DSTINVERT 0x00550009 /* dest = (NOT dest) */ +#define BLACKNESS 0x00000042 /* dest = BLACK */ +#define WHITENESS 0x00FF0062 /* dest = WHITE */ +#define NOMIRRORBITMAP 0x80000000 /* Do not Mirror the bitmap in this call */ +#define CAPTUREBLT 0x40000000 /* Include layered windows */ +}; + + + +value DWORD _GDI_ERROR +{ +#define GDI_ERROR 0xFFFFFFFFL [fail] +}; +value DWORD _HGDI_ERROR +{ +#define HGDI_ERROR 0xFFFFFFFFL [fail] +}; + +value DWORD _RegionFlags +{ +/* Region Flags */ +#define ERROR 0 [fail] +#define NULLREGION 1 +#define SIMPLEREGION 2 +#define COMPLEXREGION 3 +}; + +value int _CombineRgn +{ +/* CombineRgn() Styles */ +#define RGN_AND 1 +#define RGN_OR 2 +#define RGN_XOR 3 +#define RGN_DIFF 4 +#define RGN_COPY 5 +}; + +value DWORD _COMBINRGN_STYLE +{ +/* CombineRgn() Styles */ +/* StretchBlt() Modes */ +#define BLACKONWHITE 1 +#define WHITEONBLACK 2 +#define COLORONCOLOR 3 +#define HALFTONE 4 +}; + +value DWORD _PolyFill +{ +/* PolyFill() Modes */ +#define ALTERNATE 1 +#define WINDING 2 +#define POLYFILL_LAST 2 +}; + +mask DWORD _LAYOUT +{ +#define LAYOUT_RTL 0x00000001 // Right to left +#define LAYOUT_BTT 0x00000002 // Bottom to top +#define LAYOUT_VBH 0x00000004 // Vertical before horizontal +//#define LAYOUT_ORIENTATIONMASK (LAYOUT_RTL | LAYOUT_BTT | LAYOUT_VBH) +#define LAYOUT_BITMAPORIENTATIONPRESERVED 0x00000008 +}; + +mask DWORD _TextAlignmentOptions +{ +/* Text Alignment Options */ +//#define TA_NOUPDATECP 0 +//#define TA_UPDATECP 1 +#define TA_LEFT 0 +#define TA_RIGHT 2 +#define TA_CENTER 6 +#define TA_TOP 0 +#define TA_BOTTOM 8 +#define TA_BASELINE 24 +#define TA_RTLREADING 256 +}; + +mask DWORD _ETO +{ +#define ETO_OPAQUE 0x0002 +#define ETO_CLIPPED 0x0004 +#define ETO_GLYPH_INDEX 0x0010 +#define ETO_RTLREADING 0x0080 +#define ETO_NUMERICSLOCAL 0x0400 +#define ETO_NUMERICSLATIN 0x0800 +#define ETO_IGNORELANGUAGE 0x1000 +#define ETO_PDY 0x2000 +}; + +mask DWORD _AspectFiltering +{ +#define ASPECT_FILTERING 0x0001 +}; + + +mask DWORD _DCB +{ +/* Bounds Accumulation APIs */ +#define DCB_ERROR 0 //[fail] +#define DCB_RESET 0x0001 +#define DCB_ACCUMULATE 0x0002 +#define DCB_ENABLE 0x0004 +#define DCB_DISABLE 0x0008 +}; + +value DWORD _Meta +{ +/* Metafile Functions */ +#define META_SETBKCOLOR 0x0201 +#define META_SETBKMODE 0x0102 +#define META_SETMAPMODE 0x0103 +#define META_SETROP2 0x0104 +#define META_SETRELABS 0x0105 +#define META_SETPOLYFILLMODE 0x0106 +#define META_SETSTRETCHBLTMODE 0x0107 +#define META_SETTEXTCHAREXTRA 0x0108 +#define META_SETTEXTCOLOR 0x0209 +#define META_SETTEXTJUSTIFICATION 0x020A +#define META_SETWINDOWORG 0x020B +#define META_SETWINDOWEXT 0x020C +#define META_SETVIEWPORTORG 0x020D +#define META_SETVIEWPORTEXT 0x020E +#define META_OFFSETWINDOWORG 0x020F +#define META_SCALEWINDOWEXT 0x0410 +#define META_OFFSETVIEWPORTORG 0x0211 +#define META_SCALEVIEWPORTEXT 0x0412 +#define META_LINETO 0x0213 +#define META_MOVETO 0x0214 +#define META_EXCLUDECLIPRECT 0x0415 +#define META_INTERSECTCLIPRECT 0x0416 +#define META_ARC 0x0817 +#define META_ELLIPSE 0x0418 +#define META_FLOODFILL 0x0419 +#define META_PIE 0x081A +#define META_RECTANGLE 0x041B +#define META_ROUNDRECT 0x061C +#define META_PATBLT 0x061D +#define META_SAVEDC 0x001E +#define META_SETPIXEL 0x041F +#define META_OFFSETCLIPRGN 0x0220 +#define META_TEXTOUT 0x0521 +#define META_BITBLT 0x0922 +#define META_STRETCHBLT 0x0B23 +#define META_POLYGON 0x0324 +#define META_POLYLINE 0x0325 +#define META_ESCAPE 0x0626 +#define META_RESTOREDC 0x0127 +#define META_FILLREGION 0x0228 +#define META_FRAMEREGION 0x0429 +#define META_INVERTREGION 0x012A +#define META_PAINTREGION 0x012B +#define META_SELECTCLIPREGION 0x012C +#define META_SELECTOBJECT 0x012D +#define META_SETTEXTALIGN 0x012E +#define META_CHORD 0x0830 +#define META_SETMAPPERFLAGS 0x0231 +#define META_EXTTEXTOUT 0x0a32 +#define META_SETDIBTODEV 0x0d33 +#define META_SELECTPALETTE 0x0234 +#define META_REALIZEPALETTE 0x0035 +#define META_ANIMATEPALETTE 0x0436 +#define META_SETPALENTRIES 0x0037 +#define META_POLYPOLYGON 0x0538 +#define META_RESIZEPALETTE 0x0139 +#define META_DIBBITBLT 0x0940 +#define META_DIBSTRETCHBLT 0x0b41 +#define META_DIBCREATEPATTERNBRUSH 0x0142 +#define META_STRETCHDIB 0x0f43 +#define META_EXTFLOODFILL 0x0548 +#define META_SETLAYOUT 0x0149 +#define META_DELETEOBJECT 0x01f0 +#define META_CREATEPALETTE 0x00f7 +#define META_CREATEPATTERNBRUSH 0x01F9 +#define META_CREATEPENINDIRECT 0x02FA +#define META_CREATEFONTINDIRECT 0x02FB +#define META_CREATEBRUSHINDIRECT 0x02FC +#define META_CREATEREGION 0x06FF +}; + +//#define ELF_VERSION 0 +//#define ELF_CULTURE_LATIN 0 + +mask DWORD _EnumFontsMask +{ +/* EnumFonts Masks */ +#define RASTER_FONTTYPE 0x0001 +#define DEVICE_FONTTYPE 0x002 +#define TRUETYPE_FONTTYPE 0x004 +}; + + +/* palette entry flags */ +mask BYTE _PaletteEntryFlag +{ +#define PC_RESERVED 0x01 /* palette index used for animation */ +#define PC_EXPLICIT 0x02 /* palette index is explicit to device */ +#define PC_NOCOLLAPSE 0x04 /* do not match color to system palette */ +}; + +value DWORD _BK_Mode +{ +/* Background Modes */ +#define TRANSPARENT 1 +#define OPAQUE 2 +}; + +value DWORD _GM +{ +/* Graphics Modes */ +#define GM_COMPATIBLE 1 +#define GM_ADVANCED 2 +}; + +mask DWORD _PT +{ +/* PolyDraw and GetPath point types */ +#define PT_CLOSEFIGURE 0x01 +#define PT_LINETO 0x02 +#define PT_BEZIERTO 0x04 +#define PT_MOVETO 0x06 +}; + +value DWORD _MM +{ +/* Mapping Modes */ +#define MM_TEXT 1 +#define MM_LOMETRIC 2 +#define MM_HIMETRIC 3 +#define MM_LOENGLISH 4 +#define MM_HIENGLISH 5 +#define MM_TWIPS 6 +#define MM_ISOTROPIC 7 +#define MM_ANISOTROPIC 8 + +}; + +value DWORD _Coordinate_Mode +{ +/* Coordinate Modes */ +#define ABSOLUTE 1 +#define RELATIVE 2 +}; + +value DWORD _StockObject +{ + +/* Stock Logical Objects */ +#define WHITE_BRUSH 0 +#define LTGRAY_BRUSH 1 +#define GRAY_BRUSH 2 +#define DKGRAY_BRUSH 3 +#define BLACK_BRUSH 4 +#define NULL_BRUSH 5 +//#define HOLLOW_BRUSH NULL_BRUSH +#define WHITE_PEN 6 +#define BLACK_PEN 7 +#define NULL_PEN 8 +#define OEM_FIXED_FONT 10 +#define ANSI_FIXED_FONT 11 +#define ANSI_VAR_FONT 12 +#define SYSTEM_FONT 13 +#define DEVICE_DEFAULT_FONT 14 +#define DEFAULT_PALETTE 15 +#define SYSTEM_FIXED_FONT 16 + +#define DEFAULT_GUI_FONT 17 + +#define DC_BRUSH 18 +#define DC_PEN 19 + +}; + +value DWORD COLORREF_RETURN +{ +#define CLR_INVALID 0xFFFFFFFF [fail] +}; + +value DWORD _BrushStyles +{ +/* Brush Styles */ +#define BS_SOLID 0 +#define BS_NULL 1 +#define BS_HATCHED 2 +#define BS_PATTERN 3 +#define BS_INDEXED 4 +#define BS_DIBPATTERN 5 +#define BS_DIBPATTERNPT 6 +#define BS_PATTERN8X8 7 +#define BS_DIBPATTERN8X8 8 +#define BS_MONOPATTERN 9 +}; + +value ULONG_PTR _HatchStyle +{ +/* Hatch Styles */ +#define HS_HORIZONTAL 0 /* ----- */ +#define HS_VERTICAL 1 /* ||||| */ +#define HS_FDIAGONAL 2 /* \\\\\ */ +#define HS_BDIAGONAL 3 /* ///// */ +#define HS_CROSS 4 /* +++++ */ +#define HS_DIAGCROSS 5 /* xxxxx */ +}; + +mask int _PS +{ +/* Pen Styles */ +#define PS_SOLID 0 +#define PS_DASH 1 /* ------- */ +#define PS_DOT 2 /* ....... */ +//#define PS_DASHDOT 3 /* _._._._ */ +#define PS_DASHDOTDOT 4 /* _.._.._ */ +//#define PS_NULL 5 +//#define PS_INSIDEFRAME 6 +//#define PS_USERSTYLE 7 +#define PS_ALTERNATE 8 + +#define PS_ENDCAP_ROUND 0x00000000 +#define PS_ENDCAP_SQUARE 0x00000100 +#define PS_ENDCAP_FLAT 0x00000200 +#define PS_ENDCAP_MASK 0x00000F00 + +#define PS_JOIN_ROUND 0x00000000 +#define PS_JOIN_BEVEL 0x00001000 +#define PS_JOIN_MITER 0x00002000 +#define PS_JOIN_MASK 0x0000F000 + +#define PS_COSMETIC 0x00000000 +#define PS_GEOMETRIC 0x00010000 +#define PS_TYPE_MASK 0x000F0000 +}; + +value DWORD _AD +{ +#define AD_COUNTERCLOCKWISE 1 +#define AD_CLOCKWISE 2 +}; + +value DWORD _DeviceParameters +{ +/* Device Parameters for GetDeviceCaps() */ +#define DRIVERVERSION 0 /* Device driver version */ +#define TECHNOLOGY 2 /* Device classification */ +#define HORZSIZE 4 /* Horizontal size in millimeters */ +#define VERTSIZE 6 /* Vertical size in millimeters */ +#define HORZRES 8 /* Horizontal width in pixels */ +#define VERTRES 10 /* Vertical height in pixels */ +#define BITSPIXEL 12 /* Number of bits per pixel */ +#define PLANES 14 /* Number of planes */ +#define NUMBRUSHES 16 /* Number of brushes the device has */ +#define NUMPENS 18 /* Number of pens the device has */ +#define NUMMARKERS 20 /* Number of markers the device has */ +#define NUMFONTS 22 /* Number of fonts the device has */ +#define NUMCOLORS 24 /* Number of colors the device supports */ +#define PDEVICESIZE 26 /* Size required for device descriptor */ +#define CURVECAPS 28 /* Curve capabilities */ +#define LINECAPS 30 /* Line capabilities */ +#define POLYGONALCAPS 32 /* Polygonal capabilities */ +#define TEXTCAPS 34 /* Text capabilities */ +#define CLIPCAPS 36 /* Clipping capabilities */ +#define RASTERCAPS 38 /* Bitblt capabilities */ +#define ASPECTX 40 /* Length of the X leg */ +#define ASPECTY 42 /* Length of the Y leg */ +#define ASPECTXY 44 /* Length of the hypotenuse */ + + +#define LOGPIXELSX 88 /* Logical pixels/inch in X */ +#define LOGPIXELSY 90 /* Logical pixels/inch in Y */ + +#define SIZEPALETTE 104 /* Number of entries in physical palette */ +#define NUMRESERVED 106 /* Number of reserved entries in palette */ +#define COLORRES 108 /* Actual color resolution */ + + +// Printing related DeviceCaps. These replace the appropriate Escapes + +#define PHYSICALWIDTH 110 /* Physical Width in device units */ +#define PHYSICALHEIGHT 111 /* Physical Height in device units */ +#define PHYSICALOFFSETX 112 /* Physical Printable Area x margin */ +#define PHYSICALOFFSETY 113 /* Physical Printable Area y margin */ +#define SCALINGFACTORX 114 /* Scaling factor x */ +#define SCALINGFACTORY 115 /* Scaling factor y */ + +// Display driver specific + +#define VREFRESH 116 /* Current vertical refresh rate of the */ + /* display device (for displays only) in Hz*/ +#define DESKTOPVERTRES 117 /* Horizontal width of entire desktop in */ + /* pixels */ +#define DESKTOPHORZRES 118 /* Vertical height of entire desktop in */ + /* pixels */ +#define BLTALIGNMENT 119 /* Preferred blt alignment */ +#define SHADEBLENDCAPS 120 /* Shading and blending caps */ +#define COLORMGMTCAPS 121 /* Color Management caps */ +}; + +mask DWORD _DeviceCapabilityDT +{ + +/* Device Capability Masks: */ + +/* Device Technologies */ +#define DT_PLOTTER 0 /* Vector plotter */ +#define DT_RASDISPLAY 1 /* Raster display */ +#define DT_RASPRINTER 2 /* Raster printer */ +#define DT_RASCAMERA 3 /* Raster camera */ +#define DT_CHARSTREAM 4 /* Character-stream, PLP */ +#define DT_METAFILE 5 /* Metafile, VDM */ +#define DT_DISPFILE 6 /* Display-file */ +}; + +mask DWORD _DeviceCapabilityCC +{ +/* Curve Capabilities */ +#define CC_NONE 0 /* Curves not supported */ +#define CC_CIRCLES 1 /* Can do circles */ +#define CC_PIE 2 /* Can do pie wedges */ +#define CC_CHORD 4 /* Can do chord arcs */ +#define CC_ELLIPSES 8 /* Can do ellipese */ +#define CC_WIDE 16 /* Can do wide lines */ +#define CC_STYLED 32 /* Can do styled lines */ +#define CC_WIDESTYLED 64 /* Can do wide styled lines */ +#define CC_INTERIORS 128 /* Can do interiors */ +#define CC_ROUNDRECT 256 /* */ +}; + +mask DWORD _DeviceCapabilityLC +{ +/* Line Capabilities */ +#define LC_NONE 0 /* Lines not supported */ +#define LC_POLYLINE 2 /* Can do polylines */ +#define LC_MARKER 4 /* Can do markers */ +#define LC_POLYMARKER 8 /* Can do polymarkers */ +#define LC_WIDE 16 /* Can do wide lines */ +#define LC_STYLED 32 /* Can do styled lines */ +#define LC_WIDESTYLED 64 /* Can do wide styled lines */ +#define LC_INTERIORS 128 /* Can do interiors */ +}; + +mask DWORD _DeviceCapabilityPC +{ +/* Polygonal Capabilities */ +#define PC_NONE 0 /* Polygonals not supported */ +#define PC_POLYGON 1 /* Can do polygons */ +#define PC_RECTANGLE 2 /* Can do rectangles */ +#define PC_WINDPOLYGON 4 /* Can do winding polygons */ +#define PC_TRAPEZOID 4 /* Can do trapezoids */ +#define PC_SCANLINE 8 /* Can do scanlines */ +#define PC_WIDE 16 /* Can do wide borders */ +#define PC_STYLED 32 /* Can do styled borders */ +#define PC_WIDESTYLED 64 /* Can do wide styled borders */ +#define PC_INTERIORS 128 /* Can do interiors */ +#define PC_POLYPOLYGON 256 /* Can do polypolygons */ +#define PC_PATHS 512 /* Can do paths */ +}; + +mask DWORD _DeviceCapabilityCP +{ +/* Clipping Capabilities */ +#define CP_NONE 0 /* No clipping of output */ +#define CP_RECTANGLE 1 /* Output clipped to rects */ +#define CP_REGION 2 /* obsolete */ +}; + +mask DWORD _DeviceCapabilityTC +{ +/* Text Capabilities */ +#define TC_OP_CHARACTER 0x00000001 /* Can do OutputPrecision CHARACTER */ +#define TC_OP_STROKE 0x00000002 /* Can do OutputPrecision STROKE */ +#define TC_CP_STROKE 0x00000004 /* Can do ClipPrecision STROKE */ +#define TC_CR_90 0x00000008 /* Can do CharRotAbility 90 */ +#define TC_CR_ANY 0x00000010 /* Can do CharRotAbility ANY */ +#define TC_SF_X_YINDEP 0x00000020 /* Can do ScaleFreedom X_YINDEPENDENT */ +#define TC_SA_DOUBLE 0x00000040 /* Can do ScaleAbility DOUBLE */ +#define TC_SA_INTEGER 0x00000080 /* Can do ScaleAbility INTEGER */ +#define TC_SA_CONTIN 0x00000100 /* Can do ScaleAbility CONTINUOUS */ +#define TC_EA_DOUBLE 0x00000200 /* Can do EmboldenAbility DOUBLE */ +#define TC_IA_ABLE 0x00000400 /* Can do ItalisizeAbility ABLE */ +#define TC_UA_ABLE 0x00000800 /* Can do UnderlineAbility ABLE */ +#define TC_SO_ABLE 0x00001000 /* Can do StrikeOutAbility ABLE */ +#define TC_RA_ABLE 0x00002000 /* Can do RasterFontAble ABLE */ +#define TC_VA_ABLE 0x00004000 /* Can do VectorFontAble ABLE */ +#define TC_RESERVED 0x00008000 +#define TC_SCROLLBLT 0x00010000 /* Don't do text scroll with blt */ + +}; + +mask DWORD _DeviceCapabilityRC +{ +/* Raster Capabilities */ +#define RC_NONE 0 +#define RC_BITBLT 1 /* Can do standard BLT. */ +#define RC_BANDING 2 /* Device requires banding support */ +#define RC_SCALING 4 /* Device requires scaling support */ +#define RC_BITMAP64 8 /* Device can support >64K bitmap */ +#define RC_GDI20_OUTPUT 0x0010 /* has 2.0 output calls */ +#define RC_GDI20_STATE 0x0020 +#define RC_SAVEBITMAP 0x0040 +#define RC_DI_BITMAP 0x0080 /* supports DIB to memory */ +#define RC_PALETTE 0x0100 /* supports a palette */ +#define RC_DIBTODEV 0x0200 /* supports DIBitsToDevice */ +#define RC_BIGFONT 0x0400 /* supports >64K fonts */ +#define RC_STRETCHBLT 0x0800 /* supports StretchBlt */ +#define RC_FLOODFILL 0x1000 /* supports FloodFill */ +#define RC_STRETCHDIB 0x2000 /* supports StretchDIBits */ +#define RC_OP_DX_OUTPUT 0x4000 +#define RC_DEVBITS 0x8000 + +}; + +mask DWORD _DeviceCapabilitySB +{ +/* Shading and blending caps */ +#define SB_NONE 0x00000000 +#define SB_CONST_ALPHA 0x00000001 +#define SB_PIXEL_ALPHA 0x00000002 +#define SB_PREMULT_ALPHA 0x00000004 + +#define SB_GRAD_RECT 0x00000010 +#define SB_GRAD_TRI 0x00000020 +}; + +mask DWORD _ColorManagementCaps +{ +/* Color Management caps */ +#define CM_NONE 0x00000000 +#define CM_DEVICE_ICM 0x00000001 +#define CM_GAMMA_RAMP 0x00000002 +#define CM_CMYK_COLOR 0x00000004 +}; + + +/* DIB color table identifiers */ +value DWORD _DIB_Color +{ +#define DIB_RGB_COLORS 0 /* color table in RGBs */ +#define DIB_PAL_COLORS 1 /* color table in palette indices */ +}; + +value DWORD _SYSPAL +{ +/* constants for Get/SetSystemPaletteUse() */ +#define SYSPAL_ERROR 0 [fail] +#define SYSPAL_STATIC 1 +#define SYSPAL_NOSTATIC 2 +#define SYSPAL_NOSTATIC256 3 +}; + +value DWORD _CreateDIBitmap +{ +/* constants for CreateDIBitmap */ +#define CBM_INIT 0x04L /* initialize bitmap */ +}; + +value DWORD _FLOODFILL +{ +/* ExtFloodFill style flags */ +#define FLOODFILLBORDER 0 +#define FLOODFILLSURFACE 1 +}; +/* current version of specification */ +//#define DM_SPECVERSION 0x0401 + + + +value DWORD _PSIDENT +{ +/* + * Parameters for POSTSCRIPT_IDENTIFY escape + */ + +#define PSIDENT_GDICENTRIC 0 +#define PSIDENT_PSCENTRIC 1 +}; +value DWORD _PSINJECTMode +{ + +/* + * Constants for PSINJECTDATA.Flags field + */ + +#define PSINJECT_APPEND 0 +#define PSINJECT_REPLACE 1 +}; + +/* + * Constants for PSINJECTDATA.InjectionPoint field + */ + +/* + * The data injected at these points coexist with the output emitted + * by the driver for the same points. + */ + +value WORD _PSINJECT +{ +#define PSINJECT_BEGINSTREAM 1 +#define PSINJECT_PSADOBE 2 +#define PSINJECT_PAGESATEND 3 +#define PSINJECT_PAGES 4 + +#define PSINJECT_DOCNEEDEDRES 5 +#define PSINJECT_DOCSUPPLIEDRES 6 +#define PSINJECT_PAGEORDER 7 +#define PSINJECT_ORIENTATION 8 +#define PSINJECT_BOUNDINGBOX 9 +#define PSINJECT_DOCUMENTPROCESSCOLORS 10 + +#define PSINJECT_COMMENTS 11 +#define PSINJECT_BEGINDEFAULTS 12 +#define PSINJECT_ENDDEFAULTS 13 +#define PSINJECT_BEGINPROLOG 14 +#define PSINJECT_ENDPROLOG 15 +#define PSINJECT_BEGINSETUP 16 +#define PSINJECT_ENDSETUP 17 +#define PSINJECT_TRAILER 18 +#define PSINJECT_EOF 19 +#define PSINJECT_ENDSTREAM 20 +#define PSINJECT_DOCUMENTPROCESSCOLORSATEND 21 + +#define PSINJECT_PAGENUMBER 100 +#define PSINJECT_BEGINPAGESETUP 101 +#define PSINJECT_ENDPAGESETUP 102 +#define PSINJECT_PAGETRAILER 103 +#define PSINJECT_PLATECOLOR 104 + +#define PSINJECT_SHOWPAGE 105 +#define PSINJECT_PAGEBBOX 106 +#define PSINJECT_ENDPAGECOMMENTS 107 + +#define PSINJECT_VMSAVE 200 +#define PSINJECT_VMRESTORE 201 + +}; + +value DWORD _PSPROTOCOL +{ +/* Value returned for FEATURESETTING_PROTOCOL */ +#define PSPROTOCOL_ASCII 0 +#define PSPROTOCOL_BCP 1 +#define PSPROTOCOL_TBCP 2 +#define PSPROTOCOL_BINARY 3 +}; + +mask DWORD _QDI +{ +/* Flag returned from QUERYDIBSUPPORT */ +#define QDI_SETDIBITS 1 +#define QDI_GETDIBITS 2 +#define QDI_DIBTOSCREEN 4 +#define QDI_STRETCHDIB 8 +}; + +value DWORD _FEATURESETTING +{ +/* + * Parameter for GET_PS_FEATURESETTING escape + */ + +#define FEATURESETTING_NUP 0 +#define FEATURESETTING_OUTPUT 1 +#define FEATURESETTING_PSLEVEL 2 +#define FEATURESETTING_CUSTPAPER 3 +#define FEATURESETTING_MIRROR 4 +#define FEATURESETTING_NEGATIVE 5 +#define FEATURESETTING_PROTOCOL 6 +}; + +value DWORD _PR_JOBSTATUS +{ +#define PR_JOBSTATUS 0x0000 +}; + +value DWORD _OBJ +{ +#define OBJ_ERROR 0 [fail] +/* Object Definitions for EnumObjects() */ +#define OBJ_PEN 1 +#define OBJ_BRUSH 2 +#define OBJ_DC 3 +#define OBJ_METADC 4 +#define OBJ_PAL 5 +#define OBJ_FONT 6 +#define OBJ_BITMAP 7 +#define OBJ_REGION 8 +#define OBJ_METAFILE 9 +#define OBJ_MEMDC 10 +#define OBJ_EXTPEN 11 +#define OBJ_ENHMETADC 12 +#define OBJ_ENHMETAFILE 13 +#define OBJ_COLORSPACE 14 +}; + +value DWORD _MWT +{ +/* xform stuff */ +#define MWT_IDENTITY 1 +#define MWT_LEFTMULTIPLY 2 +#define MWT_RIGHTMULTIPLY 3 +}; + + +/* Image Color Matching color definitions */ + +value DWORD _CS +{ +#define CS_ENABLE 0x00000001L +#define CS_DISABLE 0x00000002L +#define CS_DELETE_TRANSFORM 0x00000003L +}; + + +value DWORD _OUT +{ +#define OUT_DEFAULT_PRECIS 0 +#define OUT_STRING_PRECIS 1 +#define OUT_CHARACTER_PRECIS 2 +#define OUT_STROKE_PRECIS 3 +#define OUT_TT_PRECIS 4 +#define OUT_DEVICE_PRECIS 5 +#define OUT_RASTER_PRECIS 6 +#define OUT_TT_ONLY_PRECIS 7 +#define OUT_OUTLINE_PRECIS 8 +#define OUT_SCREEN_OUTLINE_PRECIS 9 +#define OUT_PS_ONLY_PRECIS 10 +}; + +value BYTE _OUTBYTE +{ +#define OUT_DEFAULT_PRECIS 0 +#define OUT_STRING_PRECIS 1 +#define OUT_CHARACTER_PRECIS 2 +#define OUT_STROKE_PRECIS 3 +#define OUT_TT_PRECIS 4 +#define OUT_DEVICE_PRECIS 5 +#define OUT_RASTER_PRECIS 6 +#define OUT_TT_ONLY_PRECIS 7 +#define OUT_OUTLINE_PRECIS 8 +#define OUT_SCREEN_OUTLINE_PRECIS 9 +#define OUT_PS_ONLY_PRECIS 10 +}; + +mask DWORD _CLIP +{ +#define CLIP_DEFAULT_PRECIS 0 +#define CLIP_CHARACTER_PRECIS 1 +#define CLIP_STROKE_PRECIS 2 +#define CLIP_MASK 0xf +#define CLIP_LH_ANGLES 0x10 +#define CLIP_TT_ALWAYS 0x20 +#define CLIP_EMBEDDED 0x80 +}; + +mask BYTE _CLIPBYTE +{ +#define CLIP_DEFAULT_PRECIS 0 +#define CLIP_CHARACTER_PRECIS 1 +#define CLIP_STROKE_PRECIS 2 +#define CLIP_MASK 0xf +#define CLIP_LH_ANGLES 0x10 +#define CLIP_TT_ALWAYS 0x20 +#define CLIP_EMBEDDED 0x80 +}; + +value DWORD _QUALITY +{ +#define DEFAULT_QUALITY 0 +#define DRAFT_QUALITY 1 +#define PROOF_QUALITY 2 +#define NONANTIALIASED_QUALITY 3 +#define ANTIALIASED_QUALITY 4 +#define CLEARTYPE_QUALITY 5 +}; + +value BYTE _QUALITYBYTE +{ +#define DEFAULT_QUALITY 0 +#define DRAFT_QUALITY 1 +#define PROOF_QUALITY 2 +#define NONANTIALIASED_QUALITY 3 +#define ANTIALIASED_QUALITY 4 +}; + +value DWORD _PITCH +{ +#define DEFAULT_PITCH 0 +#define FIXED_PITCH 1 +#define VARIABLE_PITCH 2 +#define MONO_FONT 8 +}; + +value DWORD _CHARSET +{ +#define ANSI_CHARSET 0 +#define DEFAULT_CHARSET 1 [fail] +#define SYMBOL_CHARSET 2 +#define SHIFTJIS_CHARSET 128 +#define HANGEUL_CHARSET 129 +#define HANGUL_CHARSET 129 +#define GB2312_CHARSET 134 +#define CHINESEBIG5_CHARSET 136 +#define OEM_CHARSET 255 +#define JOHAB_CHARSET 130 +#define HEBREW_CHARSET 177 +#define ARABIC_CHARSET 178 +#define GREEK_CHARSET 161 +#define TURKISH_CHARSET 162 +#define VIETNAMESE_CHARSET 163 +#define THAI_CHARSET 222 +#define EASTEUROPE_CHARSET 238 +#define RUSSIAN_CHARSET 204 + +#define MAC_CHARSET 77 +#define BALTIC_CHARSET 186 +}; + +value BYTE _CHARSETBYTE +{ +#define ANSI_CHARSET 0 +#define DEFAULT_CHARSET 1 +#define SYMBOL_CHARSET 2 +#define SHIFTJIS_CHARSET 128 +#define HANGEUL_CHARSET 129 +#define HANGUL_CHARSET 129 +#define GB2312_CHARSET 134 +#define CHINESEBIG5_CHARSET 136 +#define OEM_CHARSET 255 +#define JOHAB_CHARSET 130 +#define HEBREW_CHARSET 177 +#define ARABIC_CHARSET 178 +#define GREEK_CHARSET 161 +#define TURKISH_CHARSET 162 +#define VIETNAMESE_CHARSET 163 +#define THAI_CHARSET 222 +#define EASTEUROPE_CHARSET 238 +#define RUSSIAN_CHARSET 204 + +#define MAC_CHARSET 77 +#define BALTIC_CHARSET 186 +}; + +mask DWORD _FS +{ + +#define FS_LATIN1 0x00000001L +#define FS_LATIN2 0x00000002L +#define FS_CYRILLIC 0x00000004L +#define FS_GREEK 0x00000008L +#define FS_TURKISH 0x00000010L +#define FS_HEBREW 0x00000020L +#define FS_ARABIC 0x00000040L +#define FS_BALTIC 0x00000080L +#define FS_VIETNAMESE 0x00000100L +#define FS_THAI 0x00010000L +#define FS_JISJAPAN 0x00020000L +#define FS_CHINESESIMP 0x00040000L +#define FS_WANSUNG 0x00080000L +#define FS_CHINESETRAD 0x00100000L +#define FS_JOHAB 0x00200000L +#define FS_SYMBOL 0x80000000L +}; + +mask DWORD _FF +{ + +/* Font Families */ +#define FF_DONTCARE 0x00 /* Don't care or don't know. */ +#define FF_ROMAN 0x10 /* Variable stroke width, serifed.Times Roman, Century Schoolbook, etc. */ +#define FF_SWISS 0x20 /* Variable stroke width, sans-serifed.Helvetica, Swiss, etc. */ +#define FF_MODERN 0x30 /* Constant stroke width, serifed or sans-serifed. Pica, Elite, Courier, etc. */ +#define FF_SCRIPT 0x40 /* Cursive, etc. */ +#define FF_DECORATIVE 0x50 /* Old English, etc. */ + +}; + +mask BYTE _FFBYTE +{ + +/* Font Families */ +#define FF_DONTCARE 0x00 /* Don't care or don't know. */ +#define FF_ROMAN 0x10 /* Variable stroke width, serifed.Times Roman, Century Schoolbook, etc. */ +#define FF_SWISS 0x20 /* Variable stroke width, sans-serifed.Helvetica, Swiss, etc. */ +#define FF_MODERN 0x30 /* Constant stroke width, serifed or sans-serifed. Pica, Elite, Courier, etc. */ +#define FF_SCRIPT 0x40 /* Cursive, etc. */ +#define FF_DECORATIVE 0x50 /* Old English, etc. */ + +}; + +mask int _FW +{ +/* Font Weights */ +#define FW_DONTCARE 0 +#define FW_THIN 100 +#define FW_EXTRALIGHT 200 +#define FW_LIGHT 300 +#define FW_NORMAL 400 +#define FW_MEDIUM 500 +#define FW_SEMIBOLD 600 +#define FW_BOLD 700 +#define FW_EXTRABOLD 800 +#define FW_HEAVY 900 + +}; + +value DWORD _PAN +{ +#define PAN_FAMILYTYPE_INDEX 0 +#define PAN_SERIFSTYLE_INDEX 1 +#define PAN_WEIGHT_INDEX 2 +#define PAN_PROPORTION_INDEX 3 +#define PAN_CONTRAST_INDEX 4 +#define PAN_STROKEVARIATION_INDEX 5 +#define PAN_ARMSTYLE_INDEX 6 +#define PAN_LETTERFORM_INDEX 7 +#define PAN_MIDLINE_INDEX 8 +#define PAN_XHEIGHT_INDEX 9 +}; + +value DWORD _PAN_CULTURE +{ +#define PAN_CULTURE_LATIN 0 +}; + +value DWORD _PAN_FAMILY +{ +#define PAN_FAMILY_ANY 0 /* Any */ +#define PAN_FAMILY_NO_FIT 1 /* No Fit */ + +#define PAN_FAMILY_TEXT_DISPLAY 2 /* Text and Display */ +#define PAN_FAMILY_SCRIPT 3 /* Script */ +#define PAN_FAMILY_DECORATIVE 4 /* Decorative */ +#define PAN_FAMILY_PICTORIAL 5 /* Pictorial */ +}; +value DWORD _PAN_SERIF +{ +#define PAN_SERIF_ANY 0 /* Any */ +#define PAN_SERIF_NO_FIT 1 /* No Fit */ +#define PAN_SERIF_COVE 2 /* Cove */ +#define PAN_SERIF_OBTUSE_COVE 3 /* Obtuse Cove */ +#define PAN_SERIF_SQUARE_COVE 4 /* Square Cove */ +#define PAN_SERIF_OBTUSE_SQUARE_COVE 5 /* Obtuse Square Cove */ +#define PAN_SERIF_SQUARE 6 /* Square */ +#define PAN_SERIF_THIN 7 /* Thin */ +#define PAN_SERIF_BONE 8 /* Bone */ +#define PAN_SERIF_EXAGGERATED 9 /* Exaggerated */ +#define PAN_SERIF_TRIANGLE 10 /* Triangle */ +#define PAN_SERIF_NORMAL_SANS 11 /* Normal Sans */ +#define PAN_SERIF_OBTUSE_SANS 12 /* Obtuse Sans */ +#define PAN_SERIF_PERP_SANS 13 /* Prep Sans */ +#define PAN_SERIF_FLARED 14 /* Flared */ +#define PAN_SERIF_ROUNDED 15 /* Rounded */ +}; +value DWORD _PAN_WEIGHT_CULTURE +{ +#define PAN_WEIGHT_ANY 0 /* Any */ +#define PAN_WEIGHT_NO_FIT 1 /* No Fit */ +#define PAN_WEIGHT_VERY_LIGHT 2 /* Very Light */ +#define PAN_WEIGHT_LIGHT 3 /* Light */ +#define PAN_WEIGHT_THIN 4 /* Thin */ +#define PAN_WEIGHT_BOOK 5 /* Book */ +#define PAN_WEIGHT_MEDIUM 6 /* Medium */ +#define PAN_WEIGHT_DEMI 7 /* Demi */ +#define PAN_WEIGHT_BOLD 8 /* Bold */ +#define PAN_WEIGHT_HEAVY 9 /* Heavy */ +#define PAN_WEIGHT_BLACK 10 /* Black */ +#define PAN_WEIGHT_NORD 11 /* Nord */ +}; +value DWORD _PAN_PROP +{ +#define PAN_PROP_ANY 0 /* Any */ +#define PAN_PROP_NO_FIT 1 /* No Fit */ +#define PAN_PROP_OLD_STYLE 2 /* Old Style */ +#define PAN_PROP_MODERN 3 /* Modern */ +#define PAN_PROP_EVEN_WIDTH 4 /* Even Width */ +#define PAN_PROP_EXPANDED 5 /* Expanded */ +#define PAN_PROP_CONDENSED 6 /* Condensed */ +#define PAN_PROP_VERY_EXPANDED 7 /* Very Expanded */ +#define PAN_PROP_VERY_CONDENSED 8 /* Very Condensed */ +#define PAN_PROP_MONOSPACED 9 /* Monospaced */ +}; +value DWORD _PAN_CONTRAST +{ +#define PAN_CONTRAST_ANY 0 /* Any */ +#define PAN_CONTRAST_NO_FIT 1 /* No Fit */ +#define PAN_CONTRAST_NONE 2 /* None */ +#define PAN_CONTRAST_VERY_LOW 3 /* Very Low */ +#define PAN_CONTRAST_LOW 4 /* Low */ +#define PAN_CONTRAST_MEDIUM_LOW 5 /* Medium Low */ +#define PAN_CONTRAST_MEDIUM 6 /* Medium */ +#define PAN_CONTRAST_MEDIUM_HIGH 7 /* Mediim High */ +#define PAN_CONTRAST_HIGH 8 /* High */ +#define PAN_CONTRAST_VERY_HIGH 9 /* Very High */ +}; +value DWORD _PAN_STROKE +{ +#define PAN_STROKE_ANY 0 /* Any */ +#define PAN_STROKE_NO_FIT 1 /* No Fit */ +#define PAN_STROKE_GRADUAL_DIAG 2 /* Gradual/Diagonal */ +#define PAN_STROKE_GRADUAL_TRAN 3 /* Gradual/Transitional */ +#define PAN_STROKE_GRADUAL_VERT 4 /* Gradual/Vertical */ +#define PAN_STROKE_GRADUAL_HORZ 5 /* Gradual/Horizontal */ +#define PAN_STROKE_RAPID_VERT 6 /* Rapid/Vertical */ +#define PAN_STROKE_RAPID_HORZ 7 /* Rapid/Horizontal */ +#define PAN_STROKE_INSTANT_VERT 8 /* Instant/Vertical */ +}; +value DWORD _PAN_ARMS +{ +#define PAN_ARMS_ANY 0 /* Any */ +#define PAN_ARMS_NO_FIT 1 /* No Fit */ +#define PAN_STRAIGHT_ARMS_HORZ 2 /* Straight Arms/Horizontal */ +#define PAN_STRAIGHT_ARMS_WEDGE 3 /* Straight Arms/Wedge */ +#define PAN_STRAIGHT_ARMS_VERT 4 /* Straight Arms/Vertical */ +#define PAN_STRAIGHT_ARMS_SINGLE_SERIF 5 /* Straight Arms/Single-Serif */ +#define PAN_STRAIGHT_ARMS_DOUBLE_SERIF 6 /* Straight Arms/Double-Serif */ +#define PAN_BENT_ARMS_HORZ 7 /* Non-Straight Arms/Horizontal */ +#define PAN_BENT_ARMS_WEDGE 8 /* Non-Straight Arms/Wedge */ +#define PAN_BENT_ARMS_VERT 9 /* Non-Straight Arms/Vertical */ +#define PAN_BENT_ARMS_SINGLE_SERIF 10 /* Non-Straight Arms/Single-Serif */ +#define PAN_BENT_ARMS_DOUBLE_SERIF 11 /* Non-Straight Arms/Double-Serif */ +}; +value DWORD _PAN_LETT +{ +#define PAN_LETT_ANY 0 /* Any */ +#define PAN_LETT_NO_FIT 1 /* No Fit */ +#define PAN_LETT_NORMAL_CONTACT 2 /* Normal/Contact */ +#define PAN_LETT_NORMAL_WEIGHTED 3 /* Normal/Weighted */ +#define PAN_LETT_NORMAL_BOXED 4 /* Normal/Boxed */ +#define PAN_LETT_NORMAL_FLATTENED 5 /* Normal/Flattened */ +#define PAN_LETT_NORMAL_ROUNDED 6 /* Normal/Rounded */ +#define PAN_LETT_NORMAL_OFF_CENTER 7 /* Normal/Off Center */ +#define PAN_LETT_NORMAL_SQUARE 8 /* Normal/Square */ +#define PAN_LETT_OBLIQUE_CONTACT 9 /* Oblique/Contact */ +#define PAN_LETT_OBLIQUE_WEIGHTED 10 /* Oblique/Weighted */ +#define PAN_LETT_OBLIQUE_BOXED 11 /* Oblique/Boxed */ +#define PAN_LETT_OBLIQUE_FLATTENED 12 /* Oblique/Flattened */ +#define PAN_LETT_OBLIQUE_ROUNDED 13 /* Oblique/Rounded */ +#define PAN_LETT_OBLIQUE_OFF_CENTER 14 /* Oblique/Off Center */ +#define PAN_LETT_OBLIQUE_SQUARE 15 /* Oblique/Square */ +}; +value DWORD _PAN_MIDLINE +{ +#define PAN_MIDLINE_ANY 0 /* Any */ +#define PAN_MIDLINE_NO_FIT 1 /* No Fit */ +#define PAN_MIDLINE_STANDARD_TRIMMED 2 /* Standard/Trimmed */ +#define PAN_MIDLINE_STANDARD_POINTED 3 /* Standard/Pointed */ +#define PAN_MIDLINE_STANDARD_SERIFED 4 /* Standard/Serifed */ +#define PAN_MIDLINE_HIGH_TRIMMED 5 /* High/Trimmed */ +#define PAN_MIDLINE_HIGH_POINTED 6 /* High/Pointed */ +#define PAN_MIDLINE_HIGH_SERIFED 7 /* High/Serifed */ +#define PAN_MIDLINE_CONSTANT_TRIMMED 8 /* Constant/Trimmed */ +#define PAN_MIDLINE_CONSTANT_POINTED 9 /* Constant/Pointed */ +#define PAN_MIDLINE_CONSTANT_SERIFED 10 /* Constant/Serifed */ +#define PAN_MIDLINE_LOW_TRIMMED 11 /* Low/Trimmed */ +#define PAN_MIDLINE_LOW_POINTED 12 /* Low/Pointed */ +#define PAN_MIDLINE_LOW_SERIFED 13 /* Low/Serifed */ +}; +value DWORD _PAN_XHEIGHT +{ +#define PAN_XHEIGHT_ANY 0 /* Any */ +#define PAN_XHEIGHT_NO_FIT 1 /* No Fit */ +#define PAN_XHEIGHT_CONSTANT_SMALL 2 /* Constant/Small */ +#define PAN_XHEIGHT_CONSTANT_STD 3 /* Constant/Standard */ +#define PAN_XHEIGHT_CONSTANT_LARGE 4 /* Constant/Large */ +#define PAN_XHEIGHT_DUCKING_SMALL 5 /* Ducking/Small */ +#define PAN_XHEIGHT_DUCKING_STD 6 /* Ducking/Standard */ +#define PAN_XHEIGHT_DUCKING_LARGE 7 /* Ducking/Large */ +}; + +mask DWORD _DISPLAY_DEVICE +{ +#define DISPLAY_DEVICE_ATTACHED_TO_DESKTOP 0x00000001 +#define DISPLAY_DEVICE_MULTI_DRIVER 0x00000002 +#define DISPLAY_DEVICE_PRIMARY_DEVICE 0x00000004 +#define DISPLAY_DEVICE_MIRRORING_DRIVER 0x00000008 +#define DISPLAY_DEVICE_VGA_COMPATIBLE 0x00000010 +#define DISPLAY_DEVICE_REMOVABLE 0x00000020 +#define DISPLAY_DEVICE_MODESPRUNED 0x08000000 +#define DISPLAY_DEVICE_REMOTE 0x04000000 +#define DISPLAY_DEVICE_DISCONNECT 0x02000000 +}; +mask DWORD _DISPLAY_DEVICE_STATE +{ +/* Child device state */ +#define DISPLAY_DEVICE_ACTIVE 0x00000001 +#define DISPLAY_DEVICE_ATTACHED 0x00000002 +}; + + +value DWORD _RDH +{ +#define RDH_RECTANGLES 1 +}; +// GetGlyphOutline constants + +value DWORD _GGO +{ +#define GGO_METRICS 0 +#define GGO_BITMAP 1 +#define GGO_NATIVE 2 +#define GGO_BEZIER 3 +#define GGO_GRAY2_BITMAP 4 +#define GGO_GRAY4_BITMAP 5 +#define GGO_GRAY8_BITMAP 6 +#define GGO_GLYPH_INDEX 0x0080 +#define GGO_UNHINTED 0x0100 +}; + +value DWORD _TT_POLYGON +{ +#define TT_POLYGON_TYPE 24 +}; + +value WORD _TT_PRIM +{ +#define TT_PRIM_LINE 1 +#define TT_PRIM_QSPLINE 2 +#define TT_PRIM_CSPLINE 3 +}; + +typedef struct tagMETAFILEPICT { + LONG mm; + LONG xExt; + LONG yExt; + HMETAFILE hMF; +} METAFILEPICT, *LPMETAFILEPICT; + + +/* Logcolorspace signature */ + +// #define LCS_SIGNATURE 'PSOC' + +/* Logcolorspace lcsType values */ + +// #define LCS_sRGB 'sRGB' +// #define LCS_WINDOWS_COLOR_SPACE 'Win ' // Windows default color space + +typedef LONG LCSCSTYPE; +value DWORD _LCSCSTYPE +{ +#define LCS_CALIBRATED_RGB 0x00000000L +#define LCS_DEVICE_RGB 0x00000001L +#define LCS_DEVICE_CMYK 0x00000002L +}; + +mask DWORD _GCP +{ +#define GCP_DBCS 0x0001 +#define GCP_REORDER 0x0002 +#define GCP_USEKERNING 0x0008 +#define GCP_GLYPHSHAPE 0x0010 +#define GCP_LIGATE 0x0020 +////#define GCP_GLYPHINDEXING 0x0080 +#define GCP_DIACRITIC 0x0100 +#define GCP_KASHIDA 0x0400 +#define GCP_ERROR 0x8000 +//#define FLI_MASK 0x103B + +#define GCP_JUSTIFY 0x00010000L +////#define GCP_NODIACRITICS 0x00020000L +#define FLI_GLYPHS 0x00040000L +#define GCP_CLASSIN 0x00080000L +#define GCP_MAXEXTENT 0x00100000L +#define GCP_JUSTIFYIN 0x00200000L +#define GCP_DISPLAYZWG 0x00400000L +#define GCP_SYMSWAPOFF 0x00800000L +#define GCP_NUMERICOVERRIDE 0x01000000L +#define GCP_NEUTRALOVERRIDE 0x02000000L +#define GCP_NUMERICSLATIN 0x04000000L +#define GCP_NUMERICSLOCAL 0x08000000L +}; +mask DWORD _GCPCLASS +{ +#define GCPCLASS_LATIN 1 +#define GCPCLASS_HEBREW 2 +//#define GCPCLASS_ARABIC 2 +#define GCPCLASS_NEUTRAL 3 +#define GCPCLASS_LOCALNUMBER 4 +#define GCPCLASS_LATINNUMBER 5 +#define GCPCLASS_LATINNUMERICTERMINATOR 6 +#define GCPCLASS_LATINNUMERICSEPARATOR 7 +#define GCPCLASS_NUMERICSEPARATOR 8 +#define GCPCLASS_PREBOUNDLTR 0x80 +#define GCPCLASS_PREBOUNDRTL 0x40 +#define GCPCLASS_POSTBOUNDLTR 0x20 +#define GCPCLASS_POSTBOUNDRTL 0x10 + +#define GCPGLYPH_LINKBEFORE 0x8000 +#define GCPGLYPH_LINKAFTER 0x4000 +}; + +typedef LONG LCSGAMUTMATCH; +value DWORD _LCSGAMUTMATCH +{ +#define LCS_GM_BUSINESS 0x00000001L +#define LCS_GM_GRAPHICS 0x00000002L +#define LCS_GM_IMAGES 0x00000004L +#define LCS_GM_ABS_COLORIMETRIC 0x00000008L +}; + + +value UINT _UpdateICMRegKey +{ +/* UpdateICMRegKey Constants */ +#define ICM_ADDPROFILE 1 +#define ICM_DELETEPROFILE 2 +#define ICM_QUERYPROFILE 3 +#define ICM_SETDEFAULTPROFILE 4 +#define ICM_REGISTERICMATCHER 5 +#define ICM_UNREGISTERICMATCHER 6 +#define ICM_QUERYMATCH 7 +}; + + +value DWORD _biCompression +{ +/* constants for the biCompression field */ +#define BI_RGB 0L +#define BI_RLE8 1L +#define BI_RLE4 2L +#define BI_BITFIELDS 3L +#define BI_JPEG 4L +#define BI_PNG 5L +}; + +value DWORD _TCI_SRC +{ +#define TCI_SRCCHARSET 1 +#define TCI_SRCCODEPAGE 2 +#define TCI_SRCFONTSIG 3 +}; + + +mask WORD _RASTERIZER_STATUS_Flag +{ +/* bits defined in wFlags of RASTERIZER_STATUS */ +#define TT_AVAILABLE 0x0001 +#define TT_ENABLED 0x0002 +}; + + +mask BYTE _PFD +{ +/* pixel types */ +#define PFD_TYPE_RGBA 0 +#define PFD_TYPE_COLORINDEX 1 +}; + +mask BYTE _PFD_LAYER +{ +/* layer types */ +#define PFD_MAIN_PLANE 0 +#define PFD_OVERLAY_PLANE 1 +#define PFD_UNDERLAY_PLANE -1 +}; +mask DWORD _PIXELFORMATDESCRIPTOR +{ +/* PIXELFORMATDESCRIPTOR flags */ +#define PFD_DOUBLEBUFFER 0x00000001 +#define PFD_STEREO 0x00000002 +#define PFD_DRAW_TO_WINDOW 0x00000004 +#define PFD_DRAW_TO_BITMAP 0x00000008 +#define PFD_SUPPORT_GDI 0x00000010 +#define PFD_SUPPORT_OPENGL 0x00000020 +#define PFD_GENERIC_FORMAT 0x00000040 +#define PFD_NEED_PALETTE 0x00000080 +#define PFD_NEED_SYSTEM_PALETTE 0x00000100 +#define PFD_SWAP_EXCHANGE 0x00000200 +#define PFD_SWAP_COPY 0x00000400 +#define PFD_SWAP_LAYER_BUFFERS 0x00000800 +#define PFD_GENERIC_ACCELERATED 0x00001000 +#define PFD_SUPPORT_DIRECTDRAW 0x00002000 + +/* PIXELFORMATDESCRIPTOR flags for use in ChoosePixelFormat only */ +#define PFD_DEPTH_DONTCARE 0x20000000 +#define PFD_DOUBLEBUFFER_DONTCARE 0x40000000 +#define PFD_STEREO_DONTCARE 0x80000000 +}; + + + +mask DWORD _DeviceMode +{ +/* mode selections for the device mode function */ +#define DM_UPDATE 1 +#define DM_COPY 2 +#define DM_PROMPT 4 +#define DM_MODIFY 8 +}; + +value DWORD _DC_PRINTRATEUNIT +{ +#define PRINTRATEUNIT_PPM 1 +#define PRINTRATEUNIT_CPS 2 +#define PRINTRATEUNIT_LPM 3 +#define PRINTRATEUNIT_IPM 4 +}; +mask DWORD _DCTT +{ +/* bit fields of the return value (DWORD) for DC_TRUETYPE */ +#define DCTT_BITMAP 0x0000001L +#define DCTT_DOWNLOAD 0x0000002L +#define DCTT_SUBDEV 0x0000004L +#define DCTT_DOWNLOAD_OUTLINE 0x0000008L +}; +value DWORD _DCBA +{ +/* return values for DC_BINADJUST */ +#define DCBA_FACEUPNONE 0x0000 +#define DCBA_FACEUPCENTER 0x0001 +#define DCBA_FACEUPLEFT 0x0002 +#define DCBA_FACEUPRIGHT 0x0003 +#define DCBA_FACEDOWNNONE 0x0100 +#define DCBA_FACEDOWNCENTER 0x0101 +#define DCBA_FACEDOWNLEFT 0x0102 +#define DCBA_FACEDOWNRIGHT 0x0103 +}; + +/* flAccel flags for the GLYPHSET structure above */ +value DWORD _GS_8BIT_INDICES +{ +#define GS_8BIT_INDICES 0x00000001 +}; + +/* flags for GetGlyphIndices */ +value DWORD _GGI_MARK_NONEXISTING_GLYPHS +{ + +#define GGI_MARK_NONEXISTING_GLYPHS 0X0001 +}; +value DWORD _FR +{ + +#define FR_PRIVATE 0x10 +#define FR_NOT_ENUM 0x20 +}; +value BYTE _AC_SRC_OVER +{ +// +// currentlly defined blend function +// +#define AC_SRC_OVER 0x00 +}; +value BYTE _AC_SRC_ALPHA +{ +// +// currentlly defined blend function +// +#define AC_SRC_ALPHA 0x01 +}; +value ULONG _GRADIENT_FILL +{ +// +// gradient drawing modes +// + +#define GRADIENT_FILL_RECT_H 0x00000000 +#define GRADIENT_FILL_RECT_V 0x00000001 +#define GRADIENT_FILL_TRIANGLE 0x00000002 +#define GRADIENT_FILL_OP_FLAG 0x000000ff +}; +value WORD _COLORADJUSTMENTValue +{ + +/* Flags value for COLORADJUSTMENT */ +#define CA_NEGATIVE 0x0001 +#define CA_LOG_FILTER 0x0002 +}; +value WORD _IlluminantIndexValue +{ + +/* IlluminantIndex values */ +#define ILLUMINANT_DEVICE_DEFAULT 0 +#define ILLUMINANT_A 1 +#define ILLUMINANT_B 2 +#define ILLUMINANT_C 3 +#define ILLUMINANT_D50 4 +#define ILLUMINANT_D55 5 +#define ILLUMINANT_D65 6 +#define ILLUMINANT_D75 7 +#define ILLUMINANT_F2 8 +}; + +value DWORD _ICM +{ +#define ICM_OFF 1 +#define ICM_ON 2 +#define ICM_QUERY 3 +#define ICM_DONE_OUTSIDEDC 4 +}; + +value DWORD _EMR +{ + +// Enhanced metafile record types. + +#define EMR_HEADER 1 +#define EMR_POLYBEZIER 2 +#define EMR_POLYGON 3 +#define EMR_POLYLINE 4 +#define EMR_POLYBEZIERTO 5 +#define EMR_POLYLINETO 6 +#define EMR_POLYPOLYLINE 7 +#define EMR_POLYPOLYGON 8 +#define EMR_SETWINDOWEXTEX 9 +#define EMR_SETWINDOWORGEX 10 +#define EMR_SETVIEWPORTEXTEX 11 +#define EMR_SETVIEWPORTORGEX 12 +#define EMR_SETBRUSHORGEX 13 +#define EMR_EOF 14 +#define EMR_SETPIXELV 15 +#define EMR_SETMAPPERFLAGS 16 +#define EMR_SETMAPMODE 17 +#define EMR_SETBKMODE 18 +#define EMR_SETPOLYFILLMODE 19 +#define EMR_SETROP2 20 +#define EMR_SETSTRETCHBLTMODE 21 +#define EMR_SETTEXTALIGN 22 +#define EMR_SETCOLORADJUSTMENT 23 +#define EMR_SETTEXTCOLOR 24 +#define EMR_SETBKCOLOR 25 +#define EMR_OFFSETCLIPRGN 26 +#define EMR_MOVETOEX 27 +#define EMR_SETMETARGN 28 +#define EMR_EXCLUDECLIPRECT 29 +#define EMR_INTERSECTCLIPRECT 30 +#define EMR_SCALEVIEWPORTEXTEX 31 +#define EMR_SCALEWINDOWEXTEX 32 +#define EMR_SAVEDC 33 +#define EMR_RESTOREDC 34 +#define EMR_SETWORLDTRANSFORM 35 +#define EMR_MODIFYWORLDTRANSFORM 36 +#define EMR_SELECTOBJECT 37 +#define EMR_CREATEPEN 38 +#define EMR_CREATEBRUSHINDIRECT 39 +#define EMR_DELETEOBJECT 40 +#define EMR_ANGLEARC 41 +#define EMR_ELLIPSE 42 +#define EMR_RECTANGLE 43 +#define EMR_ROUNDRECT 44 +#define EMR_ARC 45 +#define EMR_CHORD 46 +#define EMR_PIE 47 +#define EMR_SELECTPALETTE 48 +#define EMR_CREATEPALETTE 49 +#define EMR_SETPALETTEENTRIES 50 +#define EMR_RESIZEPALETTE 51 +#define EMR_REALIZEPALETTE 52 +#define EMR_EXTFLOODFILL 53 +#define EMR_LINETO 54 +#define EMR_ARCTO 55 +#define EMR_POLYDRAW 56 +#define EMR_SETARCDIRECTION 57 +#define EMR_SETMITERLIMIT 58 +#define EMR_BEGINPATH 59 +#define EMR_ENDPATH 60 +#define EMR_CLOSEFIGURE 61 +#define EMR_FILLPATH 62 +#define EMR_STROKEANDFILLPATH 63 +#define EMR_STROKEPATH 64 +#define EMR_FLATTENPATH 65 +#define EMR_WIDENPATH 66 +#define EMR_SELECTCLIPPATH 67 +#define EMR_ABORTPATH 68 + +#define EMR_GDICOMMENT 70 +#define EMR_FILLRGN 71 +#define EMR_FRAMERGN 72 +#define EMR_INVERTRGN 73 +#define EMR_PAINTRGN 74 +#define EMR_EXTSELECTCLIPRGN 75 +#define EMR_BITBLT 76 +#define EMR_STRETCHBLT 77 +#define EMR_MASKBLT 78 +#define EMR_PLGBLT 79 +#define EMR_SETDIBITSTODEVICE 80 +#define EMR_STRETCHDIBITS 81 +#define EMR_EXTCREATEFONTINDIRECTW 82 +#define EMR_EXTTEXTOUTA 83 +#define EMR_EXTTEXTOUTW 84 +#define EMR_POLYBEZIER16 85 +#define EMR_POLYGON16 86 +#define EMR_POLYLINE16 87 +#define EMR_POLYBEZIERTO16 88 +#define EMR_POLYLINETO16 89 +#define EMR_POLYPOLYLINE16 90 +#define EMR_POLYPOLYGON16 91 +#define EMR_POLYDRAW16 92 +#define EMR_CREATEMONOBRUSH 93 +#define EMR_CREATEDIBPATTERNBRUSHPT 94 +#define EMR_EXTCREATEPEN 95 +#define EMR_POLYTEXTOUTA 96 +#define EMR_POLYTEXTOUTW 97 + +#define EMR_SETICMMODE 98 +#define EMR_CREATECOLORSPACE 99 +#define EMR_SETCOLORSPACE 100 +#define EMR_DELETECOLORSPACE 101 +#define EMR_GLSRECORD 102 +#define EMR_GLSBOUNDEDRECORD 103 +#define EMR_PIXELFORMAT 104 + +#define EMR_RESERVED_105 105 +#define EMR_RESERVED_106 106 +#define EMR_RESERVED_107 107 +#define EMR_RESERVED_108 108 +#define EMR_RESERVED_109 109 +#define EMR_RESERVED_110 110 +#define EMR_COLORCORRECTPALETTE 111 +#define EMR_SETICMPROFILEA 112 +#define EMR_SETICMPROFILEW 113 +#define EMR_ALPHABLEND 114 +#define EMR_SETLAYOUT 115 +#define EMR_TRANSPARENTBLT 116 +#define EMR_RESERVED_117 117 +#define EMR_GRADIENTFILL 118 +#define EMR_RESERVED_119 119 +#define EMR_RESERVED_120 120 +#define EMR_COLORMATCHTOTARGETW 121 +#define EMR_CREATECOLORSPACEW 122 +}; +value DWORD _SETICMPROFILE_EMBEDED +{ + +#define SETICMPROFILE_EMBEDED 0x00000001 +}; +value DWORD _GDICOMMENT +{ +#define GDICOMMENT_IDENTIFIER 0x43494447 +#define GDICOMMENT_WINDOWS_METAFILE 0x80000001 +#define GDICOMMENT_BEGINGROUP 0x00000002 +#define GDICOMMENT_ENDGROUP 0x00000003 +#define GDICOMMENT_MULTIFORMATS 0x40000004 +#define GDICOMMENT_UNICODE_STRING 0x00000040 +#define GDICOMMENT_UNICODE_END 0x00000080 +}; + +value DWORD _WGL_FONT +{ + +#define WGL_FONT_LINES 0 +#define WGL_FONT_POLYGONS 1 +}; +value DWORD _LAYERPLANEDESCRIPTOR +{ + +/* LAYERPLANEDESCRIPTOR flags */ +#define LPD_DOUBLEBUFFER 0x00000001 +#define LPD_STEREO 0x00000002 +#define LPD_SUPPORT_GDI 0x00000010 +#define LPD_SUPPORT_OPENGL 0x00000020 +#define LPD_SHARE_DEPTH 0x00000040 +#define LPD_SHARE_STENCIL 0x00000080 +#define LPD_SHARE_ACCUM 0x00000100 +#define LPD_SWAP_EXCHANGE 0x00000200 +#define LPD_SWAP_COPY 0x00000400 +#define LPD_TRANSPARENT 0x00001000 +}; +value BYTE _LPD_TYPE +{ + +#define LPD_TYPE_RGBA 0 +#define LPD_TYPE_COLORINDEX 1 +}; +value DWORD _WGL_SWAP +{ + +/* wglSwapLayerBuffers flags */ +#define WGL_SWAP_MAIN_PLANE 0x00000001 +#define WGL_SWAP_OVERLAY1 0x00000002 +#define WGL_SWAP_OVERLAY2 0x00000004 +#define WGL_SWAP_OVERLAY3 0x00000008 +#define WGL_SWAP_OVERLAY4 0x00000010 +#define WGL_SWAP_OVERLAY5 0x00000020 +#define WGL_SWAP_OVERLAY6 0x00000040 +#define WGL_SWAP_OVERLAY7 0x00000080 +#define WGL_SWAP_OVERLAY8 0x00000100 +#define WGL_SWAP_OVERLAY9 0x00000200 +#define WGL_SWAP_OVERLAY10 0x00000400 +#define WGL_SWAP_OVERLAY11 0x00000800 +#define WGL_SWAP_OVERLAY12 0x00001000 +#define WGL_SWAP_OVERLAY13 0x00002000 +#define WGL_SWAP_OVERLAY14 0x00004000 +#define WGL_SWAP_OVERLAY15 0x00008000 +#define WGL_SWAP_UNDERLAY1 0x00010000 +#define WGL_SWAP_UNDERLAY2 0x00020000 +#define WGL_SWAP_UNDERLAY3 0x00040000 +#define WGL_SWAP_UNDERLAY4 0x00080000 +#define WGL_SWAP_UNDERLAY5 0x00100000 +#define WGL_SWAP_UNDERLAY6 0x00200000 +#define WGL_SWAP_UNDERLAY7 0x00400000 +#define WGL_SWAP_UNDERLAY8 0x00800000 +#define WGL_SWAP_UNDERLAY9 0x01000000 +#define WGL_SWAP_UNDERLAY10 0x02000000 +#define WGL_SWAP_UNDERLAY11 0x04000000 +#define WGL_SWAP_UNDERLAY12 0x08000000 +#define WGL_SWAP_UNDERLAY13 0x10000000 +#define WGL_SWAP_UNDERLAY14 0x20000000 +#define WGL_SWAP_UNDERLAY15 0x40000000 +}; + +mask DWORD _otmfsSelection +{ +#define Italic 0x00 +#define Underscore 0x01 +#define Negative 0x02 +#define Outline 0x04 +#define Strikeout 0x08 +#define Bold 0x10 +}; + +value WORD RectangleStyleValue +{ +#define BlackRectangle 0 +#define WhiteRectangle 1 +#define GrayRectangle 2 +}; + +mask BYTE _TMPF +{ +/* tmPitchAndFamily flags */ +#define TMPF_FIXED_PITCH 0x01 +#define TMPF_VECTOR 0x02 +#define TMPF_DEVICE 0x08 +#define TMPF_TRUETYPE 0x04 +}; + +mask DWORD _NTMFlags +{ +/* ntmFlags field flags */ +#define NTM_REGULAR 0x00000040L +#define NTM_BOLD 0x00000020L +#define NTM_ITALIC 0x00000001L + +#define NTM_NONNEGATIVE_AC 0x00010000 +#define NTM_PS_OPENTYPE 0x00020000 +#define NTM_TT_OPENTYPE 0x00040000 +#define NTM_MULTIPLEMASTER 0x00080000 +#define NTM_TYPE1 0x00100000 +#define NTM_DSIG 0x00200000 +}; + + + +value BYTE PanFamilyType +{ +#define PAN_ANY 0 /* Any */ +#define PAN_NO_FIT 1 /* No Fit */ + +#define PAN_FAMILY_TEXT_DISPLAY 2 /* Text and Display */ +#define PAN_FAMILY_SCRIPT 3 /* Script */ +#define PAN_FAMILY_DECORATIVE 4 /* Decorative */ +#define PAN_FAMILY_PICTORIAL 5 /* Pictorial */ +}; +value BYTE PanSerifType +{ +#define PAN_SERIF_COVE 2 /* Cove */ +#define PAN_SERIF_OBTUSE_COVE 3 /* Obtuse Cove */ +#define PAN_SERIF_SQUARE_COVE 4 /* Square Cove */ +#define PAN_SERIF_OBTUSE_SQUARE_COVE 5 /* Obtuse Square Cove */ +#define PAN_SERIF_SQUARE 6 /* Square */ +#define PAN_SERIF_THIN 7 /* Thin */ +#define PAN_SERIF_BONE 8 /* Bone */ +#define PAN_SERIF_EXAGGERATED 9 /* Exaggerated */ +#define PAN_SERIF_TRIANGLE 10 /* Triangle */ +#define PAN_SERIF_NORMAL_SANS 11 /* Normal Sans */ +#define PAN_SERIF_OBTUSE_SANS 12 /* Obtuse Sans */ +#define PAN_SERIF_PERP_SANS 13 /* Prep Sans */ +#define PAN_SERIF_FLARED 14 /* Flared */ +#define PAN_SERIF_ROUNDED 15 /* Rounded */ +}; +value BYTE PanWeightType +{ +#define PAN_WEIGHT_VERY_LIGHT 2 /* Very Light */ +#define PAN_WEIGHT_LIGHT 3 /* Light */ +#define PAN_WEIGHT_THIN 4 /* Thin */ +#define PAN_WEIGHT_BOOK 5 /* Book */ +#define PAN_WEIGHT_MEDIUM 6 /* Medium */ +#define PAN_WEIGHT_DEMI 7 /* Demi */ +#define PAN_WEIGHT_BOLD 8 /* Bold */ +#define PAN_WEIGHT_HEAVY 9 /* Heavy */ +#define PAN_WEIGHT_BLACK 10 /* Black */ +#define PAN_WEIGHT_NORD 11 /* Nord */ +}; +value BYTE PanPropType +{ +#define PAN_PROP_OLD_STYLE 2 /* Old Style */ +#define PAN_PROP_MODERN 3 /* Modern */ +#define PAN_PROP_EVEN_WIDTH 4 /* Even Width */ +#define PAN_PROP_EXPANDED 5 /* Expanded */ +#define PAN_PROP_CONDENSED 6 /* Condensed */ +#define PAN_PROP_VERY_EXPANDED 7 /* Very Expanded */ +#define PAN_PROP_VERY_CONDENSED 8 /* Very Condensed */ +#define PAN_PROP_MONOSPACED 9 /* Monospaced */ +}; +value BYTE PanConstrastType +{ +#define PAN_CONTRAST_NONE 2 /* None */ +#define PAN_CONTRAST_VERY_LOW 3 /* Very Low */ +#define PAN_CONTRAST_LOW 4 /* Low */ +#define PAN_CONTRAST_MEDIUM_LOW 5 /* Medium Low */ +#define PAN_CONTRAST_MEDIUM 6 /* Medium */ +#define PAN_CONTRAST_MEDIUM_HIGH 7 /* Mediim High */ +#define PAN_CONTRAST_HIGH 8 /* High */ +#define PAN_CONTRAST_VERY_HIGH 9 /* Very High */ +}; +value BYTE PanStrokeType +{ +#define PAN_STROKE_GRADUAL_DIAG 2 /* Gradual/Diagonal */ +#define PAN_STROKE_GRADUAL_TRAN 3 /* Gradual/Transitional */ +#define PAN_STROKE_GRADUAL_VERT 4 /* Gradual/Vertical */ +#define PAN_STROKE_GRADUAL_HORZ 5 /* Gradual/Horizontal */ +#define PAN_STROKE_RAPID_VERT 6 /* Rapid/Vertical */ +#define PAN_STROKE_RAPID_HORZ 7 /* Rapid/Horizontal */ +#define PAN_STROKE_INSTANT_VERT 8 /* Instant/Vertical */ +}; +value BYTE PanArmsType +{ +#define PAN_STRAIGHT_ARMS_HORZ 2 /* Straight Arms/Horizontal */ +#define PAN_STRAIGHT_ARMS_WEDGE 3 /* Straight Arms/Wedge */ +#define PAN_STRAIGHT_ARMS_VERT 4 /* Straight Arms/Vertical */ +#define PAN_STRAIGHT_ARMS_SINGLE_SERIF 5 /* Straight Arms/Single-Serif */ +#define PAN_STRAIGHT_ARMS_DOUBLE_SERIF 6 /* Straight Arms/Double-Serif */ +#define PAN_BENT_ARMS_HORZ 7 /* Non-Straight Arms/Horizontal */ +#define PAN_BENT_ARMS_WEDGE 8 /* Non-Straight Arms/Wedge */ +#define PAN_BENT_ARMS_VERT 9 /* Non-Straight Arms/Vertical */ +#define PAN_BENT_ARMS_SINGLE_SERIF 10 /* Non-Straight Arms/Single-Serif */ +#define PAN_BENT_ARMS_DOUBLE_SERIF 11 /* Non-Straight Arms/Double-Serif */ +}; +value BYTE PanLettType +{ +#define PAN_LETT_NORMAL_CONTACT 2 /* Normal/Contact */ +#define PAN_LETT_NORMAL_WEIGHTED 3 /* Normal/Weighted */ +#define PAN_LETT_NORMAL_BOXED 4 /* Normal/Boxed */ +#define PAN_LETT_NORMAL_FLATTENED 5 /* Normal/Flattened */ +#define PAN_LETT_NORMAL_ROUNDED 6 /* Normal/Rounded */ +#define PAN_LETT_NORMAL_OFF_CENTER 7 /* Normal/Off Center */ +#define PAN_LETT_NORMAL_SQUARE 8 /* Normal/Square */ +#define PAN_LETT_OBLIQUE_CONTACT 9 /* Oblique/Contact */ +#define PAN_LETT_OBLIQUE_WEIGHTED 10 /* Oblique/Weighted */ +#define PAN_LETT_OBLIQUE_BOXED 11 /* Oblique/Boxed */ +#define PAN_LETT_OBLIQUE_FLATTENED 12 /* Oblique/Flattened */ +#define PAN_LETT_OBLIQUE_ROUNDED 13 /* Oblique/Rounded */ +#define PAN_LETT_OBLIQUE_OFF_CENTER 14 /* Oblique/Off Center */ +#define PAN_LETT_OBLIQUE_SQUARE 15 /* Oblique/Square */ +}; +value BYTE PanMidlineType +{ +#define PAN_MIDLINE_STANDARD_TRIMMED 2 /* Standard/Trimmed */ +#define PAN_MIDLINE_STANDARD_POINTED 3 /* Standard/Pointed */ +#define PAN_MIDLINE_STANDARD_SERIFED 4 /* Standard/Serifed */ +#define PAN_MIDLINE_HIGH_TRIMMED 5 /* High/Trimmed */ +#define PAN_MIDLINE_HIGH_POINTED 6 /* High/Pointed */ +#define PAN_MIDLINE_HIGH_SERIFED 7 /* High/Serifed */ +#define PAN_MIDLINE_CONSTANT_TRIMMED 8 /* Constant/Trimmed */ +#define PAN_MIDLINE_CONSTANT_POINTED 9 /* Constant/Pointed */ +#define PAN_MIDLINE_CONSTANT_SERIFED 10 /* Constant/Serifed */ +#define PAN_MIDLINE_LOW_TRIMMED 11 /* Low/Trimmed */ +#define PAN_MIDLINE_LOW_POINTED 12 /* Low/Pointed */ +#define PAN_MIDLINE_LOW_SERIFED 13 /* Low/Serifed */ +}; +value BYTE PanXHeightType +{ +#define PAN_XHEIGHT_CONSTANT_SMALL 2 /* Constant/Small */ +#define PAN_XHEIGHT_CONSTANT_STD 3 /* Constant/Standard */ +#define PAN_XHEIGHT_CONSTANT_LARGE 4 /* Constant/Large */ +#define PAN_XHEIGHT_DUCKING_SMALL 5 /* Ducking/Small */ +#define PAN_XHEIGHT_DUCKING_STD 6 /* Ducking/Standard */ +#define PAN_XHEIGHT_DUCKING_LARGE 7 /* Ducking/Large */ +}; + +value DWORD EMRSignature +{ +#define ENHMETA_SIGNATURE 0x464D4520 +#define EPS_SIGNATURE 0x46535045 +}; + +value DWORD EMRColorSpaceFlagMask +{ +#define CREATECOLORSPACE_EMBEDED 0x00000001 +}; + +value DWORD EMRColorMatchFlagMask +{ +#define COLORMATCHTOTARGET_EMBEDED 0x00000001 +}; + + + +//================================================================================== +//================================================================================== +//================================================================================== +typedef struct tagCOLORADJUSTMENT { + WORD caSize; + _COLORADJUSTMENTValue caFlags; + _IlluminantIndexValue caIlluminantIndex; + WORD caRedGamma; + WORD caGreenGamma; + WORD caBlueGamma; + WORD caReferenceBlack; + WORD caReferenceWhite; + SHORT caContrast; + SHORT caBrightness; + SHORT caColorfulness; + SHORT caRedGreenTint; +} COLORADJUSTMENT, *PCOLORADJUSTMENT, *LPCOLORADJUSTMENT; + +typedef struct _POINTL { + LONG x; + LONG y; +} POINTL, *PPOINTL; + +typedef struct tagPOINTS { + SHORT x; + SHORT y; +} POINTS, *PPOINTS; + +typedef struct _DRAWPATRECT { + POINT ptPosition; + POINT ptSize; + RectangleStyleValue wStyle; + WORD wPattern; +} DRAWPATRECT, *PDRAWPATRECT; + +/* + * Information about output options + */ + +typedef struct _PSFEATURE_OUTPUT { + + BOOL bPageIndependent; + BOOL bSetPageDevice; + +} PSFEATURE_OUTPUT, *PPSFEATURE_OUTPUT; + +/* + * Information about custom paper size + */ + +typedef struct _PSFEATURE_CUSTPAPER { + + LONG lOrientation; + LONG lWidth; + LONG lHeight; + LONG lWidthOffset; + LONG lHeightOffset; + +} PSFEATURE_CUSTPAPER, *PPSFEATURE_CUSTPAPER; + +/* + * Header structure for the input buffer to POSTSCRIPT_INJECTION escape + */ + +typedef struct _PSINJECTDATA { + + DWORD DataBytes; /* number of raw data bytes */ + _PSINJECT InjectionPoint; /* injection point */ + WORD Flags; /* flags */ + + /* Followed by raw data to be injected */ + +} PSINJECTDATA, *PPSINJECTDATA; + + +typedef struct tagXFORM + { + FLOAT eM11; + FLOAT eM12; + FLOAT eM21; + FLOAT eM22; + FLOAT eDx; + FLOAT eDy; + } XFORM, *PXFORM, *LPXFORM; + +/* Bitmap Header Definition */ +typedef struct tagBITMAP + { + LONG bmType; + LONG bmWidth; + LONG bmHeight; + LONG bmWidthBytes; + WORD bmPlanes; + WORD bmBitsPixel; + LPVOID bmBits; + } BITMAP, *PBITMAP, *NPBITMAP, *LPBITMAP; + +typedef struct tagRGBTRIPLE { + BYTE rgbtBlue; + BYTE rgbtGreen; + BYTE rgbtRed; +} RGBTRIPLE; + +typedef struct tagRGBQUAD { + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbReserved; +} RGBQUAD; +typedef RGBQUAD * LPRGBQUAD; + + +typedef long FXPT16DOT16; +typedef long *LPFXPT16DOT16; + +typedef long FXPT2DOT30; +typedef long *LPFXPT2DOT30; + +/* ICM Color Definitions */ +// The following two structures are used for defining RGB's in terms of CIEXYZ. + +typedef struct tagCIEXYZ +{ + FXPT2DOT30 ciexyzX; + FXPT2DOT30 ciexyzY; + FXPT2DOT30 ciexyzZ; +} CIEXYZ; +typedef CIEXYZ *LPCIEXYZ; + +typedef struct tagICEXYZTRIPLE +{ + CIEXYZ ciexyzRed; + CIEXYZ ciexyzGreen; + CIEXYZ ciexyzBlue; +} CIEXYZTRIPLE; +typedef CIEXYZTRIPLE *LPCIEXYZTRIPLE; + +// The next structures the logical color space. Unlike pens and brushes, +// but like palettes, there is only one way to create a LogColorSpace. +// A pointer to it must be passed, its elements can't be pushed as +// arguments. + + +typedef struct tagLOGCOLORSPACEA { + DWORD lcsSignature; + DWORD lcsVersion; + DWORD lcsSize; + _LCSCSTYPE lcsCSType; + _LCSGAMUTMATCH lcsIntent; + CIEXYZTRIPLE lcsEndpoints; + DWORD lcsGammaRed; + DWORD lcsGammaGreen; + DWORD lcsGammaBlue; + CHAR lcsFilename[260]; +} LOGCOLORSPACEA, *LPLOGCOLORSPACEA; +typedef struct tagLOGCOLORSPACEW { + DWORD lcsSignature; + DWORD lcsVersion; + DWORD lcsSize; + _LCSCSTYPE lcsCSType; + _LCSGAMUTMATCH lcsIntent; + CIEXYZTRIPLE lcsEndpoints; + DWORD lcsGammaRed; + DWORD lcsGammaGreen; + DWORD lcsGammaBlue; + WCHAR lcsFilename[260]; +} LOGCOLORSPACEW, *LPLOGCOLORSPACEW; + + + +/* structures for defining DIBs */ +typedef struct tagBITMAPCOREHEADER { + DWORD bcSize; /* used to get to color table */ + WORD bcWidth; + WORD bcHeight; + WORD bcPlanes; + WORD bcBitCount; +} BITMAPCOREHEADER, *LPBITMAPCOREHEADER, *PBITMAPCOREHEADER; + +typedef struct tagBITMAPINFOHEADER{ + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + _biCompression biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER, *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER; + +typedef struct tagBITMAPV4HEADER { + DWORD bV4Size; + LONG bV4Width; + LONG bV4Height; + WORD bV4Planes; + WORD bV4BitCount; + _biCompression bV4V4Compression; + DWORD bV4SizeImage; + LONG bV4XPelsPerMeter; + LONG bV4YPelsPerMeter; + DWORD bV4ClrUsed; + DWORD bV4ClrImportant; + DWORD bV4RedMask; + DWORD bV4GreenMask; + DWORD bV4BlueMask; + DWORD bV4AlphaMask; + DWORD bV4CSType; + CIEXYZTRIPLE bV4Endpoints; + DWORD bV4GammaRed; + DWORD bV4GammaGreen; + DWORD bV4GammaBlue; +} BITMAPV4HEADER, *LPBITMAPV4HEADER, *PBITMAPV4HEADER; + +typedef struct tagBITMAPV5HEADER { + DWORD bV5Size; + LONG bV5Width; + LONG bV5Height; + WORD bV5Planes; + WORD bV5BitCount; + _biCompression bV5Compression; + DWORD bV5SizeImage; + LONG bV5XPelsPerMeter; + LONG bV5YPelsPerMeter; + DWORD bV5ClrUsed; + DWORD bV5ClrImportant; + DWORD bV5RedMask; + DWORD bV5GreenMask; + DWORD bV5BlueMask; + DWORD bV5AlphaMask; + DWORD bV5CSType; + CIEXYZTRIPLE bV5Endpoints; + DWORD bV5GammaRed; + DWORD bV5GammaGreen; + DWORD bV5GammaBlue; + DWORD bV5Intent; + DWORD bV5ProfileData; + DWORD bV5ProfileSize; + DWORD bV5Reserved; +} BITMAPV5HEADER, *LPBITMAPV5HEADER, *PBITMAPV5HEADER; + +// Values for bV5CSType +// #define PROFILE_LINKED 'LINK' +// #define PROFILE_EMBEDDED 'MBED' + +typedef struct tagBITMAPINFO { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} BITMAPINFO, *LPBITMAPINFO, *PBITMAPINFO; + +typedef struct tagBITMAPCOREINFO { + BITMAPCOREHEADER bmciHeader; + RGBTRIPLE bmciColors[1]; +} BITMAPCOREINFO, *LPBITMAPCOREINFO, *PBITMAPCOREINFO; + +typedef struct tagBITMAPFILEHEADER { + WORD bfType; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; +} BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER; + + +typedef struct tagFONTSIGNATURE +{ + DWORD fsUsb[4]; + DWORD fsCsb[2]; +} FONTSIGNATURE, *PFONTSIGNATURE, *LPFONTSIGNATURE; + +typedef struct tagCHARSETINFO +{ + UINT ciCharset; + UINT ciACP; + FONTSIGNATURE fs; +} CHARSETINFO, *PCHARSETINFO, *NPCHARSETINFO, *LPCHARSETINFO; + +typedef struct tagLOCALESIGNATURE +{ + DWORD lsUsb[4]; + DWORD lsCsbDefault[2]; + DWORD lsCsbSupported[2]; +} LOCALESIGNATURE, *PLOCALESIGNATURE, *LPLOCALESIGNATURE; + + + + +/* Clipboard Metafile Picture Structure */ +typedef struct tagHANDLETABLE + { + HGDIOBJ objectHandle[1]; + } HANDLETABLE, *PHANDLETABLE, *LPHANDLETABLE; + +typedef struct tagMETARECORD + { + DWORD rdSize; + WORD rdFunction; + WORD rdParm[1]; + } METARECORD, *PMETARECORD, *LPMETARECORD; + +typedef struct tagMETAHEADER +{ + WORD mtType; + WORD mtHeaderSize; + WORD mtVersion; + DWORD mtSize; + WORD mtNoObjects; + DWORD mtMaxRecord; + WORD mtNoParameters; +} METAHEADER, *PMETAHEADER, *LPMETAHEADER; + + +/* Enhanced Metafile structures */ +typedef struct tagENHMETARECORD +{ + DWORD iType; // Record type EMR_XXX + DWORD nSize; // Record size in bytes + DWORD dParm[1]; // Parameters +} ENHMETARECORD, *PENHMETARECORD, *LPENHMETARECORD; + +typedef struct tagENHMETAHEADER +{ + DWORD iType; // Record type EMR_HEADER + DWORD nSize; // Record size in bytes. This may be greater + // than the sizeof(ENHMETAHEADER). + RECTL rclBounds; // Inclusive-inclusive bounds in device units + RECTL rclFrame; // Inclusive-inclusive Picture Frame of metafile in .01 mm units + DWORD dSignature; // Signature. Must be ENHMETA_SIGNATURE. + DWORD nVersion; // Version number + DWORD nBytes; // Size of the metafile in bytes + DWORD nRecords; // Number of records in the metafile + WORD nHandles; // Number of handles in the handle table + // Handle index zero is reserved. + WORD sReserved; // Reserved. Must be zero. + DWORD nDescription; // Number of chars in the unicode description string + // This is 0 if there is no description string + DWORD offDescription; // Offset to the metafile description record. + // This is 0 if there is no description string + DWORD nPalEntries; // Number of entries in the metafile palette. + SIZEL szlDevice; // Size of the reference device in pels + SIZEL szlMillimeters; // Size of the reference device in millimeters + DWORD cbPixelFormat; // Size of PIXELFORMATDESCRIPTOR information + // This is 0 if no pixel format is set + DWORD offPixelFormat; // Offset to PIXELFORMATDESCRIPTOR + // This is 0 if no pixel format is set + DWORD bOpenGL; // TRUE if OpenGL commands are present in + // the metafile, otherwise FALSE + SIZEL szlMicrometers; // Size of the reference device in micrometers + +} ENHMETAHEADER, *PENHMETAHEADER, *LPENHMETAHEADER; + + +// +// BCHAR definition for APPs +// + +typedef struct tagTEXTMETRICA +{ + LONG tmHeight; + LONG tmAscent; + LONG tmDescent; + LONG tmInternalLeading; + LONG tmExternalLeading; + LONG tmAveCharWidth; + LONG tmMaxCharWidth; + LONG tmWeight; + LONG tmOverhang; + LONG tmDigitizedAspectX; + LONG tmDigitizedAspectY; + BYTE tmFirstChar; + BYTE tmLastChar; + BYTE tmDefaultChar; + BYTE tmBreakChar; + BYTE tmItalic; + BYTE tmUnderlined; + BYTE tmStruckOut; + _TMPF tmPitchAndFamily; + _CHARSETBYTE tmCharSet; +} TEXTMETRICA, *PTEXTMETRICA, *NPTEXTMETRICA, *LPTEXTMETRICA; + +typedef struct tagTEXTMETRICW +{ + LONG tmHeight; + LONG tmAscent; + LONG tmDescent; + LONG tmInternalLeading; + LONG tmExternalLeading; + LONG tmAveCharWidth; + LONG tmMaxCharWidth; + LONG tmWeight; + LONG tmOverhang; + LONG tmDigitizedAspectX; + LONG tmDigitizedAspectY; + WCHAR tmFirstChar; + WCHAR tmLastChar; + WCHAR tmDefaultChar; + WCHAR tmBreakChar; + BYTE tmItalic; + BYTE tmUnderlined; + BYTE tmStruckOut; + _TMPF tmPitchAndFamily; + _CHARSETBYTE tmCharSet; +} TEXTMETRICW, *PTEXTMETRICW, *NPTEXTMETRICW, *LPTEXTMETRICW; + +typedef struct tagNEWTEXTMETRICA +{ + LONG tmHeight; + LONG tmAscent; + LONG tmDescent; + LONG tmInternalLeading; + LONG tmExternalLeading; + LONG tmAveCharWidth; + LONG tmMaxCharWidth; + LONG tmWeight; + LONG tmOverhang; + LONG tmDigitizedAspectX; + LONG tmDigitizedAspectY; + BYTE tmFirstChar; + BYTE tmLastChar; + BYTE tmDefaultChar; + BYTE tmBreakChar; + BYTE tmItalic; + BYTE tmUnderlined; + BYTE tmStruckOut; + _TMPF tmPitchAndFamily; + _CHARSETBYTE tmCharSet; + DWORD ntmFlags; + UINT ntmSizeEM; + UINT ntmCellHeight; + UINT ntmAvgWidth; +} NEWTEXTMETRICA, *PNEWTEXTMETRICA, *NPNEWTEXTMETRICA, *LPNEWTEXTMETRICA; +typedef struct tagNEWTEXTMETRICW +{ + LONG tmHeight; + LONG tmAscent; + LONG tmDescent; + LONG tmInternalLeading; + LONG tmExternalLeading; + LONG tmAveCharWidth; + LONG tmMaxCharWidth; + LONG tmWeight; + LONG tmOverhang; + LONG tmDigitizedAspectX; + LONG tmDigitizedAspectY; + WCHAR tmFirstChar; + WCHAR tmLastChar; + WCHAR tmDefaultChar; + WCHAR tmBreakChar; + BYTE tmItalic; + BYTE tmUnderlined; + BYTE tmStruckOut; + _TMPF tmPitchAndFamily; + _CHARSETBYTE tmCharSet; + DWORD ntmFlags; + UINT ntmSizeEM; + UINT ntmCellHeight; + UINT ntmAvgWidth; +} NEWTEXTMETRICW, *PNEWTEXTMETRICW, *NPNEWTEXTMETRICW, *LPNEWTEXTMETRICW; + +typedef struct tagNEWTEXTMETRICEXA +{ + NEWTEXTMETRICA ntmTm; + FONTSIGNATURE ntmFontSig; +}NEWTEXTMETRICEXA; +typedef struct tagNEWTEXTMETRICEXW +{ + NEWTEXTMETRICW ntmTm; + FONTSIGNATURE ntmFontSig; +}NEWTEXTMETRICEXW; + +/* GDI Logical Objects: */ + +/* Pel Array */ +typedef struct tagPELARRAY + { + LONG paXCount; + LONG paYCount; + LONG paXExt; + LONG paYExt; + BYTE paRGBs; + } PELARRAY, *PPELARRAY, *NPPELARRAY, *LPPELARRAY; + +/* Logical Brush (or Pattern) */ +typedef struct tagLOGBRUSH + { + _BrushStyles lbStyle; + COLORREF lbColor; + _HatchStyle lbHatch; + } LOGBRUSH, *PLOGBRUSH, *NPLOGBRUSH, *LPLOGBRUSH; + +typedef struct tagLOGBRUSH32 + { + _BrushStyles lbStyle; + COLORREF lbColor; + _HatchStyle lbHatch; + } LOGBRUSH32, *PLOGBRUSH32, *NPLOGBRUSH32, *LPLOGBRUSH32; + +typedef LOGBRUSH PATTERN; +typedef PATTERN *PPATTERN; +typedef PATTERN *NPPATTERN; +typedef PATTERN *LPPATTERN; + +/* Logical Pen */ +typedef struct tagLOGPEN + { + _PS lopnStyle; + POINT lopnWidth; + COLORREF lopnColor; + } LOGPEN, *PLOGPEN, *NPLOGPEN, *LPLOGPEN; + +typedef struct tagEXTLOGPEN { + _PS elpPenStyle; + DWORD elpWidth; + _BrushStyles elpBrushStyle; + COLORREF elpColor; + _HatchStyle elpHatch; + DWORD elpNumEntries; + DWORD elpStyleEntry[1]; +} EXTLOGPEN, *PEXTLOGPEN, *NPEXTLOGPEN, *LPEXTLOGPEN; + +/* Logical Palette */ +typedef struct tagLOGPALETTE { + WORD palVersion; + WORD palNumEntries; + PALETTEENTRY palPalEntry[1]; +} LOGPALETTE, *PLOGPALETTE, *NPLOGPALETTE, *LPLOGPALETTE; + + +/* Logical Font */ +//#define LF_FACESIZE 32 + +typedef struct tagLOGFONTA +{ + LONG lfHeight; + LONG lfWidth; + LONG lfEscapement; + LONG lfOrientation; + _FW lfWeight; + BYTE lfItalic; + BYTE lfUnderline; + BYTE lfStrikeOut; + _CHARSETBYTE lfCharSet; + _OUTBYTE lfOutPrecision; + _CLIPBYTE lfClipPrecision; + _QUALITYBYTE lfQuality; + _FFBYTE lfPitchAndFamily; + CHAR lfFaceName[/*LF_FACESIZE*/ 32]; +} LOGFONTA, *PLOGFONTA, *NPLOGFONTA, *LPLOGFONTA; +typedef struct tagLOGFONTW +{ + LONG lfHeight; + LONG lfWidth; + LONG lfEscapement; + LONG lfOrientation; + _FW lfWeight; + BYTE lfItalic; + BYTE lfUnderline; + BYTE lfStrikeOut; + _CHARSETBYTE lfCharSet; + _OUTBYTE lfOutPrecision; + _CLIPBYTE lfClipPrecision; + _QUALITYBYTE lfQuality; + _FFBYTE lfPitchAndFamily; + WCHAR lfFaceName[/*LF_FACESIZE*/ 32]; +} LOGFONTW, *PLOGFONTW, *NPLOGFONTW, *LPLOGFONTW; + +//#define LF_FULLFACESIZE 64 + +/* Structure passed to FONTENUMPROC */ +typedef struct tagENUMLOGFONTA +{ + LOGFONTA elfLogFont; + BYTE elfFullName[/*LF_FULLFACESIZE*/ 64]; + BYTE elfStyle[/*LF_FACESIZE*/ 32]; +} ENUMLOGFONTA, * LPENUMLOGFONTA; +/* Structure passed to FONTENUMPROC */ +typedef struct tagENUMLOGFONTW +{ + LOGFONTW elfLogFont; + WCHAR elfFullName[/*LF_FULLFACESIZE*/ 64]; + WCHAR elfStyle[/*LF_FACESIZE*/ 32]; +} ENUMLOGFONTW, * LPENUMLOGFONTW; + +typedef struct tagENUMLOGFONTEXA +{ + LOGFONTA elfLogFont; + BYTE elfFullName[/*LF_FULLFACESIZE*/ 64]; + BYTE elfStyle[/*LF_FACESIZE*/ 32]; + BYTE elfScript[/*LF_FACESIZE*/ 32]; +} ENUMLOGFONTEXA, *LPENUMLOGFONTEXA; +typedef struct tagENUMLOGFONTEXW +{ + LOGFONTW elfLogFont; + WCHAR elfFullName[/*LF_FULLFACESIZE*/ 64]; + WCHAR elfStyle[/*LF_FACESIZE*/ 32]; + WCHAR elfScript[/*LF_FACESIZE*/ 32]; +} ENUMLOGFONTEXW, *LPENUMLOGFONTEXW; + +typedef struct tagPANOSE +{ + PanFamilyType bFamilyType; + PanSerifType bSerifStyle; + PanWeightType bWeight; + PanPropType bProportion; + PanConstrastType bContrast; + PanStrokeType bStrokeVariation; + PanArmsType bArmStyle; + PanLettType bLetterform; + PanMidlineType bMidline; + PanXHeightType bXHeight; +} PANOSE, * LPPANOSE; + +/* The extended logical font */ +/* An extension of the ENUMLOGFONT */ + +typedef struct tagEXTLOGFONTA { + LOGFONTA elfLogFont; + BYTE elfFullName[/*LF_FULLFACESIZE*/ 64]; + BYTE elfStyle[/*LF_FACESIZE*/ 32]; + DWORD elfVersion; /* 0 for the first release of NT */ + DWORD elfStyleSize; + DWORD elfMatch; + DWORD elfReserved; + BYTE elfVendorId[/*ELF_VENDOR_SIZE*/ 4]; + DWORD elfCulture; /* 0 for Latin */ + PANOSE elfPanose; +} EXTLOGFONTA, *PEXTLOGFONTA, *NPEXTLOGFONTA, *LPEXTLOGFONTA; +typedef struct tagEXTLOGFONTW { + LOGFONTW elfLogFont; + WCHAR elfFullName[/*LF_FULLFACESIZE*/ 64]; + WCHAR elfStyle[/*LF_FACESIZE*/ 32]; + DWORD elfVersion; /* 0 for the first release of NT */ + DWORD elfStyleSize; + DWORD elfMatch; + DWORD elfReserved; + BYTE elfVendorId[/*ELF_VENDOR_SIZE*/ 4]; + DWORD elfCulture; /* 0 for Latin */ + PANOSE elfPanose; +} EXTLOGFONTW, *PEXTLOGFONTW, *NPEXTLOGFONTW, *LPEXTLOGFONTW; + +/* GetRegionData/ExtCreateRegion */ + +typedef struct _ABC { + int abcA; + UINT abcB; + int abcC; +} ABC, *PABC, *NPABC, *LPABC; + +typedef struct _ABCFLOAT { + FLOAT abcfA; + FLOAT abcfB; + FLOAT abcfC; +} ABCFLOAT, *PABCFLOAT, *NPABCFLOAT, *LPABCFLOAT; + + +typedef struct _OUTLINETEXTMETRICA { + UINT otmSize; + TEXTMETRICA otmTextMetrics; + BYTE otmFiller; + PANOSE otmPanoseNumber; + _otmfsSelection otmfsSelection; + UINT otmfsType; + int otmsCharSlopeRise; + int otmsCharSlopeRun; + int otmItalicAngle; + UINT otmEMSquare; + int otmAscent; + int otmDescent; + UINT otmLineGap; + UINT otmsCapEmHeight; + UINT otmsXHeight; + RECT otmrcFontBox; + int otmMacAscent; + int otmMacDescent; + UINT otmMacLineGap; + UINT otmusMinimumPPEM; + POINT otmptSubscriptSize; + POINT otmptSubscriptOffset; + POINT otmptSuperscriptSize; + POINT otmptSuperscriptOffset; + UINT otmsStrikeoutSize; + int otmsStrikeoutPosition; + int otmsUnderscoreSize; + int otmsUnderscorePosition; + PSTR otmpFamilyName; + PSTR otmpFaceName; + PSTR otmpStyleName; + PSTR otmpFullName; +} OUTLINETEXTMETRICA, *POUTLINETEXTMETRICA, *NPOUTLINETEXTMETRICA, *LPOUTLINETEXTMETRICA; +typedef struct _OUTLINETEXTMETRICW { + UINT otmSize; + TEXTMETRICW otmTextMetrics; + BYTE otmFiller; + PANOSE otmPanoseNumber; + _otmfsSelection otmfsSelection; + UINT otmfsType; + int otmsCharSlopeRise; + int otmsCharSlopeRun; + int otmItalicAngle; + UINT otmEMSquare; + int otmAscent; + int otmDescent; + UINT otmLineGap; + UINT otmsCapEmHeight; + UINT otmsXHeight; + RECT otmrcFontBox; + int otmMacAscent; + int otmMacDescent; + UINT otmMacLineGap; + UINT otmusMinimumPPEM; + POINT otmptSubscriptSize; + POINT otmptSubscriptOffset; + POINT otmptSuperscriptSize; + POINT otmptSuperscriptOffset; + UINT otmsStrikeoutSize; + int otmsStrikeoutPosition; + int otmsUnderscoreSize; + int otmsUnderscorePosition; + PSTR otmpFamilyName; + PSTR otmpFaceName; + PSTR otmpStyleName; + PSTR otmpFullName; +} OUTLINETEXTMETRICW, *POUTLINETEXTMETRICW, *NPOUTLINETEXTMETRICW, *LPOUTLINETEXTMETRICW; + + + +typedef struct tagPOLYTEXTA +{ + int x; + int y; + UINT n; + LPCSTR lpstr; + _ETO uiFlags; + RECT rcl; + int *pdx; +} POLYTEXTA, *PPOLYTEXTA, *NPPOLYTEXTA, *LPPOLYTEXTA; +typedef struct tagPOLYTEXTW +{ + int x; + int y; + UINT n; + LPCWSTR lpstr; + _ETO uiFlags; + RECT rcl; + int *pdx; +} POLYTEXTW, *PPOLYTEXTW, *NPPOLYTEXTW, *LPPOLYTEXTW; + +typedef struct _FIXED { + WORD fract; + WORD _value; +} FIXED; + + +typedef struct _MAT2 { + FIXED eM11; + FIXED eM12; + FIXED eM21; + FIXED eM22; +} MAT2, *LPMAT2; + + + +typedef struct _GLYPHMETRICS { + UINT gmBlackBoxX; + UINT gmBlackBoxY; + POINT gmptGlyphOrigin; + short gmCellIncX; + short gmCellIncY; +} GLYPHMETRICS, *LPGLYPHMETRICS; + +typedef struct tagPOINTFX +{ + FIXED x; + FIXED y; +} POINTFX, * LPPOINTFX; + +typedef struct tagTTPOLYCURVE +{ + _TT_PRIM wType; + WORD cpfx; + POINTFX apfx[1]; +} TTPOLYCURVE, * LPTTPOLYCURVE; + +typedef struct tagTTPOLYGONHEADER +{ + DWORD cb; + _TT_POLYGON dwType; + POINTFX pfxStart; +} TTPOLYGONHEADER, * LPTTPOLYGONHEADER; + + +typedef struct tagGCP_RESULTSA + { + DWORD lStructSize; + LPSTR lpOutString; + UINT *lpOrder; + int *lpDx; + int *lpCaretPos; + _GCPCLASS * lpClass; + LPWSTR lpGlyphs; + UINT nGlyphs; + int nMaxFit; + } GCP_RESULTSA, * LPGCP_RESULTSA; +typedef struct tagGCP_RESULTSW + { + DWORD lStructSize; + LPWSTR lpOutString; + UINT *lpOrder; + int *lpDx; + int *lpCaretPos; + _GCPCLASS * lpClass; + LPWSTR lpGlyphs; + UINT nGlyphs; + int nMaxFit; + } GCP_RESULTSW, * LPGCP_RESULTSW; + +typedef struct _RASTERIZER_STATUS { + short nSize; + _RASTERIZER_STATUS_Flag wFlags; + short nLanguageID; +} RASTERIZER_STATUS, *LPRASTERIZER_STATUS; + +/* Pixel format descriptor */ +typedef struct tagPIXELFORMATDESCRIPTOR +{ + WORD nSize; + WORD nVersion; + _PIXELFORMATDESCRIPTOR dwFlags; + _PFD iPixelType; + BYTE cColorBits; + BYTE cRedBits; + BYTE cRedShift; + BYTE cGreenBits; + BYTE cGreenShift; + BYTE cBlueBits; + BYTE cBlueShift; + BYTE cAlphaBits; + BYTE cAlphaShift; + BYTE cAccumBits; + BYTE cAccumRedBits; + BYTE cAccumGreenBits; + BYTE cAccumBlueBits; + BYTE cAccumAlphaBits; + BYTE cDepthBits; + BYTE cStencilBits; + BYTE cAuxBuffers; + _PFD_LAYER iLayerType; + BYTE bReserved; + DWORD dwLayerMask; + DWORD dwVisibleMask; + DWORD dwDamageMask; +} PIXELFORMATDESCRIPTOR, *PPIXELFORMATDESCRIPTOR, *LPPIXELFORMATDESCRIPTOR; + +typedef OLDFONTENUMPROCA FONTENUMPROCA; +typedef OLDFONTENUMPROCW FONTENUMPROCW; + + +/* define types of pointers to ExtDeviceMode() and DeviceCapabilities() + * functions for Win 3.1 compatibility + */ +typedef LPVOID OLDFONTENUMPROCA; +typedef LPVOID OLDFONTENUMPROCW; +typedef LPVOID GOBJENUMPROC; +typedef LPVOID LINEDDAPROC; + +typedef LPVOID LPFNDEVMODEA; +typedef LPVOID LPFNDEVMODEW; + +typedef LPVOID LPFNDEVCAPSA; +typedef LPVOID LPFNDEVCAPSW; + +typedef struct tagWCRANGE +{ + WCHAR wcLow; + USHORT cGlyphs; +} WCRANGE, *PWCRANGE, *LPWCRANGE; + + +typedef struct tagGLYPHSET +{ + DWORD cbThis; + _GS_8BIT_INDICES flAccel; + DWORD cGlyphsSupported; + DWORD cRanges; + WCRANGE ranges[1]; +} GLYPHSET, *PGLYPHSET, *LPGLYPHSET; + +typedef struct tagDESIGNVECTOR +{ + DWORD dvReserved; + DWORD dvNumAxes; + LONG dvValues[/*MM_MAX_NUMAXES*/16]; +} DESIGNVECTOR, *PDESIGNVECTOR, *LPDESIGNVECTOR; +// The actual size of the DESIGNVECTOR and ENUMLOGFONTEXDV structures +// is determined by dvNumAxes, +// MM_MAX_NUMAXES only detemines the maximal size allowed + +//#define MM_MAX_AXES_NAMELEN 16 + +typedef struct tagAXISINFOA +{ + LONG axMinValue; + LONG axMaxValue; + BYTE axAxisName[/*MM_MAX_NUMAXES*/ 16]; +} AXISINFOA, *PAXISINFOA, *LPAXISINFOA; +typedef struct tagAXISINFOW +{ + LONG axMinValue; + LONG axMaxValue; + WCHAR axAxisName[/*MM_MAX_NUMAXES*/ 16]; +} AXISINFOW, *PAXISINFOW, *LPAXISINFOW; + +typedef struct tagAXESLISTA +{ + DWORD axlReserved; + DWORD axlNumAxes; + AXISINFOA axlAxisInfo[/*MM_MAX_NUMAXES*/ 16]; +} AXESLISTA, *PAXESLISTA, *LPAXESLISTA; +typedef struct tagAXESLISTW +{ + DWORD axlReserved; + DWORD axlNumAxes; + AXISINFOW axlAxisInfo[/*MM_MAX_NUMAXES*/ 16]; +} AXESLISTW, *PAXESLISTW, *LPAXESLISTW; + +// The actual size of the AXESLIST and ENUMTEXTMETRIC structure is +// determined by axlNumAxes, +// MM_MAX_NUMAXES only detemines the maximal size allowed + +typedef struct tagENUMLOGFONTEXDVA +{ + ENUMLOGFONTEXA elfEnumLogfontEx; + DESIGNVECTOR elfDesignVector; +} ENUMLOGFONTEXDVA, *PENUMLOGFONTEXDVA, *LPENUMLOGFONTEXDVA; +typedef struct tagENUMLOGFONTEXDVW +{ + ENUMLOGFONTEXW elfEnumLogfontEx; + DESIGNVECTOR elfDesignVector; +} ENUMLOGFONTEXDVW, *PENUMLOGFONTEXDVW, *LPENUMLOGFONTEXDVW; + + +typedef struct tagENUMTEXTMETRICA +{ + NEWTEXTMETRICEXA etmNewTextMetricEx; + AXESLISTA etmAxesList; +} ENUMTEXTMETRICA, *PENUMTEXTMETRICA, *LPENUMTEXTMETRICA; +typedef struct tagENUMTEXTMETRICW +{ + NEWTEXTMETRICEXW etmNewTextMetricEx; + AXESLISTW etmAxesList; +} ENUMTEXTMETRICW, *PENUMTEXTMETRICW, *LPENUMTEXTMETRICW; + +// +// image blt +// + +typedef USHORT COLOR16; + +typedef struct _TRIVERTEX +{ + LONG x; + LONG y; + COLOR16 Red; + COLOR16 Green; + COLOR16 Blue; + COLOR16 Alpha; +}TRIVERTEX,*PTRIVERTEX,*LPTRIVERTEX; + +typedef struct _GRADIENT_TRIANGLE +{ + ULONG Vertex1; + ULONG Vertex2; + ULONG Vertex3; +} GRADIENT_TRIANGLE,*PGRADIENT_TRIANGLE,*LPGRADIENT_TRIANGLE; + +typedef struct _GRADIENT_RECT +{ + ULONG UpperLeft; + ULONG LowerRight; +}GRADIENT_RECT,*PGRADIENT_RECT,*LPGRADIENT_RECT; + +typedef struct _BLENDFUNCTION +{ + _AC_SRC_OVER BlendOp; + BYTE BlendFlags; + BYTE SourceConstantAlpha; + _AC_SRC_ALPHA AlphaFormat; +}BLENDFUNCTION,*PBLENDFUNCTION; + +typedef LPVOID MFENUMPROC; +typedef LPVOID ENHMFENUMPROC; + + +/* new GDI */ + +typedef struct tagDIBSECTION { + BITMAP dsBm; + BITMAPINFOHEADER dsBmih; + DWORD dsBitfields[3]; + HANDLE dshSection; + DWORD dsOffset; +} DIBSECTION, *LPDIBSECTION, *PDIBSECTION; + +typedef struct tagKERNINGPAIR { + WORD wFirst; + WORD wSecond; + int iKernAmount; +} KERNINGPAIR, *LPKERNINGPAIR; + +typedef LPVOID ICMENUMPROCA; +typedef LPVOID ICMENUMPROCW; + +// Base record type for the enhanced metafile. + +typedef struct tagEMR +{ + _EMR iType; // Enhanced metafile record type + DWORD nSize; // Length of the record in bytes. + // This must be a multiple of 4. +} EMR, *PEMR; + +// Base text record type for the enhanced metafile. + +typedef struct tagEMRTEXT +{ + POINTL ptlReference; + DWORD nChars; + DWORD offString; // Offset to the string + _ETO fOptions; + RECTL rcl; + DWORD offDx; // Offset to the inter-character spacing array. + // This is always given. +} EMRTEXT, *PEMRTEXT; + +// Record structures for the enhanced metafile. + +typedef struct tagABORTPATH +{ + EMR emr; +} EMRABORTPATH, *PEMRABORTPATH, + EMRBEGINPATH, *PEMRBEGINPATH, + EMRENDPATH, *PEMRENDPATH, + EMRCLOSEFIGURE, *PEMRCLOSEFIGURE, + EMRFLATTENPATH, *PEMRFLATTENPATH, + EMRWIDENPATH, *PEMRWIDENPATH, + EMRSETMETARGN, *PEMRSETMETARGN, + EMRSAVEDC, *PEMRSAVEDC, + EMRREALIZEPALETTE, *PEMRREALIZEPALETTE; + +typedef struct tagEMRSELECTCLIPPATH +{ + EMR emr; + DWORD iMode; +} EMRSELECTCLIPPATH, *PEMRSELECTCLIPPATH, + EMRSETBKMODE, *PEMRSETBKMODE, + EMRSETMAPMODE, *PEMRSETMAPMODE, + EMRSETLAYOUT, *PEMRSETLAYOUT, + EMRSETPOLYFILLMODE, *PEMRSETPOLYFILLMODE, + EMRSETROP2, *PEMRSETROP2, + EMRSETSTRETCHBLTMODE, *PEMRSETSTRETCHBLTMODE, + EMRSETICMMODE, *PEMRSETICMMODE, + EMRSETTEXTALIGN, *PEMRSETTEXTALIGN; + +typedef struct tagEMRSETMITERLIMIT +{ + EMR emr; + FLOAT eMiterLimit; +} EMRSETMITERLIMIT, *PEMRSETMITERLIMIT; + +typedef struct tagEMRRESTOREDC +{ + EMR emr; + LONG iRelative; // Specifies a relative instance +} EMRRESTOREDC, *PEMRRESTOREDC; + +typedef struct tagEMRSETARCDIRECTION +{ + EMR emr; + DWORD iArcDirection; // Specifies the arc direction in the + // advanced graphics mode. +} EMRSETARCDIRECTION, *PEMRSETARCDIRECTION; + +typedef struct tagEMRSETMAPPERFLAGS +{ + EMR emr; + DWORD dwFlags; +} EMRSETMAPPERFLAGS, *PEMRSETMAPPERFLAGS; + +typedef struct tagEMRSETTEXTCOLOR +{ + EMR emr; + COLORREF crColor; +} EMRSETBKCOLOR, *PEMRSETBKCOLOR, + EMRSETTEXTCOLOR, *PEMRSETTEXTCOLOR; + +typedef struct tagEMRSELECTOBJECT +{ + EMR emr; + DWORD ihObject; // Object handle index +} EMRSELECTOBJECT, *PEMRSELECTOBJECT, + EMRDELETEOBJECT, *PEMRDELETEOBJECT; + +typedef struct tagEMRSELECTPALETTE +{ + EMR emr; + DWORD ihPal; // Palette handle index, background mode only +} EMRSELECTPALETTE, *PEMRSELECTPALETTE; + +typedef struct tagEMRRESIZEPALETTE +{ + EMR emr; + DWORD ihPal; // Palette handle index + DWORD cEntries; +} EMRRESIZEPALETTE, *PEMRRESIZEPALETTE; + +typedef struct tagEMRSETPALETTEENTRIES +{ + EMR emr; + DWORD ihPal; // Palette handle index + DWORD iStart; + DWORD cEntries; + PALETTEENTRY aPalEntries[1];// The peFlags fields do not contain any flags +} EMRSETPALETTEENTRIES, *PEMRSETPALETTEENTRIES; + +typedef struct tagEMRSETCOLORADJUSTMENT +{ + EMR emr; + COLORADJUSTMENT ColorAdjustment; +} EMRSETCOLORADJUSTMENT, *PEMRSETCOLORADJUSTMENT; + +typedef struct tagEMRGDICOMMENT +{ + EMR emr; + DWORD cbData; // Size of data in bytes + BYTE Data[1]; +} EMRGDICOMMENT, *PEMRGDICOMMENT; + +typedef struct tagEMREOF +{ + EMR emr; + DWORD nPalEntries; // Number of palette entries + DWORD offPalEntries; // Offset to the palette entries + DWORD nSizeLast; // Same as nSize and must be the last DWORD + // of the record. The palette entries, + // if exist, precede this field. +} EMREOF, *PEMREOF; + +typedef struct tagEMRLINETO +{ + EMR emr; + POINTL ptl; +} EMRLINETO, *PEMRLINETO, + EMRMOVETOEX, *PEMRMOVETOEX; + +typedef struct tagEMROFFSETCLIPRGN +{ + EMR emr; + POINTL ptlOffset; +} EMROFFSETCLIPRGN, *PEMROFFSETCLIPRGN; + +typedef struct tagEMRFILLPATH +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units +} EMRFILLPATH, *PEMRFILLPATH, + EMRSTROKEANDFILLPATH, *PEMRSTROKEANDFILLPATH, + EMRSTROKEPATH, *PEMRSTROKEPATH; + +typedef struct tagEMREXCLUDECLIPRECT +{ + EMR emr; + RECTL rclClip; +} EMREXCLUDECLIPRECT, *PEMREXCLUDECLIPRECT, + EMRINTERSECTCLIPRECT, *PEMRINTERSECTCLIPRECT; + +typedef struct tagEMRSETVIEWPORTORGEX +{ + EMR emr; + POINTL ptlOrigin; +} EMRSETVIEWPORTORGEX, *PEMRSETVIEWPORTORGEX, + EMRSETWINDOWORGEX, *PEMRSETWINDOWORGEX, + EMRSETBRUSHORGEX, *PEMRSETBRUSHORGEX; + +typedef struct tagEMRSETVIEWPORTEXTEX +{ + EMR emr; + SIZEL szlExtent; +} EMRSETVIEWPORTEXTEX, *PEMRSETVIEWPORTEXTEX, + EMRSETWINDOWEXTEX, *PEMRSETWINDOWEXTEX; + +typedef struct tagEMRSCALEVIEWPORTEXTEX +{ + EMR emr; + LONG xNum; + LONG xDenom; + LONG yNum; + LONG yDenom; +} EMRSCALEVIEWPORTEXTEX, *PEMRSCALEVIEWPORTEXTEX, + EMRSCALEWINDOWEXTEX, *PEMRSCALEWINDOWEXTEX; + +typedef struct tagEMRSETWORLDTRANSFORM +{ + EMR emr; + XFORM xform; +} EMRSETWORLDTRANSFORM, *PEMRSETWORLDTRANSFORM; + +typedef struct tagEMRMODIFYWORLDTRANSFORM +{ + EMR emr; + XFORM xform; + _MWT iMode; +} EMRMODIFYWORLDTRANSFORM, *PEMRMODIFYWORLDTRANSFORM; + +typedef struct tagEMRSETPIXELV +{ + EMR emr; + POINTL ptlPixel; + COLORREF crColor; +} EMRSETPIXELV, *PEMRSETPIXELV; + +typedef struct tagEMREXTFLOODFILL +{ + EMR emr; + POINTL ptlStart; + COLORREF crColor; + _FLOODFILL iMode; +} EMREXTFLOODFILL, *PEMREXTFLOODFILL; + +typedef struct tagEMRELLIPSE +{ + EMR emr; + RECTL rclBox; // Inclusive-inclusive bounding rectangle +} EMRELLIPSE, *PEMRELLIPSE, + EMRRECTANGLE, *PEMRRECTANGLE; + +typedef struct tagEMRROUNDRECT +{ + EMR emr; + RECTL rclBox; // Inclusive-inclusive bounding rectangle + SIZEL szlCorner; +} EMRROUNDRECT, *PEMRROUNDRECT; + +typedef struct tagEMRARC +{ + EMR emr; + RECTL rclBox; // Inclusive-inclusive bounding rectangle + POINTL ptlStart; + POINTL ptlEnd; +} EMRARC, *PEMRARC, + EMRARCTO, *PEMRARCTO, + EMRCHORD, *PEMRCHORD, + EMRPIE, *PEMRPIE; + +typedef struct tagEMRANGLEARC +{ + EMR emr; + POINTL ptlCenter; + DWORD nRadius; + FLOAT eStartAngle; + FLOAT eSweepAngle; +} EMRANGLEARC, *PEMRANGLEARC; + +typedef struct tagEMRPOLYLINE +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cptl; + POINTL aptl[1]; +} EMRPOLYLINE, *PEMRPOLYLINE, + EMRPOLYBEZIER, *PEMRPOLYBEZIER, + EMRPOLYGON, *PEMRPOLYGON, + EMRPOLYBEZIERTO, *PEMRPOLYBEZIERTO, + EMRPOLYLINETO, *PEMRPOLYLINETO; + +typedef struct tagEMRPOLYLINE16 +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cpts; + POINTS apts[1]; +} EMRPOLYLINE16, *PEMRPOLYLINE16, + EMRPOLYBEZIER16, *PEMRPOLYBEZIER16, + EMRPOLYGON16, *PEMRPOLYGON16, + EMRPOLYBEZIERTO16, *PEMRPOLYBEZIERTO16, + EMRPOLYLINETO16, *PEMRPOLYLINETO16; + +typedef struct tagEMRPOLYDRAW +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cptl; // Number of points + POINTL aptl[1]; // Array of points + BYTE abTypes[1]; // Array of point types +} EMRPOLYDRAW, *PEMRPOLYDRAW; + +typedef struct tagEMRPOLYDRAW16 +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cpts; // Number of points + POINTS apts[1]; // Array of points + BYTE abTypes[1]; // Array of point types +} EMRPOLYDRAW16, *PEMRPOLYDRAW16; + +typedef struct tagEMRPOLYPOLYLINE +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD nPolys; // Number of polys + DWORD cptl; // Total number of points in all polys + DWORD aPolyCounts[1]; // Array of point counts for each poly + POINTL aptl[1]; // Array of points +} EMRPOLYPOLYLINE, *PEMRPOLYPOLYLINE, + EMRPOLYPOLYGON, *PEMRPOLYPOLYGON; + +typedef struct tagEMRPOLYPOLYLINE16 +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD nPolys; // Number of polys + DWORD cpts; // Total number of points in all polys + DWORD aPolyCounts[1]; // Array of point counts for each poly + POINTS apts[1]; // Array of points +} EMRPOLYPOLYLINE16, *PEMRPOLYPOLYLINE16, + EMRPOLYPOLYGON16, *PEMRPOLYPOLYGON16; + +typedef struct tagEMRINVERTRGN +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cbRgnData; // Size of region data in bytes + BYTE RgnData[1]; +} EMRINVERTRGN, *PEMRINVERTRGN, + EMRPAINTRGN, *PEMRPAINTRGN; + +typedef struct tagEMRFILLRGN +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cbRgnData; // Size of region data in bytes + DWORD ihBrush; // Brush handle index + BYTE RgnData[1]; +} EMRFILLRGN, *PEMRFILLRGN; + +typedef struct tagEMRFRAMERGN +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD cbRgnData; // Size of region data in bytes + DWORD ihBrush; // Brush handle index + SIZEL szlStroke; + BYTE RgnData[1]; +} EMRFRAMERGN, *PEMRFRAMERGN; + + +typedef struct tagEMREXTSELECTCLIPRGN +{ + EMR emr; + DWORD cbRgnData; // Size of region data in bytes + DWORD iMode; + BYTE RgnData[1]; +} EMREXTSELECTCLIPRGN, *PEMREXTSELECTCLIPRGN; + +typedef struct tagEMREXTTEXTOUTA +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + _GM iGraphicsMode; // Current graphics mode + FLOAT exScale; // X and Y scales from Page units to .01mm units + FLOAT eyScale; // if graphics mode is GM_COMPATIBLE. + EMRTEXT emrtext; // This is followed by the string and spacing + // array +} EMREXTTEXTOUTA, *PEMREXTTEXTOUTA, + EMREXTTEXTOUTW, *PEMREXTTEXTOUTW; + +typedef struct tagEMRPOLYTEXTOUTA +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + _GM iGraphicsMode; // Current graphics mode + FLOAT exScale; // X and Y scales from Page units to .01mm units + FLOAT eyScale; // if graphics mode is GM_COMPATIBLE. + LONG cStrings; + EMRTEXT aemrtext[1]; // Array of EMRTEXT structures. This is + // followed by the strings and spacing arrays. +} EMRPOLYTEXTOUTA, *PEMRPOLYTEXTOUTA, + EMRPOLYTEXTOUTW, *PEMRPOLYTEXTOUTW; + +typedef struct tagEMRBITBLT +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG cxDest; + LONG cyDest; + _TernaryDrawMode dwRop; + LONG xSrc; + LONG ySrc; + XFORM xformSrc; // Source DC transform + COLORREF crBkColorSrc; // Source DC BkColor in RGB + _DIB_Color iUsageSrc; // Source bitmap info color table usage + // (DIB_RGB_COLORS) + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits +} EMRBITBLT, *PEMRBITBLT; + +typedef struct tagEMRSTRETCHBLT +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG cxDest; + LONG cyDest; + _TernaryDrawMode dwRop; + LONG xSrc; + LONG ySrc; + XFORM xformSrc; // Source DC transform + COLORREF crBkColorSrc; // Source DC BkColor in RGB + _DIB_Color iUsageSrc; // Source bitmap info color table usage + // (DIB_RGB_COLORS) + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + LONG cxSrc; + LONG cySrc; +} EMRSTRETCHBLT, *PEMRSTRETCHBLT; + +typedef struct tagEMRMASKBLT +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG cxDest; + LONG cyDest; + _TernaryDrawMode dwRop; + LONG xSrc; + LONG ySrc; + XFORM xformSrc; // Source DC transform + COLORREF crBkColorSrc; // Source DC BkColor in RGB + _DIB_Color iUsageSrc; // Source bitmap info color table usage + // (DIB_RGB_COLORS) + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + LONG xMask; + LONG yMask; + DWORD iUsageMask; // Mask bitmap info color table usage + DWORD offBmiMask; // Offset to the mask BITMAPINFO structure if any + DWORD cbBmiMask; // Size of the mask BITMAPINFO structure if any + DWORD offBitsMask; // Offset to the mask bitmap bits if any + DWORD cbBitsMask; // Size of the mask bitmap bits if any +} EMRMASKBLT, *PEMRMASKBLT; + +typedef struct tagEMRPLGBLT +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + POINTL aptlDest[3]; + LONG xSrc; + LONG ySrc; + LONG cxSrc; + LONG cySrc; + XFORM xformSrc; // Source DC transform + COLORREF crBkColorSrc; // Source DC BkColor in RGB + _DIB_Color iUsageSrc; // Source bitmap info color table usage + // (DIB_RGB_COLORS) + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + LONG xMask; + LONG yMask; + DWORD iUsageMask; // Mask bitmap info color table usage + DWORD offBmiMask; // Offset to the mask BITMAPINFO structure if any + DWORD cbBmiMask; // Size of the mask BITMAPINFO structure if any + DWORD offBitsMask; // Offset to the mask bitmap bits if any + DWORD cbBitsMask; // Size of the mask bitmap bits if any +} EMRPLGBLT, *PEMRPLGBLT; + +typedef struct tagEMRSETDIBITSTODEVICE +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG xSrc; + LONG ySrc; + LONG cxSrc; + LONG cySrc; + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + _DIB_Color iUsageSrc; // Source bitmap info color table usage + DWORD iStartScan; + DWORD cScans; +} EMRSETDIBITSTODEVICE, *PEMRSETDIBITSTODEVICE; + +typedef struct tagEMRSTRETCHDIBITS +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG xSrc; + LONG ySrc; + LONG cxSrc; + LONG cySrc; + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + _DIB_Color iUsageSrc; // Source bitmap info color table usage + _TernaryDrawMode dwRop; + LONG cxDest; + LONG cyDest; +} EMRSTRETCHDIBITS, *PEMRSTRETCHDIBITS; + +typedef struct tagEMREXTCREATEFONTINDIRECTW +{ + EMR emr; + DWORD ihFont; // Font handle index + EXTLOGFONTW elfw; +} EMREXTCREATEFONTINDIRECTW, *PEMREXTCREATEFONTINDIRECTW; + + +typedef struct tagEMRCREATEPALETTE +{ + EMR emr; + DWORD ihPal; // Palette handle index + LOGPALETTE lgpl; // The peFlags fields in the palette entries + // do not contain any flags +} EMRCREATEPALETTE, *PEMRCREATEPALETTE; + +typedef struct tagEMRCREATEPEN +{ + EMR emr; + DWORD ihPen; // Pen handle index + LOGPEN lopn; +} EMRCREATEPEN, *PEMRCREATEPEN; + +typedef struct tagEMREXTCREATEPEN +{ + EMR emr; + DWORD ihPen; // Pen handle index + DWORD offBmi; // Offset to the BITMAPINFO structure if any + DWORD cbBmi; // Size of the BITMAPINFO structure if any + // The bitmap info is followed by the bitmap + // bits to form a packed DIB. + DWORD offBits; // Offset to the brush bitmap bits if any + DWORD cbBits; // Size of the brush bitmap bits if any + EXTLOGPEN elp; // The extended pen with the style array. +} EMREXTCREATEPEN, *PEMREXTCREATEPEN; + +typedef struct tagEMRCREATEBRUSHINDIRECT +{ + EMR emr; + DWORD ihBrush; // Brush handle index + LOGBRUSH32 lb; // The style must be BS_SOLID, BS_HOLLOW, + // BS_NULL or BS_HATCHED. +} EMRCREATEBRUSHINDIRECT, *PEMRCREATEBRUSHINDIRECT; + +typedef struct tagEMRCREATEMONOBRUSH +{ + EMR emr; + DWORD ihBrush; // Brush handle index + DWORD iUsage; // Bitmap info color table usage + DWORD offBmi; // Offset to the BITMAPINFO structure + DWORD cbBmi; // Size of the BITMAPINFO structure + DWORD offBits; // Offset to the bitmap bits + DWORD cbBits; // Size of the bitmap bits +} EMRCREATEMONOBRUSH, *PEMRCREATEMONOBRUSH; + +typedef struct tagEMRCREATEDIBPATTERNBRUSHPT +{ + EMR emr; + DWORD ihBrush; // Brush handle index + DWORD iUsage; // Bitmap info color table usage + DWORD offBmi; // Offset to the BITMAPINFO structure + DWORD cbBmi; // Size of the BITMAPINFO structure + // The bitmap info is followed by the bitmap + // bits to form a packed DIB. + DWORD offBits; // Offset to the bitmap bits + DWORD cbBits; // Size of the bitmap bits +} EMRCREATEDIBPATTERNBRUSHPT, *PEMRCREATEDIBPATTERNBRUSHPT; + +typedef struct tagEMRFORMAT +{ + EMRSignature dSignature; // Format signature, e.g. ENHMETA_SIGNATURE. + DWORD nVersion; // Format version number. + DWORD cbData; // Size of data in bytes. + DWORD offData; // Offset to data from GDICOMMENT_IDENTIFIER. + // It must begin at a DWORD offset. +} EMRFORMAT, *PEMRFORMAT; + +typedef struct tagEMRGLSRECORD +{ + EMR emr; + DWORD cbData; // Size of data in bytes + BYTE Data[1]; +} EMRGLSRECORD, *PEMRGLSRECORD; + +typedef struct tagEMRGLSBOUNDEDRECORD +{ + EMR emr; + RECTL rclBounds; // Bounds in recording coordinates + DWORD cbData; // Size of data in bytes + BYTE Data[1]; +} EMRGLSBOUNDEDRECORD, *PEMRGLSBOUNDEDRECORD; + +typedef struct tagEMRPIXELFORMAT +{ + EMR emr; + PIXELFORMATDESCRIPTOR pfd; +} EMRPIXELFORMAT, *PEMRPIXELFORMAT; + + +typedef struct tagEMRCREATECOLORSPACE +{ + EMR emr; + DWORD ihCS; // ColorSpace handle index + LOGCOLORSPACEA lcs; // Ansi version of LOGCOLORSPACE +} EMRCREATECOLORSPACE, *PEMRCREATECOLORSPACE; + +typedef struct tagEMRSETCOLORSPACE +{ + EMR emr; + DWORD ihCS; // ColorSpace handle index +} EMRSETCOLORSPACE, *PEMRSETCOLORSPACE, + EMRSELECTCOLORSPACE, *PEMRSELECTCOLORSPACE, + EMRDELETECOLORSPACE, *PEMRDELETECOLORSPACE; + + +typedef struct tagEMREXTESCAPE +{ + EMR emr; + INT iEscape; // Escape code + INT cbEscData; // Size of escape data + BYTE EscData[1]; // Escape data +} EMREXTESCAPE, *PEMREXTESCAPE, + EMRDRAWESCAPE, *PEMRDRAWESCAPE; + +typedef struct tagEMRNAMEDESCAPE +{ + EMR emr; + INT iEscape; // Escape code + INT cbDriver; // Size of driver name + INT cbEscData; // Size of escape data + BYTE EscData[1]; // Driver name and Escape data +} EMRNAMEDESCAPE, *PEMRNAMEDESCAPE, + EMRSETICMPROFILEA, *PEMRSETICMPROFILEA, + EMRSETICMPROFILEW, *PEMRSETICMPROFILEW; + + +typedef struct tagEMRCREATECOLORSPACEA +{ + EMR emr; + DWORD ihCS; // ColorSpace handle index + LOGCOLORSPACEA lcs; // Unicode version of logical color space structure + EMRColorSpaceFlagMask dwFlags; // flags + DWORD cbData; // size of raw source profile data if attached + BYTE Data[1]; // Array size is cbData +} EMRCREATECOLORSPACEA, *PEMRCREATECOLORSPACEA; + +typedef struct tagEMRCREATECOLORSPACEW +{ + EMR emr; + DWORD ihCS; // ColorSpace handle index + LOGCOLORSPACEW lcs; // Unicode version of logical color space structure + EMRColorSpaceFlagMask dwFlags; // flags + DWORD cbData; // size of raw source profile data if attached + BYTE Data[1]; // Array size is cbData +} EMRCREATECOLORSPACEW, *PEMRCREATECOLORSPACEW; + +//#define COLORMATCHTOTARGET_EMBEDED 0x00000001 + +typedef struct tagCOLORMATCHTOTARGET +{ + EMR emr; + _CS dwAction; // CS_ENABLE, CS_DISABLE or CS_DELETE_TRANSFORM + EMRColorMatchFlagMask dwFlags; // flags + DWORD cbName; // Size of desired target profile name + DWORD cbData; // Size of raw target profile data if attached + BYTE Data[1]; // Array size is cbName + cbData +} EMRCOLORMATCHTOTARGET, *PEMRCOLORMATCHTOTARGET; + +typedef struct tagCOLORCORRECTPALETTE +{ + EMR emr; + DWORD ihPalette; // Palette handle index + DWORD nFirstEntry; // Index of first entry to correct + DWORD nPalEntries; // Number of palette entries to correct + DWORD nReserved; // Reserved +} EMRCOLORCORRECTPALETTE, *PEMRCOLORCORRECTPALETTE; + +typedef struct tagEMRALPHABLEND +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG cxDest; + LONG cyDest; + _TernaryDrawMode dwRop; + LONG xSrc; + LONG ySrc; + XFORM xformSrc; // Source DC transform + COLORREF crBkColorSrc; // Source DC BkColor in RGB + _DIB_Color iUsageSrc; // Source bitmap info color table usage + // (DIB_RGB_COLORS) + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + LONG cxSrc; + LONG cySrc; +} EMRALPHABLEND, *PEMRALPHABLEND; + +typedef struct tagEMRGRADIENTFILL +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + DWORD nVer; + DWORD nTri; + _GRADIENT_FILL ulMode; + TRIVERTEX Ver[1]; +}EMRGRADIENTFILL,*PEMRGRADIENTFILL; + +typedef struct tagEMRTRANSPARENTBLT +{ + EMR emr; + RECTL rclBounds; // Inclusive-inclusive bounds in device units + LONG xDest; + LONG yDest; + LONG cxDest; + LONG cyDest; + _TernaryDrawMode dwRop; + LONG xSrc; + LONG ySrc; + XFORM xformSrc; // Source DC transform + COLORREF crBkColorSrc; // Source DC BkColor in RGB + _DIB_Color iUsageSrc; // Source bitmap info color table usage + // (DIB_RGB_COLORS) + DWORD offBmiSrc; // Offset to the source BITMAPINFO structure + DWORD cbBmiSrc; // Size of the source BITMAPINFO structure + DWORD offBitsSrc; // Offset to the source bitmap bits + DWORD cbBitsSrc; // Size of the source bitmap bits + LONG cxSrc; + LONG cySrc; +} EMRTRANSPARENTBLT, *PEMRTRANSPARENTBLT; + +typedef struct tagEMRSETICMPROFILE +{ + EMR emr; + DWORD dwFlags; // flags + DWORD cbName; // Size of desired profile name + DWORD cbData; // Size of raw profile data if attached + BYTE Data[1]; // Array size is cbName and cbData +} EMRSETICMPROFILE, *PEMRSETICMPROFILE; + +typedef struct _POINTFLOAT { + FLOAT x; + FLOAT y; +} POINTFLOAT, *PPOINTFLOAT; + +typedef struct _GLYPHMETRICSFLOAT { + FLOAT gmfBlackBoxX; + FLOAT gmfBlackBoxY; + POINTFLOAT gmfptGlyphOrigin; + FLOAT gmfCellIncX; + FLOAT gmfCellIncY; +} GLYPHMETRICSFLOAT, *PGLYPHMETRICSFLOAT, *LPGLYPHMETRICSFLOAT; + +/* Layer plane descriptor */ +typedef struct tagLAYERPLANEDESCRIPTOR { // lpd + WORD nSize; + WORD nVersion; + _LAYERPLANEDESCRIPTOR dwFlags; + _LPD_TYPE iPixelType; + BYTE cColorBits; + BYTE cRedBits; + BYTE cRedShift; + BYTE cGreenBits; + BYTE cGreenShift; + BYTE cBlueBits; + BYTE cBlueShift; + BYTE cAlphaBits; + BYTE cAlphaShift; + BYTE cAccumBits; + BYTE cAccumRedBits; + BYTE cAccumGreenBits; + BYTE cAccumBlueBits; + BYTE cAccumAlphaBits; + BYTE cDepthBits; + BYTE cStencilBits; + BYTE cAuxBuffers; + BYTE iLayerPlane; + BYTE bReserved; + COLORREF crTransparent; +} LAYERPLANEDESCRIPTOR, *PLAYERPLANEDESCRIPTOR, *LPLAYERPLANEDESCRIPTOR; + +typedef struct _WGLSWAP +{ + HDC hdc; + UINT uiFlags; +} WGLSWAP, *PWGLSWAP, *LPWGLSWAP; + +//========================================================================================================= +//========================================================================================================= +//========================================================================================================= +//========================================================================================================= + + +IntFailIfZero AddFontResourceA( + [in] LPCSTR lpszFilename +); +IntFailIfZero AddFontResourceW( + [in] LPCWSTR lpszFilename +); + +FailOnFalse [gle] AnimatePalette( + [in] HPALETTE hpal, // handle to logical palette + [in] UINT iStartIndex, // first entry in logical palette + [in] UINT cEntries, // number of entries + [in] PALETTEENTRY *ppe // first replacement +); +FailOnFalse [gle] Arc( + [in] HDC hdc, // handle to device context + [in] int nLeftRect, // x-coord of rectangle's upper-left corner + [in] int nTopRect, // y-coord of rectangle's upper-left corner + [in] int nRightRect, // x-coord of rectangle's lower-right corner + [in] int nBottomRect, // y-coord of rectangle's lower-right corner + [in] int nXStartArc, // x-coord of first radial ending poIN int + [in] int nYStartArc, // y-coord of first radial ending poIN int + [in] int nXEndArc, // x-coord of second radial ending poIN int + [in] int nYEndArc // y-coord of second radial ending poIN int +); +FailOnFalse [gle] BitBlt( + [in] HDC hdcDest, // handle to destination DC + [in] int nXDest, // x-coord of destination upper-left corner + [in] int nYDest, // y-coord of destination upper-left corner + [in] int nWidth, // width of destination rectangle + [in] int nHeight, // height of destination rectangle + [in] HDC hdcSrc, // handle to source DC + [in] int nXSrc, // x-coordinate of source upper-left corner + [in] int nYSrc, // y-coordinate of source upper-left corner + [in] _TernaryDrawMode dwRop // raster operation code +); +FailOnFalse [gle] CancelDC( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] Chord( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner of rectangle + [in] int nTopRect, // y-coord of upper-left corner of rectangle + [in] int nRightRect, // x-coord of lower-right corner of rectangle + [in] int nBottomRect, // y-coord of lower-right corner of rectangle + [in] int nXRadial1, // x-coord of first radial's endpoIN int + [in] int nYRadial1, // y-coord of first radial's endpoIN int + [in] int nXRadial2, // x-coord of second radial's endpoIN int + [in] int nYRadial2 // y-coord of second radial's endpoIN int +); +IntFailIfZero [gle] ChoosePixelFormat( + [in] HDC hdc, // device context to search for a best pixel format + // match + [in] PIXELFORMATDESCRIPTOR * ppfd + // pixel format for which a best match is sought +); + +HMETAFILE [gle] CloseMetaFile( + [in] HDC hdc // handle to Windows-metafile DC +); +_RegionFlags CombineRgn( + [in] HRGN hrgnDest, // handle to destination region + [in] HRGN hrgnSrc1, // handle to source region + [in] HRGN hrgnSrc2, // handle to source region + [in] _CombineRgn fnCombineMode // region combining mode +); +HMETAFILE [gle] CopyMetaFileA( + [in] HMETAFILE hmfSrc, // handle to Windows-format metafile + [in] LPCSTR lpszFile // file name +); +HMETAFILE [gle] CopyMetaFileW( + [in] HMETAFILE hmfSrc, // handle to Windows-format metafile + [in] LPCWSTR lpszFile // file name +); +HBITMAP [gle] CreateBitmap( + [in] int nWidth, // bitmap width, in pixels + [in] int nHeight, // bitmap height, in pixels + [in] UINT cPlanes, // number of color planes + [in] UINT cBitsPerPel, // number of bits to identify color + [in] VOID *lpvBits // color data array +); +HBITMAP [gle] CreateBitmapIndirect( + [in] BITMAP *lpbm // bitmap data +); +HBRUSH [gle] CreateBrushIndirect( + [in] LOGBRUSH *lplb // brush information +); +HBITMAP [gle] CreateCompatibleBitmap( + [in] HDC hdc, // handle to DC + [in] int nWidth, // width of bitmap, in pixels + [in] int nHeight // height of bitmap, in pixels +); +HBITMAP [gle] CreateDiscardableBitmap( + [in] HDC hdc, // handle to DC + [in] int nWidth, // bitmap width + [in] int nHeight // bitmap height +); +HDC [gle] CreateCompatibleDC( + [in] HDC hdc // handle to DC +); +HDC [gle] CreateDCA( + [in] LPCSTR lpszDriver, // driver name + [in] LPCSTR lpszDevice, // device name + [in] LPCSTR lpszOutput, // not used; should be NULL + [in] DEVMODEA *lpInitData // optional prIN inter data +); +HDC [gle] CreateDCW( + [in] LPCWSTR lpszDriver, // driver name + [in] LPCWSTR lpszDevice, // device name + [in] LPCWSTR lpszOutput, // not used; should be NULL + [in] DEVMODEW *lpInitData // optional prIN inter data +); +HBITMAP [gle] CreateDIBitmap( + [in] HDC hdc, // handle to DC + [in] BITMAPV5HEADER *lpbmih, // bitmap data + [in] _CreateDIBitmap fdwInit, // initialization option + [in] VOID *lpbInit, // initialization data + [in] BITMAPINFO *lpbmi, // color-format data + [in] _DIB_Color fuUsage // color-data usage +); + +HBRUSH [gle] CreateDIBPatternBrush( + [in] HGLOBAL hglbDIBPacked, // handle to DIB + [in] _DIB_Color fuColorSpec // color table data +); + +HBRUSH [gle] CreateDIBPatternBrushPt( + [in] VOID *lpPackedDIB, // bitmap bits + [in] _DIB_Color iUsage // usage +); +HRGN [gle] CreateEllipticRgn( + [in] int nLeftRect, // x-coord of upper-left corner of rectangle + [in] int nTopRect, // y-coord of upper-left corner of rectangle + [in] int nRightRect, // x-coord of lower-right corner of rectangle + [in] int nBottomRect // y-coord of lower-right corner of rectangle +); +HRGN [gle] CreateEllipticRgnIndirect( + [in] RECT *lprc // bounding rectangle +); +HFONT [gle] CreateFontIndirectA( + [in] LOGFONTA *lplf +); +HFONT [gle] CreateFontIndirectW( + [in] LOGFONTW *lplf +); + +HFONT [gle] CreateFontA( + [in] int nHeight, // height of font + [in] int nWidth, // average character width + [in] int nEscapement, // angle of escapement + [in] int nOrientation, // base-line orientation angle + [in] _FW fnWeight, // font weight + [in] DWORD fdwItalic, // italic attribute option + [in] DWORD fdwUnderline, // underline attribute option + [in] DWORD fdwStrikeOut, // strikeout attribute option + [in] _CHARSET fdwCharSet, // character set identifier + [in] _OUT fdwOutputPrecision, // output precision + [in] _CLIP fdwClipPrecision, // clipping precision + [in] _OUT fdwQuality, // output quality + [in] _FF fdwPitchAndFamily, // pitch and family + [in] LPCSTR lpszFace // typeface name +); + +HFONT [gle] CreateFontW( + [in] int nHeight, // height of font + [in] int nWidth, // average character width + [in] int nEscapement, // angle of escapement + [in] int nOrientation, // base-line orientation angle + [in] _FW fnWeight, // font weight + [in] DWORD fdwItalic, // italic attribute option + [in] DWORD fdwUnderline, // underline attribute option + [in] DWORD fdwStrikeOut, // strikeout attribute option + [in] _CHARSET fdwCharSet, // character set identifier + [in] _OUT fdwOutputPrecision, // output precision + [in] _CLIP fdwClipPrecision, // clipping precision + [in] _OUT fdwQuality, // output quality + [in] _FF fdwPitchAndFamily, // pitch and family + [in] LPCWSTR lpszFace // typeface name +); + +HBRUSH [gle] CreateHatchBrush( + [in] int fnStyle, // hatch style + [in] COLORREF clrref // foreground color +); +HDC [gle] CreateICA( + [in] LPCSTR lpszDriver, // driver name + [in] LPCSTR lpszDevice, // device name + [in] LPCSTR lpszOutput, // port or file name + [in] DEVMODEA *lpdvmInit // optional initialization data +); +HDC [gle] CreateICW( + [in] LPCWSTR lpszDriver, // driver name + [in] LPCWSTR lpszDevice, // device name + [in] LPCWSTR lpszOutput, // port or file name + [in] DEVMODEW *lpdvmInit // optional initialization data +); +HDC [gle] CreateMetaFileA( + [in] LPCSTR lpszFile +); +HDC [gle] CreateMetaFileW( + [in] LPCWSTR lpszFile +); + +HPALETTE [gle] CreatePalette( + [in] LOGPALETTE * lplgpl +); +HPEN [gle] CreatePen( + [in] _PS fnPenStyle, // pen style + [in] int nWidth, // pen width + [in] COLORREF crColor // pen color +); +HPEN [gle] CreatePenIndirect( + [in] LOGPEN *lplgpn +); +HRGN CreatePolyPolygonRgn( + [in] POINT *lppt, // poIN inter to array of poIN ints + [in] INT *lpPolyCounts, // poIN inter to count of vertices + [in] int nCount, // number of [in] integers in array + [in] _PolyFill fnPolyFillMode // polygon fill mode +); +HBRUSH [gle] CreatePatternBrush( + [in] HBITMAP hbmp // handle to bitmap +); +HRGN CreateRectRgn( + [in] int nLeftRect, // x-coordinate of upper-left corner + [in] int nTopRect, // y-coordinate of upper-left corner + [in] int nRightRect, // x-coordinate of lower-right corner + [in] int nBottomRect // y-coordinate of lower-right corner +); +HRGN [gle] CreateRectRgnIndirect( + [in] RECT *lprc // rectangle +); +HRGN [gle] CreateRoundRectRgn( + [in] int nLeftRect, // x-coordinate of upper-left corner + [in] int nTopRect, // y-coordinate of upper-left corner + [in] int nRightRect, // x-coordinate of lower-right corner + [in] int nBottomRect, // y-coordinate of lower-right corner + [in] int nWidthEllipse, // height of ellipse + [in] int nHeightEllipse // width of ellipse +); +FailOnFalse [gle] CreateScalableFontResourceA( + DWORD fdwHidden, // read-only option + [in] LPCSTR lpszFontRes, // font file name + [in] LPCSTR lpszFontFile, // scalable font file name + [in] LPCSTR lpszCurrentPath // scalable font file path +); +FailOnFalse [gle] CreateScalableFontResourceW( + DWORD fdwHidden, // read-only option + [in] LPCWSTR lpszFontRes, // font file name + [in] LPCWSTR lpszFontFile, // scalable font file name + [in] LPCWSTR lpszCurrentPath // scalable font file path +); +HBRUSH [gle] CreateSolidBrush( + [in] COLORREF crColor // brush color value +); + +FailOnFalse [gle] DeleteDC( + [in] HDC hdc +); +FailOnFalse [gle] DeleteMetaFile( + [in] HMETAFILE hmf +); +FailOnFalse [gle] DeleteObject( + [in] HGDIOBJ hobj +); + +IntFailIfZero [gle] DescribePixelFormat( + [in] HDC hdc, // device context of interest + [in] int iPixelFormat, // pixel format selector + [in] UINT nBytes, // size of buffer pointed to by ppfd + [out] LPPIXELFORMATDESCRIPTOR ppfd + // pointer to structure to receive pixel + // format data +); + +SpoolerError [gle] DrawEscape( + [in] HDC hdc, // handle to DC + [in] int nEscape, // escape function + [in] int cbInput, // size of structure for input + [in] LPCSTR lpszInData // structure for input +); +FailOnFalse [gle] Ellipse( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner of rectangle + [in] int nTopRect, // y-coord of upper-left corner of rectangle + [in] int nRightRect, // x-coord of lower-right corner of rectangle + [in] int nBottomRect // y-coord of lower-right corner of rectangle +); + +int EnumFontFamiliesExA( + [in] HDC hdc, // handle to DC + [in] LPLOGFONTA lpLogfont, // font information + [in] FONTENUMPROCA lpEnumFontFamExProc, // callback function + [in] LPARAM lParam, // additional data + [in] DWORD dwFlags // not used; must be 0 +); +int EnumFontFamiliesExW( + [in] HDC hdc, // handle to DC + [in] LPLOGFONTW lpLogfont, // font information + [in] FONTENUMPROCW lpEnumFontFamExProc, // callback function + [in] LPARAM lParam, // additional data + [in] DWORD dwFlags // not used; must be 0 +); +int EnumFontFamiliesA( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpszFamily, // font family + [in] FONTENUMPROCA lpEnumFontFamProc, // callback function + [in] LPARAM lParam // additional data +); +int EnumFontFamiliesW( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpszFamily, // font family + [in] FONTENUMPROCW lpEnumFontFamProc, // callback function + [in] LPARAM lParam // additional data +); + +int EnumFontsA( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpFaceName, // font typeface name + [in] FONTENUMPROCA lpFontFunc, // callback function + [in] LPARAM lParam // application-supplied data +); +int EnumFontsW( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpFaceName, // font typeface name + [in] FONTENUMPROCW lpFontFunc, // callback function + [in] LPARAM lParam // application-supplied data +); + +int EnumObjects( + [in] HDC hdc, // handle to DC + [in] int nObjectType, // object-type identifier + [in] GOBJENUMPROC lpObjectFunc, // callback function + [in] LPARAM lParam // application-supplied data +); + + +FailOnFalse EqualRgn( + [in] HRGN hSrcRgn1, // handle to first region + [in] HRGN hSrcRgn2 // handle to second region +); +_RegionFlags ExcludeClipRect( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner + [in] int nTopRect, // y-coord of upper-left corner + [in] int nRightRect, // x-coord of lower-right corner + [in] int nBottomRect // y-coord of lower-right corner +); +HRGN [gle] ExtCreateRegion( + [in] XFORM *lpXform, // transformation data + [in] DWORD nCount, // size of region data + [in] RGNDATA *lpRgnData // region data buffer +); + +FailOnFalse [gle] ExtFloodFill( + [in] HDC hdc, // handle to DC + [in] int nXStart, // starting x-coordinate + [in] int nYStart, // starting y-coordinate + [in] COLORREF crColor, // fill color + [in] _FLOODFILL fuFillType // fill type +); +FailOnFalse [gle] FillRgn( + [in] HDC hdc, // handle to device context + [in] HRGN hrgn, // handle to region to be filled + [in] HBRUSH hbr // handle to brush used to fill the region +); + +FailOnFalse [gle] FloodFill( + [in] HDC hdc, // handle to DC + [in] int nXStart, // starting x-coordinate + [in] int nYStart, // starting y-coordinate + [in] COLORREF crFill // fill color +); +FailOnFalse [gle] FrameRgn( + [in] HDC hdc, // handle to device context + [in] HRGN hrgn, // handle to region to be framed + [in] HBRUSH hbr, // handle to brush used to draw border + [in] int nWidth, // width of region frame + [in] int nHeight // height of region frame +); +_BinaryDrawMode [gle] GetROP2( + [in] HDC hdc // handle to device context +); +FailOnFalse [gle] GetAspectRatioFilterEx( + [in] HDC hdc, // handle to DC + [out] LPSIZE lpAspectRatio // aspect-ratio filter +); +COLORREF_RETURN GetBkColor( + HDC hdc +); + +COLORREF_RETURN GetDCBrushColor( + HDC hdc +); +COLORREF_RETURN GetDCPenColor( + HDC hdc +); + +IntFailIfZero GetBkMode( + [in] HDC hdc +); +LONG [gle] GetBitmapBits( + [in] HBITMAP hbmp, // handle to bitmap + [in] LONG cbBuffer, // number of bytes to copy + [out] LPVOID lpvBits // buffer to receive bits +); + +FailOnFalse [gle] GetBitmapDimensionEx( + [in] HBITMAP hBitmap, // handle to bitmap + [out] LPSIZE lpDimension // dimensions +); + +_DCB GetBoundsRect( + [in] HDC hdc, // handle to device context + [out] LPRECT lprcBounds, // bounding rectangle + [in] _DCB flags // function options +); + +FailOnFalse [gle] GetBrushOrgEx( + [in] HDC hdc, // handle to DC + [out] LPPOINT lppt // coordinates of origin +); + +FailOnFalse [gle] GetCharWidthA( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first character in range + [in] UINT iLastChar, // last character in range + [in] LPINT lpBuffer // buffer for widths +); +FailOnFalse [gle] GetCharWidthW( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first character in range + [in] UINT iLastChar, // last character in range + [out] LPINT lpBuffer // buffer for widths +); + +FailOnFalse [gle] GetCharWidth32A( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first character in range + [in] UINT iLastChar, // last character in range + [out] LPINT lpBuffer // buffer for widths +); +FailOnFalse [gle] GetCharWidth32W( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first character in range + [in] UINT iLastChar, // last character in range + [out] LPINT lpBuffer // buffer for widths +); +FailOnFalse [gle] GetCharWidthFloatA( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first-character code point + [in] UINT iLastChar, // last-character code point + [out] PFLOAT pxBuffer // buffer for widths +); +FailOnFalse [gle] GetCharWidthFloatW( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first-character code point + [in] UINT iLastChar, // last-character code point + [out] PFLOAT pxBuffer // buffer for widths +); +FailOnFalse [gle] GetCharABCWidthsA( + [in] HDC hdc, // handle to DC + [in] UINT uFirstChar, // first character in range + [in] UINT uLastChar, // last character in range + [out] LPABC lpabc // array of character widths +); +FailOnFalse [gle] GetCharABCWidthsW( + [in] HDC hdc, // handle to DC + [in] UINT uFirstChar, // first character in range + [in] UINT uLastChar, // last character in range + [out] LPABC lpabc // array of character widths +); +FailOnFalse [gle] GetCharABCWidthsFloatA( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first character in range + [in] UINT iLastChar, // last character in range + [out] LPABCFLOAT lpABCF // array of character widths +); +FailOnFalse [gle] GetCharABCWidthsFloatW( + [in] HDC hdc, // handle to DC + [in] UINT iFirstChar, // first character in range + [in] UINT iLastChar, // last character in range + [out] LPABCFLOAT lpABCF // array of character widths +); +_RegionFlags [gle] GetClipBox( + [in] HDC hdc, // handle to DC + [out] LPRECT lprc // rectangle +); +IntFailIfNeg1 [gle] GetClipRgn( + [in] HDC hdc, // handle to DC + [in] HRGN hrgn // handle to region +); +IntFailIfZero [gle] GetMetaRgn( + [in] HDC hdc, // handle to DC + [in] HRGN hrgn // handle to region +); +HGDIOBJ [gle] GetCurrentObject( + [in] HDC hdc, // handle to DC + [in] _OBJ uObjectType // object type +); +FailOnFalse [gle] GetCurrentPositionEx( + [in] HDC hdc, // handle to device context + [out] LPPOINT lpPoint // current position +); +int GetDeviceCaps( + [in] HDC hdc, // handle to DC + [in] _DeviceParameters nIndex // index of capability +); + +IntFailIfZero GetDIBits( + [in] HDC hdc, // handle to DC + [in] HBITMAP hbmp, // handle to bitmap + [in] UINT uStartScan, // first scan line to set + [in] UINT cScanLines, // number of scan lines to copy + [out] LPVOID lpvBits, // array for bitmap bits + LPBITMAPINFO lpbi, // bitmap data buffer + [in] _DIB_Color uUsage // RGB or palette index +); +_GDI_ERROR [gle] GetFontData( + [in] HDC hdc, // handle to DC + [in] DWORD dwTable, // metric table name + [in] DWORD dwOffset, // offset into table + [in] LPVOID lpvBuffer, // buffer for returned data + [in] DWORD cbData // length of data +); + +DWORD [gle] GetGlyphOutlineA( + [in] HDC hdc, // handle to DC + [in] UINT uChar, // character to query + [in] _GGO uFormat, // data format + [out] LPGLYPHMETRICS lpgm, // glyph metrics + [in] DWORD cbBuffer, // size of data buffer + [out] LPVOID lpvBuffer, // data buffer + [in] MAT2 *lpmat2 // transformation matrix +); +DWORD [gle] GetGlyphOutlineW( + [in] HDC hdc, // handle to DC + [in] UINT uChar, // character to query + [in] _GGO uFormat, // data format + [out] LPGLYPHMETRICS lpgm, // glyph metrics + [in] DWORD cbBuffer, // size of data buffer + [out] LPVOID lpvBuffer, // data buffer + [in] MAT2 *lpmat2 // transformation matrix +); + +_GM [gle] GetGraphicsMode( + [in] HDC hdc +); +_MM [gle] GetMapMode( + [in] HDC hdc // handle to device context +); + +UintFailIfZero [gle] GetMetaFileBitsEx( + [in] HMETAFILE hmf, + [in] UINT nSize, + [out] LPVOID lpvData +); + +HMETAFILE GetMetaFileA( + [in] LPCSTR lpszMetaFile +); +HMETAFILE GetMetaFileW( + [in] LPCWSTR lpszMetaFile +); +COLORREF_RETURN [gle] GetNearestColor( + [in] HDC hdc, // handle to DC + [in] COLORREF crColor // color to be matched +); + +COLORREF_RETURN [gle] GetNearestPaletteIndex( + [in] HPALETTE hpal, // handle to logical palette + [in] COLORREF crColor // color to be matched +); + +_OBJ [gle] GetObjectType( + [in] HGDIOBJ h // handle to graphics object +); + +UintFailIfZero [gle] GetOutlineTextMetricsA( + [in] HDC hdc, // handle to DC + [in] UINT cbData, // size of metric data array + [out] LPOUTLINETEXTMETRICA lpOTM // metric data array +); +UintFailIfZero [gle] GetOutlineTextMetricsW( + [in] HDC hdc, // handle to DC + [in] UINT cbData, // size of metric data array + [out] LPOUTLINETEXTMETRICW lpOTM // metric data array +); + +UintFailIfZero [gle] GetPaletteEntries( + [in] HPALETTE hpal, // handle to logical palette + [in] UINT iStartIndex, // first entry to retrieve + [in] UINT nEntries, // number of entries to retrieve + [out] LPPALETTEENTRY lppe // array that receives entries +); +COLORREF_RETURN GetPixel( + [in] HDC hdc, // handle to DC + [in] int nXPos, // x-coordinate of pixel + [in] int nYPos // y-coordinate of pixel +); + +IntFailIfZero [gle] GetPixelFormat( + HDC hdc +); + +_PolyFill [gle] GetPolyFillMode( + [in] HDC hdc // handle to device context +); + +FailOnFalse [gle] GetRasterizerCaps( + [out] LPRASTERIZER_STATUS lprs , + [in] UINT cb +); +IntFailIfNeg1 GetRandomRgn ( + [in] HDC hdc , + [in] HRGN hrgn, + [in] INT iNum +); + +IntFailIfZero GetRegionData( + [in] HRGN hRgn, // handle to region + [in] DWORD dwCount, // size of region data buffer + [out] LPRGNDATA lpRgnData // region data buffer +); +_RegionFlags GetRgnBox( + [in] HRGN hrgn, // handle to a region + [out] LPRECT lprc // bounding rectangle +); +HGDIOBJ [gle] GetStockObject( + [in] _StockObject fnObject // stock object type +); +IntFailIfZero [gle] GetStretchBltMode( + [in] HDC hdc // handle to DC +); + +UINT [gle] GetSystemPaletteEntries( + [in] HDC hdc, // handle to DC + [in] UINT iStartIndex, // first entry to be retrieved + [in] UINT nEntries, // number of entries to be retrieved + [out] LPPALETTEENTRY lppe // array that receives entries +); + +_SYSPAL [gle] GetSystemPaletteUse( + [in] HDC hdc // handle to DC +); + +_ODD_FAILURE [gle] GetTextCharacterExtra( + [in] HDC hdc // handle to DC +); +_TextAlignmentOptions [gle] GetTextAlign( + [in] HDC hdc // handle to DC +); +COLORREF_RETURN GetTextColor( + HDC hdc +); + +FailOnFalse [gle] GetTextExtentPointA( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpString, // text string + [in] int cbString, // number of characters in string + [out] LPSIZE lpSize // string size +); +FailOnFalse [gle] GetTextExtentPointW( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpString, // text string + [in] int cbString, // number of characters in string + [out] LPSIZE lpSize // string size +); + +FailOnFalse [gle] GetTextExtentPoint32A( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpString, // text string + [in] int cbString, // characters in string + [out] LPSIZE lpSize // string size +); +FailOnFalse [gle] GetTextExtentPoint32W( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpString, // text string + [in] int cbString, // characters in string + [out] LPSIZE lpSize // string size +); + +FailOnFalse [gle] GetTextExtentExPointA( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpszStr, // character string + [in] int cchString, // number of characters + [in] int nMaxExtent, // maximum width of formatted string + [out] LPINT lpnFit, // maximum number of characters + [out] LPINT alpDx, // array of partial string widths + [out] LPSIZE lpSize // string dimensions +); +FailOnFalse [gle] GetTextExtentExPointW( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpszStr, // character string + [in] int cchString, // number of characters + [in] int nMaxExtent, // maximum width of formatted string + [out] LPINT lpnFit, // maximum number of characters + [out] LPINT alpDx, // array of partial string widths + [out] LPSIZE lpSize // string dimensions +); +_CHARSET GetTextCharset( + [in] HDC hdc // handle to DC +); + +_CHARSET GetTextCharsetInfo( + [in] HDC hdc, // handle to DC + [out] LPFONTSIGNATURE lpSig, // data buffer + [in] DWORD dwFlags // reserved; must be zero +); +FailOnFalse [gle] TranslateCharsetInfo( + [out] DWORD *lpSrc, // information + [out] LPCHARSETINFO lpCs, // character set information + [in] _TCI_SRC dwFlags // translation option +); + +_GCP [gle] GetFontLanguageInfo( + [in] HDC hdc // handle to DC +); +IntFailIfZero [gle] GetCharacterPlacementA( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpString, // character string + [in] int nCount, // number of characters + [in] int nMaxExtent, // maximum extent for string + LPGCP_RESULTSA lpResults, // placement result + [in] _GCP dwFlags // placement options +); +IntFailIfZero [gle] GetCharacterPlacementW( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpString, // character string + [in] int nCount, // number of characters + [in] int nMaxExtent, // maximum extent for string + LPGCP_RESULTSW lpResults, // placement result + [in] _GCP dwFlags // placement options +); + + +IntFailIfZero GetFontUnicodeRanges( + [in] HDC hdc, // handle to DC + [out] LPGLYPHSET lpgs // glyph set +); +DwordFailIfZero [gle] GetGlyphIndicesA( + [in] HDC hdc, // handle to DC + [in] LPCSTR lpstr, // string to convert + [in] int c, // number of characters in string + [out] LPWORD pgi, // array of glyph indices + [in] _GGI_MARK_NONEXISTING_GLYPHS fl // glyph options +); +DwordFailIfZero [gle] GetGlyphIndicesW( + [in] HDC hdc, // handle to DC + [in] LPCWSTR lpstr, // string to convert + [in] int c, // number of characters in string + [out] LPWORD pgi, // array of glyph indices + [in] _GGI_MARK_NONEXISTING_GLYPHS fl // glyph options +); +FailOnFalse [gle] GetTextExtentPointI( + [in] HDC hdc, // handle to DC + [in] LPWORD pgiIn, // glyph indices + [in] int cgi, // number of indices in array + [out] LPSIZE lpSize // string size +); +FailOnFalse [gle] GetTextExtentExPointI( + [in] HDC hdc, // handle to DC + [in] LPWORD pgiIn, // array of glyph indices + [in] int cgi, // number of glyphs in array + [in] int nMaxExtent, // maximum width of formatted string + [out] LPINT lpnFit, // maximum number of characters + [out] LPINT alpDx, // array of partial string widths + [out] LPSIZE lpSize // string dimensions +); +FailOnFalse [gle] GetCharWidthI( + [in] HDC hdc, // handle to DC + [in] UINT giFirst, // first glyph index in range + [in] UINT cgi, // number of glyph indicies in range + [in] LPWORD pgi, // array of glyph indices + [out] LPINT lpBuffer // buffer for widths +); +FailOnFalse [gle] GetCharABCWidthsI( + [in] HDC hdc, // handle to DC + [in] UINT giFirst, // first glyph index in range + [in] UINT cgi, // count of glyph indices in range + [in] LPWORD pgi, // array of glyph indices + [out] LPABC lpabc // array of character widths +); + + +IntFailIfZero AddFontResourceExA( + [in] LPCSTR lpszFilename, // font file name + [in] _FR fl, // font characteristics + [in] DESIGNVECTOR * pdv // reserved +); + +IntFailIfZero AddFontResourceExW( + [in] LPCWSTR lpszFilename, // font file name + [in] _FR fl, // font characteristics + [in] DESIGNVECTOR * pdv // reserved +); + +FailOnFalse RemoveFontResourceExA( + [in] LPCSTR lpFileName, // name of font file + [in] _FR fl, // font characteristics + [in] DESIGNVECTOR * pdv // reserved +); +FailOnFalse RemoveFontResourceExW( + [in] LPCWSTR lpFileName, // name of font file + [in] _FR fl, // font characteristics + [in] DESIGNVECTOR * pdv // reserved +); +HANDLE AddFontMemResourceEx( + [in] PVOID pbFont, // font resource + [in] DWORD cbFont, // number of bytes in font resource + [in] PVOID pdv, // Reserved. Must be 0. + [in] DWORD *pcFonts // number of fonts installed +); + +FailOnFalse RemoveFontMemResourceEx( + [in] HANDLE fh // handle to the font resource +); + + +HFONT CreateFontIndirectExA( + [in] ENUMLOGFONTEXDVA *penumlfex // characteristiccs +); + +HFONT CreateFontIndirectExW( + [in] ENUMLOGFONTEXDVW *penumlfex // characteristiccs +); + + +FailOnFalse [gle] GetViewportExtEx( + [in] HDC hdc, // handle to device context + [out] LPSIZE lpSize // viewport dimensions +); +FailOnFalse [gle] GetViewportOrgEx( + [in] HDC hdc, // handle to device context + [out] LPPOINT lpPoint // viewport origin +); +FailOnFalse [gle] GetWindowExtEx( + [in] HDC hdc, // handle to device context + [out] LPSIZE lpSize // window extents +); +FailOnFalse [gle] GetWindowOrgEx( + [in] HDC hdc, // handle to device context + [out] LPPOINT lpPoint // window origin +); +_RegionFlags IntersectClipRect( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner + [in] int nTopRect, // y-coord of upper-left corner + [in] int nRightRect, // x-coord of lower-right corner + [in] int nBottomRect // y-coord of lower-right corner +); +FailOnFalse [gle] InvertRgn( + [in] HDC hdc, // handle to device context + [in] HRGN hrgn // handle to region to be inverted +); + +FailOnFalse [gle] LineDDA( + [in] int nXStart, // x-coordinate of starting point + [in] int nYStart, // y-coordinate of starting point + [in] int nXEnd, // x-coordinate of ending point + [in] int nYEnd, // y-coordinate of ending point + [in] LINEDDAPROC lpLineFunc, // callback function + [in] LPARAM lpData // application-defined data +); +FailOnFalse [gle] LineTo( + [in] HDC hdc, // device context handle + [in] int nXEnd, // x-coordinate of ending point + [in] int nYEnd // y-coordinate of ending point +); +FailOnFalse [gle] MaskBlt( + [in] HDC hdcDest, // handle to destination DC + [in] int nXDest, // x-coord of destination upper-left corner + [in] int nYDest, // y-coord of destination upper-left corner + [in] int nWidth, // width of source and destination + [in] int nHeight, // height of source and destination + [in] HDC hdcSrc, // handle to source DC + [in] int nXSrc, // x-coord of upper-left corner of source + [in] int nYSrc, // y-coord of upper-left corner of source + [in] HBITMAP hbmMask, // handle to monochrome bit mask + [in] int xMask, // horizontal offset into mask bitmap + [in] int yMask, // vertical offset into mask bitmap + [in] _TernaryDrawMode dwRop // raster operation code +); +FailOnFalse [gle] PlgBlt( + [in] HDC hdcDest, // handle to destination DC + [in] POINT *lpPoint, // destination vertices + [in] HDC hdcSrc, // handle to source DC + [in] int nXSrc, // x-coord of source upper-left corner + [in] int nYSrc, // y-coord of source upper-left corner + [in] int nWidth, // width of source rectangle + [in] int nHeight, // height of source rectangle + [in] HBITMAP hbmMask, // handle to bitmask + [in] int xMask, // x-coord of bitmask upper-left corner + [in] int yMask // y-coord of bitmask upper-left corner +); +_RegionFlags OffsetClipRgn( + [in] HDC hdc, // handle to DC + [in] int nXOffset, // offset along x-axis + [in] int nYOffset // offset along y-axis +); +_RegionFlags OffsetRgn( + [in] HRGN hrgn, // handle to region + [in] int nXOffset, // offset along x-axis + [in] int nYOffset // offset along y-axis +); +FailOnFalse [gle] PatBlt( + [in] HDC hdc, // handle to DC + [in] int nXLeft, // x-coord of upper-left rectangle corner + [in] int nYLeft, // y-coord of upper-left rectangle corner + [in] int nWidth, // width of rectangle + [in] int nHeight, // height of rectangle + [in] _TernaryDrawMode dwRop // raster operation code +); +FailOnFalse [gle] Pie( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner of rectangle + [in] int nTopRect, // y-coord of upper-left corner of rectangle + [in] int nRightRect, // x-coord of lower-right corner of rectangle + [in] int nBottomRect, // y-coord of lower-right corner of rectangle + [in] int nXRadial1, // x-coord of first radial's endpoint + [in] int nYRadial1, // y-coord of first radial's endpoint + [in] int nXRadial2, // x-coord of second radial's endpoint + [in] int nYRadial2 // y-coord of second radial's endpoint +); +FailOnFalse [gle] PlayMetaFile( + [in] HDC hdc, // handle to DC + [in] HMETAFILE hmf // handle to metafile +); +FailOnFalse PaintRgn( + [in] HDC hdc, // handle to device context + [in] HRGN hrgn // handle to region to be painted +); + + +FailOnFalse [gle] PolyPolygon( + [in] HDC hdc, // handle to DC + [in] POINT *lpPoints, // array of vertices + [in] INT *lpPolyCounts, // array of count of vertices + [in] int nCount // count of polygons +); +BOOL PtInRegion( + [in] HRGN hrgn, // handle to region + [in] int X, // x-coordinate of point + [in] int Y // y-coordinate of point +); +BOOL PtVisible( + [in] HDC hdc, // handle to DC + [in] int X, // x-coordinate of point + [in] int Y // y-coordinate of point +); + +BOOL RectInRegion( + [in] HRGN hrgn, // handle to region + [in] RECT *lprc // pointer to rectangle +); +BOOL RectVisible( + [in] HDC hdc, // handle to DC + [in] RECT *lprc // rectangle +); +FailOnFalse [gle] Rectangle( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner of rectangle + [in] int nTopRect, // y-coord of upper-left corner of rectangle + [in] int nRightRect, // x-coord of lower-right corner of rectangle + [in] int nBottomRect // y-coord of lower-right corner of rectangle +); + +FailOnFalse [gle] RestoreDC( + [in] HDC hdc, // handle to DC + [in] int nSavedDC // restore state +); +HDC [gle] ResetDCA( + [in] HDC hdc, + [in] DEVMODEA *lpInitData +); +HDC [gle] ResetDCW( + [in] HDC hdc, + [in] DEVMODEW *lpInitData +); + +_GDI_ERROR [gle] RealizePalette( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] RemoveFontResourceA( + [in] LPCSTR lpFileName +); +FailOnFalse [gle] RemoveFontResourceW( + [in] LPCWSTR lpFileName +); +FailOnFalse [gle] RoundRect( + [in] HDC hdc, // handle to DC + [in] int nLeftRect, // x-coord of upper-left corner of rectangle + [in] int nTopRect, // y-coord of upper-left corner of rectangle + [in] int nRightRect, // x-coord of lower-right corner of rectangle + [in] int nBottomRect, // y-coord of lower-right corner of rectangle + [in] int nWidth, // width of ellipse + [in] int nHeight // height of ellipse +); + +FailOnFalse [gle] ResizePalette( + [in] HPALETTE hpal, // handle to logical palette + [in] UINT nEntries // number of entries in logical palette +); + +IntFailIfZero [gle] SaveDC( + [in] HDC hdc // handle to DC +); +_RegionFlags [gle] SelectClipRgn( + [in] HDC hdc, // handle to DC + [in] HRGN hrgn // handle to region +); +_RegionFlags [gle] ExtSelectClipRgn( + [in] HDC hdc, // handle to DC + [in] HRGN hrgn, // handle to region + [in] _CombineRgn fnMode // region-selection mode +); +_RegionFlags SetMetaRgn( + HDC hdc +); + +HGDIOBJ SelectObject( + [in] HDC hdc, // handle to DC + [in] HGDIOBJ hgdiobj // handle to object +); + +HPALETTE [gle] SelectPalette( + [in] HDC hdc, // handle to DC + [in] HPALETTE hpal, // handle to logical palette + [in] BOOL bForceBackground // foreground or background mode +); +COLORREF_RETURN [gle] SetBkColor( + [in] HDC hdc, // handle to DC + [in] COLORREF crColor // background color value +); + +COLORREF_RETURN SetDCBrushColor( + [in] HDC hdc, // handle to DC + [in] COLORREF crColor // new brush color +); +COLORREF_RETURN SetDCPenColor( + [in] HDC hdc, // handle to DC + [in] COLORREF crColor // new pen color +); + +IntFailIfZero [gle] SetBkMode( + [in] HDC hdc, // handle to DC + [in] int iBkMode // background mode +); +LongFailIfZero [gle] SetBitmapBits( + [in] HBITMAP hbmp, // handle to bitmap + [in] DWORD cBytes, // number of bytes in bitmap array + [in] VOID *lpBits // array with bitmap bits +); +_DCB [gle] SetBoundsRect( + [in] HDC hdc, // handle to DC + [in] RECT *lprcBounds, // bounding rectangle + [in] _DCB flags // rectangle combination option +); + +IntFailIfZero [gle] SetDIBits( + [in] HDC hdc, // handle to DC + [in] HBITMAP hbmp, // handle to bitmap + [in] UINT uStartScan, // starting scan line + [in] UINT cScanLines, // number of scan lines + [in] VOID *lpvBits, // array of bitmap bits + [in] BITMAPINFO *lpbmi, // bitmap data + [in] _DIB_Color fuColorUse // type of color indexes to use +); + +IntFailIfZero [gle] SetDIBitsToDevice( + [in] HDC hdc, // handle to DC + [in] int XDest, // x-coord of destination upper-left corner + [in] int YDest, // y-coord of destination upper-left corner + [in] DWORD dwWidth, // source rectangle width + [in] DWORD dwHeight, // source rectangle height + [in] int XSrc, // x-coord of source lower-left corner + [in] int YSrc, // y-coord of source lower-left corner + [in] UINT uStartScan, // first scan line in array + [in] UINT cScanLines, // number of scan lines + [in] VOID *lpvBits, // array of DIB bits + [in] BITMAPINFO *lpbmi, // bitmap information + [in] _DIB_Color fuColorUse // RGB or palette indexes +); + +_GDI_ERROR [gle] SetMapperFlags( + [in] HDC hdc, // handle to DC + [in] DWORD dwFlag // font-mapper option +); +IntFailIfZero [gle] SetGraphicsMode( + [in] HDC hdc, // handle to device context + [in] _GM iMode // graphics mode +); + +IntFailIfZero [gle] SetMapMode( + [in] HDC hdc, // handle to device context + [in] _MM fnMapMode // new mapping mode +); + +_GDI_ERROR [gle] SetLayout( + [in] HDC hdc, + [in] _LAYOUT dwLayout +); +_GDI_ERROR [gle] GetLayout( + [in] HDC hdc +); + +HMETAFILE [gle] SetMetaFileBitsEx( + [in] UINT nSize, // size of Windows-format metafile + [in] BYTE *lpData // metafile data +); + +UintFailIfZero [gle] SetPaletteEntries( + [in] HPALETTE hpal, // handle to logical palette + [in] UINT iStart, // index of first entry to set + [in] UINT cEntries, // number of entries to set + [in] PALETTEENTRY *lppe // array of palette entries +); + +COLORREF_RETURN [gle] SetPixel( + [in] HDC hdc, // handle to DC + [in] int X, // x-coordinate of pixel + [in] int Y, // y-coordinate of pixel + [in] COLORREF crColor // pixel color +); + +FailOnFalse [gle] SetPixelV( + [in] HDC hdc, // handle to device context + [in] int X, // x-coordinate of pixel + [in] int Y, // y-coordinate of pixel + [in] COLORREF crColor // new pixel color +); + +FailOnFalse [gle] SetPixelFormat( + [in] HDC hdc, // device context whose pixel format the function + // attempts to set + [in] int iPixelFormat, + // pixel format index (one-based) + [in] PIXELFORMATDESCRIPTOR * ppfd + // pointer to logical pixel format specification +); + +FailOnFalse [gle] SetPolyFillMode( + [in] HDC hdc, // handle to device context + [in] _PolyFill iPolyFillMode // polygon fill mode +); +FailOnFalse [gle] StretchBlt( + [in] HDC hdcDest, // handle to destination DC + [in] int nXOriginDest, // x-coord of destination upper-left corner + [in] int nYOriginDest, // y-coord of destination upper-left corner + [in] int nWidthDest, // width of destination rectangle + [in] int nHeightDest, // height of destination rectangle + [in] HDC hdcSrc, // handle to source DC + [in] int nXOriginSrc, // x-coord of source upper-left corner + [in] int nYOriginSrc, // y-coord of source upper-left corner + [in] int nWidthSrc, // width of source rectangle + [in] int nHeightSrc, // height of source rectangle + [in] _TernaryDrawMode dwRop // raster operation code +); + + +FailOnFalse [gle] SetRectRgn( + [in] HRGN hrgn, // handle to region + [in] int nLeftRect, // x-coordinate of upper-left corner of rectangle + [in] int nTopRect, // y-coordinate of upper-left corner of rectangle + [in] int nRightRect, // x-coordinate of lower-right corner of rectangle + [in] int nBottomRect // y-coordinate of lower-right corner of rectangle +); + +_GDI_ERROR [gle] StretchDIBits( + [in] HDC hdc, // handle to DC + [in] int XDest, // x-coord of destination upper-left corner + [in] int YDest, // y-coord of destination upper-left corner + [in] int nDestWidth, // width of destination rectangle + [in] int nDestHeight, // height of destination rectangle + [in] int XSrc, // x-coord of source upper-left corner + [in] int YSrc, // y-coord of source upper-left corner + [in] int nSrcWidth, // width of source rectangle + [in] int nSrcHeight, // height of source rectangle + [in] VOID *lpBits, // bitmap bits + [in] BITMAPINFO *lpBitsInfo, // bitmap data + [in] _DIB_Color iUsage, // usage options + [in] _TernaryDrawMode dwRop // raster operation code +); + +IntFailIfZero [gle] SetROP2( + [in] HDC hdc, // handle to DC + [in] _BinaryDrawMode fnDrawMode // drawing mode +); +IntFailIfZero [gle] SetStretchBltMode( + [in] HDC hdc, // handle to DC + [in] _COMBINRGN_STYLE iStretchMode // bitmap stretching mode +); + +_SYSPAL [gle] SetSystemPaletteUse( + [in] HDC hdc, // handle to DC + [in] _SYSPAL uUsage // palette usage +); + +_ODD_FAILURE [gle] SetTextCharacterExtra( + [in] HDC hdc, // handle to DC + [in] int nCharExtra // extra-space value +); + +COLORREF_RETURN [gle] SetTextColor( + [in] HDC hdc, // handle to DC + [in] COLORREF crColor // text color +); + +UINT [gle] SetTextAlign( + [in] HDC hdc, // handle to DC + [in] _TextAlignmentOptions fMode // text-alignment option +); + +FailOnFalse [gle] SetTextJustification( + [in] HDC hdc, // handle to DC + [in] int nBreakExtra, // length of extra space + [in] int nBreakCount // count of space characters +); +FailOnFalse [gle] UpdateColors( + [in] HDC hdc // handle to DC +); + +module MSIMG32.DLL: +FailOnFalse [gle] AlphaBlend( + [in] HDC hdcDest, // handle to destination DC + [in] int nXOriginDest, // x-coord of upper-left corner + [in] int nYOriginDest, // y-coord of upper-left corner + [in] int nWidthDest, // destination width + [in] int nHeightDest, // destination height + [in] HDC hdcSrc, // handle to source DC + [in] int nXOriginSrc, // x-coord of upper-left corner + [in] int nYOriginSrc, // y-coord of upper-left corner + [in] int nWidthSrc, // source width + [in] int nHeightSrc, // source height + [in] BLENDFUNCTION blendFunction // alpha-blending function +); + +FailOnFalse [gle] TransparentBlt( + [in] HDC hdcDest, // handle to destination DC + [in] int nXOriginDest, // x-coord of destination upper-left corner + [in] int nYOriginDest, // y-coord of destination upper-left corner + [in] int nWidthDest, // width of destination rectangle + [in] int hHeightDest, // height of destination rectangle + [in] HDC hdcSrc, // handle to source DC + [in] int nXOriginSrc, // x-coord of source upper-left corner + [in] int nYOriginSrc, // y-coord of source upper-left corner + [in] int nWidthSrc, // width of source rectangle + [in] int nHeightSrc, // height of source rectangle + [in] UINT crTransparent // color to make transparent +); + + +FailOnFalse [gle] GradientFill( + [in] HDC hdc, // handle to DC + [in] PTRIVERTEX pVertex, // array of vertices + [in] ULONG dwNumVertex, // number of vertices + [in] PVOID pMesh, // array of gradients + [in] ULONG dwNumMesh, // size of gradient array + [in] _GRADIENT_FILL dwMode // gradient fill mode +); + +module GDI32.DLL: +FailOnFalse [gle] PlayMetaFileRecord( + [in] HDC hdc, // handle to DC + [in] LPHANDLETABLE lpHandletable, // metafile handle table + [in] LPMETARECORD lpMetaRecord, // metafile record + [in] UINT nHandles // count of handles +); + +FailOnFalse [gle] EnumMetaFile( + [in] HDC hdc, // handle to DC + [in] HMETAFILE hmf, // handle to Windows-format metafile + [in] MFENUMPROC lpMetaFunc, // callback function + [in] LPARAM lParam // optional data +); + +// Enhanced Metafile Function Declarations + +HENHMETAFILE [gle] CloseEnhMetaFile( + [in] HDC hdc // handle to enhanced-metafile DC +); +HENHMETAFILE [gle] CopyEnhMetaFileA( + [in] HENHMETAFILE hemfSrc, // handle to enhanced metafile + [in] LPCSTR lpszFile // file name +); +HENHMETAFILE [gle] CopyEnhMetaFileW( + [in] HENHMETAFILE hemfSrc, // handle to enhanced metafile + [in] LPCWSTR lpszFile // file name +); + +HDC [gle] CreateEnhMetaFileA( + [in] HDC hdcRef, // handle to reference DC + [in] LPCSTR lpFilename, // file name + [in] RECT *lpRect, // bounding rectangle + [in] LPCSTR lpDescription // description string +); +HDC [gle] CreateEnhMetaFileW( + [in] HDC hdcRef, // handle to reference DC + [in] LPCWSTR lpFilename, // file name + [in] RECT *lpRect, // bounding rectangle + [in] LPCWSTR lpDescription // description string +); + +FailOnFalse [gle] DeleteEnhMetaFile( + [in] HENHMETAFILE hemf // handle to an enhanced metafile +); +FailOnFalse [gle] EnumEnhMetaFile( + [in] HDC hdc, // handle to DC + [in] HENHMETAFILE hemf, // handle to enhanced metafile + [in] ENHMFENUMPROC lpEnhMetaFunc, // callback function + [in] LPVOID lpData, // callback-function data + [in] RECT *lpRect // bounding rectangle +); +HENHMETAFILE [gle] GetEnhMetaFileA( + [in] LPCSTR lpszMetaFile // file name +); +HENHMETAFILE [gle] GetEnhMetaFileW( + [in] LPCWSTR lpszMetaFile // file name +); +UintFailIfZero [gle] GetEnhMetaFileBits( + [in] HENHMETAFILE hemf, // handle to metafile + [in] UINT cbBuffer, // size of data buffer + [out] LPBYTE lpbBuffer // data buffer +); +UintFailIfZero [gle] GetEnhMetaFileDescriptionA( + [in] HENHMETAFILE hemf, // handle to enhanced metafile + [in] UINT cchBuffer, // size of text buffer + [out] LPSTR lpszDescription // text buffer +); +UintFailIfZero [gle] GetEnhMetaFileDescriptionW( + [in] HENHMETAFILE hemf, // handle to enhanced metafile + [in] UINT cchBuffer, // size of text buffer + [out] LPWSTR lpszDescription // text buffer +); +UintFailIfZero [gle] GetEnhMetaFileHeader( + [in] HENHMETAFILE hemf, // handle to enhanced metafile + [in] UINT cbBuffer, // size of buffer + [out] LPENHMETAHEADER lpemh // data buffer +); + +UintFailIfZero [gle] GetEnhMetaFilePaletteEntries( + [in] HENHMETAFILE hemf, // handle to enhanced metafile + [in] UINT cEntries, // count of palette entries + [out] LPPALETTEENTRY lppe // array of palette entries +); + +UintFailIfZero [gle] GetEnhMetaFilePixelFormat( + [in] HENHMETAFILE hemf, // handle to an enhanced metafile + [in] DWORD cbBuffer, // buffer size + [out] PIXELFORMATDESCRIPTOR * ppfd + // pointer to logical pixel format specification +); + +UintFailIfZero [gle] GetWinMetaFileBits( + [in] HENHMETAFILE hemf, // handle to the enhanced metafile + [in] UINT cbBuffer, // buffer size + [out] LPBYTE lpbBuffer, // records buffer + [in] INT fnMapMode, // mapping mode + [in] HDC hdcRef // handle to reference DC +); +FailOnFalse [gle] PlayEnhMetaFile( + [in] HDC hdc, // handle to DC + [in] HENHMETAFILE hemf, // handle to an enhanced metafile + [in] RECT *lpRect // bounding rectangle +); + +FailOnFalse [gle] PlayEnhMetaFileRecord( + [in] HDC hdc, // handle to DC + [in] LPHANDLETABLE lpHandletable, // metafile handle table + [in] ENHMETARECORD *lpEnhMetaRecord, // metafile record + [in] UINT nHandles // count of handles +); + +HENHMETAFILE [gle] SetEnhMetaFileBits( + [in] UINT cbBuffer, // buffer size + [in] BYTE *lpData // enhanced metafile data buffer +); + + +HENHMETAFILE [gle] SetWinMetaFileBits( + [in] UINT cbBuffer, // size of buffer + [in] BYTE *lpbBuffer, // metafile data buffer + [in] HDC hdcRef, // handle to reference DC + [in] METAFILEPICT *lpmfp // size of metafile picture +); +FailOnFalse [gle] GdiComment( + [in] HDC hdc, // handle to a device context + [in] UINT cbSize, // size of text buffer + [in] BYTE *lpData // text buffer +); + +FailOnFalse [gle] GetTextMetricsA( + [in] HDC hdc, // handle to DC + [out] LPTEXTMETRICA lptm // text metrics +); +FailOnFalse [gle] GetTextMetricsW( + [in] HDC hdc, // handle to DC + [out] LPTEXTMETRICW lptm // text metrics +); + +FailOnFalse [gle] AngleArc( + [in] HDC hdc, // handle to device context + [in] int X, // x-coordinate of circle's center + [in] int Y, // y-coordinate of circle's center + [in] DWORD dwRadius, // circle's radius + [in] FLOAT eStartAngle, // arc's start angle + [in] FLOAT eSweepAngle // arc's sweep angle +); +FailOnFalse [gle] PolyPolyline( + [in] HDC hdc, // handle to device context + [in] POINT *lppt, // array of points + [in] DWORD *lpdwPolyPoints, // array of values + [in] DWORD cCount // number of entries in values array +); +FailOnFalse [gle] GetWorldTransform( + [in] HDC hdc, // handle to device context + [out] LPXFORM lpXform // transformation +); +FailOnFalse [gle] SetWorldTransform( + [in] HDC hdc, // handle to device context + [in] XFORM *lpXform // transformation data +); +FailOnFalse [gle] ModifyWorldTransform( + [in] HDC hdc, // handle to device context + [in] XFORM *lpXform, // transformation data + [in] DWORD iMode // modification mode +); +FailOnFalse [gle] CombineTransform( + [out] LPXFORM lpxformResult, // combined transformation + [in] XFORM *lpxform1, // first transformation + [in] XFORM *lpxform2 // second transformation +); +HBITMAP [gle] CreateDIBSection( + [in] HDC hdc, // handle to DC + [in] BITMAPINFO *pbmi, // bitmap data + [in] UINT iUsage, // data type indicator + [out] VOID **ppvBits, // bit values + [in] HANDLE hSection, // handle to file mapping object + [in] DWORD dwOffset // offset to bitmap bit values +); +UintFailIfZero [gle] GetDIBColorTable( + [in] HDC hdc, // handle to DC + [in] UINT uStartIndex, // color table index of first entry + [in] UINT cEntries, // number of entries to retrieve + [out] RGBQUAD *pColors // array of color table entries +); +UintFailIfZero [gle] SetDIBColorTable( + [in] HDC hdc, // handle to DC + [in] UINT uStartIndex, // color table index of first entry + [in] UINT cEntries, // number of color table entries + [in] RGBQUAD *pColors // array of color table entries +); + +FailOnFalse [gle] SetColorAdjustment( + [in] HDC hdc, // handle to DC + [in] COLORADJUSTMENT *lpca // color adjustment values +); +FailOnFalse [gle] GetColorAdjustment( + [in] HDC hdc, // handle to DC + [out] LPCOLORADJUSTMENT lpca // color adjustment values +); + +HPALETTE [gle] CreateHalftonePalette( + [in] HDC hdc // handle to DC +); + +FailOnFalse [gle] AbortPath( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] ArcTo( + [in] HDC hdc, // handle to device context + [in] int nLeftRect, // x-coord of rectangle's upper-left corner + [in] int nTopRect, // y-coord of rectangle's upper-left corner + [in] int nRightRect, // x-coord of rectangle's lower-right corner + [in] int nBottomRect, // y-coord of rectangle's lower-right corner + [in] int nXRadial1, // x-coord of first radial ending point + [in] int nYRadial1, // y-coord of first radial ending point + [in] int nXRadial2, // x-coord of second radial ending point + [in] int nYRadial2 // y-coord of second radial ending point +); +FailOnFalse [gle] BeginPath( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] CloseFigure( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] EndPath( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] FillPath( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] FlattenPath( + [in] HDC hdc // handle to DC +); +IntFailIfNeg1 [gle] GetPath( + [in] HDC hdc, // handle to DC + [out] LPPOINT lpPoints, // path vertices + [out] _PT * lpTypes, // array of path vertex types + [in] int nSize // count of points defining path +); +HRGN [gle] PathToRegion( + [in] HDC hdc // handle to DC +); + +FailOnFalse [gle] PolyDraw( + [in] HDC hdc, // handle to device context + [in] POINT *lppt, // array of points + [in] _PT *lpbTypes, // line and curve identifiers + [in] int cCount // count of points +); +FailOnFalse [gle] SelectClipPath( + [in] HDC hdc, // handle to DC + [in] _CombineRgn iMode // clipping mode +); +IntFailIfZero [gle] SetArcDirection( + [in] HDC hdc, // handle to device context + [in] _AD ArcDirection // new arc direction +); + +FailOnFalse [gle] SetMiterLimit( + [in] HDC hdc, // handle to DC + [in] FLOAT eNewLimit, // new miter limit + [out] PFLOAT peOldLimit // previous miter limit +); +FailOnFalse [gle] StrokeAndFillPath( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] StrokePath( + [in] HDC hdc // handle to DC +); +FailOnFalse [gle] WidenPath( + [in] HDC hdc // handle to DC +); +HPEN [gle] ExtCreatePen( + [in] _PS dwPenStyle, // pen style + [in] DWORD dwWidth, // pen width + [in] LOGBRUSH *lplb, // brush attributes + [in] DWORD dwStyleCount, // length of custom style array + [in] DWORD *lpStyle // custom style array +); + +FailOnFalse [gle] GetMiterLimit( + [in] HDC hdc, // handle to DC + [out] PFLOAT peLimit // miter limit +); +_AD GetArcDirection( + [in] HDC hdc // handle to device context +); + +IntFailIfZero [gle] GetObjectA( + [in] HGDIOBJ hgdiobj, // handle to graphics object + [in] int cbBuffer, // size of buffer for object information + [out] LPVOID lpvObject // buffer for object information +); +IntFailIfZero [gle] GetObjectW( + [in] HGDIOBJ hgdiobj, // handle to graphics object + [in] int cbBuffer, // size of buffer for object information + [out] LPVOID lpvObject // buffer for object information +); +FailOnFalse [gle] MoveToEx( + [in] HDC hdc, // handle to device context + [in] int X, // x-coordinate of new current position + [in] int Y, // y-coordinate of new current position + [out] LPPOINT lpPoint // old current position +); +FailOnFalse [gle] TextOutA( + [in] HDC hdc, // handle to DC + [in] int nXStart, // x-coordinate of starting position + [in] int nYStart, // y-coordinate of starting position + [in] LPCSTR lpString, // character string + [in] int cbString // number of characters +); +FailOnFalse [gle] TextOutW( + [in] HDC hdc, // handle to DC + [in] int nXStart, // x-coordinate of starting position + [in] int nYStart, // y-coordinate of starting position + [in] LPCWSTR lpString, // character string + [in] int cbString // number of characters +); +FailOnFalse [gle] ExtTextOutA( + [in] HDC hdc, // handle to DC + [in] int X, // x-coordinate of reference point + [in] int Y, // y-coordinate of reference point + [in] _ETO fuOptions, // text-output options + [in] RECT *lprc, // optional dimensions + [in] LPCSTR lpString, // string + [in] UINT cbCount, // number of characters in string + [in] INT *lpDx // array of spacing values +); + +FailOnFalse [gle] ExtTextOutW( + [in] HDC hdc, // handle to DC + [in] int X, // x-coordinate of reference point + [in] int Y, // y-coordinate of reference point + [in] _ETO fuOptions, // text-output options + [in] RECT *lprc, // optional dimensions + [in] LPCWSTR lpString, // string + [in] UINT cbCount, // number of characters in string + [in] INT *lpDx // array of spacing values +); + +FailOnFalse [gle] PolyTextOutA( + [in] HDC hdc, // handle to DC + [in] POLYTEXTA *pptxt, // array of strings + [in] int cStrings // number of strings in array +); +FailOnFalse [gle] PolyTextOutW( + [in] HDC hdc, // handle to DC + [in] POLYTEXTW *pptxt, // array of strings + [in] int cStrings // number of strings in array +); + +HRGN [gle] CreatePolygonRgn( + [in] POINT *lppt, // array of points + [in] int cPoints, // number of points in array + [in] _PolyFill fnPolyFillMode // polygon-filling mode +); +FailOnFalse [gle] DPtoLP( + [in] HDC hdc, // handle to device context + LPPOINT lpPoints, // array of points + [in] int nCount // count of points in array +); +FailOnFalse [gle] LPtoDP( + [in] HDC hdc, // handle to device context + LPPOINT lpPoints, // array of points + [in] int nCount // count of points in array +); +FailOnFalse [gle] Polygon( + [in] HDC hdc, // handle to DC + [in] POINT *lpPoints, // polygon vertices + [in] int nCount // count of polygon vertices +); +FailOnFalse [gle] Polyline( + [in] HDC hdc, // handle to device context + [in] POINT *lppt, // array of endpoints + [in] int cPoints // number of points in array +); + +FailOnFalse [gle] PolyBezier( + [in] HDC hdc, // handle to device context + [in] POINT *lppt, // endpoints and control points + [in] DWORD cPoints // count of endpoints and control points +); +FailOnFalse [gle] PolyBezierTo( + [in] HDC hdc, // handle to device context + [in] POINT *lppt, // endpoints and control points + [in] DWORD cCount // count of endpoints and control points +); +FailOnFalse PolylineTo( + [in] HDC hdc, // handle to device context + [in] POINT *lppt, // array of points + [in] DWORD cCount // number of points in array +); + +FailOnFalse [gle] SetViewportExtEx( + [in] HDC hdc, // handle to device context + [in] int nXExtent, // new horizontal viewport extent + [in] int nYExtent, // new vertical viewport extent + [out] LPSIZE lpSize // original viewport extent +); +FailOnFalse [gle] SetViewportOrgEx( + [in] HDC hdc, // handle to device context + [in] int X, // new x-coordinate of viewport origin + [in] int Y, // new y-coordinate of viewport origin + [out] LPPOINT lpPoint // original viewport origin +); +FailOnFalse [gle] SetWindowExtEx( + [in] HDC hdc, // handle to device context + [in] int nXExtent, // new horizontal window extent + [in] int nYExtent, // new vertical window extent + [out] LPSIZE lpSize // original window extent +); +FailOnFalse [gle] SetWindowOrgEx( + [in] HDC hdc, // handle to device context + [in] int X, // new x-coordinate of window origin + [in] int Y, // new y-coordinate of window origin + [out] LPPOINT lpPoint // original window origin +); + +FailOnFalse [gle] OffsetViewportOrgEx( + [in] HDC hdc, // handle to device context + [in] int nXOffset, // horizontal offset + [in] int nYOffset, // vertical offset + [out] LPPOINT lpPoint // original origin +); +FailOnFalse [gle] OffsetWindowOrgEx( + [in] HDC hdc, // handle to device context + [in] int nXOffset, // horizontal offset + [in] int nYOffset, // vertical offset + [out] LPPOINT lpPoint // original origin +); +FailOnFalse [gle] ScaleViewportExtEx( + [in] HDC hdc, // handle to device context + [in] int Xnum, // horizontal multiplicand + [in] int Xdenom, // horizontal divisor + [in] int Ynum, // vertical multiplicand + [in] int Ydenom, // vertical divisor + [out] LPSIZE lpSize // previous viewport extents +); +FailOnFalse [gle] ScaleWindowExtEx( + [in] HDC hdc, // handle to device context + [in] int Xnum, // horizontal multiplicand + [in] int Xdenom, // horizontal divisor + [in] int Ynum, // vertical multiplicand + [in] int Ydenom, // vertical divisor + [out] LPSIZE lpSize // previous window extents +); +FailOnFalse [gle] SetBitmapDimensionEx( + [in] HBITMAP hBitmap, // handle to bitmap + [in] int nWidth, // bitmap width in .01-mm units + [in] int nHeight, // bitmap height in .01-mm units + [out] LPSIZE lpSize // original dimensions +); +FailOnFalse [gle] SetBrushOrgEx( + [in] HDC hdc, // handle to device context + [in] int nXOrg, // x-coord of new origin + [in] int nYOrg, // y-coord of new origin + [out] LPPOINT lppt // points to previous brush origin +); + +IntFailIfZero [gle] GetTextFaceA( + [in] HDC hdc, // handle to DC + [in] int nCount, // length of typeface name buffer + [out] LPSTR lpFaceName // typeface name buffer +); +IntFailIfZero [gle] GetTextFaceW( + [in] HDC hdc, // handle to DC + [in] int nCount, // length of typeface name buffer + [out] LPWSTR lpFaceName // typeface name buffer +); + +DwordFailIfZero [gle] GetKerningPairsA( + [in] HDC hdc, // handle to DC + [in] DWORD nNumPairs, // number of kerning pairs + [out] LPKERNINGPAIR lpkrnpair // array of kerning pairs +); +DwordFailIfZero [gle] GetKerningPairsW( + [in] HDC hdc, // handle to DC + [in] DWORD nNumPairs, // number of kerning pairs + [out] LPKERNINGPAIR lpkrnpair // array of kerning pairs +); +FailOnFalse [gle] GetDCOrgEx( + [in] HDC hdc, // handle to a DC + [out] LPPOINT lpPoint // translation origin +); +FailOnFalse [gle] FixBrushOrgEx( + HDC hdc, + int arg1, + int arg2, + LPPOINT point +); +FailOnFalse [gle] UnrealizeObject( + [in] HGDIOBJ hgdiobj // handle to logical palette +); + +FailOnFalse GdiFlush(); + +DwordFailIfZero [gle] GdiSetBatchLimit( + [in] DWORD dwLimit // batch limit +); + +DwordFailIfZero [gle] GdiGetBatchLimit(); + + +IntFailIfZero SetICMMode( + [in] HDC hDC, + [in] _ICM iEnableICM +); + +FailOnFalse CheckColorsInGamut( + [in] HDC hDC, // device context handle + [in] LPVOID lpRGBTriples, // array of RGB triples + [out] LPVOID lpBuffer, // buffer for results + [in] UINT nCount // number of triples +); + +HCOLORSPACE GetColorSpace( + [in] HDC hDC +); +FailOnFalse GetLogColorSpaceA( + [in] HCOLORSPACE hColorSpace, + [out] LPLOGCOLORSPACEA lpBuffer, + [in] DWORD nSize +); +FailOnFalse GetLogColorSpaceW( + [in] HCOLORSPACE hColorSpace, + [out] LPLOGCOLORSPACEW lpBuffer, + [in] DWORD nSize +); +HCOLORSPACE CreateColorSpaceA( + [in] LPLOGCOLORSPACEA lpLogColorSpace +); +HCOLORSPACE CreateColorSpaceW( + [in] LPLOGCOLORSPACEW lpLogColorSpace +); +HCOLORSPACE SetColorSpace( + [in] HDC hDC, + [in] HCOLORSPACE hColorSpace +); + +FailOnFalse DeleteColorSpace( + [in] HCOLORSPACE hColorSpace +); +FailOnFalse GetICMProfileA( + [in] HDC hDC, + [out] LPDWORD lpcbName, + [out] LPSTR lpszFilename +); +FailOnFalse GetICMProfileW( + [in] HDC hDC, + [out] LPDWORD lpcbName, + [out] LPWSTR lpszFilename +); +FailOnFalse SetICMProfileA( + [in] HDC hDC, + [in] LPSTR lpFileName +); +FailOnFalse SetICMProfileW( + [in] HDC hDC, + [in] LPWSTR lpFileName +); +FailOnFalse GetDeviceGammaRamp( + [in] HDC hDC, + [out] LPVOID lpRamp +); +FailOnFalse SetDeviceGammaRamp( + [in] HDC hDC, + [in] LPVOID lpRamp +); +FailOnFalse ColorMatchToTarget( + [in] HDC hDC, + [in] HDC hdcTarget, + [in] DWORD uiAction +); +int EnumICMProfilesA( + [in] HDC hDC, + [in] ICMENUMPROCA lpEnumICMProfilesFunc, + [in] LPARAM lParam +); +int EnumICMProfilesW( + [in] HDC hDC, + [in] ICMENUMPROCW lpEnumICMProfilesFunc, + [in] LPARAM lParam +); +FailOnFalse UpdateICMRegKeyA( + [in] DWORD dwReserved, + [in] LPSTR lpszCMID, + [in] LPSTR lpszFileName, + [in] _UpdateICMRegKey nCommand +); +FailOnFalse UpdateICMRegKeyW( + [in] DWORD dwReserved, + [in] LPWSTR lpszCMID, + [in] LPWSTR lpszFileName, + [in] _UpdateICMRegKey nCommand +); + +FailOnFalse ColorCorrectPalette( + [in] HDC hDC, + [in] HPALETTE hPalette, + [in] DWORD dwFirstEntry, + [in] DWORD dwNumOfEntries +); + + +// OpenGL wgl prototypes +/* +FailOnFalse wglCopyContext(HGLRC, HGLRC, UINT); +HGLRC wglCreateContext(HDC); +HGLRC wglCreateLayerContext(HDC, int); +FailOnFalse wglDeleteContext(HGLRC); +HGLRC wglGetCurrentContext(); +HDC wglGetCurrentDC(); +PROC wglGetProcAddress(LPCSTR); +FailOnFalse wglMakeCurrent(HDC, HGLRC); +FailOnFalse wglShareLists(HGLRC, HGLRC); +FailOnFalse wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); +FailOnFalse wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); +FailOnFalse SwapBuffers(HDC); + +FailOnFalse wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); +FailOnFalse wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, +FailOnFalse wglDescribeLayerPlane(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +IntFailIfZero wglSetLayerPaletteEntries(HDC, int, int, int, + COLORREF *); +IntFailIfZero wglGetLayerPaletteEntries(HDC, int, int, int, + COLORREF *); +FailOnFalse wglRealizeLayerPalette(HDC, int, BOOL); +FailOnFalse wglSwapLayerBuffers(HDC, UINT); + + +FailOnFalse wglSwapMultipleBuffers(UINT, WGLSWAP *); + +*/ diff --git a/tools/Debugging Tools for Windows/winext/manifest/hook.h b/tools/Debugging Tools for Windows/winext/manifest/hook.h new file mode 100644 index 0000000000..7e3d5ff662 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/hook.h @@ -0,0 +1,144 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Hook Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +typedef struct tagCWPSTRUCT { + LPARAM lParam; + WPARAM wParam; + UINT message; + HWND hwnd; +} CWPSTRUCT, *PCWPSTRUCT; + +typedef struct tagCWPRETSTRUCT { + LRESULT lResult; + LPARAM lParam; + WPARAM wParam; + UINT message; + HWND hwnd; +} CWPRETSTRUCT, *PCWPRETSTRUCT; + +value INT CBTHookCodes +{ +#define HCBT_MOVESIZE 0 +#define HCBT_MINMAX 1 +#define HCBT_QS 2 +#define HCBT_CREATEWND 3 +#define HCBT_DESTROYWND 4 +#define HCBT_ACTIVATE 5 +#define HCBT_CLICKSKIPPED 6 +#define HCBT_KEYSKIPPED 7 +#define HCBT_SYSCOMMAND 8 +#define HCBT_SETFOCUS 9 +}; + +value WPARAM HookProcType +{ +#define WH_CALLWNDPROC 4 +#define WH_CALLWNDPROCRET 12 +#define WH_CBT 5 +#define WH_DEBUG 9 +#define WH_FOREGROUNDIDLE 11 +#define WH_GETMESSAGE 3 +#define WH_JOURNALPLAYBACK 1 +#define WH_JOURNALRECORD 0 +#define WH_KEYBOARD 2 +#define WH_KEYBOARD_LL 13 +#define WH_MOUSE 7 +#define WH_MOUSE_LL 14 +#define WH_MSGFILTER -1 +#define WH_SHELL 10 +#define WH_SYSMSGFILTER 6 +}; + +typedef struct tagDEBUGHOOKINFO { + DWORD idThread; + DWORD idThreadInstaller; + LPARAM lParam; + WPARAM wParam; + INT code; +} DEBUGHOOKINFO, *PDEBUGHOOKINFO; + +typedef struct tagEVENTMSG { + UINT message; + UINT paramL; + UINT paramH; + DWORD time; + HWND hwnd; +} EVENTMSG, *PEVENTMSG; + +value INT HookCode +{ +#define HC_GETNEXT 1 +#define HC_SKIP 2 +#define HC_NOREMOVE 3 +#define HC_SYSMODALON 4 +#define HC_SYSMODALOFF 5 +}; + +typedef struct tagKBDLLHOOKSTRUCT { + DWORD vkCode; + DWORD scanCode; + DWORD flags; + DWORD time; + ULONG_PTR dwExtraInfo; +} KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT; + +typedef struct tagMSLLHOOKSTRUCT { + POINT pt; + DWORD mouseData; + DWORD flags; + DWORD time; + ULONG_PTR dwExtraInfo; +} MSLLHOOKSTRUCT, *PMSLLHOOKSTRUCT; + +value INT InputWhichGeneratedMessage +{ +#define MSGF_DDEMGR 0x8001 +#define MSGF_DIALOGBOX 0 +#define MSGF_MENU 2 +#define MSGF_SCROLLBAR 5 +}; + +typedef struct tagMOUSEHOOKSTRUCT { + POINT pt; + HWND hwnd; + UINT wHitTestCode; + ULONG_PTR dwExtraInfo; +} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT; + +value INT ShellProcHookCode +{ +#define HSHELL_ACCESSIBILITYSTATE 11 +#define HSHELL_ACTIVATESHELLWINDOW 3 +#define HSHELL_GETMINRECT 5 +#define HSHELL_LANGUAGE 8 +#define HSHELL_REDRAW 6 +#define HSHELL_TASKMAN 7 +#define HSHELL_WINDOWACTIVATED 4 +#define HSHELL_WINDOWCREATED 1 +#define HSHELL_WINDOWDESTROYED 2 +}; + +category HookingFunctions: +module USER32.DLL: + +BOOL CallMsgFilter(LPMSG lpMsg, int nCode); + +LRESULT CallNextHookEx(HHOOK hhk, + int nCode, + WPARAM wParam, + LPARAM lParam); + +HHOOK [gle] SetWindowsHookExA(HookProcType idHook, + HOOKPROC lpfn, + HINSTANCE hMod, + DWORD dwThreadId); + +HHOOK [gle] SetWindowsHookExW(HookProcType idHook, + HOOKPROC lpfn, + HINSTANCE hmod, + DWORD dwThreadId); + +FailOnFalse [gle] UnhookWindowsHookEx( [da] HHOOK hhk); diff --git a/tools/Debugging Tools for Windows/winext/manifest/kernel32.h b/tools/Debugging Tools for Windows/winext/manifest/kernel32.h new file mode 100644 index 0000000000..fe1cdafb89 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/kernel32.h @@ -0,0 +1,186 @@ +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +// KERNEL32 API Set +// +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +typedef LPVOID LPCONTEXT; + +mask DWORD DesiredSecurityAccess +{ +#define KEY_QUERY_VALUE 0x0001 +#define KEY_SET_VALUE 0x0002 +#define KEY_CREATE_SUB_KEY 0x0004 +#define KEY_ENUMERATE_SUB_KEYS 0x0008 +#define KEY_NOTIFY 0x0010 +#define KEY_CREATE_LINK 0x0020 +}; + +mask DWORD ServiceTypes +{ +#define SERVICE_WIN32_OWN_PROCESS 0x00000010 +#define SERVICE_WIN32_SHARE_PROCESS 0x00000020 +#define SERVICE_KERNEL_DRIVER 0x00000001 +#define SERVICE_FILE_SYSTEM_DRIVER 0x00000002 +#define SERVICE_INTERACTIVE_PROCESS 0x00000100 +}; + +value DWORD StartTypes +{ +#define SERVICE_BOOT_START 0x00000000 +#define SERVICE_SYSTEM_START 0x00000001 +#define SERVICE_AUTO_START 0x00000002 +#define SERVICE_DEMAND_START 0x00000003 +#define SERVICE_DISABLED 0x00000004 +}; + +value DWORD ErrorControls +{ +#define SERVICE_ERROR_IGNORE 0x00000000 +#define SERVICE_ERROR_NORMAL 0x00000001 +#define SERVICE_ERROR_SEVERE 0x00000002 +#define SERVICE_ERROR_CRITICAL 0x00000003 +}; + +value DWORD ControlCodes +{ +#define SERVICE_CONTROL_STOP 0x00000001 +#define SERVICE_CONTROL_PAUSE 0x00000002 +#define SERVICE_CONTROL_CONTINUE 0x00000003 +#define SERVICE_CONTROL_INTERROGATE 0x00000004 +#define SERVICE_CONTROL_SHUTDOWN 0x00000005 +#define SERVICE_CONTROL_PARAMCHANGE 0x00000006 +#define SERVICE_CONTROL_NETBINDADD 0x00000007 +#define SERVICE_CONTROL_NETBINDREMOVE 0x00000008 +#define SERVICE_CONTROL_NETBINDENABLE 0x00000009 +#define SERVICE_CONTROL_NETBINDDISABLE 0x0000000A +}; + +mask DWORD DesiredAccessTypes +{ +#define SC_MANAGER_CONNECT 0x0001 +#define SC_MANAGER_CREATE_SERVICE 0x0002 +#define SC_MANAGER_ENUMERATE_SERVICE 0x0004 +#define SC_MANAGER_LOCK 0x0008 +#define SC_MANAGER_QUERY_LOCK_STATUS 0x0010 +#define SC_MANAGER_MODIFY_BOOT_CONFIG 0x0020 +}; + +value DWORD InfoLevels +{ +#define SERVICE_CONFIG_DESCRIPTION 1 +#define SERVICE_CONFIG_FAILURE_ACTIONS 2 +}; + +value DWORD Status +{ +#define STATUS_WAIT_0 0x00000000L +#define STATUS_ABANDONED_WAIT_0 0x00000080L +#define STATUS_USER_APC 0x000000C0L +#define STATUS_TIMEOUT 0x00000102L +#define STATUS_PENDING 0x00000103L +#define STATUS_SEGMENT_NOTIFICATION 0x40000005L +#define STATUS_GUARD_PAGE_VIOLATION 0x80000001L +#define STATUS_DATATYPE_MISALIGNMENT 0x80000002L +#define STATUS_BREAKPOINT 0x80000003L +#define STATUS_SINGLE_STEP 0x80000004L +#define STATUS_ACCESS_VIOLATION 0xC0000005L +#define STATUS_IN_PAGE_ERROR 0xC0000006L +#define STATUS_INVALID_HANDLE 0xC0000008L +#define STATUS_NO_MEMORY 0xC0000017L +#define STATUS_ILLEGAL_INSTRUCTION 0xC000001DL +#define STATUS_NONCONTINUABLE_EXCEPTION 0xC0000025L +#define STATUS_INVALID_DISPOSITION 0xC0000026L +#define STATUS_ARRAY_BOUNDS_EXCEEDED 0xC000008CL +#define STATUS_FLOAT_DENORMAL_OPERAND 0xC000008DL +#define STATUS_FLOAT_DIVIDE_BY_ZERO 0xC000008EL +#define STATUS_FLOAT_INEXACT_RESULT 0xC000008FL +#define STATUS_FLOAT_INVALID_OPERATION 0xC0000090L +#define STATUS_FLOAT_OVERFLOW 0xC0000091L +#define STATUS_FLOAT_STACK_CHECK 0xC0000092L +#define STATUS_FLOAT_UNDERFLOW 0xC0000093L +#define STATUS_INTEGER_DIVIDE_BY_ZERO 0xC0000094L +#define STATUS_INTEGER_OVERFLOW 0xC0000095L +#define STATUS_PRIVILEGED_INSTRUCTION 0xC0000096L +#define STATUS_STACK_OVERFLOW 0xC00000FDL +#define STATUS_CONTROL_C_EXIT 0xC000013AL +#define STATUS_FLOAT_MULTIPLE_FAULTS 0xC00002B4L +#define STATUS_FLOAT_MULTIPLE_TRAPS 0xC00002B5L +#define STATUS_ILLEGAL_VLM_REFERENCE 0xC00002C0L +}; + +mask DWORD ControlEvents +{ +#define CTRL_C_EVENT 0 +#define CTRL_BREAK_EVENT 1 +#define CTRL_CLOSE_EVENT 2 +#define CTRL_LOGOFF_EVENT 5 +#define CTRL_SHUTDOWN_EVENT 6 +}; + +mask DWORD GenericAccessRights +{ +#define GENERIC_READ 0x80000000L +#define GENERIC_WRITE 0x40000000L +#define GENERIC_EXECUTE 0x20000000L +#define GENERIC_ALL 0x10000000L +}; + +mask DWORD ShareRights +{ +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define FILE_SHARE_DELETE 0x00000004 +}; + +value DWORD CreationActions +{ +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define TRUNCATE_EXISTING 5 +}; + +typedef struct _OVERLAPPED { + DWORD Internal; + DWORD InternalHigh; + DWORD Offset; + DWORD OffsetHigh; + HANDLE hEvent; +} OVERLAPPED; + +value INT FilePointerStartingPosition +{ +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 +}; + +value LONG ThreadBasePriority +{ +#define THREAD_BASE_PRIORITY_LOWRT 15 // value that gets a thread to LowRealtime-1 +#define THREAD_BASE_PRIORITY_MAX 2 // maximum thread base priority boost +#define THREAD_BASE_PRIORITY_MIN -2 // minimum thread base priority boost +#define THREAD_BASE_PRIORITY_IDLE -15 // value that gets a thread to idle +}; + +value LONG ThreadPriority +{ +#define THREAD_PRIORITY_LOWEST -2 +#define THREAD_PRIORITY_BELOW_NORMAL -1 +#define THREAD_PRIORITY_NORMAL 0 +#define THREAD_PRIORITY_HIGHEST 2 +#define THREAD_PRIORITY_ABOVE_NORMAL 1 +#define THREAD_PRIORITY_ERROR_RETURN 0x7FFFFFFF +#define THREAD_PRIORITY_TIME_CRITICAL 15 +#define THREAD_PRIORITY_IDLE -15 +}; + +#include "debugging.h" +#include "processes.h" +#include "memory.h" +#include "registry.h" +#include "fileio.h" +#include "strings.h" diff --git a/tools/Debugging Tools for Windows/winext/manifest/main.h b/tools/Debugging Tools for Windows/winext/manifest/main.h new file mode 100644 index 0000000000..f876b388d0 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/main.h @@ -0,0 +1,1149 @@ +// --------------------------------------------------------------------------- +// LogExts API Logging Manifest +// +// Created 05-Jan-2000 markder +// --------------------------------------------------------------------------- + + + +// --------------------------------------------------------------------------- +// +// Built-In Types +// +// VOID -- 0 bytes +// BYTE -- 1 byte (hex) +// WORD -- 2 bytes (hex) +// DWORD -- 4 bytes (hex) +// LONG -- 4 bytes (dec) +// BOOL -- Boolean +// FLOAT -- Float +// DOUBLE -- Double +// CHAR -- Character +// WCHAR -- Wide character +// LPSTR -- ANSI string +// LPWSTR -- Wide character string +// GUID -- Globally Unique Identifier +// COM_INTERFACE_PTR -- COM interface pointer +// SIZE_T -- 32-Bit on x86/64-Bit on x64 +// +// --------------------------------------------------------------------------- + +// -------------------------------------- Basic type definitions +typedef SIZE_T int; +typedef LONG long; +typedef CHAR char; +typedef WORD short; +typedef LONG INT; +typedef LONG UINT; +typedef LPSTR LPCSTR; +typedef LPSTR *PSTR; +typedef LPWSTR LPCWSTR; +typedef WORD USHORT; +typedef WORD *LPWORD; +typedef DWORD ULONG; +typedef SIZE_T LPVOID; +typedef LPVOID PVOID; +typedef LPVOID LPCVOID; +typedef BYTE *LPBYTE; +typedef BYTE *PBYTE; +typedef BOOL *PBOOL; +typedef BOOL *LPBOOL; +typedef DWORD *LPDWORD; +typedef DWORD *PDWORD; +typedef LONG *LPLONG; +typedef LONG *PLONG; +typedef LONG *PULONG; +typedef BYTE BOOLEAN; +typedef DWORD HINF; +typedef FLOAT float; +typedef VOID void; + +// -------------------------------------- Memory management definitions + +value SIZE_T HANDLE +{ +#define NULL 0 [fail] +#define INVALID_HANDLE_VALUE -1 [fail] +}; + +typedef HANDLE HGLOBAL; +typedef HANDLE HLOCAL; + +// -------------------------------------- Windows definitions +typedef DWORD ATOM; +typedef GUID *LPGUID; +typedef GUID *PGUID; +typedef HANDLE HDEVNOTIFY; +typedef HANDLE HDEVINFO; +typedef HANDLE *LPHANDLE; +typedef HANDLE HMETAFILE; +typedef DWORD WPARAM; +typedef LPVOID LPARAM; +typedef HANDLE HKEY; +typedef HKEY *PHKEY; +typedef LPVOID LPTOP_LEVEL_EXCEPTION_FILTER; +typedef LPVOID LPTHREAD_START_ROUTINE; +typedef LPVOID LPFIBER_START_ROUTINE; +typedef DWORD LRESULT; +typedef LPVOID HOOKPROC; +typedef LPVOID FARPROC; +typedef LPVOID LPOVERLAPPED; + +// -------------------------------------- 64-bit definitions +typedef SIZE_T __int64; +typedef LPVOID UINT_PTR; +typedef LPVOID ULONG_PTR; +typedef LPVOID DWORD_PTR; +typedef LPVOID PULONG_PTR; + +// +// Include winerror.h for the definition of HRESULT +// +#include "winerror.h" + +// -------------------------------------- COM definitions +typedef HRESULT STDAPI; +typedef LPWSTR BSTR; +typedef DWORD LCID; +typedef WCHAR LPOLECHAR; +typedef LPWSTR LPOLESTR; +typedef LPWSTR LPCOLESTR; +typedef DWORD DISPID; +typedef DWORD DISPPARAMS; +typedef DWORD VARIANT; +typedef DWORD EXCEPINFO; +typedef GUID *REFGUID; +typedef GUID *REFCLSID; +typedef GUID *REFIID; + + +typedef struct _CLSID +{ + DWORD x; + WORD s1; + WORD s2; + BYTE c[8]; +} CLSID; + + +value DWORD ThreadId +{ +#define NULL 0 [fail] +}; + +value DWORD ProcessId +{ +#define NULL 0 [fail] +}; + + +typedef struct _LONGLONG +{ + DWORD Low; + LONG High; +} LONGLONG; + + +typedef struct _ULONGLONG +{ + DWORD Low; + DWORD High; +} ULONGLONG; + +typedef struct _ULARGE_INTEGER +{ + DWORD Low; + DWORD High; +} ULARGE_INTEGER,*PULARGE_INTEGER; + +typedef struct _LARGE_INTEGER +{ + ULONG Low; + LONG High; +} LARGE_INTEGER,*PLARGE_INTEGER; + +typedef struct _DWORDLONG +{ + DWORD Low; + DWORD High; +} DWORDLONG; + +typedef LPVOID PVOID64; + +typedef WORD SHORT; +typedef int *LPINT; +typedef float *LPFLOAT; +typedef float *PFLOAT; + +// -------------------------------------- Basic error definitions +value BOOL FailOnFalse +{ +#define FALSE 0 [fail] +#define TRUE 1 +}; + +value LONG IntFailIfNeg1 +{ +#define NEGATIVE_ONE -1 [fail] +}; + +value LONG IntFailIfZero +{ +#define ZERO 0 [fail] +}; + +value LONG LongFailIfNeg1 +{ +#define NEGATIVE_ONE -1 [fail] +}; + +value LONG LongFailIfZero +{ +#define ZERO 0 [fail] +}; + +value WORD WordFailIfZero +{ +#define ZERO 0 [fail] +}; + +value DWORD DwordFailIfZero +{ +#define ZERO 0 [fail] +}; + +value LPVOID HMODULE +{ +#define NULL 0 [fail] +}; + +value LPVOID HINSTANCE +{ +#define NULL 0 [fail] +}; + +value DWORD UintFailIfZero +{ +#define ZERO 0 [fail] +}; + + +value DWORD DwordFailIf0xFFFFFFFF +{ +#define _0xFFFFFFFF 0xFFFFFFFF [fail] +}; + +value DWORD DwordFailIfNeg1 +{ +#define NEGATIVE_ONE -1 [fail] +}; + +value LPSTR LpstrFailIfNull +{ +#define NULL 0 [fail] +}; + +value LPWSTR LpwstrFailIfNull +{ +#define NULL 0 [fail] +}; + +value LPVOID LpvoidFailIfNull +{ +#define NULL 0 [fail] +}; + +typedef HANDLE *PHANDLE; +typedef HANDLE HFILE; + +value HANDLE HWND +{ +#define NULL 0 [fail] +#define HWND_BOTTOM 1 +#define HWND_TOPMOST -1 +#define HWND_NOTOPMOST -2 +}; + +value int SpoolerError +{ +/* Spooler Error Codes */ +#define SP_NOTREPORTED 0x4000 [fail] +#define SP_ZERO 0 [fail] +#define SP_ERROR -1 [fail] +#define SP_APPABORT -2 [fail] +#define SP_USERABORT -3 [fail] +#define SP_OUTOFDISK -4 [fail] +#define SP_OUTOFMEMORY -5 [fail] +}; + +value LONG UnhandledException +{ +#define EXCEPTION_EXECUTE_HANDLER 1 +#define EXCEPTION_CONTINUE_SEARCH 0 +}; + +mask DWORD FileFlagsAndAttributes +{ +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#define FILE_ATTRIBUTE_HIDDEN 0x00000002 +#define FILE_ATTRIBUTE_SYSTEM 0x00000004 +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 +#define FILE_ATTRIBUTE_ENCRYPTED 0x00000040 +#define FILE_ATTRIBUTE_NORMAL 0x00000080 +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 +#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 +#define FILE_ATTRIBUTE_COMPRESSED 0x00000800 +#define FILE_ATTRIBUTE_OFFLINE 0x00001000 +#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +}; + +typedef DWORD FTime; + +alias FTime; + +typedef struct _FILETIME { + FTime dwLowDateTime; + FTime dwHighDateTime; +} FILETIME, *LPFILETIME, *PFILETIME; + +typedef struct _STARTUPINFOA { + DWORD cb; + FILLER64 align1[4]; + LPSTR lpReserved; + LPSTR lpDesktop; + LPSTR lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + FILLER64 align2[4]; + LPBYTE lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} STARTUPINFOA, *LPSTARTUPINFOA; + +typedef struct _STARTUPINFOW { + DWORD cb; + FILLER64 align1[4]; + LPWSTR lpReserved; + LPWSTR lpDesktop; + LPWSTR lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + FILLER64 align2[4]; + LPBYTE lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} STARTUPINFOW, *LPSTARTUPINFOW; + +typedef struct _PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + ProcessId dwProcessId; + ThreadId dwThreadId; +} PROCESS_INFORMATION, *LPPROCESS_INFORMATION; + +typedef struct _WIN32_FIND_DATAW { + FileFlagsAndAttributes dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + WCHAR cFileName[ 260 ]; + WCHAR cAlternateFileName[ 14 ]; +} WIN32_FIND_DATAW, *LPWIN32_FIND_DATAW; + +typedef struct _WIN32_FIND_DATAA { + FileFlagsAndAttributes dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + CHAR cFileName[ 260 ]; + CHAR cAlternateFileName[ 14 ]; +} WIN32_FIND_DATAA, *LPWIN32_FIND_DATAA; + +typedef struct tagPOINT { + LONG x; + LONG y; +} POINT, *PPOINT, *LPPOINT; + +typedef struct _RECT { + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECT, *PRECT, *LPRECT, *LPCRECT, *LPCRECTL; + +typedef struct tagPALETTEENTRY { + BYTE peRed; + BYTE peGreen; + BYTE peBlue; + BYTE peFlags; +} PALETTEENTRY, *LPPALETTEENTRY; + +typedef struct tagSIZE { + LONG cx; + LONG cy; +} SIZE, *PSIZE, *LPSIZE; + +typedef struct _RGNDATAHEADER + { + DWORD dwSize; + DWORD iType; + DWORD nCount; + DWORD nRgnSize; + RECT rcBound; + } RGNDATAHEADER; + +typedef struct _RGNDATA { + RGNDATAHEADER rdh; + char Buffer[1]; +} RGNDATA, *PRGNDATA, *LPRGNDATA; + +typedef struct _RECTL +{ + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECTL, *PRECTL, *LPRECTL; +typedef struct tagSIZEL + { + LONG cx; + LONG cy; + } SIZEL; +typedef SIZEL *PSIZEL; +typedef SIZEL *LPSIZEL; + +value HANDLE HDC +{ +#define NULL 0 [fail] +}; + +value HANDLE HRGN +{ +#define ERROR 0 [fail] +#define NULLREGION 1 +#define SIMPLEREGION 2 +#define COMPLEXREGION 3 +}; + +value HANDLE HMENU +{ +#define NULL 0 [fail] +}; + +value HANDLE HRSRC +{ +#define NULL 0 [fail] +}; + +value HANDLE HACCEL +{ +#define NULL 0 [fail] +}; + +value LPVOID WNDPROC +{ +#define NULL 0 [fail] +}; + +value HANDLE HKL +{ +#define NULL 0 [fail] +#define HKL_NEXT 1 +}; + +value HANDLE HHOOK +{ +#define NULL 0 [fail] +}; + + +value DWORD WaitReturnValues +{ +#define WAIT_OBJECT_0 0x00000000 +#define WAIT_OBJECT_1 0x00000001 +#define WAIT_OBJECT_2 0x00000002 +#define WAIT_OBJECT_3 0x00000003 +#define WAIT_OBJECT_4 0x00000004 +#define WAIT_OBJECT_5 0x00000005 +#define WAIT_OBJECT_6 0x00000006 +#define WAIT_OBJECT_7 0x00000007 +#define WAIT_OBJECT_8 0x00000008 +#define WAIT_OBJECT_9 0x00000009 +#define WAIT_OBJECT_10 0x0000000A +#define WAIT_OBJECT_11 0x0000000B +#define WAIT_OBJECT_12 0x0000000C +#define WAIT_ABANDONED_0 0x00000080 +#define WAIT_ABANDONED_1 0x00000081 +#define WAIT_ABANDONED_2 0x00000082 +#define WAIT_ABANDONED_3 0x00000083 +#define WAIT_ABANDONED_4 0x00000084 +#define WAIT_ABANDONED_5 0x00000085 +#define WAIT_ABANDONED_6 0x00000086 +#define WAIT_ABANDONED_7 0x00000087 +#define WAIT_ABANDONED_8 0x00000088 +#define WAIT_ABANDONED_9 0x00000089 +#define WAIT_ABANDONED_10 0x0000008A +#define WAIT_ABANDONED_11 0x0000008B +#define WAIT_ABANDONED_12 0x0000008C +#define WAIT_IO_COMPLETION 0x000000C0 +#define WAIT_TIMEOUT 0x00000102 +#define WAIT_FAILED 0xFFFFFFFF [fail] +}; + +value HANDLE HDESK +{ +#define NULL 0 [fail] +}; + +value HANDLE HWINSTA +{ +#define NULL 0 [fail] +}; + +value HANDLE HMONITOR +{ +#define NULL 0 [fail] +}; + +value HANDLE HPRINTER +{ +#define NULL 0 [fail] +}; + +typedef DWORD ACCESS_MASK; + + +typedef struct _SECURITY_ATTRIBUTES { + DWORD nLength; + LPVOID lpSecurityDescriptor; + BOOL bInheritHandle; +} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; + + + + +mask DWORD DMFieldFlags +{ +/* field selection bits */ +#define DM_ORIENTATION 0x00000001L +#define DM_PAPERSIZE 0x00000002L +#define DM_PAPERLENGTH 0x00000004L +#define DM_PAPERWIDTH 0x00000008L +#define DM_SCALE 0x00000010L +#define DM_POSITION 0x00000020L +#define DM_NUP 0x00000040L +#define DM_COPIES 0x00000100L +#define DM_DEFAULTSOURCE 0x00000200L +#define DM_PRINTQUALITY 0x00000400L +#define DM_COLOR 0x00000800L +#define DM_DUPLEX 0x00001000L +#define DM_YRESOLUTION 0x00002000L +#define DM_TTOPTION 0x00004000L +#define DM_COLLATE 0x00008000L +#define DM_FORMNAME 0x00010000L +#define DM_LOGPIXELS 0x00020000L +#define DM_BITSPERPEL 0x00040000L +#define DM_PELSWIDTH 0x00080000L +#define DM_PELSHEIGHT 0x00100000L +#define DM_DISPLAYFLAGS 0x00200000L +#define DM_DISPLAYFREQUENCY 0x00400000L +#define DM_ICMMETHOD 0x00800000L +#define DM_ICMINTENT 0x01000000L +#define DM_MEDIATYPE 0x02000000L +#define DM_DITHERTYPE 0x04000000L +#define DM_PANNINGWIDTH 0x08000000L +#define DM_PANNINGHEIGHT 0x10000000L +}; + +value WORD DMBinValues +{ +/* bin selections */ +#define DMBIN_UPPER 1 +#define DMBIN_ONLYONE 1 +#define DMBIN_LOWER 2 +#define DMBIN_MIDDLE 3 +#define DMBIN_MANUAL 4 +#define DMBIN_ENVELOPE 5 +#define DMBIN_ENVMANUAL 6 +#define DMBIN_AUTO 7 +#define DMBIN_TRACTOR 8 +#define DMBIN_SMALLFMT 9 +#define DMBIN_LARGEFMT 10 +#define DMBIN_LARGECAPACITY 11 +#define DMBIN_CASSETTE 14 +#define DMBIN_FORMSOURCE 15 +#define DMBIN_USER 256 /* device specific bins start here */ +}; + + +value WORD DMPrintQualityValues +{ +/* print qualities */ +#define DMRES_DRAFT -1 +#define DMRES_LOW -2 +#define DMRES_MEDIUM -3 +#define DMRES_HIGH -4 +}; + +value WORD DMColorEnableValues +{ +/* color enable/disable for color printers */ +#define DMCOLOR_MONOCHROME 1 +#define DMCOLOR_COLOR 2 +}; + +value WORD DMDuplexValues +{ +/* duplex enable */ +#define DMDUP_SIMPLEX 1 +#define DMDUP_VERTICAL 2 +#define DMDUP_HORIZONTAL 3 +}; + +value WORD DMTrueTypeValues +{ +/* TrueType options */ +#define DMTT_BITMAP 1 /* print TT fonts as graphics */ +#define DMTT_DOWNLOAD 2 /* download TT fonts as soft fonts */ +#define DMTT_SUBDEV 3 /* substitute device fonts for TT fonts */ +#define DMTT_DOWNLOAD_OUTLINE 4 /* download TT fonts as outline soft fonts */ +}; + +value WORD DMCollateValues +{ +/* Collation selections */ +#define DMCOLLATE_FALSE 0 +#define DMCOLLATE_TRUE 1 +}; + +/* DEVMODE dmDisplayFlags flags */ + +value DWORD DMDisplayFlagFields +{ +#define DM_GRAYSCALE 0x00000001 /* This flag is no longer valid */ +#define DM_INTERLACED 0x00000002 /* This flag is no longer valid */ +#define DMDISPLAYFLAGS_TEXTMODE 0x00000004 +}; + +value DWORD DMLogicalPageValues +{ +/* dmNup , multiple logical page per physical page options */ +#define DMNUP_SYSTEM 1 +#define DMNUP_ONEUP 2 +}; + +value DWORD DMICMMethodValues +{ +/* ICM methods */ +#define DMICMMETHOD_NONE 1 /* ICM disabled */ +#define DMICMMETHOD_SYSTEM 2 /* ICM handled by system */ +#define DMICMMETHOD_DRIVER 3 /* ICM handled by driver */ +#define DMICMMETHOD_DEVICE 4 /* ICM handled by device */ + +#define DMICMMETHOD_USER 256 /* Device-specific methods start here */ +}; +value DWORD DMICMIntentsValues +{ +/* ICM Intents */ +#define DMICM_SATURATE 1 /* Maximize color saturation */ +#define DMICM_CONTRAST 2 /* Maximize color contrast */ +#define DMICM_COLORIMETRIC 3 /* Use specific color metric */ +#define DMICM_ABS_COLORIMETRIC 4 /* Use specific color metric */ + +#define DMICM_USER 256 /* Device-specific intents start here */ +}; +value DWORD DMMediaTypeValues +{ +/* Media types */ +#define DMMEDIA_STANDARD 1 /* Standard paper */ +#define DMMEDIA_TRANSPARENCY 2 /* Transparency */ +#define DMMEDIA_GLOSSY 3 /* Glossy paper */ + +#define DMMEDIA_USER 256 /* Device-specific media start here */ +}; +value DWORD DMDitherTypeValues +{ +/* Dither types */ +#define DMDITHER_NONE 1 /* No dithering */ +#define DMDITHER_COARSE 2 /* Dither with a coarse brush */ +#define DMDITHER_FINE 3 /* Dither with a fine brush */ +#define DMDITHER_LINEART 4 /* LineArt dithering */ +#define DMDITHER_ERRORDIFFUSION 5 /* LineArt dithering */ +#define DMDITHER_RESERVED6 6 /* LineArt dithering */ +#define DMDITHER_RESERVED7 7 /* LineArt dithering */ +#define DMDITHER_RESERVED8 8 /* LineArt dithering */ +#define DMDITHER_RESERVED9 9 /* LineArt dithering */ +#define DMDITHER_GRAYSCALE 10 /* Device does grayscaling */ + +#define DMDITHER_USER 256 /* Device-specific dithers start here */ +}; + + +value WORD DMOrientationValues +{ +/* orientation selections */ +#define DMORIENT_PORTRAIT 1 +#define DMORIENT_LANDSCAPE 2 +}; + +value WORD DMPaperSelectionValues +{ +/* paper selections */ +#define DMPAPER_LETTER 1 /* Letter 8 1/2 x 11 in */ +#define DMPAPER_LETTERSMALL 2 /* Letter Small 8 1/2 x 11 in */ +#define DMPAPER_TABLOID 3 /* Tabloid 11 x 17 in */ +#define DMPAPER_LEDGER 4 /* Ledger 17 x 11 in */ +#define DMPAPER_LEGAL 5 /* Legal 8 1/2 x 14 in */ +#define DMPAPER_STATEMENT 6 /* Statement 5 1/2 x 8 1/2 in */ +#define DMPAPER_EXECUTIVE 7 /* Executive 7 1/4 x 10 1/2 in */ +#define DMPAPER_A3 8 /* A3 297 x 420 mm */ +#define DMPAPER_A4 9 /* A4 210 x 297 mm */ +#define DMPAPER_A4SMALL 10 /* A4 Small 210 x 297 mm */ +#define DMPAPER_A5 11 /* A5 148 x 210 mm */ +#define DMPAPER_B4 12 /* B4 (JIS) 250 x 354 */ +#define DMPAPER_B5 13 /* B5 (JIS) 182 x 257 mm */ +#define DMPAPER_FOLIO 14 /* Folio 8 1/2 x 13 in */ +#define DMPAPER_QUARTO 15 /* Quarto 215 x 275 mm */ +#define DMPAPER_10X14 16 /* 10x14 in */ +#define DMPAPER_11X17 17 /* 11x17 in */ +#define DMPAPER_NOTE 18 /* Note 8 1/2 x 11 in */ +#define DMPAPER_ENV_9 19 /* Envelope #9 3 7/8 x 8 7/8 */ +#define DMPAPER_ENV_10 20 /* Envelope #10 4 1/8 x 9 1/2 */ +#define DMPAPER_ENV_11 21 /* Envelope #11 4 1/2 x 10 3/8 */ +#define DMPAPER_ENV_12 22 /* Envelope #12 4 \276 x 11 */ +#define DMPAPER_ENV_14 23 /* Envelope #14 5 x 11 1/2 */ +#define DMPAPER_CSHEET 24 /* C size sheet */ +#define DMPAPER_DSHEET 25 /* D size sheet */ +#define DMPAPER_ESHEET 26 /* E size sheet */ +#define DMPAPER_ENV_DL 27 /* Envelope DL 110 x 220mm */ +#define DMPAPER_ENV_C5 28 /* Envelope C5 162 x 229 mm */ +#define DMPAPER_ENV_C3 29 /* Envelope C3 324 x 458 mm */ +#define DMPAPER_ENV_C4 30 /* Envelope C4 229 x 324 mm */ +#define DMPAPER_ENV_C6 31 /* Envelope C6 114 x 162 mm */ +#define DMPAPER_ENV_C65 32 /* Envelope C65 114 x 229 mm */ +#define DMPAPER_ENV_B4 33 /* Envelope B4 250 x 353 mm */ +#define DMPAPER_ENV_B5 34 /* Envelope B5 176 x 250 mm */ +#define DMPAPER_ENV_B6 35 /* Envelope B6 176 x 125 mm */ +#define DMPAPER_ENV_ITALY 36 /* Envelope 110 x 230 mm */ +#define DMPAPER_ENV_MONARCH 37 /* Envelope Monarch 3.875 x 7.5 in */ +#define DMPAPER_ENV_PERSONAL 38 /* 6 3/4 Envelope 3 5/8 x 6 1/2 in */ +#define DMPAPER_FANFOLD_US 39 /* US Std Fanfold 14 7/8 x 11 in */ +#define DMPAPER_FANFOLD_STD_GERMAN 40 /* German Std Fanfold 8 1/2 x 12 in */ +#define DMPAPER_FANFOLD_LGL_GERMAN 41 /* German Legal Fanfold 8 1/2 x 13 in */ +#define DMPAPER_ISO_B4 42 /* B4 (ISO) 250 x 353 mm */ +#define DMPAPER_JAPANESE_POSTCARD 43 /* Japanese Postcard 100 x 148 mm */ +#define DMPAPER_9X11 44 /* 9 x 11 in */ +#define DMPAPER_10X11 45 /* 10 x 11 in */ +#define DMPAPER_15X11 46 /* 15 x 11 in */ +#define DMPAPER_ENV_INVITE 47 /* Envelope Invite 220 x 220 mm */ +#define DMPAPER_RESERVED_48 48 /* RESERVED--DO NOT USE */ +#define DMPAPER_RESERVED_49 49 /* RESERVED--DO NOT USE */ +#define DMPAPER_LETTER_EXTRA 50 /* Letter Extra 9 \275 x 12 in */ +#define DMPAPER_LEGAL_EXTRA 51 /* Legal Extra 9 \275 x 15 in */ +#define DMPAPER_TABLOID_EXTRA 52 /* Tabloid Extra 11.69 x 18 in */ +#define DMPAPER_A4_EXTRA 53 /* A4 Extra 9.27 x 12.69 in */ +#define DMPAPER_LETTER_TRANSVERSE 54 /* Letter Transverse 8 \275 x 11 in */ +#define DMPAPER_A4_TRANSVERSE 55 /* A4 Transverse 210 x 297 mm */ +#define DMPAPER_LETTER_EXTRA_TRANSVERSE 56 /* Letter Extra Transverse 9\275 x 12 in */ +#define DMPAPER_A_PLUS 57 /* SuperA/SuperA/A4 227 x 356 mm */ +#define DMPAPER_B_PLUS 58 /* SuperB/SuperB/A3 305 x 487 mm */ +#define DMPAPER_LETTER_PLUS 59 /* Letter Plus 8.5 x 12.69 in */ +#define DMPAPER_A4_PLUS 60 /* A4 Plus 210 x 330 mm */ +#define DMPAPER_A5_TRANSVERSE 61 /* A5 Transverse 148 x 210 mm */ +#define DMPAPER_B5_TRANSVERSE 62 /* B5 (JIS) Transverse 182 x 257 mm */ +#define DMPAPER_A3_EXTRA 63 /* A3 Extra 322 x 445 mm */ +#define DMPAPER_A5_EXTRA 64 /* A5 Extra 174 x 235 mm */ +#define DMPAPER_B5_EXTRA 65 /* B5 (ISO) Extra 201 x 276 mm */ +#define DMPAPER_A2 66 /* A2 420 x 594 mm */ +#define DMPAPER_A3_TRANSVERSE 67 /* A3 Transverse 297 x 420 mm */ +#define DMPAPER_A3_EXTRA_TRANSVERSE 68 /* A3 Extra Transverse 322 x 445 mm */ + +#define DMPAPER_DBL_JAPANESE_POSTCARD 69 /* Japanese Double Postcard 200 x 148 mm */ +#define DMPAPER_A6 70 /* A6 105 x 148 mm */ +#define DMPAPER_JENV_KAKU2 71 /* Japanese Envelope Kaku #2 */ +#define DMPAPER_JENV_KAKU3 72 /* Japanese Envelope Kaku #3 */ +#define DMPAPER_JENV_CHOU3 73 /* Japanese Envelope Chou #3 */ +#define DMPAPER_JENV_CHOU4 74 /* Japanese Envelope Chou #4 */ +#define DMPAPER_LETTER_ROTATED 75 /* Letter Rotated 11 x 8 1/2 11 in */ +#define DMPAPER_A3_ROTATED 76 /* A3 Rotated 420 x 297 mm */ +#define DMPAPER_A4_ROTATED 77 /* A4 Rotated 297 x 210 mm */ +#define DMPAPER_A5_ROTATED 78 /* A5 Rotated 210 x 148 mm */ +#define DMPAPER_B4_JIS_ROTATED 79 /* B4 (JIS) Rotated 364 x 257 mm */ +#define DMPAPER_B5_JIS_ROTATED 80 /* B5 (JIS) Rotated 257 x 182 mm */ +#define DMPAPER_JAPANESE_POSTCARD_ROTATED 81 /* Japanese Postcard Rotated 148 x 100 mm */ +#define DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED 82 /* Double Japanese Postcard Rotated 148 x 200 mm */ +#define DMPAPER_A6_ROTATED 83 /* A6 Rotated 148 x 105 mm */ +#define DMPAPER_JENV_KAKU2_ROTATED 84 /* Japanese Envelope Kaku #2 Rotated */ +#define DMPAPER_JENV_KAKU3_ROTATED 85 /* Japanese Envelope Kaku #3 Rotated */ +#define DMPAPER_JENV_CHOU3_ROTATED 86 /* Japanese Envelope Chou #3 Rotated */ +#define DMPAPER_JENV_CHOU4_ROTATED 87 /* Japanese Envelope Chou #4 Rotated */ +#define DMPAPER_B6_JIS 88 /* B6 (JIS) 128 x 182 mm */ +#define DMPAPER_B6_JIS_ROTATED 89 /* B6 (JIS) Rotated 182 x 128 mm */ +#define DMPAPER_12X11 90 /* 12 x 11 in */ +#define DMPAPER_JENV_YOU4 91 /* Japanese Envelope You #4 */ +#define DMPAPER_JENV_YOU4_ROTATED 92 /* Japanese Envelope You #4 Rotated*/ +#define DMPAPER_P16K 93 /* PRC 16K 146 x 215 mm */ +#define DMPAPER_P32K 94 /* PRC 32K 97 x 151 mm */ +#define DMPAPER_P32KBIG 95 /* PRC 32K(Big) 97 x 151 mm */ +#define DMPAPER_PENV_1 96 /* PRC Envelope #1 102 x 165 mm */ +#define DMPAPER_PENV_2 97 /* PRC Envelope #2 102 x 176 mm */ +#define DMPAPER_PENV_3 98 /* PRC Envelope #3 125 x 176 mm */ +#define DMPAPER_PENV_4 99 /* PRC Envelope #4 110 x 208 mm */ +#define DMPAPER_PENV_5 100 /* PRC Envelope #5 110 x 220 mm */ +#define DMPAPER_PENV_6 101 /* PRC Envelope #6 120 x 230 mm */ +#define DMPAPER_PENV_7 102 /* PRC Envelope #7 160 x 230 mm */ +#define DMPAPER_PENV_8 103 /* PRC Envelope #8 120 x 309 mm */ +#define DMPAPER_PENV_9 104 /* PRC Envelope #9 229 x 324 mm */ +#define DMPAPER_PENV_10 105 /* PRC Envelope #10 324 x 458 mm */ +#define DMPAPER_P16K_ROTATED 106 /* PRC 16K Rotated */ +#define DMPAPER_P32K_ROTATED 107 /* PRC 32K Rotated */ +#define DMPAPER_P32KBIG_ROTATED 108 /* PRC 32K(Big) Rotated */ +#define DMPAPER_PENV_1_ROTATED 109 /* PRC Envelope #1 Rotated 165 x 102 mm */ +#define DMPAPER_PENV_2_ROTATED 110 /* PRC Envelope #2 Rotated 176 x 102 mm */ +#define DMPAPER_PENV_3_ROTATED 111 /* PRC Envelope #3 Rotated 176 x 125 mm */ +#define DMPAPER_PENV_4_ROTATED 112 /* PRC Envelope #4 Rotated 208 x 110 mm */ +#define DMPAPER_PENV_5_ROTATED 113 /* PRC Envelope #5 Rotated 220 x 110 mm */ +#define DMPAPER_PENV_6_ROTATED 114 /* PRC Envelope #6 Rotated 230 x 120 mm */ +#define DMPAPER_PENV_7_ROTATED 115 /* PRC Envelope #7 Rotated 230 x 160 mm */ +#define DMPAPER_PENV_8_ROTATED 116 /* PRC Envelope #8 Rotated 309 x 120 mm */ +#define DMPAPER_PENV_9_ROTATED 117 /* PRC Envelope #9 Rotated 324 x 229 mm */ +#define DMPAPER_PENV_10_ROTATED 118 /* PRC Envelope #10 Rotated 458 x 324 mm */ + +#define DMPAPER_USER 256 +}; + + +/* size of a device name string */ +//#define CCHDEVICENAME 32 + +/* size of a form name string */ +//#define CCHFORMNAME 32 + +typedef struct _devicemodeA { + BYTE dmDeviceName[/*CCHDEVICENAME */ 32]; + WORD dmSpecVersion; + WORD dmDriverVersion; + WORD dmSize; + WORD dmDriverExtra; + DMFieldFlags dmFields; +// union { +// struct { + DMOrientationValues dmOrientation; + DMPaperSelectionValues dmPaperSize; + WORD dmPaperLength; + WORD dmPaperWidth; + //}; +// POINTL dmPosition; +// }; + WORD dmScale; + WORD dmCopies; + DMBinValues dmDefaultSource; + DMPrintQualityValues dmPrintQuality; + DMColorEnableValues dmColor; + DMDuplexValues dmDuplex; + WORD dmYResolution; + DMTrueTypeValues dmTTOption; + DMCollateValues dmCollate; + BYTE dmFormName[/*CCHFORMNAME */ 32]; + WORD dmLogPixels; + DWORD dmBitsPerPel; + DWORD dmPelsWidth; + DWORD dmPelsHeight; + DMDisplayFlagFields dmDisplayFlags_OR_Nup; + DWORD dmDisplayFrequency; + DMICMMethodValues dmICMMethod; + DMICMIntentsValues dmICMIntent; + DMMediaTypeValues dmMediaType; + DMDitherTypeValues dmDitherType; + DWORD dmReserved1; + DWORD dmReserved2; + DWORD dmPanningWidth; + DWORD dmPanningHeight; +} DEVMODEA, *PDEVMODEA, *NPDEVMODEA, *LPDEVMODEA; + +typedef struct _devicemodeW { + WCHAR dmDeviceName[/*CCHDEVICENAME */ 32]; + WORD dmSpecVersion; + WORD dmDriverVersion; + WORD dmSize; + WORD dmDriverExtra; + DMFieldFlags dmFields; +// union { +// struct { + DMOrientationValues dmOrientation; + DMPaperSelectionValues dmPaperSize; + WORD dmPaperLength; + WORD dmPaperWidth; +// }; +// POINTL dmPosition; +// }; + WORD dmScale; + WORD dmCopies; + DMBinValues dmDefaultSource; + DMPrintQualityValues dmPrintQuality; + DMColorEnableValues dmColor; + DMDuplexValues dmDuplex; + WORD dmYResolution; + DMTrueTypeValues dmTTOption; + DMCollateValues dmCollate; + WCHAR dmFormName[/*CCHFORMNAME */ 32]; + WORD dmLogPixels; + DWORD dmBitsPerPel; + DWORD dmPelsWidth; + DWORD dmPelsHeight; + DMDisplayFlagFields dmDisplayFlags_OR_Nup; + DWORD dmDisplayFrequency; + DMICMMethodValues dmICMMethod; + DMICMIntentsValues dmICMIntent; + DMMediaTypeValues dmMediaType; + DMDitherTypeValues dmDitherType; + DWORD dmReserved1; + DWORD dmReserved2; + DWORD dmPanningWidth; + DWORD dmPanningHeight; +} DEVMODEW, *PDEVMODEW, *NPDEVMODEW, *LPDEVMODEW; + + + + + +value LONG GdiEscapeCode +{ +#define NEWFRAME 1 +#define ABORTDOC 2 +#define NEXTBAND 3 +#define SETCOLORTABLE 4 +#define GETCOLORTABLE 5 +#define FLUSHOUTPUT 6 +#define DRAFTMODE 7 +#define QUERYESCSUPPORT 8 +#define SETABORTPROC 9 +#define STARTDOC 10 +#define ENDDOC 11 +#define GETPHYSPAGESIZE 12 +#define GETPRINTINGOFFSET 13 +#define GETSCALINGFACTOR 14 +#define MFCOMMENT 15 +#define GETPENWIDTH 16 +#define SETCOPYCOUNT 17 +#define SELECTPAPERSOURCE 18 +#define DEVICEDATA 19 +#define PASSTHROUGH 19 +#define GETTECHNOLGY 20 +#define GETTECHNOLOGY 20 +#define SETLINECAP 21 +#define SETLINEJOIN 22 +#define SETMITERLIMIT 23 +#define BANDINFO 24 +#define DRAWPATTERNRECT 25 +#define GETVECTORPENSIZE 26 +#define GETVECTORBRUSHSIZE 27 +#define ENABLEDUPLEX 28 +#define GETSETPAPERBINS 29 +#define GETSETPRINTORIENT 30 +#define ENUMPAPERBINS 31 +#define SETDIBSCALING 32 +#define EPSPRINTING 33 +#define ENUMPAPERMETRICS 34 +#define GETSETPAPERMETRICS 35 +#define POSTSCRIPT_DATA 37 +#define POSTSCRIPT_IGNORE 38 +#define MOUSETRAILS 39 +#define GETDEVICEUNITS 42 + +#define GETEXTENDEDTEXTMETRICS 256 +#define GETEXTENTTABLE 257 +#define GETPAIRKERNTABLE 258 +#define GETTRACKKERNTABLE 259 +#define EXTTEXTOUT 512 +#define GETFACENAME 513 +#define DOWNLOADFACE 514 +#define ENABLERELATIVEWIDTHS 768 +#define ENABLEPAIRKERNING 769 +#define SETKERNTRACK 770 +#define SETALLJUSTVALUES 771 +#define SETCHARSET 772 + +#define STRETCHBLT 2048 +#define METAFILE_DRIVER 2049 +#define GETSETSCREENPARAMS 3072 +#define QUERYDIBSUPPORT 3073 +#define BEGIN_PATH 4096 +#define CLIP_TO_PATH 4097 +#define END_PATH 4098 +#define EXT_DEVICE_CAPS 4099 +#define RESTORE_CTM 4100 +#define SAVE_CTM 4101 +#define SET_ARC_DIRECTION 4102 +#define SET_BACKGROUND_COLOR 4103 +#define SET_POLY_MODE 4104 +#define SET_SCREEN_ANGLE 4105 +#define SET_SPREAD 4106 +#define TRANSFORM_CTM 4107 +#define SET_CLIP_BOX 4108 +#define SET_BOUNDS 4109 +#define SET_MIRROR_MODE 4110 +#define OPENCHANNEL 4110 +#define DOWNLOADHEADER 4111 +#define CLOSECHANNEL 4112 +#define POSTSCRIPT_PASSTHROUGH 4115 +#define ENCAPSULATED_POSTSCRIPT 4116 + +#define POSTSCRIPT_IDENTIFY 4117 /* new escape for NT5 pscript driver */ +#define POSTSCRIPT_INJECTION 4118 /* new escape for NT5 pscript driver */ + +#define CHECKJPEGFORMAT 4119 +#define CHECKPNGFORMAT 4120 + +#define GET_PS_FEATURESETTING 4121 /* new escape for NT5 pscript driver */ + +#define SPCLPASSTHROUGH2 4568 /* new escape for NT5 pscript driver */ +}; + +value LONG RegistryType +{ +#define REG_NONE 0 // No value type +#define REG_SZ 1 // Unicode nul terminated string +#define REG_EXPAND_SZ 2 // Unicode nul terminated string + // (with environment variable references) +#define REG_BINARY 3 // Free form binary +#define REG_DWORD 4 // 32-bit number +#define REG_DWORD_BIG_ENDIAN 5 // 32-bit number +#define REG_LINK 6 // Symbolic Link (unicode) +#define REG_MULTI_SZ 7 // Multiple Unicode strings +#define REG_RESOURCE_LIST 8 // Resource list in the resource map +#define REG_FULL_RESOURCE_DESCRIPTOR 9 // Resource list in the hardware description +#define REG_RESOURCE_REQUIREMENTS_LIST 10 +#define REG_QWORD 11 // 64-bit number +}; + +value LONG ShowWindowCommand +{ +#define SW_HIDE 0 +#define SW_NORMAL 1 +#define SW_SHOWMINIMIZED 2 +#define SW_MAXIMIZE 3 +#define SW_SHOWNOACTIVATE 4 +#define SW_SHOW 5 +#define SW_MINIMIZE 6 +#define SW_SHOWMINNOACTIVE 7 +#define SW_SHOWNA 8 +#define SW_RESTORE 9 +#define SW_SHOWDEFAULT 10 +#define SW_FORCEMINIMIZE 11 +}; + +mask DWORD AccessMode +{ +#define PIPE_ACCESS_INBOUND 0x00000001 +#define PIPE_ACCESS_OUTBOUND 0x00000002 +#define PIPE_ACCESS_DUPLEX 0x00000003 +#define FILE_FLAG_WRITE_THROUGH 0x80000000 +#define FILE_FLAG_OVERLAPPED 0x40000000 +#define ACCESS_SYSTEM_SECURITY 0x01000000 + +#define DELETE 0x00010000 +#define READ_CONTROL 0x00020000 +#define WRITE_DAC 0x00040000 +#define WRITE_OWNER 0x00080000 +#define SYNCHRONIZE 0x00100000 + +// +// AccessSystemAcl access type +// + +#define ACCESS_SYSTEM_SECURITY 0x01000000 + +// +// MaximumAllowed access type +// + +#define MAXIMUM_ALLOWED 0x02000000 + +// +// These are the generic rights. +// + +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 +#define GENERIC_EXECUTE 0x20000000 +#define GENERIC_ALL 0x10000000 +}; + +alias HWND; +alias LPVOID; +alias HANDLE; +alias HDC; +alias HRGN; +alias HMENU; +alias HRSRC; +alias HACCEL; +alias HHOOK; +alias HINSTANCE; +alias HMODULE; +alias HKL; +alias HDESK; +alias HWINSTA; +alias HMONITOR; +alias WNDPROC; +alias HPRINTER; +alias ThreadId; +alias ProcessId; + +#include "kernel32.h" +#include "user32.h" +#include "gdi32.h" +#include "winspool.h" +#include "version.h" +#include "winsock2.h" +#include "advapi32.h" + +// +// uuids.h and com.h are needed for all the headers that have COM definitions. +// Those headers are: shell.h ole32.h ddraw.h dplay.h +// +#include "uuids.h" +#include "com.h" + +#include "shell.h" +#include "ole32.h" + +// +// ddraw is needed for all the headers that have DirectX definitions. +// Those headers are: d3d.h d3d8.h +// +#include "ddraw.h" +#include "winmm.h" +#include "avifile.h" +#include "dplay.h" +#include "d3d.h" +#include "d3d8.h" +#include "dsound.h" diff --git a/tools/Debugging Tools for Windows/winext/manifest/memory.h b/tools/Debugging Tools for Windows/winext/manifest/memory.h new file mode 100644 index 0000000000..ab440816ee --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/memory.h @@ -0,0 +1,251 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Memory Management Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category MemoryManagementFunctions: + +mask DWORD AccessProtectionType +{ +#define PAGE_NOACCESS 0x01 +#define PAGE_READONLY 0x02 +#define PAGE_READWRITE 0x04 +#define PAGE_WRITECOPY 0x08 +#define PAGE_EXECUTE 0x10 +#define PAGE_EXECUTE_READ 0x20 +#define PAGE_EXECUTE_READWRITE 0x40 +#define PAGE_EXECUTE_WRITECOPY 0x80 +#define PAGE_GUARD 0x100 +#define PAGE_NOCACHE 0x200 +#define PAGE_WRITECOMBINE 0x400 +#define SEC_FILE 0x800000 +#define SEC_IMAGE 0x1000000 +#define SEC_VLM 0x2000000 +#define SEC_RESERVE 0x4000000 +#define SEC_COMMIT 0x8000000 +#define SEC_NOCACHE 0x10000000 +}; + + +value UINT UintFailIfGlobalMemInvalid +{ +#define GMEM_INVALID_HANDLE 0x8000 [fail] +}; + +value UINT UintFailIfLocalMemInvalid +{ +#define LMEM_INVALID_HANDLE 0x8000 [fail] +}; + +value PVOID64 Pvoid64FailIfNull +{ +#define NULL 0 [fail] +}; + +value LPVOID HeapAllocFailureReturns +{ +#define NULL 0 [fail] +#define STATUS_NO_MEMORY 0xC0000017L [fail] +#define STATUS_ACCESS_VIOLATION 0xC0000005L [fail] +}; + +mask UINT AllocationAttributes +{ +#define GMEM_FIXED 0x0000 +#define GMEM_MOVEABLE 0x0002 +// #define GMEM_DDESHARE 0x2000 +#define GMEM_SHARE 0x2000 +#define GMEM_DISCARDABLE 0x0100 +#define GMEM_LOWER 0x1000 +#define GMEM_NOCOMPACT 0x0010 +#define GMEM_NODISCARD 0x0020 +#define GMEM_NOT_BANKED 0x1000 +#define GMEM_NOTIFY 0x4000 +#define GMEM_ZEROINIT 0x0040 +// #define GMEM_MODIFY 0x0080 +// #define GMEM_VALID_FLAGS 0x7F72 +// #define GMEM_INVALID_HANDLE 0x8000 +}; + +mask UINT LocalAllocationAttributes +{ +#define LMEM_FIXED 0x0000 +#define LMEM_MOVEABLE 0x0002 +#define LMEM_DISCARDABLE 0x0F00 +#define LMEM_NOCOMPACT 0x0010 +#define LMEM_NODISCARD 0x0020 +#define LMEM_ZEROINIT 0x0040 +// #define LMEM_MODIFY 0x0080 +// #define LMEM_VALID_FLAGS 0x0F72 +// #define LMEM_INVALID_HANDLE 0x8000 +}; + +typedef struct _MEMORYSTATUS { + DWORD dwLength; + DWORD dwMemoryLoad; + SIZE_T dwTotalPhys; + SIZE_T dwAvailPhys; + SIZE_T dwTotalPageFile; + SIZE_T dwAvailPageFile; + SIZE_T dwTotalVirtual; + SIZE_T dwAvailVirtual; +} MEMORYSTATUS, *LPMEMORYSTATUS; + +typedef struct _MEMORYSTATUSEX { + DWORD dwLength; + DWORD dwMemoryLoad; + DWORDLONG ullTotalPhys; + DWORDLONG ullAvailPhys; + DWORDLONG ullTotalPageFile; + DWORDLONG ullAvailPageFile; + DWORDLONG ullTotalVirtual; + DWORDLONG ullAvailVirtual; + DWORDLONG ullAvailExtendedVirtual; +} MEMORYSTATUSEX, *LPMEMORYSTATUSEX; + +mask DWORD HeapAllocationControl +{ +#define HEAP_GENERATE_EXCEPTIONS 0x00000004 +#define HEAP_NO_SERIALIZE 0x00000001 +#define HEAP_ZERO_MEMORY 0x00000008 +}; + +typedef struct _Region { + DWORD dwCommittedSize; + DWORD dwUnCommittedSize; + LPVOID lpFirstBlock; + LPVOID lpLastBlock; + } Region; + +typedef struct _PROCESS_HEAP_ENTRY { + PVOID lpData; + DWORD cbData; + BYTE cbOverhead; + BYTE iRegionIndex; + WORD wFlags; + Region Region; +} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY; + +value DWORD FileAccessMode +{ +#define FILE_MAP_COPY 0x0001 +#define FILE_MAP_WRITE 0x0002 +#define FILE_MAP_READ 0x0004 +}; + +mask DWORD AllocationType +{ +#define MEM_COMMIT 0x1000 +#define MEM_RESERVE 0x2000 +#define MEM_DECOMMIT 0x4000 +#define MEM_RELEASE 0x8000 +#define MEM_FREE 0x10000 +#define MEM_PRIVATE 0x20000 +#define MEM_MAPPED 0x40000 +#define MEM_RESET 0x80000 +#define MEM_TOP_DOWN 0x100000 +#define MEM_4MB_PAGES 0x80000000 +//#define MEM_IMAGE SEC_IMAGE +}; + +typedef struct _MEMORY_BASIC_INFORMATION { + PVOID BaseAddress; + PVOID AllocationBase; + AccessProtectionType AllocationProtect; + SIZE_T RegionSize; + AllocationType State; + AllocationType Protect; + DWORD Type; +} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; + +typedef struct _MEMORY_BASIC_INFORMATION_VLM { + PVOID64 BaseAddress; + // ULONGLONG BaseAddressAsUlongLong; Part of a Union + PVOID64 AllocationBase; + // ULONGLONG AllocationBaseAsUlongLong; Part of a Union + ULONGLONG RegionSize; + AccessProtectionType AllocationProtect; + AllocationType State; + AllocationType Protect; + DWORD Type; +} MEMORY_BASIC_INFORMATION_VLM, *PMEMORY_BASIC_INFORMATION_VLM; + +typedef struct _SYSTEM_INFO { + DWORD dwOemId; + DWORD dwPageSize; + LPVOID lpMinimumApplicationAddress; + LPVOID lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; +} SYSTEM_INFO; + +typedef HANDLE HeapHandle; + +alias HeapHandle; + + +module KERNEL32.DLL: + +FailOnFalse [gle] AllocateUserPhysicalPages(HANDLE hProcess,[out] PULONG_PTR NumberOfPages,[out] PULONG_PTR UserPfnArray); +FailOnFalse [gle] FreeUserPhysicalPages(HANDLE hProcess,[out] PULONG_PTR NumberOfPages,PULONG_PTR UserPfnArray); +HANDLE [gle] GetProcessHeap(); +DwordFailIfZero [gle] GetProcessHeaps(DWORD NumberOfHeaps,[out] PHANDLE ProcessHeaps); +UINT GetWriteWatch(DWORD dwFlags,PVOID lpBaseAddress,SIZE_T dwRegionSize,[out] PVOID *lpAddresses,[out] PULONG_PTR lpdwCount,[out] PULONG lpdwGranularity); +HGLOBAL [gle] GlobalAlloc(AllocationAttributes uFlags,SIZE_T dwBytes); +UintFailIfGlobalMemInvalid [gle] GlobalFlags(HGLOBAL hMem); +HGLOBAL [gle] GlobalFree(HGLOBAL hMem); +HGLOBAL [gle] GlobalHandle(LPCVOID pMem); +LpvoidFailIfNull [gle] GlobalLock(HGLOBAL hMem); +VOID GlobalMemoryStatus([out] LPMEMORYSTATUS lpBuffer); +FailOnFalse [gle] GlobalMemoryStatusEx([out] LPMEMORYSTATUSEX lpBuffer); +HGLOBAL [gle] GlobalReAlloc(HGLOBAL hMem,SIZE_T dwBytes,AllocationAttributes uFlags); +DwordFailIfZero [gle] GlobalSize(HGLOBAL hMem); +FailOnFalse [gle] GlobalUnlock(HGLOBAL hMem); + +HeapAllocFailureReturns HeapAlloc(HeapHandle hHeap,HeapAllocationControl dwFlags,SIZE_T dwBytes); +LongFailIfZero [gle] HeapCompact(HeapHandle hHeap,HeapAllocationControl dwFlags); +HeapHandle [gle] HeapCreate(HeapAllocationControl flOptions,SIZE_T dwInitialSize,SIZE_T dwMaximumSize); +FailOnFalse [gle] HeapDestroy( [da] HeapHandle hHeap); +FailOnFalse [gle] HeapFree(HeapHandle hHeap,HeapAllocationControl dwFlags,LPVOID lpMem); +FailOnFalse [gle] HeapLock(HeapHandle hHeap); +HeapAllocFailureReturns HeapReAlloc(HeapHandle hHeap,HeapAllocationControl dwFlags,LPVOID lpMem,SIZE_T dwBytes); +DwordFailIfNeg1 HeapSize(HeapHandle hHeap,HeapAllocationControl dwFlags,LPCVOID lpMem); +FailOnFalse [gle] HeapUnlock(HeapHandle hHeap); +FailOnFalse HeapValidate(HeapHandle hHeap,HeapAllocationControl dwFlags,LPCVOID lpMem); +FailOnFalse [gle] HeapWalk(HeapHandle hHeap,[out] LPPROCESS_HEAP_ENTRY lpEntry); + +HLOCAL [gle] LocalAlloc(LocalAllocationAttributes uFlags,SIZE_T uBytes); +UintFailIfLocalMemInvalid LocalFlags(HLOCAL hMem); +HLOCAL [gle] LocalFree(HLOCAL hMem); +HLOCAL [gle] LocalHandle(LPCVOID pMem); +LpvoidFailIfNull [gle] LocalLock(HLOCAL hMem); +HLOCAL [gle] LocalReAlloc(HLOCAL hMem,SIZE_T uBytes,LocalAllocationAttributes uFlags); +LongFailIfZero [gle] LocalSize(HLOCAL hMem); +FailOnFalse [gle] LocalUnlock(HLOCAL hMem); +FailOnFalse [gle] MapUserPhysicalPages(PVOID lpAddress,ULONG_PTR NumberOfPages,PULONG_PTR UserPfnArray); +FailOnFalse [gle] MapUserPhysicalPagesScatter(PVOID *VirtualAddresses,ULONG_PTR NumberOfPages,PULONG_PTR PageArray); +UINT ResetWriteWatch(LPVOID lpBaseAddress,SIZE_T dwRegionSize); +LpvoidFailIfNull [gle] VirtualAlloc(LPVOID lpAddress,SIZE_T dwSize,AllocationType flAllocationType,AccessProtectionType flProtect); +LpvoidFailIfNull [gle] VirtualAllocEx(HANDLE hProcess,LPVOID lpAddress,SIZE_T dwSize,AllocationType flAllocationType,AccessProtectionType flProtect); +FailOnFalse [gle] VirtualFree(LPVOID lpAddress,SIZE_T dwSize,AllocationType dwFreeType); +FailOnFalse [gle] VirtualFreeEx(HANDLE hProcess,LPVOID lpAddress,SIZE_T dwSize,AllocationType dwFreeType); +FailOnFalse [gle] VirtualLock(LPVOID lpAddress,SIZE_T dwSize); +FailOnFalse [gle] VirtualProtect(LPVOID lpAddress,SIZE_T dwSize,AccessProtectionType flNewProtect,[out] PDWORD lpflOldProtect); +FailOnFalse [gle] VirtualProtectEx(HANDLE hProcess,LPVOID lpAddress,SIZE_T dwSize,AccessProtectionType flNewProtect,[out] PDWORD lpflOldProtect); +DWORD VirtualQuery(LPCVOID lpAddress,[out] PMEMORY_BASIC_INFORMATION lpBuffer,DWORD dwLength); +DWORD VirtualQueryEx(HANDLE hProcess,LPCVOID lpAddress,[out] PMEMORY_BASIC_INFORMATION lpBuffer,DWORD dwLength); +FailOnFalse [gle] VirtualUnlock(LPVOID lpAddress,SIZE_T dwSize); + +// File mapping functions +HANDLE [gle] CreateFileMappingA(HANDLE hFile,LPSECURITY_ATTRIBUTES lpAttributes,AccessProtectionType flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPCSTR lpName); +HANDLE [gle] CreateFileMappingW(HANDLE hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,AccessProtectionType flProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPCWSTR lpName); +FailOnFalse [gle] FlushViewOfFile (LPCVOID lpBaseAddress,SIZE_T dwNumberOfBytesToFlush); +LpvoidFailIfNull [gle] MapViewOfFile(HANDLE hFileMappingObject,FileAccessMode dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap); +LpvoidFailIfNull [gle] MapViewOfFileEx(HANDLE hFileMappingObject,FileAccessMode dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap,LPVOID lpBaseAddress); +HANDLE [gle] OpenFileMappingA(FileAccessMode dwDesiredAccess,BOOL bInheritHandle,LPCSTR lpName); +HANDLE [gle] OpenFileMappingW(FileAccessMode dwDesiredAccess,BOOL bInheritHandle,LPCWSTR lpName); +FailOnFalse [gle] UnmapViewOfFile(LPCVOID lpBaseAddress); diff --git a/tools/Debugging Tools for Windows/winext/manifest/ole32.h b/tools/Debugging Tools for Windows/winext/manifest/ole32.h new file mode 100644 index 0000000000..e25ca59de6 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/ole32.h @@ -0,0 +1,86 @@ +module OLE32.DLL: +category ComponentObjectModel: + +value DWORD CLSCTX +{ +#define CLSCTX_INPROC_SERVER 1 +#define CLSCTX_INPROC_HANDLER 2 +#define CLSCTX_LOCAL_SERVER 4 +#define CLSCTX_REMOTE_SERVER 16 +}; + +typedef struct _COAUTHIDENTITY + { + USHORT *User; + ULONG UserLength; + USHORT *Domain; + ULONG DomainLength; + USHORT *Password; + ULONG PasswordLength; + ULONG Flags; + } COAUTHIDENTITY; + +typedef struct _COAUTHINFO + { + DWORD dwAuthnSvc; + DWORD dwAuthzSvc; + LPWSTR pwszServerPrincName; + DWORD dwAuthnLevel; + DWORD dwImpersonationLevel; + COAUTHIDENTITY *pAuthIdentityData; + DWORD dwCapabilities; + } COAUTHINFO; + +typedef struct _COSERVERINFO + { + DWORD dwReserved1; + LPWSTR pwszName; + COAUTHINFO *pAuthInfo; + DWORD dwReserved2; + } COSERVERINFO; + +typedef struct _COSERVERINFO2 + { + DWORD dwFlags; + LPWSTR pwszName; + COAUTHINFO *pAuthInfo; + IUnknown** ppCall; + LPWSTR pwszCodeURL; + DWORD dwFileVersionMS; + DWORD dwFileVersionLS; + LPWSTR pwszContentType; + } COSERVERINFO2; + + +STDAPI CoCreateInstance( + REFCLSID rclsid, //Class identifier (CLSID) of the object + LPUNKNOWN pUnkOuter, //Pointer to controlling IUnknown + CLSCTX dwClsContext, //Context for running executable code + [iid] REFIID riid, //Reference to the identifier of the interface + [out] COM_INTERFACE_PTR * ppv //Address of output variable that receives + // the interface pointer requested in riid +); + +HRESULT CoCreateInstanceEx( + REFCLSID rclsid, //CLSID of the object to be created + IUnknown *punkOuter, //If part of an aggregate, the + // controlling IUnknown + CLSCTX dwClsCtx, //CLSCTX values + COSERVERINFO *pServerInfo, //Machine on which the object is to + // be instantiated + ULONG cmq, //Number of MULTI_QI structures in + // pResults + PVOID *pResults //Array of MULTI_QI structures +); + +STDAPI CoGetClassObject( + REFCLSID rclsid, //CLSID associated with the class object + CLSCTX dwClsContext, + //Context for running executable code + COSERVERINFO * pServerInfo, + //Pointer to machine on which the object is to + // be instantiated + [iid] REFIID riid, //Reference to the identifier of the interface + [out] COM_INTERFACE_PTR * ppv //Address of output variable that receives the + // interface pointer requested in riid +); diff --git a/tools/Debugging Tools for Windows/winext/manifest/processes.h b/tools/Debugging Tools for Windows/winext/manifest/processes.h new file mode 100644 index 0000000000..1e024e35d1 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/processes.h @@ -0,0 +1,616 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Processes and Threads +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category ProcessesAndThreads: + +mask DWORD CreateProcessCreationFlags +{ +#define DEBUG_PROCESS 0x00000001 +#define DEBUG_ONLY_THIS_PROCESS 0x00000002 +#define CREATE_SUSPENDED 0x00000004 +#define DETACHED_PROCESS 0x00000008 +#define CREATE_NEW_CONSOLE 0x00000010 +#define NORMAL_PRIORITY_CLASS 0x00000020 +#define IDLE_PRIORITY_CLASS 0x00000040 +#define HIGH_PRIORITY_CLASS 0x00000080 +#define REALTIME_PRIORITY_CLASS 0x00000100 +#define CREATE_NEW_PROCESS_GROUP 0x00000200 +#define CREATE_UNICODE_ENVIRONMENT 0x00000400 +#define CREATE_SEPARATE_WOW_VDM 0x00000800 +#define CREATE_SHARED_WOW_VDM 0x00001000 +#define CREATE_FORCEDOS 0x00002000 +#define CREATE_DEFAULT_ERROR_MODE 0x04000000 +#define CREATE_NO_WINDOW 0x08000000 + +}; + +value DWORD DLLReason +{ +#define DLL_PROCESS_DETACH 0 +#define DLL_PROCESS_ATTACH 1 +#define DLL_THREAD_ATTACH 2 +#define DLL_THREAD_DETACH 3 +}; + +mask DWORD LoadLibFlags +{ +#define DONT_RESOLVE_DLL_REFERENCES 0x00000001 +#define LOAD_LIBRARY_AS_DATAFILE 0x00000002 +#define LOAD_WITH_ALTERED_SEARCH_PATH 0x00000008 +}; + + +value DWORD GetGuiResourcesFlags +{ +#define GR_GDIOBJECTS 0 +#define GR_USEROBJECTS 1 +}; + +typedef struct _IO_COUNTERS { + ULONGLONG ReadOperationCount; + ULONGLONG WriteOperationCount; + ULONGLONG OtherOperationCount; + ULONGLONG ReadTransferCount; + ULONGLONG WriteTransferCount; + ULONGLONG OtherTransferCount; +} IO_COUNTERS; +typedef IO_COUNTERS *LPIO_COUNTERS; + +value LPDWORD ProcessShutdownFlags +{ +#define SHUTDOWN_NORETRY 0x00000001 +}; + +/* +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME, *LPFILETIME; +*/ + +mask DWORD JobObjectAccess +{ +#define JOB_OBJECT_ASSIGN_PROCESS 0x0001 +#define JOB_OBJECT_SET_ATTRIBUTES 0x0002 +#define JOB_OBJECT_QUERY 0x0004 +#define JOB_OBJECT_TERMINATE 0x0008 +#define JOB_OBJECT_SET_SECURITY_ATTRIBUTES 0x0010 +}; + +mask DWORD ProcessAccess +{ +#define PROCESS_CREATE_PROCESS 0x0080 +#define PROCESS_CREATE_THREAD 0x0002 +#define PROCESS_DUP_HANDLE 0x0040 +#define PROCESS_QUERY_INFORMATION 0x0400 +#define PROCESS_SET_QUOTA 0x0100 +#define PROCESS_SET_INFORMATION 0x0200 +#define PROCESS_TERMINATE 0x0001 +#define PROCESS_VM_OPERATION 0x0008 +#define PROCESS_VM_READ 0x0010 +#define PROCESS_VM_WRITE 0x0020 +#define SYNCHRONIZE 0x00100000L +}; + +mask DWORD ThreadAccess +{ +#define SYNCHRONIZE 0x00100000L +#define THREAD_TERMINATE 0x0001 +#define THREAD_SUSPEND_RESUME 0x0002 +#define THREAD_GET_CONTEXT 0x0008 +#define THREAD_SET_CONTEXT 0x0010 +#define THREAD_SET_INFORMATION 0x0020 +#define THREAD_QUERY_INFORMATION 0x0040 +#define THREAD_SET_THREAD_TOKEN 0x0080 +}; + +mask ULONG QueueUserWorkItemFlags +{ +#define WT_EXECUTEDEFAULT 0x00000000 +#define WT_EXECUTEINIOTHREAD 0x00000001 +#define WT_EXECUTEINUITHREAD 0x00000002 +#define WT_EXECUTEINWAITTHREAD 0x00000004 +#define WT_EXECUTEDELETEWAIT 0x00000008 +#define WT_EXECUTEINLONGTHREAD 0x00000010 +}; + +value DWORD SleepExReturnValue +{ +#define Interval_Expired 0 +#define Returned_due_to_CallBack 0x000000C0 +}; + +value DWORD ExceptionFlags +{ +#define NULL 0 +#define EXCEPTION_NONCONTINUABLE 0x1 +}; + +module KERNEL32.DLL: + +FailOnFalse AssignProcessToJobObject( + HANDLE hJob, + HANDLE hProcess + ); + +module SHELL32.DLL: +LpwstrFailIfNull * [gle] CommandLineToArgvW( + LPCWSTR lpCmdLine, + INT *pNumArgs + ); + +module KERNEL32.DLL: +LpvoidFailIfNull [gle] ConvertThreadToFiber( + LPVOID lpParameter + ); + +LpvoidFailIfNull [gle] CreateFiber( + DWORD dwStackSize, + LPFIBER_START_ROUTINE lpStartAddress, + LPVOID lpParameter + ); + +HANDLE [gle] CreateJobObjectA( + LPSECURITY_ATTRIBUTES lpJobAttributes, + LPCSTR lpName + ); + +HANDLE [gle] CreateJobObjectW( + LPSECURITY_ATTRIBUTES lpJobAttributes, + LPCWSTR lpName + ); + +FailOnFalse [gle] CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + CreateProcessCreationFlags dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + [out] LPSTARTUPINFOA lpStartupInfo, + [out] LPPROCESS_INFORMATION lpProcessInformation + ); + +FailOnFalse [gle] CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + CreateProcessCreationFlags dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + +module ADVAPI32.DLL: +FailOnFalse [gle] CreateProcessAsUserA ( + HANDLE hToken, + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + CreateProcessCreationFlags dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + +FailOnFalse [gle] CreateProcessAsUserW ( + HANDLE hToken, + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + CreateProcessCreationFlags dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + +FailOnFalse [gle] CreateProcessWithLogonW( + LPCWSTR lpUsername, + LPCWSTR lpDomain, + LPCWSTR lpPassword, + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + CreateProcessCreationFlags dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ); + +module KERNEL32.DLL: +HANDLE [gle] CreateRemoteThread( + HANDLE hProcess, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + DWORD dwStackSize, + LPTHREAD_START_ROUTINE lpStartAddress, + LPVOID lpParameter, + CreateProcessCreationFlags dwCreationFlags, + LPDWORD lpThreadId + ); + +HANDLE [gle] CreateThread( + LPSECURITY_ATTRIBUTES lpThreadAttributes, + DWORD dwStackSize, + LPTHREAD_START_ROUTINE lpStartAddress, + LPVOID lpParameter, + CreateProcessCreationFlags dwCreationFlags, + LPDWORD lpThreadId + ); + +VOID DeleteFiber( + LPVOID lpFiber + ); + +VOID ExitProcess( + UINT uExitCode + ); + +VOID ExitThread( + DWORD dwExitCode + ); + +FailOnFalse [gle] FreeEnvironmentStringsA( + LPSTR lpszEnvironmentBlock + ); + +FailOnFalse [gle] FreeEnvironmentStringsW( + LPWSTR lpszEnvironmentBlock + ); + +LPSTR GetCommandLineA(); + +LPWSTR GetCommandLineW(); + +DWORD GetCurrentProcess(); + +ProcessId GetCurrentProcessId(); +ThreadId GetCurrentThreadId(); + +HANDLE GetCurrentThread(); + +LPSTR GetEnvironmentStrings(); + +LPWSTR GetEnvironmentStringsW(); + +DwordFailIfZero GetEnvironmentVariableA( + LPCSTR lpName, + [out] LPSTR lpBuffer, + DWORD nSize + ); + +DwordFailIfZero GetEnvironmentVariableW( + LPCWSTR lpName, + [out] LPWSTR lpBuffer, + DWORD nSize + ); + +FailOnFalse [gle] GetExitCodeProcess( + HANDLE hProcess, + [out] LPDWORD lpExitCode + ); + +FailOnFalse [gle] GetExitCodeThread( + HANDLE hThread, + [out] LPDWORD lpExitCode + ); + +module USER32.DLL: +DwordFailIfZero GetGuiResources ( + HANDLE hProcess, + GetGuiResourcesFlags uiFlags + ); + +module KERNEL32.DLL: +DWORD GetPriorityClass( + HANDLE hProcess + ); + +FailOnFalse [gle] GetProcessAffinityMask( + HANDLE hProcess, + [out] LPDWORD lpProcessAffinityMask, + [out] LPDWORD lpSystemAffinityMask + ); + +FailOnFalse [gle] GetProcessIoCounters( + HANDLE hProcess, + [out] LPIO_COUNTERS lpIoCounters + ); + +FailOnFalse [gle] GetProcessPriorityBoost( + HANDLE hProcess, + [out] PBOOL pDisablePriorityBoost + ); + +FailOnFalse [gle] GetProcessShutdownParameters( + [out] LPDWORD lpdwLevel, + [out] ProcessShutdownFlags *lpdwFlags + ); + +FailOnFalse [gle] GetProcessTimes( + HANDLE hProcess, + [out] LPFILETIME lpCreationTime, + [out] LPFILETIME lpExitTime, + [out] LPFILETIME lpKernelTime, + [out] LPFILETIME lpUserTime + ); + +DwordFailIfZero [gle] GetProcessVersion( + ProcessId dwProcessId + ); + +FailOnFalse [gle] GetProcessWorkingSetSize( + HANDLE hProcess, + [out] LPDWORD lpMinimumWorkingSetSize, + [out] LPDWORD lpMaximumWorkingSetSize + ); + +VOID GetStartupInfoA( + [out] LPSTARTUPINFOA lpStartupInfo + ); + +VOID GetStartupInfoW( + [out] LPSTARTUPINFOW lpStartupInfo + ); + +ThreadPriority [gle] GetThreadPriority( + HANDLE hThread + ); + +FailOnFalse [gle] GetThreadPriorityBoost( + HANDLE hThread, + [out] PBOOL pDisablePriorityBoost + ); + +FailOnFalse [gle] GetThreadTimes( + HANDLE hThread, + [out] LPFILETIME lpCreationTime, + [out] LPFILETIME lpExitTime, + [out] LPFILETIME lpKernelTime, + [out] LPFILETIME lpUserTime + ); + +HANDLE [gle] OpenJobObjectA( + JobObjectAccess dwDesiredAccess, + BOOL bInheritHandles, + LPCSTR lpName + ); + +HANDLE [gle] OpenJobObjectW( + JobObjectAccess dwDesiredAccess, + BOOL bInheritHandles, + LPCWSTR lpName + ); + +HANDLE [gle] OpenProcess( + ProcessAccess dwDesiredAccess, + BOOL bInheritHandle, + DWORD dwProcessId + ); + +HANDLE [gle] OpenThread( + ThreadAccess dwDesiredAccess, + BOOL bInheritHandle, + DWORD dwThreadId + ); + +FailOnFalse [gle] QueryInformationJobObject( + HANDLE hJob, + DWORD JobObjectInformationClass, + LPVOID lpJobObjectInformation, + DWORD cbJobObjectInformationLength, + [out] LPDWORD lpReturnLength + ); + +FailOnFalse QueueUserWorkItem( + LPTHREAD_START_ROUTINE Function, + PVOID Context, + QueueUserWorkItemFlags Flags + ); + +VOID RaiseException(DWORD dwExceptionCode, + ExceptionFlags dwExceptionFlags, + DWORD nNumberOfArguments, + DWORD* lpArguments); + +DwordFailIf0xFFFFFFFF [gle] ResumeThread( + HANDLE hThread + ); + +FailOnFalse [gle] SetEnvironmentVariableA( + LPCSTR lpName, + LPCSTR lpValue + ); + +FailOnFalse [gle] SetEnvironmentVariableW( + LPCWSTR lpName, + LPCWSTR lpValue + ); + +BOOL SetInformationJobObject( + HANDLE hJob, + DWORD JobObjectInformationClass, + LPVOID lpJobObjectInformation, + DWORD cbJobObjectInformationLength + ); + +FailOnFalse [gle] SetPriorityClass( + HANDLE hProcess, + CreateProcessCreationFlags dwPriorityClass + ); + +FailOnFalse [gle] SetProcessAffinityMask( + HANDLE hProcess, + DWORD dwProcessAffinityMask + ); + +FailOnFalse [gle] SetProcessPriorityBoost( + HANDLE hProcess, + BOOL DisablePriorityBoost + ); + +FailOnFalse [gle] SetProcessShutdownParameters( + DWORD dwLevel, + ProcessShutdownFlags dwFlags + ); + +FailOnFalse [gle] SetProcessWorkingSetSize( + HANDLE hProcess, + DWORD dwMinimumWorkingSetSize, + DWORD dwMaximumWorkingSetSize + ); + +DwordFailIfZero SetThreadAffinityMask ( + HANDLE hThread, + DWORD dwThreadAffinityMask + ); + +DwordFailIfNeg1 [gle] SetThreadIdealProcessor( + HANDLE hThread, + DWORD dwIdealProcessor + ); + +FailOnFalse [gle] SetThreadPriority( + HANDLE hThread, + ThreadPriority nPriority + ); + +FailOnFalse [gle] SetThreadPriorityBoost( + HANDLE hThread, + BOOL DisablePriorityBoost + ); + +VOID Sleep( + DWORD dwMilliseconds + ); + +SleepExReturnValue SleepEx( + DWORD dwMilliseconds, + BOOL bAlertable + ); + +DwordFailIf0xFFFFFFFF [gle] SuspendThread( + HANDLE hThread + ); + +VOID SwitchToFiber( + LPVOID lpFiber + ); + +BOOL SwitchToThread(); + +FailOnFalse [gle] TerminateJobObject( + HANDLE hJob, + UINT uExitCode + ); + +FailOnFalse [gle] TerminateProcess( + HANDLE hProcess, + UINT uExitCode + ); + +FailOnFalse [gle] TerminateThread( + HANDLE hThread, + DWORD dwExitCode + ); + +category ThreadLocalStorage: +DwordFailIf0xFFFFFFFF [gle] TlsAlloc(); + +FailOnFalse [gle] TlsFree( + DWORD dwTlsIndex + ); + +LpvoidFailIfNull [gle] TlsGetValue(DWORD dwTlsIndex); + +FailOnFalse [gle] TlsSetValue( + DWORD dwTlsIndex, + LPVOID lpTlsValue + ); + +category ProcessesAndThreads: + +module USER32.DLL: +FailOnFalse [gle] UserHandleGrantAccess( + HANDLE hUserHandle, + HANDLE hJob, + BOOL bGrant + ); + +module KERNEL32.DLL: +WinError WinExec( + LPCSTR lpCmdLine, + UINT uCmdShow + ); + + + +DwordFailIfZero [gle] DisableThreadLibraryCalls(HMODULE hLibModule); + +DwordFailIfZero [gle] FreeLibrary( + HMODULE hLibModule + ); + +DwordFailIfZero [gle] GetModuleFileNameA( + HMODULE hModule, + [out] LPSTR lpFilename, + DWORD nSize + ); + +DwordFailIfZero [gle] GetModuleFileNameW( + HMODULE hModule, + [out] LPWSTR lpFilename, + DWORD nSize + ); + + +VOID FreeLibraryAndExitThread( + HMODULE hLibModule, + DWORD dwExitCode + ); + + + +DwordFailIfZero [gle] GetModuleHandleA( + LPCSTR lpModuleName + ); + +DwordFailIfZero [gle] GetModuleHandleW( + LPCWSTR lpModuleName + ); + + + +LpvoidFailIfNull [gle] GetProcAddress( + HMODULE hModule, + LPCSTR lpProcName + ); + +HMODULE [gle] LoadLibraryA(LPCSTR lpLibFileName); +HMODULE [gle] LoadLibraryW(LPCWSTR lpLibFileName); + + +HMODULE [gle] LoadLibraryExA(LPCSTR lpLibFileName, + HANDLE hFile, + LoadLibFlags dwFlags); + +HMODULE [gle] LoadLibraryExW(LPCWSTR lpLibFileName, + HANDLE hFile, + LoadLibFlags dwFlags); + +DWORD LoadModule( + LPCSTR lpModuleName, + LPVOID lpParameterBlock + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/registry.h b/tools/Debugging Tools for Windows/winext/manifest/registry.h new file mode 100644 index 0000000000..c6920c2f5d --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/registry.h @@ -0,0 +1,132 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Registry Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +category RegistryFunctions: + + +value DWORD SpecialOptions +{ +#define REG_OPTION_RESERVED 0x00000000L +#define REG_OPTION_NON_VOLATILE 0x00000000L +#define REG_OPTION_VOLATILE 0x00000001L +#define REG_OPTION_CREATE_LINK 0x00000002L +#define REG_OPTION_BACKUP_RESTORE 0x00000004L +#define REG_OPTION_OPEN_LINK 0x00000008L +}; + + +value LPDWORD DispositionValue +{ +#define REG_CREATED_NEW_KEY 0x00000001L +#define REG_OPENED_EXISTING_KEY 0x00000002L +}; + +value HKEY HandleToOpenKey +{ +#define HKEY_CLASSES_ROOT 0x80000000 +#define HKEY_CURRENT_USER 0x80000001 +#define HKEY_LOCAL_MACHINE 0x80000002 +#define HKEY_USERS 0x80000003 +#define HKEY_PERFORMANCE_DATA 0x80000004 +#define HKEY_CURRENT_CONFIG 0x80000005 +#define HKEY_DYN_DATA 0x80000006 +}; + +alias HandleToOpenKey; + +mask DWORD ChangesToReport +{ +#define REG_NOTIFY_CHANGE_NAME 0x00000001L +#define REG_NOTIFY_CHANGE_ATTRIBUTES 0x00000002L +#define REG_NOTIFY_CHANGE_LAST_SET 0x00000004L +#define REG_NOTIFY_CHANGE_SECURITY 0x00000008L +}; + +/* +typedef struct _FILETIME { + FTime dwLowDateTime; + FTime dwHighDateTime; +}FILETIME, *PFILETIME; +*/ + +module KERNEL32.DLL: + +UINT GetPrivateProfileIntA(LPCSTR lpAppName,LPCSTR lpKeyName,INT nDefault,LPCSTR lpFileName); +UINT GetPrivateProfileIntW(LPCWSTR lpAppName,LPCWSTR lpKeyName,INT nDefault,LPCWSTR lpFileName); +DWORD GetPrivateProfileSectionA(LPCSTR lpAppName,[out] LPSTR lpReturnedString,DWORD nSize,LPCSTR lpFileName); +DWORD GetPrivateProfileSectionW(LPCWSTR lpAppName,[out] LPWSTR lpReturnedString,DWORD nSize,LPCWSTR lpFileName); +DWORD GetPrivateProfileSectionNamesA([out] LPSTR lpszReturnBuffer,DWORD nSize,LPCSTR lpFileName); +DWORD GetPrivateProfileSectionNamesW([out] LPWSTR lpszReturnBuffer,DWORD nSize,LPCWSTR lpFileName); +DWORD GetPrivateProfileStringA(LPCSTR lpAppName,LPCSTR lpKeyName,LPCSTR lpDefault,[out] LPSTR lpReturnedString,DWORD nSize,LPCSTR lpFileName); +DWORD GetPrivateProfileStringW(LPCWSTR lpAppName,LPCWSTR lpKeyName,LPCWSTR lpDefault,[out] LPWSTR lpReturnedString,DWORD nSize,LPCWSTR lpFileName); +FailOnFalse GetPrivateProfileStructA(LPCSTR lpszSection,LPCSTR lpszKey,[out] LPVOID lpStruct,UINT uSizeStruct,LPCSTR szFile); +FailOnFalse GetPrivateProfileStructW(LPCWSTR lpszSection,LPCWSTR lpszKey,[out] LPVOID lpStruct,UINT uSizeStruct,LPCWSTR szFile); +UINT GetProfileIntA(LPCSTR lpAppName,LPCSTR lpKeyName,INT nDefault); +UINT GetProfileIntW(LPCWSTR lpAppName,LPCWSTR lpKeyName,INT nDefault); +DWORD GetProfileSectionA(LPCSTR lpAppName,[out] LPSTR lpReturnedString,DWORD nSize); +DWORD GetProfileSectionW(LPCWSTR lpAppName,[out] LPWSTR lpReturnedString,DWORD nSize); +DWORD GetProfileStringA(LPCSTR lpAppName,LPCSTR lpKeyName,LPCSTR lpDefault,[out] LPSTR lpReturnedString,DWORD nSize); +DWORD GetProfileStringW(LPCWSTR lpAppName,LPCWSTR lpKeyName,LPCWSTR lpDefault,[out] LPWSTR lpReturnedString,DWORD nSize); +FailOnFalse [gle] WritePrivateProfileSectionA(LPCSTR lpAppName,LPCSTR lpString,LPCSTR lpFileName); +FailOnFalse [gle] WritePrivateProfileSectionW(LPCWSTR lpAppName,LPCWSTR lpString,LPCWSTR lpFileName); +FailOnFalse [gle] WritePrivateProfileStringA(LPCSTR lpAppName,LPCSTR lpKeyName,LPCSTR lpString,LPCSTR lpFileName); +FailOnFalse [gle] WritePrivateProfileStringW(LPCWSTR lpAppName,LPCWSTR lpKeyName,LPCWSTR lpString,LPCWSTR lpFileName); +FailOnFalse [gle] WritePrivateProfileStructA(LPCSTR lpszSection,LPCSTR lpszKey,LPVOID lpStruct,UINT uSizeStruct,LPCSTR szFile); +FailOnFalse [gle] WritePrivateProfileStructW(LPCWSTR lpszSection,LPCWSTR lpszKey,LPVOID lpStruct,UINT uSizeStruct,LPCWSTR szFile); +FailOnFalse [gle] WriteProfileSectionA(LPCSTR lpAppName,LPCSTR lpString); +FailOnFalse [gle] WriteProfileSectionW(LPCWSTR lpAppName,LPCWSTR lpString); +FailOnFalse [gle] WriteProfileStringA(LPCSTR lpAppName,LPCSTR lpKeyName,LPCSTR lpString); +FailOnFalse [gle] WriteProfileStringW(LPCWSTR lpAppName,LPCWSTR lpKeyName,LPCWSTR lpString); + +module ADVAPI32.DLL: + +WinError RegCloseKey( [da] HandleToOpenKey hKey); +WinError RegConnectRegistryA(LPCSTR lpMachineName,HandleToOpenKey hKey,[out] HandleToOpenKey* phkResult); +WinError RegConnectRegistryW(LPCWSTR lpMachineName,HandleToOpenKey hKey,[out] HandleToOpenKey* phkResult); +WinError RegCreateKeyA(HandleToOpenKey hKey,LPCSTR lpSubKey,[out] HandleToOpenKey* phkResult); +WinError RegCreateKeyW(HandleToOpenKey hKey,LPCWSTR lpSubKey,[out] HandleToOpenKey* phkResult); +WinError RegCreateKeyExA(HandleToOpenKey hKey,LPCSTR lpSubKey,DWORD Reserved,LPSTR lpClass,SpecialOptions dwOptions,DesiredSecurityAccess samDesired,LPSECURITY_ATTRIBUTES lpSecurityAttributes,[out] HandleToOpenKey* phkResult,[out] DispositionValue lpdwDisposition); +WinError RegCreateKeyExW(HandleToOpenKey hKey,LPCWSTR lpSubKey,DWORD Reserved,LPWSTR lpClass,SpecialOptions dwOptions,DesiredSecurityAccess samDesired,LPSECURITY_ATTRIBUTES lpSecurityAttributes,[out] HandleToOpenKey* phkResult,[out] DispositionValue lpdwDisposition); +WinError RegDeleteKeyA(HandleToOpenKey hKey,LPCSTR lpSubKey); +WinError RegDeleteKeyW(HandleToOpenKey hKey,LPCWSTR lpSubKey); +WinError RegDeleteValueA(HandleToOpenKey hKey,LPCSTR lpValueName); +WinError RegDeleteValueW(HandleToOpenKey hKey,LPCWSTR lpValueName); +WinError RegEnumKeyA(HandleToOpenKey hKey,DWORD dwIndex,[out] LPSTR lpName,DWORD cbName); +WinError RegEnumKeyW(HandleToOpenKey hKey,DWORD dwIndex,[out] LPWSTR lpName,DWORD cbName); +WinError RegEnumKeyExA(HandleToOpenKey hKey,DWORD dwIndex,[out] LPSTR lpName,[out] LPDWORD lpcName,LPDWORD lpReserved,[out] LPSTR lpClass,[out] LPDWORD lpcClass,[out] PFILETIME lpftLastWriteTime); +WinError RegEnumKeyExW(HandleToOpenKey hKey,DWORD dwIndex,[out] LPWSTR lpName,[out] LPDWORD lpcbName,LPDWORD lpReserved,[out] LPWSTR lpClass,[out] LPDWORD lpcbClass,[out] PFILETIME lpftLastWriteTime); +WinError RegEnumValueA(HandleToOpenKey hKey,DWORD dwIndex,[out] LPSTR lpValueName,[out] LPDWORD lpcValueName,LPDWORD lpReserved,[out] LPDWORD lpType,[out] LPBYTE lpData,LPDWORD lpcbData); +WinError RegEnumValueW(HandleToOpenKey hKey,DWORD dwIndex,[out] LPWSTR lpValueName,[out] LPDWORD lpcbValueName,LPDWORD lpReserved,[out] LPDWORD lpType,[out] LPBYTE lpData,LPDWORD lpcbData); +WinError RegFlushKey(HandleToOpenKey hKey); +WinError RegLoadKeyA(HandleToOpenKey hKey,LPCSTR lpSubKey,LPCSTR lpFile); +WinError RegLoadKeyW(HandleToOpenKey hKey,LPCWSTR lpSubKey,LPCWSTR lpFile); +WinError RegNotifyChangeKeyValue(HandleToOpenKey hKey,BOOL bWatchSubtree,ChangesToReport dwNotifyFilter,HANDLE hEvent,BOOL fAsynchronous); +WinError RegOpenCurrentUser(DesiredSecurityAccess samDesired,[out] HandleToOpenKey* phkResult); +WinError RegOpenKeyA(HandleToOpenKey hKey,LPCSTR lpSubKey,[out] HandleToOpenKey* phkResult); +WinError RegOpenKeyW(HandleToOpenKey hKey,LPCWSTR lpSubKey,[out] HandleToOpenKey* phkResult); +WinError RegOpenKeyExA(HandleToOpenKey hKey,LPCSTR lpSubKey,DWORD ulOptions,DesiredSecurityAccess samDesired,[out] HandleToOpenKey* phkResult); +WinError RegOpenKeyExW(HandleToOpenKey hKey,LPCWSTR lpSubKey,DWORD ulOptions,DesiredSecurityAccess samDesired,[out] HandleToOpenKey* phkResult); +WinError RegOpenUserClassesRoot(HANDLE hToken,DWORD dwOptions,DesiredSecurityAccess samDesired,[out] HandleToOpenKey* phkResult); +WinError RegOverridePredefKey(HandleToOpenKey hKey,HandleToOpenKey hNewHKey); +WinError RegQueryInfoKeyA(HandleToOpenKey hKey,[out] LPSTR lpClass,[out] LPDWORD lpcClass,LPDWORD lpReserved,[out] LPDWORD lpcSubKeys,[out] LPDWORD lpcMaxSubKeyLen,[out] LPDWORD lpcMaxClassLen,[out] LPDWORD lpcValues,[out] LPDWORD lpcMaxValueNameLen,[out] LPDWORD lpcMaxValueLen,[out] LPDWORD lpcbSecurityDescriptor,[out] PFILETIME lpftLastWriteTime); +WinError RegQueryInfoKeyW(HandleToOpenKey hKey,[out] LPWSTR lpClass,[out] LPDWORD lpcbClass,LPDWORD lpReserved,[out] LPDWORD lpcSubKeys,[out] LPDWORD lpcbMaxSubKeyLen,[out] LPDWORD lpcbMaxClassLen,[out] LPDWORD lpcValues, [out] LPDWORD lpcbMaxValueNameLen,[out] LPDWORD lpcbMaxValueLen,[out] LPDWORD lpcbSecurityDescriptor,[out] PFILETIME lpftLastWriteTime); +WinError RegQueryMultipleValuesA(HandleToOpenKey hKey,[out] LPVOID val_list,DWORD num_vals,[out] LPSTR lpValueBuf,[out] LPDWORD ldwTotsize); +WinError RegQueryMultipleValuesW(HandleToOpenKey hKey,[out] LPVOID val_list,DWORD num_vals,[out] LPWSTR lpValueBuf,[out] LPDWORD ldwTotsize); +WinError RegQueryValueA(HandleToOpenKey hKey,LPCSTR lpSubKey,[out] LPSTR lpValue,[out] PLONG lpcbValue); +WinError RegQueryValueW(HandleToOpenKey hKey,LPCWSTR lpSubKey,[out] LPWSTR lpValue,[out] PLONG lpcbValue); +WinError RegQueryValueExA(HandleToOpenKey hKey,LPCSTR lpValueName,LPDWORD lpReserved,[out] LPDWORD lpType,[out] LPBYTE lpData,[out] LPDWORD lpcbData); +WinError RegQueryValueExW(HandleToOpenKey hKey,LPCWSTR lpValueName,LPDWORD lpReserved,[out] LPDWORD lpType,[out] LPBYTE lpData,[out] LPDWORD lpcbData); +WinError RegReplaceKeyA(HandleToOpenKey hKey,LPCSTR lpSubKey,LPCSTR lpNewFile,LPCSTR lpOldFile); +WinError RegReplaceKeyW(HandleToOpenKey hKey,LPCWSTR lpSubKey,LPCWSTR lpNewFile,LPCWSTR lpOldFile); +WinError RegRestoreKeyA(HandleToOpenKey hKey,LPCSTR lpFile,DWORD dwFlags); +WinError RegRestoreKeyW(HandleToOpenKey hKey,LPCWSTR lpFile,DWORD dwFlags); +WinError RegSaveKeyA(HandleToOpenKey hKey,LPCSTR lpFile,LPSECURITY_ATTRIBUTES lpSecurityAttributes); +WinError RegSaveKeyW(HandleToOpenKey hKey,LPCWSTR lpFile,LPSECURITY_ATTRIBUTES lpSecurityAttributes); +WinError RegSetValueA(HandleToOpenKey hKey,LPCSTR lpSubKey,DWORD dwType,LPCSTR lpData,DWORD cbData); +WinError RegSetValueW(HandleToOpenKey hKey,LPCWSTR lpSubKey,DWORD dwType,LPCWSTR lpData,DWORD cbData); +WinError RegSetValueExA(HandleToOpenKey hKey,LPCSTR lpValueName,DWORD Reserved,DWORD dwType,BYTE *lpData,DWORD cbData); +WinError RegSetValueExW(HandleToOpenKey hKey,LPCWSTR lpValueName,DWORD Reserved,DWORD dwType,BYTE* lpData,DWORD cbData); +WinError RegUnLoadKeyA(HandleToOpenKey hKey,LPCSTR lpSubKey); +WinError RegUnLoadKeyW(HandleToOpenKey hKey,LPCWSTR lpSubKey); diff --git a/tools/Debugging Tools for Windows/winext/manifest/shell.h b/tools/Debugging Tools for Windows/winext/manifest/shell.h new file mode 100644 index 0000000000..c86f26d614 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/shell.h @@ -0,0 +1,1038 @@ + +typedef HANDLE HDROP; + +typedef LPVOID LPSHELLFLAGSTATE; +typedef LPVOID LPBC; +typedef LPVOID IEnumIDList; +typedef LPVOID LPSOFTDISTINFO; + +typedef struct _SHITEMID // mkid +{ + USHORT cb; // Size of the ID (including cb itself) + BYTE abID[1]; // The item ID (variable length) +} SHITEMID; +typedef SHITEMID *LPSHITEMID; +typedef SHITEMID *LPCSHITEMID; + +// +// ITEMIDLIST -- List if item IDs (combined with 0-terminator) +// +typedef struct _ITEMIDLIST // idl +{ + SHITEMID mkid; +} ITEMIDLIST; +typedef ITEMIDLIST * LPITEMIDLIST; +typedef ITEMIDLIST * LPCITEMIDLIST; + +value UINT AppBarEdge +{ +#define ABE_LEFT 0 +#define ABE_TOP 1 +#define ABE_RIGHT 2 +#define ABE_BOTTOM 3 +}; + +typedef struct _AppBarData { + DWORD cbSize; + HWND hWnd; + UINT uCallbackMessage; + AppBarEdge uEdge; + RECT rc; + LPARAM lParam; +} APPBARDATA, *PAPPBARDATA; + +value DWORD AppBarMessage +{ +#define ABM_NEW 0x00000000 +#define ABM_REMOVE 0x00000001 +#define ABM_QUERYPOS 0x00000002 +#define ABM_SETPOS 0x00000003 +#define ABM_GETSTATE 0x00000004 +#define ABM_GETTASKBARPOS 0x00000005 +#define ABM_ACTIVATE 0x00000006 // lParam == TRUE/FALSE means activate/deactivate +#define ABM_GETAUTOHIDEBAR 0x00000007 +#define ABM_SETAUTOHIDEBAR 0x00000008 // this can fail at any time. MUST check the result + // lParam = TRUE/FALSE Set/Unset + // uEdge = what edge +#define ABM_WINDOWPOSCHANGED 0x0000009 +}; + +value DWORD FindExecutableError +{ +#define OutOfMemory 0L [fail] +#define ERROR_FILE_NOT_FOUND 2L [fail] +#define ERROR_PATH_NOT_FOUND 3L [fail] +#define ERROR_BAD_FORMAT 11L [fail] +#define NoAssociation 31L [fail] +}; + +value UINT SHAddToRecentDocsFlags +{ +#define SHARD_PIDL 0x00000001L +#define SHARD_PATHA 0x00000002L +#define SHARD_PATHW 0x00000003L +}; + +mask UINT BrowseInfoFlags +{ +#define BIF_RETURNONLYFSDIRS 0x0001 // For finding a folder to start document searching +#define BIF_DONTGOBELOWDOMAIN 0x0002 // For starting the Find Computer +#define BIF_STATUSTEXT 0x0004 +#define BIF_RETURNFSANCESTORS 0x0008 +#define BIF_EDITBOX 0x0010 +#define BIF_VALIDATE 0x0020 // insist on valid result (or CANCEL) + +#define BIF_BROWSEFORCOMPUTER 0x1000 // Browsing for Computers. +#define BIF_BROWSEFORPRINTER 0x2000 // Browsing for Printers +#define BIF_BROWSEINCLUDEFILES 0x4000 // Browsing for Everything +}; + +typedef struct _browseinfoA { + HWND hwndOwner; + LPCITEMIDLIST pidlRoot; + LPSTR pszDisplayName;// Return display name of item selected. + LPCSTR lpszTitle; // text to go in the banner over the tree. + BrowseInfoFlags ulFlags; // Flags that control the return stuff + LPVOID lpfn; + LPARAM lParam; // extra info that's passed back in callbacks + + int iImage; // output var: where to return the Image index. +} BROWSEINFOA, *PBROWSEINFOA, *LPBROWSEINFOA; + +typedef struct _browseinfoW { + HWND hwndOwner; + LPCITEMIDLIST pidlRoot; + LPWSTR pszDisplayName;// Return display name of item selected. + LPCWSTR lpszTitle; // text to go in the banner over the tree. + BrowseInfoFlags ulFlags; // Flags that control the return stuff + LPVOID lpfn; + LPARAM lParam; // extra info that's passed back in callbacks + + int iImage; // output var: where to return the Image index. +} BROWSEINFOW, *PBROWSEINFOW, *LPBROWSEINFOW; + + +value LONG ChangeNotifyEventId +{ +#define SHCNE_RENAMEITEM 0x00000001L +#define SHCNE_CREATE 0x00000002L +#define SHCNE_DELETE 0x00000004L +#define SHCNE_MKDIR 0x00000008L +#define SHCNE_RMDIR 0x00000010L +#define SHCNE_MEDIAINSERTED 0x00000020L +#define SHCNE_MEDIAREMOVED 0x00000040L +#define SHCNE_DRIVEREMOVED 0x00000080L +#define SHCNE_DRIVEADD 0x00000100L +#define SHCNE_NETSHARE 0x00000200L +#define SHCNE_NETUNSHARE 0x00000400L +#define SHCNE_ATTRIBUTES 0x00000800L +#define SHCNE_UPDATEDIR 0x00001000L +#define SHCNE_UPDATEITEM 0x00002000L +#define SHCNE_SERVERDISCONNECT 0x00004000L +#define SHCNE_UPDATEIMAGE 0x00008000L +#define SHCNE_DRIVEADDGUI 0x00010000L +#define SHCNE_RENAMEFOLDER 0x00020000L +#define SHCNE_FREESPACE 0x00040000L + +#define SHCNE_EXTENDED_EVENT 0x04000000L +#define SHCNE_ASSOCCHANGED 0x08000000L + +#define SHCNE_DISKEVENTS 0x0002381FL +#define SHCNE_GLOBALEVENTS 0x0C0581E0L +#define SHCNE_ALLEVENTS 0x7FFFFFFFL +#define SHCNE_INTERRUPT 0x80000000L // The presence of this flag indicates + // that the event was generated by an + // interrupt. It is stripped out before + // the clients of SHCNNotify_ see it. +}; + + +value LONG ChangeNotifyFlags +{ +#define SHCNF_IDLIST 0x0000 // LPITEMIDLIST +#define SHCNF_PATHA 0x0001 // path name +#define SHCNF_PRINTERA 0x0002 // printer friendly name +#define SHCNF_DWORD 0x0003 // DWORD +#define SHCNF_PATHW 0x0005 // path name +#define SHCNF_PRINTERW 0x0006 // printer friendly name +}; + +mask ULONG SHCreateProcessInfoMask +{ +#define SEE_MASK_CLASSNAME 0x00000001 +#define SEE_MASK_CLASSKEY 0x00000003 +#define SEE_MASK_IDLIST 0x00000004 +#define SEE_MASK_INVOKEIDLIST 0x0000000c +#define SEE_MASK_ICON 0x00000010 +#define SEE_MASK_HOTKEY 0x00000020 +#define SEE_MASK_NOCLOSEPROCESS 0x00000040 +#define SEE_MASK_CONNECTNETDRV 0x00000080 +#define SEE_MASK_FLAG_DDEWAIT 0x00000100 +#define SEE_MASK_DOENVSUBST 0x00000200 +#define SEE_MASK_FLAG_NO_UI 0x00000400 +#define SEE_MASK_UNICODE 0x00004000 +#define SEE_MASK_NO_CONSOLE 0x00008000 +#define SEE_MASK_ASYNCOK 0x00100000 +#define SEE_MASK_HMONITOR 0x00200000 +#define SEE_MASK_NOQUERYCLASSSTORE 0x01000000 +#define SEE_MASK_WAITFORINPUTIDLE 0x02000000 +}; + +typedef struct _SHCREATEPROCESSINFOW +{ + DWORD cbSize; + SHCreateProcessInfoMask fMask; + HWND hwnd; + LPCWSTR pwszFile; + LPCWSTR pwszParameters; + LPCWSTR pwszCurrentDirectory; + HANDLE hUserToken; + LPSECURITY_ATTRIBUTES lpProcessAttributes; + LPSECURITY_ATTRIBUTES lpThreadAttributes; + BOOL bInheritHandles; + DWORD dwCreationFlags; + LPSTARTUPINFOW lpStartupInfo; + LPPROCESS_INFORMATION lpProcessInformation; +} SHCREATEPROCESSINFOW, *PSHCREATEPROCESSINFOW; + +value DWORD NotifyIconMessage +{ +#define NIM_ADD 0x00000000 +#define NIM_MODIFY 0x00000001 +#define NIM_DELETE 0x00000002 +#define NIM_SETFOCUS 0x00000003 +#define NIM_SETVERSION 0x00000004 +}; + +typedef LPVOID PNOTIFYICONDATA; +/* --- Can't handle variable length structures +typedef struct _NOTIFYICONDATA { + DWORD cbSize; + HWND hWnd; + UINT uID; + UINT uFlags; + UINT uCallbackMessage; + HICON hIcon; + TCHAR szTip[64]; + DWORD dwState; //Version 5.0 + DWORD dwStateMask; //Version 5.0 + TCHAR szInfo[256]; //Version 5.0 + union { + UINT uTimeout; //Version 5.0 + UINT uVersion; //Version 5.0 + } DUMMYUNIONNAME; + TCHAR szInfoTitle[64]; //Version 5.0 + DWORD dwInfoFlags; //Version 5.0 +} NOTIFYICONDATA, *PNOTIFYICONDATA; +*/ + +value DWORD ShellExecuteError +{ +#define OutOfMemory 0L [fail] +#define ERROR_FILE_NOT_FOUND 2L [fail] +#define ERROR_PATH_NOT_FOUND 3L [fail] +#define ERROR_BAD_FORMAT 11L [fail] +#define SE_ERR_ACCESSDENIED 5 [fail] +#define SE_ERR_OOM 8 [fail] +#define SE_ERR_DLLNOTFOUND 32 [fail] +#define SE_ERR_SHARE 26 [fail] +#define SE_ERR_ASSOCINCOMPLETE 27 [fail] +#define SE_ERR_DDETIMEOUT 28 [fail] +#define SE_ERR_DDEFAIL 29 [fail] +#define SE_ERR_DDEBUSY 30 [fail] +#define SE_ERR_NOASSOC 31 [fail] +}; + +typedef struct _SHELLEXECUTEINFOA +{ + DWORD cbSize; + ULONG fMask; + HWND hwnd; + LPCSTR lpVerb; + LPCSTR lpFile; + LPCSTR lpParameters; + LPCSTR lpDirectory; + INT nShow; + HINSTANCE hInstApp; + // Optional fields + LPVOID lpIDList; + LPCSTR lpClass; + HKEY hkeyClass; + DWORD dwHotKey; + HRSRC hIcon; + HANDLE hProcess; +} SHELLEXECUTEINFOA, *LPSHELLEXECUTEINFOA; + +typedef struct _SHELLEXECUTEINFOW +{ + DWORD cbSize; + ULONG fMask; + HWND hwnd; + LPCWSTR lpVerb; + LPCWSTR lpFile; + LPCWSTR lpParameters; + LPCWSTR lpDirectory; + INT nShow; + HINSTANCE hInstApp; + // Optional fields + LPVOID lpIDList; + LPCWSTR lpClass; + HKEY hkeyClass; + DWORD dwHotKey; + HRSRC hIcon; + HANDLE hProcess; +} SHELLEXECUTEINFOW, *LPSHELLEXECUTEINFOW; + +mask DWORD SHEmptyRecycleBinFlags +{ +#define SHERB_NOCONFIRMATION 0x00000001 +#define SHERB_NOPROGRESSUI 0x00000002 +#define SHERB_NOSOUND 0x00000004 +}; + +mask DWORD FILEOP_FLAGS +{ +#define FOF_MULTIDESTFILES 0x0001 +#define FOF_CONFIRMMOUSE 0x0002 +#define FOF_SILENT 0x0004 // don't create progress/report +#define FOF_RENAMEONCOLLISION 0x0008 +#define FOF_NOCONFIRMATION 0x0010 // Don't prompt the user. +#define FOF_WANTMAPPINGHANDLE 0x0020 // Fill in SHFILEOPSTRUCT.hNameMappings + // Must be freed using SHFreeNameMappings +#define FOF_ALLOWUNDO 0x0040 +#define FOF_FILESONLY 0x0080 // on *.*, do only files +#define FOF_SIMPLEPROGRESS 0x0100 // means don't show names of files +#define FOF_NOCONFIRMMKDIR 0x0200 // don't confirm making any needed dirs +#define FOF_NOERRORUI 0x0400 // don't put up error UI +#define FOF_NOCOPYSECURITYATTRIBS 0x0800 // dont copy NT file Security Attributes +#define FOF_NORECURSION 0x1000 // don't recurse into directories. +#define FOF_NO_CONNECTED_ELEMENTS 0x2000 // don't operate on connected elements. +#define FOF_WANTNUKEWARNING 0x4000 // during delete operation, warn if nuking instead of recycling (partially overrides FOF_NOCONFIRMATION) +}; + +typedef struct _SHFILEOPSTRUCTA +{ + HWND hwnd; + UINT wFunc; + LPCSTR pFrom; + LPCSTR pTo; + FILEOP_FLAGS fFlags; + BOOL fAnyOperationsAborted; + LPVOID hNameMappings; + LPCSTR lpszProgressTitle; // only used if FOF_SIMPLEPROGRESS +} SHFILEOPSTRUCTA, *LPSHFILEOPSTRUCTA; + +typedef struct _SHFILEOPSTRUCTW +{ + HWND hwnd; + UINT wFunc; + LPCWSTR pFrom; + LPCWSTR pTo; + FILEOP_FLAGS fFlags; + BOOL fAnyOperationsAborted; + LPVOID hNameMappings; + LPCWSTR lpszProgressTitle; // only used if FOF_SIMPLEPROGRESS +} SHFILEOPSTRUCTW, *LPSHFILEOPSTRUCTW; + +value INT SHGetDataFromIDListFormat +{ +#define SHGDFIL_FINDDATA 1 +#define SHGDFIL_NETRESOURCE 2 +#define SHGDFIL_DESCRIPTIONID 3 +}; + +mask UINT SHGetFileInfoFlags +{ +#define SHGFI_ICON 0x000000100 // get icon +#define SHGFI_DISPLAYNAME 0x000000200 // get display name +#define SHGFI_TYPENAME 0x000000400 // get type name +#define SHGFI_ATTRIBUTES 0x000000800 // get attributes +#define SHGFI_ICONLOCATION 0x000001000 // get icon location +#define SHGFI_EXETYPE 0x000002000 // return exe type +#define SHGFI_SYSICONINDEX 0x000004000 // get system icon index +#define SHGFI_LINKOVERLAY 0x000008000 // put a link overlay on icon +#define SHGFI_SELECTED 0x000010000 // show icon in selected state +#define SHGFI_ATTR_SPECIFIED 0x000020000 // get only specified attributes +#define SHGFI_LARGEICON 0x000000000 // get large icon +#define SHGFI_SMALLICON 0x000000001 // get small icon +#define SHGFI_OPENICON 0x000000002 // get open icon +#define SHGFI_SHELLICONSIZE 0x000000004 // get shell size icon +#define SHGFI_PIDL 0x000000008 // pszPath is a pidl +#define SHGFI_USEFILEATTRIBUTES 0x000000010 // use passed dwFileAttribute +#define SHGFI_ADDOVERLAYS 0x000000020 // apply the appropriate overlays +#define SHGFI_OVERLAYINDEX 0x000000040 // Get the index of the overlay + // in the upper 8 bits of the iIcon +}; + +mask DWORD ShellFolderGetAttributesOf +{ +#define SFGAO_CANCOPY 0x1 // Objects can be copied (0x1) +#define SFGAO_CANMOVE 0x2 // Objects can be moved (0x2) +#define SFGAO_CANLINK 0x4 // Objects can be linked (0x4) +#define SFGAO_CANRENAME 0x00000010L // Objects can be renamed +#define SFGAO_CANDELETE 0x00000020L // Objects can be deleted +#define SFGAO_HASPROPSHEET 0x00000040L // Objects have property sheets +#define SFGAO_DROPTARGET 0x00000100L // Objects are drop target +#define SFGAO_CAPABILITYMASK 0x00000177L +#define SFGAO_LINK 0x00010000L // Shortcut (link) +#define SFGAO_SHARE 0x00020000L // shared +#define SFGAO_READONLY 0x00040000L // read-only +#define SFGAO_GHOSTED 0x00080000L // ghosted icon +#define SFGAO_HIDDEN 0x00080000L // hidden object +#define SFGAO_DISPLAYATTRMASK 0x000F0000L +#define SFGAO_FILESYSANCESTOR 0x10000000L // It contains file system folder +#define SFGAO_FOLDER 0x20000000L // It's a folder. +#define SFGAO_FILESYSTEM 0x40000000L // is a file system thing (file/folder/root) +#define SFGAO_HASSUBFOLDER 0x80000000L // Expandable in the map pane +#define SFGAO_CONTENTSMASK 0x80000000L +#define SFGAO_VALIDATE 0x01000000L // invalidate cached information +#define SFGAO_REMOVABLE 0x02000000L // is this removeable media? +#define SFGAO_COMPRESSED 0x04000000L // Object is compressed (use alt color) +#define SFGAO_BROWSABLE 0x08000000L // is in-place browsable +#define SFGAO_NONENUMERATED 0x00100000L // is a non-enumerated object +#define SFGAO_NEWCONTENT 0x00200000L // should show bold in explorer tree +#define SFGAO_CANMONIKER 0x00400000L // can create monikers for its objects +}; + +typedef struct _SHFILEINFOA +{ + HRSRC hIcon; // out: icon + int iIcon; // out: icon index + ShellFolderGetAttributesOf dwAttributes; // out: SFGAO_ flags + CHAR szDisplayName[260]; // out: display name (or path) + CHAR szTypeName[80]; // out: type name +} SHFILEINFOA; +typedef struct _SHFILEINFOW +{ + HRSRC hIcon; // out: icon + int iIcon; // out: icon index + ShellFolderGetAttributesOf dwAttributes; // out: SFGAO_ flags + WCHAR szDisplayName[260]; // out: display name (or path) + WCHAR szTypeName[80]; // out: type name +} SHFILEINFOW; + +value DWORD SHGetFolderPathFlags +{ +#define SHGFP_TYPE_CURRENT 0 // current value for user, verify it exists +#define SHGFP_TYPE_DEFAULT 1 // default value, may not exist +}; + +value int SHGetIconOverlayIndexValue +{ +#define IDO_SHGIOI_SHARE 0x0FFFFFFF +#define IDO_SHGIOI_LINK 0x0FFFFFFE +#define IDO_SHGIOI_SLOWFILE 0x0FFFFFFFD +}; + +mask DWORD SHGetSettingsMask +{ +#define SSF_SHOWALLOBJECTS 0x00000001 +#define SSF_SHOWEXTENSIONS 0x00000002 +#define SSF_SHOWCOMPCOLOR 0x00000008 +#define SSF_SHOWSYSFILES 0x00000020 +#define SSF_DOUBLECLICKINWEBVIEW 0x00000080 +#define SSF_SHOWATTRIBCOL 0x00000100 +#define SSF_DESKTOPHTML 0x00000200 +#define SSF_WIN95CLASSIC 0x00000400 +#define SSF_DONTPRETTYPATH 0x00000800 +#define SSF_SHOWINFOTIP 0x00002000 +#define SSF_MAPNETDRVBUTTON 0x00001000 +#define SSF_NOCONFIRMRECYCLE 0x00008000 +#define SSF_HIDEICONS 0x00004000 +}; + + +value int CSIDL +{ +#define CSIDL_DESKTOP 0x0000 // <desktop> +#define CSIDL_INTERNET 0x0001 // Internet Explorer (icon on desktop) +#define CSIDL_PROGRAMS 0x0002 // Start Menu\Programs +#define CSIDL_CONTROLS 0x0003 // My Computer\Control Panel +#define CSIDL_PRINTERS 0x0004 // My Computer\Printers +#define CSIDL_PERSONAL 0x0005 // My Documents +#define CSIDL_FAVORITES 0x0006 // <user name>\Favorites +#define CSIDL_STARTUP 0x0007 // Start Menu\Programs\Startup +#define CSIDL_RECENT 0x0008 // <user name>\Recent +#define CSIDL_SENDTO 0x0009 // <user name>\SendTo +#define CSIDL_BITBUCKET 0x000a // <desktop>\Recycle Bin +#define CSIDL_STARTMENU 0x000b // <user name>\Start Menu +#define CSIDL_DESKTOPDIRECTORY 0x0010 // <user name>\Desktop +#define CSIDL_DRIVES 0x0011 // My Computer +#define CSIDL_NETWORK 0x0012 // Network Neighborhood +#define CSIDL_NETHOOD 0x0013 // <user name>\nethood +#define CSIDL_FONTS 0x0014 // windows\fonts +#define CSIDL_TEMPLATES 0x0015 +#define CSIDL_COMMON_STARTMENU 0x0016 // All Users\Start Menu +#define CSIDL_COMMON_PROGRAMS 0X0017 // All Users\Programs +#define CSIDL_COMMON_STARTUP 0x0018 // All Users\Startup +#define CSIDL_COMMON_DESKTOPDIRECTORY 0x0019 // All Users\Desktop +#define CSIDL_APPDATA 0x001a // <user name>\Application Data +#define CSIDL_PRINTHOOD 0x001b // <user name>\PrintHood +#define CSIDL_LOCAL_APPDATA 0x001c // <user name>\Local Settings\Applicaiton Data (non roaming) +#define CSIDL_ALTSTARTUP 0x001d // non localized startup +#define CSIDL_COMMON_ALTSTARTUP 0x001e // non localized common startup +#define CSIDL_COMMON_FAVORITES 0x001f +#define CSIDL_INTERNET_CACHE 0x0020 +#define CSIDL_COOKIES 0x0021 +#define CSIDL_HISTORY 0x0022 +#define CSIDL_COMMON_APPDATA 0x0023 // All Users\Application Data +#define CSIDL_WINDOWS 0x0024 // GetWindowsDirectory() +#define CSIDL_SYSTEM 0x0025 // GetSystemDirectory() +#define CSIDL_PROGRAM_FILES 0x0026 // C:\Program Files +#define CSIDL_MYPICTURES 0x0027 // C:\Program Files\My Pictures +#define CSIDL_PROFILE 0x0028 // USERPROFILE +#define CSIDL_SYSTEMX86 0x0029 // x86 system directory on RISC +#define CSIDL_PROGRAM_FILESX86 0x002a // x86 C:\Program Files on RISC +#define CSIDL_PROGRAM_FILES_COMMON 0x002b // C:\Program Files\Common +#define CSIDL_PROGRAM_FILES_COMMONX86 0x002c // x86 Program Files\Common on RISC +#define CSIDL_COMMON_TEMPLATES 0x002d // All Users\Templates +#define CSIDL_COMMON_DOCUMENTS 0x002e // All Users\Documents +#define CSIDL_COMMON_ADMINTOOLS 0x002f // All Users\Start Menu\Programs\Administrative Tools +#define CSIDL_ADMINTOOLS 0x0030 // <user name>\Start Menu\Programs\Administrative Tools +#define CSIDL_CONNECTIONS 0x0031 // Network and Dial-up Connections + +#define CSIDL_FLAG_CREATE 0x8000 // combine with CSIDL_ value to force folder creation in SHGetFolderPath() +#define CSIDL_FLAG_DONT_VERIFY 0x4000 // combine with CSIDL_ value to return an unverified folder path +#define CSIDL_FLAG_MASK 0xFF00 // mask for all possible flag values +}; + +value UINT SHInvokePrinterCommandAction +{ +#define PRINTACTION_OPEN 0 +#define PRINTACTION_PROPERTIES 1 +#define PRINTACTION_NETINSTALL 2 +#define PRINTACTION_NETINSTALLLINK 3 +#define PRINTACTION_TESTPAGE 4 +#define PRINTACTION_OPENNETPRN 5 +#define PRINTACTION_DOCUMENTDEFAULTS 6 +#define PRINTACTION_SERVERPROPERTIES 7 +}; + +typedef struct _SHQUERYRBINFO { + DWORD cbSize; + __int64 i64Size; + __int64 i64NumItems; +} SHQUERYRBINFO, *LPSHQUERYRBINFO; + +value DWORD TranslateUrlFlags +{ +#define TRANSLATEURL_FL_GUESS_PROTOCOL 0x0001 // Guess protocol if missing +#define TRANSLATEURL_FL_USE_DEFAULT_PROTOCOL 0x0002 // Use default protocol if missing +}; + +value DWORD URLAssociationDialogFlags +{ +#define URLASSOCDLG_FL_USE_DEFAULT_NAME 0x0001 +#define URLASSOCDLG_FL_REGISTER_ASSOC 0x0002 +}; + +value UINT WinHelpCommands +{ +#define HELP_CONTEXT 0x0001L /* Display topic in ulTopic */ +#define HELP_QUIT 0x0002L /* Terminate help */ +#define HELP_INDEX 0x0003L /* Display index */ +#define HELP_CONTENTS 0x0003L +#define HELP_HELPONHELP 0x0004L /* Display help on using help */ +#define HELP_SETINDEX 0x0005L /* Set current Index for multi index help */ +#define HELP_CONTEXTPOPUP 0x0008L +#define HELP_FORCEFILE 0x0009L +#define HELP_KEY 0x0101L /* Display topic for keyword in offabData */ +#define HELP_COMMAND 0x0102L +#define HELP_PARTIALKEY 0x0105L +#define HELP_MULTIKEY 0x0201L +#define HELP_SETWINPOS 0x0203L +#define HELP_CONTEXTMENU 0x000a +#define HELP_FINDER 0x000b +#define HELP_WM_HELP 0x000c +#define HELP_SETPOPUP_POS 0x000d + +#define HELP_TCARD 0x8000 +#define HELP_TCARD_DATA 0x0010 +#define HELP_TCARD_OTHER_CALLER 0x0011 +}; + +typedef struct _STRRET { + UINT uType; + CHAR cStr[260]; +} STRRET, *LPSTRRET; + + +module SHELL32.DLL: +category Shell: + +HRESULT DllGetClassObject( + REFCLSID rclsid, + [iid] REFIID riid, + [out] COM_INTERFACE_PTR * ppv + ); + +interface IShellFolder : IUnknown +{ + HRESULT ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pszDisplayName, + [out] ULONG *pchEaten, [out] LPITEMIDLIST *ppidl, [out] ULONG *pdwAttributes); + + HRESULT EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList); + + HRESULT BindToObject(LPCITEMIDLIST pidl, LPBC pbc, [iid] REFIID riid, [out] COM_INTERFACE_PTR *ppv); + HRESULT BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, [iid] REFIID riid, [out] COM_INTERFACE_PTR *ppv); + HRESULT CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2); + HRESULT CreateViewObject(HWND hwndOwner, [iid] REFIID riid, [out] COM_INTERFACE_PTR *ppv); + HRESULT GetAttributesOf(UINT cidl, [out] LPCITEMIDLIST * apidl, [out] ULONG * rgfInOut); + HRESULT GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST * apidl, + [iid] REFIID riid, UINT * prgfInOut, [out] COM_INTERFACE_PTR *ppv); + HRESULT GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags, [out] LPSTRRET lpName); + HRESULT SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, + DWORD uFlags, LPITEMIDLIST *ppidlOut); +}; + +interface IShellLinkA : IUnknown +{ + HRESULT GetPath( [out] LPSTR pszFile, INT cchMaxPath, [out] LPWIN32_FIND_DATAA pfd, DWORD fFlags ); + + HRESULT GetIDList([out] LPITEMIDLIST * ppidl); + HRESULT SetIDList(LPCITEMIDLIST pidl); + + HRESULT GetDescription([out] LPSTR pszName, INT cchMaxName); + HRESULT SetDescription(LPCSTR pszName); + + HRESULT GetWorkingDirectory([out] LPSTR pszDir, INT cchMaxPath); + HRESULT SetWorkingDirectory(LPCSTR pszDir); + + HRESULT GetArguments([out] LPSTR pszArgs, INT cchMaxPath); + HRESULT SetArguments(LPCSTR pszArgs); + + HRESULT GetHotkey([out] WORD *pwHotkey); + HRESULT SetHotkey(WORD wHotkey); + + HRESULT GetShowCmd([out] INT *piShowCmd); + HRESULT SetShowCmd(INT iShowCmd); + + HRESULT GetIconLocation([out] LPSTR pszIconPath, INT cchIconPath, [out] INT *piIcon); + HRESULT SetIconLocation(LPCSTR pszIconPath, INT iIcon); + + HRESULT SetRelativePath(LPCSTR pszPathRel, DWORD dwReserved); + + HRESULT Resolve(HWND hwnd, DWORD fFlags); + + HRESULT SetPath(LPCSTR pszFile); +}; + +interface IShellLinkW : IUnknown +{ + HRESULT GetPath( [out] LPWSTR pszFile, INT cchMaxPath, [out] LPWIN32_FIND_DATAW pfd, DWORD fFlags ); + + HRESULT GetIDList([out] LPITEMIDLIST * ppidl); + HRESULT SetIDList(LPCITEMIDLIST pidl); + + HRESULT GetDescription([out] LPWSTR pszName, INT cchMaxName); + HRESULT SetDescription(LPCWSTR pszName); + + HRESULT GetWorkingDirectory([out] LPWSTR pszDir, INT cchMaxPath); + HRESULT SetWorkingDirectory(LPCWSTR pszDir); + + HRESULT GetArguments([out] LPWSTR pszArgs, INT cchMaxPath); + HRESULT SetArguments(LPCWSTR pszArgs); + + HRESULT GetHotkey([out] WORD *pwHotkey); + HRESULT SetHotkey(WORD wHotkey); + + HRESULT GetShowCmd([out] INT *piShowCmd); + HRESULT SetShowCmd(INT iShowCmd); + + HRESULT GetIconLocation([out] LPWSTR pszIconPath, INT cchIconPath, [out] INT *piIcon); + HRESULT SetIconLocation(LPCWSTR pszIconPath, INT iIcon); + + HRESULT SetRelativePath(LPCWSTR pszPathRel, DWORD dwReserved); + + HRESULT Resolve(HWND hwnd, DWORD fFlags); + + HRESULT SetPath(LPCWSTR pszFile); +}; + +DWORD DoEnvironmentSubstA( + LPCSTR pszString, + UINT cbSize +); + +DWORD DoEnvironmentSubstW( + LPCWSTR pszString, + UINT cbSize +); + +VOID DragAcceptFiles( + HWND hWnd, + BOOL fAccept +); + +VOID DragFinish( + HDROP hDrop +); + +UINT DragQueryFileA( + HDROP hDrop, + UINT iFile, + [out] LPSTR lpszFile, + UINT cch +); + +UINT DragQueryFileW( + HDROP hDrop, + UINT iFile, + [out] LPWSTR lpszFile, + UINT cch +); + +BOOL DragQueryPoint( + HDROP hDrop, + LPPOINT lppt +); + +FindExecutableError FindExecutableA( + LPCSTR lpFile, + LPCSTR lpDirectory, + [out] LPSTR lpResult +); + +FindExecutableError FindExecutableW( + LPCWSTR lpFile, + LPCWSTR lpDirectory, + [out] LPWSTR lpResult +); + +VOID SHAddToRecentDocs( + SHAddToRecentDocsFlags uFlags, + LPCVOID pv +); + +UINT SHAppBarMessage( + DWORD dwMessage, + PAPPBARDATA pData +); + +HRESULT SHBindToParent( + LPCITEMIDLIST pidl, + [iid] REFIID riid, + [out] COM_INTERFACE_PTR *ppv, + [out] LPCITEMIDLIST *ppidlLast +); + +LPITEMIDLIST SHBrowseForFolderA( + LPBROWSEINFOA lpbi +); + +LPITEMIDLIST SHBrowseForFolderW( + LPBROWSEINFOW lpbi +); + +VOID SHChangeNotify( + ChangeNotifyEventId wEventId, + ChangeNotifyFlags uFlags, + LPCVOID dwItem1, + LPCVOID dwItem2 +); + +int SHCreateDirectoryExA( + HWND hwnd, + LPCSTR pszPath, + SECURITY_ATTRIBUTES *psa +); + +int SHCreateDirectoryExW( + HWND hwnd, + LPCWSTR pszPath, + SECURITY_ATTRIBUTES *psa +); + +BOOL Shell_NotifyIcon( + NotifyIconMessage dwMessage, + PNOTIFYICONDATA pnid +); + +int ShellAboutA( + HWND hWnd, + LPCSTR szApp, + LPCSTR szOtherStuff, + HRSRC hIcon +); + +int ShellAboutW( + HWND hWnd, + LPCWSTR szApp, + LPCWSTR szOtherStuff, + HRSRC hIcon +); + +ShellExecuteError ShellExecuteA( + HWND hwnd, + LPCSTR lpVerb, + LPCSTR lpFile, + LPCSTR lpParameters, + LPCSTR lpDirectory, + ShowWindowCommand nShowCmd +); + +ShellExecuteError ShellExecuteW( + HWND hwnd, + LPCSTR lpVerb, + LPCSTR lpFile, + LPCSTR lpParameters, + LPCSTR lpDirectory, + ShowWindowCommand nShowCmd +); + +FailOnFalse [gle] ShellExecuteExA( + LPSHELLEXECUTEINFOA lpExecInfo +); + +FailOnFalse [gle] ShellExecuteExW( + LPSHELLEXECUTEINFOW lpExecInfo +); + +HRESULT SHEmptyRecycleBinA( + HWND hwnd, + LPCSTR pszRootPath, + DWORD dwFlags +); + +HRESULT SHEmptyRecycleBinW( + HWND hwnd, + LPCWSTR pszRootPath, + SHEmptyRecycleBinFlags dwFlags +); + +INT SHFileOperationA( + LPSHFILEOPSTRUCTA lpFileOp +); + +INT SHFileOperationW( + LPSHFILEOPSTRUCTW lpFileOp +); + +VOID SHFreeNameMappings( + HANDLE hNameMappings +); + +HRESULT SHGetDataFromIDListA( + IShellFolder* psf, + LPCITEMIDLIST pidl, + SHGetDataFromIDListFormat nFormat, + PVOID pv, + int cb +); + +HRESULT SHGetDataFromIDListW( + IShellFolder* psf, + LPCITEMIDLIST pidl, + SHGetDataFromIDListFormat nFormat, + PVOID pv, + int cb +); + +HRESULT SHGetDesktopFolder( + [out] IShellFolder **ppshf +); + +FailOnFalse SHGetDiskFreeSpaceA( + LPCSTR pszVolume, + [out] ULARGE_INTEGER *pqwFreeCaller, + [out] ULARGE_INTEGER *pqwTot, + [out] ULARGE_INTEGER *pqwFree +); + +DWORD_PTR SHGetFileInfoA( + LPCSTR pszPath, + ShellFolderGetAttributesOf dwFileAttributes, + [out] SHFILEINFOA *psfi, + UINT cbFileInfo, + SHGetFileInfoFlags uFlags +); + +DWORD_PTR SHGetFileInfoW( + LPCWSTR pszPath, + ShellFolderGetAttributesOf dwFileAttributes, + [out] SHFILEINFOW *psfi, + UINT cbFileInfo, + SHGetFileInfoFlags uFlags +); + +HRESULT SHGetFolderLocation( + HWND hwndOwner, + int nFolder, + HANDLE hToken, + DWORD dwReserved, + LPITEMIDLIST *ppidl +); + +HRESULT SHGetFolderPathA( + HWND hwndOwner, + CSIDL nFolder, + HANDLE hToken, + SHGetFolderPathFlags dwFlags, + [out] LPSTR pszPath +); + +HRESULT SHGetFolderPathW( + HWND hwndOwner, + CSIDL nFolder, + HANDLE hToken, + SHGetFolderPathFlags dwFlags, + [out] LPWSTR pszPath +); + +int SHGetIconOverlayIndexA( + LPCSTR pszIconPath, + SHGetIconOverlayIndexValue iIconIndex +); + +int SHGetIconOverlayIndexW( + LPCWSTR pszIconPath, + SHGetIconOverlayIndexValue iIconIndex +); + +HRESULT SHGetInstanceExplorer( + [out] IUnknown **ppunk +); + +//HRESULT SHGetMalloc( +// [out] IMalloc **ppMalloc +//); + +HRESULT SHGetMalloc( + LPVOID ppMalloc +); + +FailOnFalse SHGetNewLinkInfoA( + LPCSTR pszLinkTo, + LPCSTR pszDir, + [out] LPSTR pszName, + [out] BOOL *pfMustCopy, + UINT uFlags +); + +FailOnFalse SHGetNewLinkInfoW( + LPCWSTR pszLinkTo, + LPCWSTR pszDir, + [out] LPWSTR pszName, + [out] BOOL *pfMustCopy, + UINT uFlags +); + +FailOnFalse SHGetPathFromIDListA( + LPCITEMIDLIST pidl, + [out] LPSTR pszPath +); + +FailOnFalse SHGetPathFromIDListW( + LPCITEMIDLIST pidl, + [out] LPWSTR pszPath +); + +VOID SHGetSettings( + LPSHELLFLAGSTATE lpsfs, + SHGetSettingsMask dwMask +); + +HRESULT SHGetSpecialFolderLocation( + HWND hwndOwner, + CSIDL nFolder, + [out] LPITEMIDLIST *ppidl +); + +FailOnFalse SHGetSpecialFolderPathA( + HWND hwndOwner, + [out] LPSTR lpszPath, + CSIDL nFolder, + BOOL fCreate +); + +FailOnFalse SHGetSpecialFolderPathW( + HWND hwndOwner, + [out] LPWSTR lpszPath, + CSIDL nFolder, + BOOL fCreate +); + +FailOnFalse SHInvokePrinterCommandW( + HWND hwnd, + SHInvokePrinterCommandAction uAction, + LPCWSTR lpBuf1, + LPCWSTR lpBuf2, + BOOL fModal +); + +FailOnFalse SHInvokePrinterCommandA( + HWND hwnd, + SHInvokePrinterCommandAction uAction, + LPCSTR lpBuf1, + LPCSTR lpBuf2, + BOOL fModal +); + +HRESULT SHLoadInProc( + REFCLSID rclsid +); + +HRESULT SHQueryRecycleBinA( + LPCSTR pszRootPath, + LPSHQUERYRBINFO pSHQueryRBInfo +); + +HRESULT SHQueryRecycleBinW( + LPCWSTR pszRootPath, + LPSHQUERYRBINFO pSHQueryRBInfo +); + +module USER32.DLL: +FailOnFalse [gle] WinHelpA( + HWND hWndMain, + LPCSTR lpszHelp, + WinHelpCommands uCommand, + DWORD dwData +); + +FailOnFalse [gle] WinHelpW( + HWND hWndMain, + LPCWSTR lpszHelp, + WinHelpCommands uCommand, + DWORD dwData +); + + +module URL.DLL: + +BOOL InetIsOffline( + DWORD dwFlags +); + +HRESULT MIMEAssociationDialogA( + HWND hwndParent, + DWORD dwInFlags, + LPCSTR pcszFile, + LPCSTR pcszMIMEContentType, + LPSTR pszAppBuf, + UINT ucAppBufLen +); + +HRESULT MIMEAssociationDialogW( + HWND hwndParent, + DWORD dwInFlags, + LPCWSTR pcszFile, + LPCWSTR pcszMIMEContentType, + LPWSTR pszAppBuf, + UINT ucAppBufLen +); + +HRESULT TranslateURL( + LPCSTR pcszURL, + TranslateUrlFlags dwInFlags, + [out] LPSTR *ppszTranslatedURL +); + +HRESULT URLAssociationDialog( + HWND hwndParent, + URLAssociationDialogFlags dwInFlags, + LPCSTR pcszFile, + LPCSTR pcszURL, + LPSTR pszAppBuf, + UINT ucAppBufLen +); diff --git a/tools/Debugging Tools for Windows/winext/manifest/strings.h b/tools/Debugging Tools for Windows/winext/manifest/strings.h new file mode 100644 index 0000000000..1906e8ad75 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/strings.h @@ -0,0 +1,115 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// String manipulation Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +module KERNEL32.DLL: +category StringManipulation: + +int CompareStringA( + LCID Locale, // locale identifier + DWORD dwCmpFlags, // comparison-style options + LPCSTR lpString1, // first string + int cchCount1, // size of first string + LPCSTR lpString2, // second string + int cchCount2 // size of second string +); + +int CompareStringW( + LCID Locale, // locale identifier + DWORD dwCmpFlags, // comparison-style options + LPCWSTR lpString1, // first string + int cchCount1, // size of first string + LPCWSTR lpString2, // second string + int cchCount2 // size of second string +); + +LPSTR lstrcat( + LPSTR lpString1, // first string + LPCSTR lpString2 // second string +); + +LPSTR lstrcatA( + LPSTR lpString1, // first string + LPCSTR lpString2 // second string +); + +LPWSTR lstrcatW( + LPWSTR lpString1, // first string + LPCWSTR lpString2 // second string +); + +int lstrcmp( + LPCSTR lpString1, // first string + LPCSTR lpString2 // second string +); + +int lstrcmpA( + LPCSTR lpString1, // first string + LPCSTR lpString2 // second string +); + +int lstrcmpW( + LPCWSTR lpString1, // first string + LPCWSTR lpString2 // second string +); + +int lstrcmpi( + LPCSTR lpString1, // first string + LPCSTR lpString2 // second string +); + +int lstrcmpiA( + LPCSTR lpString1, // first string + LPCSTR lpString2 // second string +); + +int lstrcmpiW( + LPCWSTR lpString1, // first string + LPCWSTR lpString2 // second string +); + +LPSTR lstrcpy( + LPSTR lpString1, // destination buffer + LPCSTR lpString2 // string +); + +LPSTR lstrcpyA( + LPSTR lpString1, // destination buffer + LPCSTR lpString2 // string +); + +LPWSTR lstrcpyW( + LPWSTR lpString1, // destination buffer + LPCWSTR lpString2 // string +); + +LPSTR lstrcpyn( + [out] LPSTR lpString1, // destination buffer + LPCSTR lpString2, // string + int iMaxLength // number of characters to copy +); + +LPSTR lstrcpynA( + LPSTR lpString1, // destination buffer + LPCSTR lpString2, // string + int iMaxLength // number of characters to copy +); + +LPWSTR lstrcpynW( + LPWSTR lpString1, // destination buffer + LPCWSTR lpString2, // string + int iMaxLength // number of characters to copy +); + +int lstrlen( + LPCSTR lpszString + ); + +int lstrlenA( + LPCSTR lpszString + ); + +int lstrlenW( + LPCWSTR lpszString + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/user32.h b/tools/Debugging Tools for Windows/winext/manifest/user32.h new file mode 100644 index 0000000000..06f7668ab7 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/user32.h @@ -0,0 +1,3657 @@ +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +// USER32 API Set +// +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +module USER32.DLL: +category User32: + +value DWORD PeekMessageOptions +{ +#define PM_NOREMOVE 0x0000 +#define PM_REMOVE 0x0001 +#define PM_NOYIELD 0x0002 +}; + +value DWORD WindowsMessage +{ +#define WM_NULL 0x0000 +#define WM_CREATE 0x0001 +#define WM_DESTROY 0x0002 +#define WM_MOVE 0x0003 +#define WM_SIZE 0x0005 +#define WM_ACTIVATE 0x0006 +#define WM_SETFOCUS 0x0007 +#define WM_KILLFOCUS 0x0008 +#define WM_ENABLE 0x000A +#define WM_SETREDRAW 0x000B +#define WM_SETTEXT 0x000C +#define WM_GETTEXT 0x000D +#define WM_GETTEXTLENGTH 0x000E +#define WM_PAINT 0x000F +#define WM_CLOSE 0x0010 +#define WM_QUERYENDSESSION 0x0011 +#define WM_QUIT 0x0012 +#define WM_QUERYOPEN 0x0013 +#define WM_ERASEBKGND 0x0014 +#define WM_SYSCOLORCHANGE 0x0015 +#define WM_ENDSESSION 0x0016 +#define WM_SHOWWINDOW 0x0018 +#define WM_WININICHANGE 0x001A +#define WM_DEVMODECHANGE 0x001B +#define WM_ACTIVATEAPP 0x001C +#define WM_FONTCHANGE 0x001D +#define WM_TIMECHANGE 0x001E +#define WM_CANCELMODE 0x001F +#define WM_SETCURSOR 0x0020 +#define WM_MOUSEACTIVATE 0x0021 +#define WM_CHILDACTIVATE 0x0022 +#define WM_QUEUESYNC 0x0023 +#define WM_GETMINMAXINFO 0x0024 +#define WM_PAINTICON 0x0026 +#define WM_ICONERASEBKGND 0x0027 +#define WM_NEXTDLGCTL 0x0028 +#define WM_SPOOLERSTATUS 0x002A +#define WM_DRAWITEM 0x002B +#define WM_MEASUREITEM 0x002C +#define WM_DELETEITEM 0x002D +#define WM_VKEYTOITEM 0x002E +#define WM_CHARTOITEM 0x002F +#define WM_SETFONT 0x0030 +#define WM_GETFONT 0x0031 +#define WM_SETHOTKEY 0x0032 +#define WM_GETHOTKEY 0x0033 +#define WM_QUERYDRAGICON 0x0037 +#define WM_COMPAREITEM 0x0039 +#define WM_GETOBJECT 0x003D +#define WM_COMPACTING 0x0041 +#define WM_COMMNOTIFY 0x0044 +#define WM_WINDOWPOSCHANGING 0x0046 +#define WM_WINDOWPOSCHANGED 0x0047 +#define WM_POWER 0x0048 +#define WM_COPYDATA 0x004A +#define WM_CANCELJOURNAL 0x004B +#define WM_NOTIFY 0x004E +#define WM_INPUTLANGCHANGEREQUEST 0x0050 +#define WM_INPUTLANGCHANGE 0x0051 +#define WM_TCARD 0x0052 +#define WM_HELP 0x0053 +#define WM_USERCHANGED 0x0054 +#define WM_NOTIFYFORMAT 0x0055 +#define WM_CONTEXTMENU 0x007B +#define WM_STYLECHANGING 0x007C +#define WM_STYLECHANGED 0x007D +#define WM_DISPLAYCHANGE 0x007E +#define WM_GETICON 0x007F +#define WM_SETICON 0x0080 +#define WM_NCCREATE 0x0081 +#define WM_NCDESTROY 0x0082 +#define WM_NCCALCSIZE 0x0083 +#define WM_NCHITTEST 0x0084 +#define WM_NCPAINT 0x0085 +#define WM_NCACTIVATE 0x0086 +#define WM_GETDLGCODE 0x0087 +#define WM_SYNCPAINT 0x0088 +#define WM_NCMOUSEMOVE 0x00A0 +#define WM_NCLBUTTONDOWN 0x00A1 +#define WM_NCLBUTTONUP 0x00A2 +#define WM_NCLBUTTONDBLCLK 0x00A3 +#define WM_NCRBUTTONDOWN 0x00A4 +#define WM_NCRBUTTONUP 0x00A5 +#define WM_NCRBUTTONDBLCLK 0x00A6 +#define WM_NCMBUTTONDOWN 0x00A7 +#define WM_NCMBUTTONUP 0x00A8 +#define WM_NCMBUTTONDBLCLK 0x00A9 + +#define EM_GETSEL 0x00B0 +#define EM_SETSEL 0x00B1 +#define EM_GETRECT 0x00B2 +#define EM_SETRECT 0x00B3 +#define EM_SETRECTNP 0x00B4 +#define EM_SCROLL 0x00B5 +#define EM_LINESCROLL 0x00B6 +#define EM_SCROLLCARET 0x00B7 +#define EM_GETMODIFY 0x00B8 +#define EM_SETMODIFY 0x00B9 +#define EM_GETLINECOUNT 0x00BA +#define EM_LINEINDEX 0x00BB +#define EM_SETHANDLE 0x00BC +#define EM_GETHANDLE 0x00BD +#define EM_GETTHUMB 0x00BE +#define EM_LINELENGTH 0x00C1 +#define EM_REPLACESEL 0x00C2 +#define EM_GETLINE 0x00C4 +#define EM_LIMITTEXT 0x00C5 +#define EM_CANUNDO 0x00C6 +#define EM_UNDO 0x00C7 +#define EM_FMTLINES 0x00C8 +#define EM_LINEFROMCHAR 0x00C9 +#define EM_SETTABSTOPS 0x00CB +#define EM_SETPASSWORDCHAR 0x00CC +#define EM_EMPTYUNDOBUFFER 0x00CD +#define EM_GETFIRSTVISIBLELINE 0x00CE +#define EM_SETREADONLY 0x00CF +#define EM_SETWORDBREAKPROC 0x00D0 +#define EM_GETWORDBREAKPROC 0x00D1 +#define EM_GETPASSWORDCHAR 0x00D2 +#define EM_SETMARGINS 0x00D3 +#define EM_GETMARGINS 0x00D4 +#define EM_GETLIMITTEXT 0x00D5 +#define EM_POSFROMCHAR 0x00D6 +#define EM_CHARFROMPOS 0x00D7 +#define EM_SETIMESTATUS 0x00D8 +#define EM_GETIMESTATUS 0x00D9 + +#define WM_KEYFIRST 0x0100 +#define WM_KEYDOWN 0x0100 +#define WM_KEYUP 0x0101 +#define WM_CHAR 0x0102 +#define WM_DEADCHAR 0x0103 +#define WM_SYSKEYDOWN 0x0104 +#define WM_SYSKEYUP 0x0105 +#define WM_SYSCHAR 0x0106 +#define WM_SYSDEADCHAR 0x0107 +#define WM_KEYLAST 0x0108 +#define WM_IME_STARTCOMPOSITION 0x010D +#define WM_IME_ENDCOMPOSITION 0x010E +#define WM_IME_COMPOSITION 0x010F +#define WM_IME_KEYLAST 0x010F +#define WM_INITDIALOG 0x0110 +#define WM_COMMAND 0x0111 +#define WM_SYSCOMMAND 0x0112 +#define WM_TIMER 0x0113 +#define WM_HSCROLL 0x0114 +#define WM_VSCROLL 0x0115 +#define WM_INITMENU 0x0116 +#define WM_INITMENUPOPUP 0x0117 +#define WM_SYSTIMER 0x0118 +#define WM_MENUSELECT 0x011F +#define WM_MENUCHAR 0x0120 +#define WM_ENTERIDLE 0x0121 +#define WM_MENURBUTTONUP 0x0122 +#define WM_MENUDRAG 0x0123 +#define WM_MENUGETOBJECT 0x0124 +#define WM_UNINITMENUPOPUP 0x0125 +#define WM_MENUCOMMAND 0x0126 +#define WM_CTLCOLORMSGBOX 0x0132 +#define WM_CTLCOLOREDIT 0x0133 +#define WM_CTLCOLORLISTBOX 0x0134 +#define WM_CTLCOLORBTN 0x0135 +#define WM_CTLCOLORDLG 0x0136 +#define WM_CTLCOLORSCROLLBAR 0x0137 +#define WM_CTLCOLORSTATIC 0x0138 +#define WM_MOUSEFIRST 0x0200 +#define WM_MOUSEMOVE 0x0200 +#define WM_LBUTTONDOWN 0x0201 +#define WM_LBUTTONUP 0x0202 +#define WM_LBUTTONDBLCLK 0x0203 +#define WM_RBUTTONDOWN 0x0204 +#define WM_RBUTTONUP 0x0205 +#define WM_RBUTTONDBLCLK 0x0206 +#define WM_MBUTTONDOWN 0x0207 +#define WM_MBUTTONUP 0x0208 +#define WM_MBUTTONDBLCLK 0x0209 +#define WM_MOUSEWHEEL 0x020A +#define WM_MOUSELAST 0x0209 +#define WM_PARENTNOTIFY 0x0210 +#define WM_ENTERMENULOOP 0x0211 +#define WM_EXITMENULOOP 0x0212 +#define WM_NEXTMENU 0x0213 +#define WM_SIZING 0x0214 +#define WM_CAPTURECHANGED 0x0215 +#define WM_MOVING 0x0216 +#define WM_POWERBROADCAST 0x0218 +#define WM_DEVICECHANGE 0x0219 +#define WM_MDICREATE 0x0220 +#define WM_MDIDESTROY 0x0221 +#define WM_MDIACTIVATE 0x0222 +#define WM_MDIRESTORE 0x0223 +#define WM_MDINEXT 0x0224 +#define WM_MDIMAXIMIZE 0x0225 +#define WM_MDITILE 0x0226 +#define WM_MDICASCADE 0x0227 +#define WM_MDIICONARRANGE 0x0228 +#define WM_MDIGETACTIVE 0x0229 +#define WM_MDISETMENU 0x0230 +#define WM_ENTERSIZEMOVE 0x0231 +#define WM_EXITSIZEMOVE 0x0232 +#define WM_DROPFILES 0x0233 +#define WM_MDIREFRESHMENU 0x0234 +#define WM_IME_SETCONTEXT 0x0281 +#define WM_IME_NOTIFY 0x0282 +#define WM_IME_CONTROL 0x0283 +#define WM_IME_COMPOSITIONFULL 0x0284 +#define WM_IME_SELECT 0x0285 +#define WM_IME_CHAR 0x0286 +#define WM_IME_REQUEST 0x0288 +#define WM_IME_KEYDOWN 0x0290 +#define WM_IME_KEYUP 0x0291 +#define WM_MOUSEHOVER 0x02A1 +#define WM_MOUSELEAVE 0x02A3 +#define WM_CUT 0x0300 +#define WM_COPY 0x0301 +#define WM_PASTE 0x0302 +#define WM_CLEAR 0x0303 +#define WM_UNDO 0x0304 +#define WM_RENDERFORMAT 0x0305 +#define WM_RENDERALLFORMATS 0x0306 +#define WM_DESTROYCLIPBOARD 0x0307 +#define WM_DRAWCLIPBOARD 0x0308 +#define WM_PAINTCLIPBOARD 0x0309 +#define WM_VSCROLLCLIPBOARD 0x030A +#define WM_SIZECLIPBOARD 0x030B +#define WM_ASKCBFORMATNAME 0x030C +#define WM_CHANGECBCHAIN 0x030D +#define WM_HSCROLLCLIPBOARD 0x030E +#define WM_QUERYNEWPALETTE 0x030F +#define WM_PALETTEISCHANGING 0x0310 +#define WM_PALETTECHANGED 0x0311 +#define WM_HOTKEY 0x0312 +#define WM_PRINT 0x0317 +#define WM_PRINTCLIENT 0x0318 +#define WM_HANDHELDFIRST 0x0358 +#define WM_HANDHELDLAST 0x035F +#define WM_AFXFIRST 0x0360 +#define WM_AFXLAST 0x037F +}; + +value DWORD SystemObjectId +{ +#define OBJID_WINDOW 0x00000000 +#define OBJID_SYSMENU 0xFFFFFFFF +#define OBJID_TITLEBAR 0xFFFFFFFE +#define OBJID_MENU 0xFFFFFFFD +#define OBJID_CLIENT 0xFFFFFFFC +#define OBJID_VSCROLL 0xFFFFFFFB +#define OBJID_HSCROLL 0xFFFFFFFA +#define OBJID_SIZEGRIP 0xFFFFFFF9 +#define OBJID_CARET 0xFFFFFFF8 +#define OBJID_CURSOR 0xFFFFFFF7 +#define OBJID_ALERT 0xFFFFFFF6 +#define OBJID_SOUND 0xFFFFFFF5 +#define OBJID_QUERYCLASSNAMEIDX 0xFFFFFFF4 +#define OBJID_NATIVEOM 0xFFFFFFF0 +}; + +typedef struct tagMSG { // msg + HWND hwnd; + WindowsMessage message; + WPARAM wParam; + LPARAM lParam; + DWORD time; + POINT pt; +} MSG, *LPMSG; + +mask DWORD WindowStyle +{ +#define WS_OVERLAPPED 0x00000000 +#define WS_POPUP 0x80000000 +#define WS_CHILD 0x40000000 +#define WS_MINIMIZE 0x20000000 +#define WS_VISIBLE 0x10000000 +#define WS_DISABLED 0x08000000 +#define WS_CLIPSIBLINGS 0x04000000 +#define WS_CLIPCHILDREN 0x02000000 +#define WS_MAXIMIZE 0x01000000 +#define WS_BORDER 0x00800000 +#define WS_DLGFRAME 0x00400000 +#define WS_VSCROLL 0x00200000 +#define WS_HSCROLL 0x00100000 +#define WS_SYSMENU 0x00080000 +#define WS_THICKFRAME 0x00040000 +#define WS_GROUP 0x00020000 +#define WS_TABSTOP 0x00010000 +#define WS_ACTIVECAPTION 0x00000001 +}; + +mask DWORD WindowStyleEx +{ +#define WS_EX_LEFT 0x00000000 + +#define WS_EX_DLGMODALFRAME 0x00000001 +#define WS_EX_NOPARENTNOTIFY 0x00000004 +#define WS_EX_TOPMOST 0x00000008 +#define WS_EX_ACCEPTFILES 0x00000010 +#define WS_EX_TRANSPARENT 0x00000020 +#define WS_EX_MDICHILD 0x00000040 +#define WS_EX_TOOLWINDOW 0x00000080 +#define WS_EX_WINDOWEDGE 0x00000100 +#define WS_EX_CLIENTEDGE 0x00000200 +#define WS_EX_CONTEXTHELP 0x00000400 + +#define WS_EX_RIGHT 0x00001000 +#define WS_EX_RTLREADING 0x00002000 +#define WS_EX_LEFTSCROLLBAR 0x00004000 + +#define WS_EX_CONTROLPARENT 0x00010000 +#define WS_EX_STATICEDGE 0x00020000 +#define WS_EX_APPWINDOW 0x00040000 +#define WS_EX_LAYERED 0x00080000 + +#define WS_EX_NOINHERITLAYOUT 0x00100000 +#define WS_EX_LAYOUTRTL 0x00400000 +#define WS_EX_COMPOSITED 0x02000000 +#define WS_EX_NOACTIVATE 0x08000000 +}; + + +HWND [gle] CreateWindowExA( + WindowStyleEx dwExStyle, + LPCSTR lpClassName, + LPCSTR lpWindowName, + WindowStyle dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + ULONG hMenu, + HINSTANCE hInstance, + LPVOID lpParam); + +HWND [gle] CreateWindowExW( + WindowStyleEx dwExStyle, + LPCWSTR lpClassName, + LPCWSTR lpWindowName, + WindowStyle dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + ULONG hMenu, + HINSTANCE hInstance, + LPVOID lpParam); + +FailOnFalse [gle] DestroyWindow( [da] HWND hWnd); + +FailOnFalse [gle] CloseWindow(HWND hWnd); + +mask DWORD ActivateKeyboardLayoutFlags +{ +#define KLF_ACTIVATE 0x00000001 +#define KLF_SUBSTITUTE_OK 0x00000002 +#define KLF_REORDER 0x00000008 +#define KLF_REPLACELANG 0x00000010 +#define KLF_NOTELLSHELL 0x00000080 +#define KLF_SETFORPROCESS 0x00000100 +#define KLF_SHIFTLOCK 0x00010000 +#define KLF_RESET 0x40000000 +}; + + +HKL ActivateKeyboardLayout(HKL hkl, ActivateKeyboardLayoutFlags Flags); +HKL GetKeyboardLayout(ThreadId idThread); + +// BUGBUG: lpList is an array but there is no easy way to show all the values in it. +LongFailIfZero [gle] GetKeyboardLayoutList(int nBuff, [out] HKL* lpList); + +FailOnFalse [gle] GetKeyboardLayoutNameA([out] LPSTR pwszKLID); +FailOnFalse [gle] GetKeyboardLayoutNameW([out] LPWSTR pwszKLID); + + +UINT GetKBCodePage(); + +mask DWORD KeyboardLayoutFlags +{ +#define KLF_ACTIVATE 0x00000001 +#define KLF_SUBSTITUTE_OK 0x00000002 +#define KLF_REORDER 0x00000008 +#define KLF_REPLACELANG 0x00000010 +#define KLF_NOTELLSHELL 0x00000080 +#define KLF_SETFORPROCESS 0x00000100 +#define KLF_SHIFTLOCK 0x00010000 +#define KLF_RESET 0x40000000 +}; + +HKL [gle] LoadKeyboardLayoutA(LPCSTR pwszKLID, KeyboardLayoutFlags Flags); +HKL [gle] LoadKeyboardLayoutW(LPCWSTR pwszKLID, KeyboardLayoutFlags Flags); + +FailOnFalse [gle] UnloadKeyboardLayout([da] HKL hkl); + +FailOnFalse [gle] SetProcessDefaultLayout(DWORD dwDefaultLayout); +FailOnFalse [gle] GetProcessDefaultLayout([out] LPDWORD lpdwDefaultLayout); + + + +FailOnFalse AdjustWindowRect([out] LPRECT lpRect, WindowStyle dwStyle, BOOL bMenu); +FailOnFalse AdjustWindowRectEx([out] LPRECT lpRect, WindowStyle dwStyle, BOOL bMenu, WindowStyleEx dwExStyle); + +FailOnFalse AllowSetForegroundWindow(ProcessId dwProcessId); + +HWND GetForegroundWindow(); +FailOnFalse SetForegroundWindow(HWND hWnd); + +value DWORD LockForegroundFlags +{ +#define LSFW_LOCK 1 +#define LSFW_UNLOCK 2 +}; + +FailOnFalse [gle] LockSetForegroundWindow(LockForegroundFlags uLockCode); + +HWND GetActiveWindow(); +HWND [gle] SetActiveWindow(HWND hWnd); + + +mask DWORD AnimateWindowFlags +{ +#define AW_HOR_POSITIVE 0x00000001 +#define AW_HOR_NEGATIVE 0x00000002 +#define AW_VER_POSITIVE 0x00000004 +#define AW_VER_NEGATIVE 0x00000008 +#define AW_CENTER 0x00000010 +#define AW_HIDE 0x00010000 +#define AW_ACTIVATE 0x00020000 +#define AW_SLIDE 0x00040000 +#define AW_BLEND 0x00080000 +}; + +FailOnFalse AnimateWindow(HWND hWnd, DWORD dwTime, AnimateWindowFlags dwFlags); + +FailOnFalse AnyPopup(); + +// Menu flags: + +mask DWORD MenuFlags +{ +#define MF_ENABLED 0x00000000 +#define MF_GRAYED 0x00000001 +#define MF_DISABLED 0x00000002 +#define MF_BITMAP 0x00000004 +#define MF_CHECKED 0x00000008 +#define MF_POPUP 0x00000010 +#define MF_MENUBARBREAK 0x00000020 +#define MF_MENUBREAK 0x00000040 +#define MF_CHANGE_HILITE 0x00000080 +#define MF_OWNERDRAW 0x00000100 +#define MF_DELETE 0x00000200 +#define MF_BYPOSITION 0x00000400 +#define MF_SEPARATOR 0x00000800 +}; + + + +FailOnFalse [gle] AppendMenuA(HMENU hMenu, MenuFlags uFlags, DWORD uIDNewItem, LPCSTR lpNewItem); +FailOnFalse [gle] AppendMenuW(HMENU hMenu, MenuFlags uFlags, DWORD uIDNewItem, LPCWSTR lpNewItem); + +HMENU [gle] LoadMenuA(HINSTANCE hInstance, LPCSTR lpMenuName); +HMENU [gle] LoadMenuW(HINSTANCE hInstance, LPCWSTR lpMenuName); + +HMENU [gle] LoadMenuIndirectA(LPVOID lpMenuTemplate); +HMENU [gle] LoadMenuIndirectW(LPVOID lpMenuTemplate); + + +HMENU [gle] CreateMenu(); + +FailOnFalse [gle] DestroyMenu( [da] HMENU hMenu); + +FailOnFalse [gle] DeleteMenu(HMENU hMenu, UINT uPosition, MenuFlags uFlags); + +MenuFlags CheckMenuItem(HMENU hMenu, UINT uIDCheckItem, MenuFlags uCheck); + +FailOnFalse [gle] CheckMenuRadioItem(HMENU hMenu, + UINT idFirst, + UINT idLast, + UINT idCheck, + MenuFlags uFlags); + +HMENU [gle] CreatePopupMenu(); + +FailOnFalse [gle] DrawMenuBar(HWND hWnd); + +MenuFlags EnableMenuItem(HMENU hMenu, UINT uIDEnableItem, MenuFlags uEnable); + +FailOnFalse [gle] EndMenu(); + +HMENU GetMenu(HWND hWnd); + +typedef struct tagMENUBARINFO +{ + DWORD cbSize; + RECT rcBar; + HMENU hMenu; + HWND hwndMenu; + DWORD fFocused; +} MENUBARINFO, *PMENUBARINFO, *LPMENUBARINFO; + +FailOnFalse [gle] GetMenuBarInfo(HWND hwnd, + SystemObjectId idObject, + LONG idItem, + [out] MENUBARINFO pmbi); + +LONG GetMenuCheckMarkDimensions(); + +DWORD GetMenuContextHelpId(HMENU hmenu); + +mask DWORD GetMenuDefItemFlags +{ +#define GMDI_USEDISABLED 0x0001 +#define GMDI_GOINTOPOPUPS 0x0002 +}; + +LongFailIfNeg1 [gle] GetMenuDefaultItem(HMENU hMenu, BOOL bByPos, GetMenuDefItemFlags gmdiFlags); + +mask DWORD MenuInfoMask +{ +#define MIM_MAXHEIGHT 0x00000001 +#define MIM_BACKGROUND 0x00000002 +#define MIM_HELPID 0x00000004 +#define MIM_MENUDATA 0x00000008 +#define MIM_STYLE 0x00000010 +#define MIM_APPLYTOSUBMENUS 0x80000000 +}; + +mask DWORD MenuInfoStyle +{ +#define MNS_NOCHECK 0x80000000 +#define MNS_MODELESS 0x40000000 +#define MNS_DRAGDROP 0x20000000 +#define MNS_AUTODISMISS 0x10000000 +#define MNS_NOTIFYBYPOS 0x08000000 +#define MNS_CHECKORBMP 0x04000000 +}; + +typedef struct tagMENUINFO +{ + DWORD cbSize; + MenuInfoMask fMask; + MenuInfoStyle dwStyle; + UINT cyMax; + HRSRC hbrBack; + DWORD dwContextHelpID; + DWORD dwMenuData; +} MENUINFO, *LPMENUINFO; + +FailOnFalse [gle] GetMenuInfo(HMENU hMenu, [out] LPMENUINFO lpmi); + +LongFailIfNeg1 [gle] GetMenuItemCount(HMENU hMenu); + +LongFailIfNeg1 GetMenuItemID(HMENU hMenu, int nPos); + +mask DWORD MenuItemInfoMask +{ +#define MIIM_STATE 0x00000001 +#define MIIM_ID 0x00000002 +#define MIIM_SUBMENU 0x00000004 +#define MIIM_CHECKMARKS 0x00000008 +#define MIIM_TYPE 0x00000010 +#define MIIM_DATA 0x00000020 + +#define MIIM_STRING 0x00000040 +#define MIIM_BITMAP 0x00000080 +#define MIIM_FTYPE 0x00000100 +}; + +mask DWORD MenuItemInfoType +{ +#define MFT_STRING 0x00000000 +#define MFT_BITMAP 0x00000004 +#define MFT_MENUBARBREAK 0x00000020 +#define MFT_MENUBREAK 0x00000040 +#define MFT_OWNERDRAW 0x00000100 +#define MFT_RADIOCHECK 0x00000200 +#define MFT_SEPARATOR 0x00000800 +#define MFT_RIGHTORDER 0x00002000 +#define MFT_RIGHTJUSTIFY 0x00004000 +}; + +mask DWORD MenuItemInfoState +{ +#define MFS_CHECKED 0x00000008 +#define MFS_DEFAULT 0x00001000 +#define MFS_DISABLED1 0x00000001 +#define MFS_DISABLED2 0x00000002 +#define MFS_HILITE 0x00000080 +}; + +typedef struct tagMENUITEMINFOA +{ + UINT cbSize; + MenuItemInfoMask fMask; + MenuItemInfoType fType; + MenuItemInfoState fState; + UINT wID; + HMENU hSubMenu; + HRSRC hbmpChecked; + HRSRC hbmpUnchecked; + DWORD dwItemData; + LPSTR dwTypeData; + UINT cch; + HRSRC hbmpItem; +} MENUITEMINFOA, *LPMENUITEMINFOA; + +typedef struct tagMENUITEMINFOW +{ + UINT cbSize; + MenuItemInfoMask fMask; + MenuItemInfoType fType; + MenuItemInfoState fState; + UINT wID; + HMENU hSubMenu; + HRSRC hbmpChecked; + HRSRC hbmpUnchecked; + DWORD dwItemData; + LPWSTR dwTypeData; + UINT cch; + HRSRC hbmpItem; +} MENUITEMINFOW, *LPMENUITEMINFOW; + + +FailOnFalse [gle] GetMenuItemInfoA(HMENU hMenu, + UINT uItem, + BOOL fByPosition, + [out] LPMENUITEMINFOA lpmii); + +FailOnFalse [gle] GetMenuItemInfoW(HMENU hMenu, + UINT uItem, + BOOL fByPosition, + [out] LPMENUITEMINFOW lpmii); + +FailOnFalse [gle] GetMenuItemRect(HWND hWnd, HMENU hMenu, UINT uItem, [out] LPRECT lprcItem); + +MenuFlags GetMenuState(HMENU hMenu, UINT uId, MenuFlags uFlags); + +UintFailIfZero GetMenuStringA(HMENU hMenu, + UINT uIDItem, + [out] LPSTR lpString, + int nMaxCount, + MenuFlags uFlag); + +UintFailIfZero GetMenuStringW(HMENU hMenu, + UINT uIDItem, + [out] LPWSTR lpString, + int nMaxCount, + MenuFlags uFlag); + +HMENU GetSubMenu(HMENU hMenu, int nPos); + +HMENU GetSystemMenu(HWND hWnd, BOOL bRevert); + +FailOnFalse HiliteMenuItem(HWND hWnd, HMENU hMenu, UINT uIDHiliteItem, MenuFlags uHilite); + +FailOnFalse [gle] InsertMenuA(HMENU hMenu, + UINT uPosition, + MenuFlags uFlags, + DWORD uIDNewItem, + DWORD lpNewItem); + +FailOnFalse [gle] InsertMenuW(HMENU hMenu, + UINT uPosition, + MenuFlags uFlags, + DWORD uIDNewItem, + DWORD lpNewItem); + +FailOnFalse InsertMenuItemA(HMENU hMenu, + UINT uItem, + BOOL bByPosition, + LPMENUITEMINFOA lpmii); + +FailOnFalse InsertMenuItemW(HMENU hMenu, + UINT uItem, + BOOL bByPosition, + LPMENUITEMINFOW lpmii); + + +LongFailIfNeg1 MenuItemFromPoint(HWND hWnd, HMENU hMenu, int x, int y); + +FailOnFalse [gle] ModifyMenuA(HMENU hMenu, + UINT uPosition, + MenuFlags uFlags, + DWORD uIDNewItem, + DWORD lpNewItem); + +FailOnFalse [gle] ModifyMenuW(HMENU hMenu, + UINT uPosition, + MenuFlags uFlags, + DWORD uIDNewItem, + DWORD lpNewItem); + + +FailOnFalse [gle] RemoveMenu(HMENU hMenu, + UINT uPosition, + MenuFlags uFlags); + +FailOnFalse [gle] SetMenu(HWND hWnd, HMENU hMenu); + +FailOnFalse [gle] SetMenuContextHelpId(HMENU hmenu, DWORD dwContextHelpId); + +FailOnFalse [gle] SetMenuDefaultItem(HMENU hMenu, UINT uItem, BOOL fByPos); + +FailOnFalse [gle] SetMenuInfo(HMENU hMenu, LPMENUINFO lpmi); + +FailOnFalse [gle] SetMenuItemBitmaps(HMENU hMenu, + UINT uPosition, + MenuFlags uFlags, + HRSRC hBitmapUnchecked, + HRSRC hBitmapChecked); + +FailOnFalse [gle] SetMenuItemInfoA(HMENU hMenu, + UINT uItem, + BOOL fByPosition, + LPMENUITEMINFOA lpmii); + +FailOnFalse [gle] SetMenuItemInfoW(HMENU hMenu, + UINT uItem, + BOOL fByPosition, + LPMENUITEMINFOW lpmii); + +mask DWORD TrackPopupMenuFlags +{ +#define TPM_LEFTTOPALIGN 0x0000 +#define TPM_RECURSE 0x0001 +#define TPM_RIGHTBUTTON 0x0002 +#define TPM_CENTERALIGN 0x0004 +#define TPM_RIGHTALIGN 0x0008 +#define TPM_VCENTERALIGN 0x0010 +#define TPM_BOTTOMALIGN 0x0020 +#define TPM_VERTICAL 0x0040 +#define TPM_NONOTIFY 0x0080 +#define TPM_RETURNCMD 0x0100 +#define TPM_HORPOSANIMATION 0x0400 +#define TPM_HORNEGANIMATION 0x0800 +#define TPM_VERPOSANIMATION 0x1000 +#define TPM_VERNEGANIMATION 0x2000 +#define TPM_NOANIMATION 0x4000 +}; + + +LongFailIfZero [gle] TrackPopupMenu(HMENU hMenu, + TrackPopupMenuFlags uFlags, + int x, + int y, + int nReserved, + HWND hWnd, + LPRECT lprect); + +typedef struct tagTPMPARAMS +{ + UINT cbSize; + RECT rcExclude; +} TPMPARAMS, *LPTPMPARAMS; + +LongFailIfZero [gle] TrackPopupMenuEx(HMENU hMenu, + TrackPopupMenuFlags uFlags, + int x, + int y, + HWND hWnd, + LPTPMPARAMS lpParams); + + +FailOnFalse [gle] ArrangeIconicWindows(HWND hwnd); + +FailOnFalse [gle] AttachThreadInput(ThreadId idAttach, ThreadId idAttachTo, BOOL fAttach); + +value DWORD HDWP +{ +#define NULL 0 [fail] +}; + +alias HDWP; + +mask DWORD SetWindowPosFlags +{ +#define SWP_NOSIZE 0x0001 +#define SWP_NOMOVE 0x0002 +#define SWP_NOZORDER 0x0004 +#define SWP_NOREDRAW 0x0008 +#define SWP_NOACTIVATE 0x0010 +#define SWP_FRAMECHANGED 0x0020 +#define SWP_SHOWWINDOW 0x0040 +#define SWP_HIDEWINDOW 0x0080 +#define SWP_NOCOPYBITS 0x0100 +#define SWP_NOOWNERZORDER 0x0200 +#define SWP_NOSENDCHANGING 0x0400 +#define SWP_DEFERERASE 0x2000 +#define SWP_ASYNCWINDOWPOS 0x4000 +}; + +FailOnFalse [gle] SetWindowPos(HWND hWnd, + HWND hWndInsertAfter, + int x, + int y, + int cx, + int cy, + SetWindowPosFlags uFlags); + +BOOL ShowWindow(HWND hWnd, ShowWindowCommand nCmdShow); + +BOOL ShowWindowAsync(HWND hWnd, ShowWindowCommand nCmdShow); + +typedef struct tagWINDOWPLACEMENT { + UINT length; + UINT flags; + UINT showCmd; + POINT ptMinPosition; + POINT ptMaxPosition; + RECT rcNormalPosition; +} WINDOWPLACEMENT, *LPWINDOWPLACEMENT; + + +FailOnFalse [gle] SetWindowPlacement(HWND hWnd, + LPWINDOWPLACEMENT lpwndpl); + +FailOnFalse [gle] GetWindowPlacement(HWND hWnd, [out] LPWINDOWPLACEMENT lpwndpl); + + +HDWP BeginDeferWindowPos(int nNumWindows); +HDWP DeferWindowPos(HDWP hWinPosInfo, + HWND hWnd, + HWND hWndInsertAfter, + int x, + int y, + int cx, + int cy, + SetWindowPosFlags uFlags); + +FailOnFalse [gle] EndDeferWindowPos( [da] HDWP hWinPosInfo); + +FailOnFalse [gle] MoveWindow(HWND hWnd, + int X, + int Y, + int nWidth, + int nHeight, + BOOL bRepaint); + +typedef struct tagPAINTSTRUCT { + HDC hdc; + BOOL fErase; + RECT rcPaint; + BOOL fRestore; + BOOL fIncUpdate; + BYTE rgbReserved[32]; +} PAINTSTRUCT, *PPAINTSTRUCT, *LPPAINTSTRUCT; + + +HDC BeginPaint(HWND hWnd, [out] LPPAINTSTRUCT lpPaint); +FailOnFalse EndPaint(HWND hWnd, LPPAINTSTRUCT lpPaint); + +mask DWORD RedrawWindowFlags +{ +#define RDW_INVALIDATE 0x0001 +#define RDW_INTERNALPAINT 0x0002 +#define RDW_ERASE 0x0004 + +#define RDW_VALIDATE 0x0008 +#define RDW_NOINTERNALPAINT 0x0010 +#define RDW_NOERASE 0x0020 + +#define RDW_NOCHILDREN 0x0040 +#define RDW_ALLCHILDREN 0x0080 + +#define RDW_UPDATENOW 0x0100 +#define RDW_ERASENOW 0x0200 + +#define RDW_FRAME 0x0400 +#define RDW_NOFRAME 0x0800 +}; + +FailOnFalse [gle] RedrawWindow(HWND hWnd, + LPRECT lprcUpdate, + HRGN hrgnUpdate, + RedrawWindowFlags flags); + +FailOnFalse [gle] PaintDesktop(HDC hdc); + +HWND GetDesktopWindow(); + +HDC [gle] GetDC(HWND hWnd); + +mask DWORD GetDCExFlags +{ +#define DCX_WINDOW 0x00000001 +#define DCX_CACHE 0x00000002 +#define DCX_NORESETATTRS 0x00000004 +#define DCX_CLIPCHILDREN 0x00000008 +#define DCX_CLIPSIBLINGS 0x00000010 +#define DCX_PARENTCLIP 0x00000020 +#define DCX_EXCLUDERGN 0x00000040 +#define DCX_INTERSECTRGN 0x00000080 +#define DCX_EXCLUDEUPDATE 0x00000100 +#define DCX_INTERSECTUPDATE 0x00000200 +#define DCX_LOCKWINDOWUPDATE 0x00000400 +#define DCX_VALIDATE 0x00200000 +}; + +HDC [gle] GetDCEx(HWND hWnd, HRGN hrgnClip, GetDCExFlags flags); + +HDC [gle] GetWindowDC(HWND hWnd); + +FailOnFalse ReleaseDC(HWND hWnd, [da] HDC hDC); + +FailOnFalse [gle] ScrollDC(HDC hDC, + int dx, + int dy, + LPRECT lprcScroll, + LPRECT lprcClip, + HRGN hrgnUpdate, + [out] LPRECT lprcUpdate); + +FailOnFalse [gle] UpdateWindow(HWND hWnd); + +FailOnFalse [gle] InvalidateRect(HWND hWnd, LPRECT lpRect, BOOL bErase); +FailOnFalse [gle] InvalidateRgn(HWND hWnd, HRGN hRgn, BOOL bErase); + +FailOnFalse [gle] ValidateRect(HWND hWnd, LPRECT lpRect); +FailOnFalse [gle] ValidateRgn(HWND hWnd, HRGN hRgn); + +HRGN ExcludeUpdateRgn(HDC hDC, HWND hWnd); + +FailOnFalse [gle] GetUpdateRect(HWND hWnd, + [out] LPRECT lpRect, + BOOL bErase); + +HRGN GetUpdateRgn(HWND hWnd, HRGN hRgn, BOOL bErase); + + +HWND WindowFromDC(HDC hDC); + +HWND WindowFromPoint(long x, long y); + +HWND ChildWindowFromPoint(HWND hWndParent, int x, int y); + +mask DWORD ChildWindowFromPointExFlags +{ +#define CWP_ALL 0x0000 +#define CWP_SKIPINVISIBLE 0x0001 +#define CWP_SKIPDISABLED 0x0002 +#define CWP_SKIPTRANSPARENT 0x0004 +}; + +HWND ChildWindowFromPointEx(HWND hwndParent, int x, int y, ChildWindowFromPointExFlags dwFlags); + +HWND RealChildWindowFromPoint(HWND hwndParent, int x, int y); + +// BUGBUG: need to use [in] [ou] +FailOnFalse [gle] ClientToScreen(HWND hWnd, [out] LPPOINT lpPoint); +FailOnFalse [gle] ScreenToClient(HWND hWnd, [out] LPPOINT lpPoint); + + +FailOnFalse [gle] ScrollWindow(HWND hWnd, + int XAmount, + int YAmount, + LPRECT lpRect, + LPRECT lpClipRect); + +mask DWORD ScrollWindowExFlags +{ +#define SW_SCROLLCHILDREN 0x0001 +#define SW_INVALIDATE 0x0002 +#define SW_ERASE 0x0004 +#define SW_SMOOTHSCROLL 0x0010 +}; + +HRGN [gle] ScrollWindowEx(HWND hWnd, + int dx, + int dy, + LPRECT lprcScroll, + LPRECT lprcClip, + HRGN hrgnUpdate, + [out] LPRECT lprcUpdate, + ScrollWindowExFlags flags); + + + +LRESULT SendMessageA(HWND hWnd, WindowsMessage Msg, WPARAM wParam, LPARAM lParam); +LRESULT SendMessageW(HWND hWnd, WindowsMessage Msg, WPARAM wParam, LPARAM lParam); + +LRESULT SendDlgItemMessageA(HWND hDlg, int nIDDlgItem, WindowsMessage Msg, WPARAM wParam, LPARAM lParam); +LRESULT SendDlgItemMessageW(HWND hDlg, int nIDDlgItem, WindowsMessage Msg, WPARAM wParam, LPARAM lParam); + +FailOnFalse [gle] PostMessageA(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +FailOnFalse [gle] PostMessageW(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +VOID PostQuitMessage(int nExitCode); + +FailOnFalse [gle] PostThreadMessageA(ThreadId idThread, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +FailOnFalse [gle] PostThreadMessageW(ThreadId idThread, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +BOOL TranslateMessage(MSG* lpMsg); + + +typedef DWORD SENDASYNCPROC; + +alias SENDASYNCPROC; + +FailOnFalse [gle] SendMessageCallbackA(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam, + SENDASYNCPROC lpResultCallBack, + ULONG_PTR dwData); + +FailOnFalse [gle] SendMessageCallbackW(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam, + SENDASYNCPROC lpResultCallBack, + ULONG_PTR dwData); + +mask DWORD SendMessageTimeoutFlags +{ +#define SMTO_NORMAL 0x0000 +#define SMTO_BLOCK 0x0001 +#define SMTO_ABORTIFHUNG 0x0002 +#define SMTO_NOTIMEOUTIFNOTHUNG 0x0008 +}; + +LRESULT SendMessageTimeoutA(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam, + SendMessageTimeoutFlags fuFlags, + UINT uTimeout, + [out] LPDWORD lpdwResult); + +LRESULT SendMessageTimeoutW(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam, + SendMessageTimeoutFlags fuFlags, + UINT uTimeout, + [out] LPDWORD lpdwResult); + +FailOnFalse [gle] SendNotifyMessageA(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +FailOnFalse [gle] SendNotifyMessageW(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + + +BOOL InSendMessage(); + +mask DWORD InSendMessageExFlags +{ +#define ISMEX_NOSEND 0x00000000 +#define ISMEX_SEND 0x00000001 +#define ISMEX_NOTIFY 0x00000002 +#define ISMEX_CALLBACK 0x00000004 +#define ISMEX_REPLIED 0x00000008 +}; + +InSendMessageExFlags InSendMessageEx(LPVOID lpReserved); + + +mask DWORD BroadcastSystemMessageFlags +{ +#define BSF_QUERY 0x00000001 +#define BSF_IGNORECURRENTTASK 0x00000002 +#define BSF_FLUSHDISK 0x00000004 +#define BSF_NOHANG 0x00000008 +#define BSF_POSTMESSAGE 0x00000010 +#define BSF_FORCEIFHUNG 0x00000020 +#define BSF_NOTIMEOUTIFNOTHUNG 0x00000040 +#define BSF_ALLOWSFW 0x00000080 +#define BSF_SENDNOTIFYMESSAGE 0x00000100 +#define BSF_RETURNHDESK 0x00000200 +#define BSF_LUID 0x00000400 +}; + +mask DWORD BroadcastSystemMessageRecipients +{ +#define BSM_ALLCOMPONENTS 0x00000000 +#define BSM_VXDS 0x00000001 +#define BSM_NETDRIVER 0x00000002 +#define BSM_INSTALLABLEDRIVERS 0x00000004 +#define BSM_APPLICATIONS 0x00000008 +#define BSM_ALLDESKTOPS 0x00000010 +}; + +LongFailIfNeg1 [gle] BroadcastSystemMessageA(BroadcastSystemMessageFlags Flags, + [out] BroadcastSystemMessageRecipients* lpRecipients, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +LongFailIfNeg1 [gle] BroadcastSystemMessageW(BroadcastSystemMessageFlags Flags, + [out] BroadcastSystemMessageRecipients* lpRecipients, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DispatchMessageA(MSG* lpMsg); +LRESULT DispatchMessageW(MSG* lpMsg); + +LongFailIfNeg1 [gle] GetMessageA( [out] LPMSG lpMsg, + HWND hWnd, + WindowsMessage wMsgFilterMin, + WindowsMessage wMsgFilterMax); + +LongFailIfNeg1 [gle] GetMessageW( [out] LPMSG lpMsg, + HWND hWnd, + WindowsMessage wMsgFilterMin, + WindowsMessage wMsgFilterMax); + +FailOnFalse [gle] WaitMessage(); + +BOOL ReplyMessage(LRESULT lResult); + +mask DWORD PeekMessageFlags +{ +#define PM_NOREMOVE 0x0000 +#define PM_REMOVE 0x0001 +#define PM_NOYIELD 0x0002 +}; + +BOOL PeekMessageA( [out] LPMSG lpMsg, + HWND hWnd, + WindowsMessage wMsgFilterMin, + WindowsMessage wMsgFilterMax, + PeekMessageFlags wRemoveMsg); + +BOOL PeekMessageW( [out] LPMSG lpMsg, + HWND hWnd, + WindowsMessage wMsgFilterMin, + WindowsMessage wMsgFilterMax, + PeekMessageFlags wRemoveMsg); + + +LPARAM GetMessageExtraInfo(); +DWORD GetMessagePos(); +LONG GetMessageTime(); + +LRESULT DefWindowProcA(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DefWindowProcW(HWND hWnd, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DefDlgProcA(HWND hDlg, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DefDlgProcW(HWND hDlg, + WindowsMessage Msg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DefFrameProcA(HWND hWnd, + HWND hWndMDIClient, + WindowsMessage uMsg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DefFrameProcW(HWND hWnd, + HWND hWndMDIClient, + WindowsMessage uMsg, + WPARAM wParam, + LPARAM lParam); + + +LRESULT CallWindowProcA(WNDPROC lpPrevWndFunc, HWND hWnd, WindowsMessage Msg, WPARAM wParam, LPARAM lParam); +LRESULT CallWindowProcW(WNDPROC lpPrevWndFunc, HWND hWnd, WindowsMessage Msg, WPARAM wParam, LPARAM lParam); + + + +value DWORD WindowLongIndex +{ +#define GWL_WNDPROC -4 +#define GWL_HINSTANCE -6 +#define GWL_HWNDPARENT -8 +#define GWL_STYLE -16 +#define GWL_EXSTYLE -20 +#define GWL_USERDATA -21 +#define GWL_ID -12 +#define DWL_MSGRESULT 0 +#define DWL_DLGPROC 4 +#define DWL_USER 8 +}; + +DwordFailIfZero [gle] GetWindowLongA(HWND hWnd, WindowLongIndex nIndex); +DwordFailIfZero [gle] GetWindowLongW(HWND hWnd, WindowLongIndex nIndex); + +DwordFailIfZero [gle] SetWindowLongA(HWND hWnd, WindowLongIndex nIndex, DWORD dwNewLong); +DwordFailIfZero [gle] SetWindowLongW(HWND hWnd, WindowLongIndex nIndex, DWORD dwNewLong); + +DwordFailIfZero [gle] GetWindowWord(HWND hWnd, int nIndex); +DwordFailIfZero [gle] SetWindowWord(HWND hWnd, int nIndex, DWORD wNewWord); + + +LongFailIfZero [gle] SetWindowTextA(HWND hWnd, LPCSTR lpString); +LongFailIfZero [gle] SetWindowTextW(HWND hWnd, LPCWSTR lpString); + +LongFailIfZero [gle] GetWindowTextA(HWND hWnd, [out] LPSTR lpString, int nMaxCount); +LongFailIfZero [gle] GetWindowTextW(HWND hWnd, [out] LPWSTR lpString, int nMaxCount); + +LongFailIfZero [gle] GetWindowTextLengthA(HWND hWnd); +LongFailIfZero [gle] GetWindowTextLengthW(HWND hWnd); + +LongFailIfZero [gle] GetDlgItemTextA(HWND hDlg, int nIDDlgItem, [out] LPSTR lpString, int nMaxCount); +LongFailIfZero [gle] GetDlgItemTextW(HWND hDlg, int nIDDlgItem, [out] LPWSTR lpString, int nMaxCount); + +FailOnFalse [gle] SetDlgItemInt(HWND hDlg, int nIDDlgItem, UINT uValue, BOOL bSigned); + +FailOnFalse [gle] SetDlgItemTextA(HWND hDlg, int nIDDlgItem, LPCSTR lpString); +FailOnFalse [gle] SetDlgItemTextW(HWND hDlg, int nIDDlgItem, LPCWSTR lpString); + +HWND [gle] GetNextDlgGroupItem(HWND hDlg, HWND hCtl, BOOL bPrevious); +HWND [gle] GetNextDlgTabItem(HWND hDlg, HWND hCtl, BOOL bPrevious); + +mask DWORD DlgDirListType +{ +#define DDL_READWRITE 0x0000 +#define DDL_READONLY 0x0001 +#define DDL_HIDDEN 0x0002 +#define DDL_SYSTEM 0x0004 +#define DDL_DIRECTORY 0x0010 +#define DDL_ARCHIVE 0x0020 + +#define DDL_POSTMSGS 0x2000 +#define DDL_DRIVES 0x4000 +#define DDL_EXCLUSIVE 0x8000 +}; + +LongFailIfZero [gle] DlgDirListA(HWND hDlg, + [out] LPSTR lpPathSpec, + int nIDListBox, + int nIDStaticPath, + DlgDirListType uFileType); + +LongFailIfZero [gle] DlgDirListW(HWND hDlg, + [out] LPWSTR lpPathSpec, + int nIDListBox, + int nIDStaticPath, + DlgDirListType uFileType); + +BOOL DlgDirSelectExA(HWND hDlg, + [out] LPSTR lpString, + int nCount, + int nIDListBox); + +BOOL DlgDirSelectExW(HWND hDlg, + [out] LPWSTR lpString, + int nCount, + int nIDListBox); + +LongFailIfZero [gle] DlgDirListComboBoxA(HWND hDlg, + [out] LPSTR lpPathSpec, + int nIDComboBox, + int nIDStaticPath, + DlgDirListType uFiletype); + +LongFailIfZero [gle] DlgDirListComboBoxW(HWND hDlg, + [out] LPWSTR lpPathSpec, + int nIDComboBox, + int nIDStaticPath, + DlgDirListType uFiletype); + +BOOL DlgDirSelectComboBoxExA(HWND hDlg, + [out] LPSTR lpString, + int nCount, + int nIDComboBox); + +BOOL DlgDirSelectComboBoxExW(HWND hDlg, + [out] LPWSTR lpString, + int nCount, + int nIDComboBox); + + +value DWORD ChangeDisplaySettingsRet +{ +#define DISP_CHANGE_SUCCESSFUL 0 +#define DISP_CHANGE_RESTART 1 [fail] +#define DISP_CHANGE_FAILED -1 [fail] +#define DISP_CHANGE_BADMODE -2 [fail] +#define DISP_CHANGE_NOTUPDATED -3 [fail] +#define DISP_CHANGE_BADFLAGS -4 [fail] +#define DISP_CHANGE_BADPARAM -5 [fail] +}; + + +mask DWORD ChangeDisplaySettingsFlags +{ +#define CDS_UPDATEREGISTRY 0x00000001 +#define CDS_TEST 0x00000002 +#define CDS_FULLSCREEN 0x00000004 +#define CDS_GLOBAL 0x00000008 +#define CDS_SET_PRIMARY 0x00000010 +#define CDS_VIDEOPARAMETERS 0x00000020 +#define CDS_RESET 0x40000000 +#define CDS_NORESET 0x10000000 +}; + +ChangeDisplaySettingsRet ChangeDisplaySettingsA(LPDEVMODEA lpDevMode, ChangeDisplaySettingsFlags dwFlags); +ChangeDisplaySettingsRet ChangeDisplaySettingsW(LPDEVMODEW lpDevMode, ChangeDisplaySettingsFlags dwFlags); + +ChangeDisplaySettingsRet ChangeDisplaySettingsExA(LPCSTR lpszDeviceName, + LPDEVMODEA lpDevMode, + HWND hwnd, + ChangeDisplaySettingsFlags dwflags, + LPVOID lParam); + +ChangeDisplaySettingsRet ChangeDisplaySettingsExW(LPCWSTR lpszDeviceName, + LPDEVMODEW lpDevMode, + HWND hwnd, + ChangeDisplaySettingsFlags dwflags, + LPVOID lParam); + +typedef struct _DISPLAY_DEVICEA { + DWORD cb; + char DeviceName[32]; + char DeviceString[128]; + DWORD StateFlags; + char DeviceID[128]; + char DeviceKey[128]; +} DISPLAY_DEVICEA, *PDISPLAY_DEVICEA, *LPDISPLAY_DEVICEA; + +typedef struct _DISPLAY_DEVICEW { + DWORD cb; + WCHAR DeviceName[32]; + WCHAR DeviceString[128]; + DWORD StateFlags; + WCHAR DeviceID[128]; + WCHAR DeviceKey[128]; +} DISPLAY_DEVICEW, *PDISPLAY_DEVICEW, *LPDISPLAY_DEVICEW; + + +LongFailIfZero EnumDisplayDevicesA(LPCSTR lpDevice, + DWORD iDevNum, + [out] PDISPLAY_DEVICEA lpDisplayDevice, + DWORD dwFlags); + +LongFailIfZero EnumDisplayDevicesW(LPCWSTR lpDevice, + DWORD iDevNum, + [out] PDISPLAY_DEVICEW lpDisplayDevice, + DWORD dwFlags); + +typedef LPVOID MONITORENUMPROC; + +FailOnFalse [gle] EnumDisplayMonitors(HDC hdc, + LPRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData); + +typedef struct tagMONITORINFO +{ + DWORD cbSize; + RECT rcMonitor; + RECT rcWork; + DWORD dwFlags; +} MONITORINFO, *LPMONITORINFO; + +FailOnFalse [gle] GetMonitorInfoA(HMONITOR hMonitor, [out] LPMONITORINFO lpmi); +FailOnFalse [gle] GetMonitorInfoW(HMONITOR hMonitor, [out] LPMONITORINFO lpmi); + +mask DWORD MonitorFlags +{ +#define MONITOR_DEFAULTTONULL 0x00000000 +#define MONITOR_DEFAULTTOPRIMARY 0x00000001 +#define MONITOR_DEFAULTTONEAREST 0x00000002 +}; + +HMONITOR MonitorFromPoint(int x, int y, MonitorFlags dwFlags); +HMONITOR MonitorFromRect(LPRECT lprc, MonitorFlags dwFlags); +HMONITOR MonitorFromWindow(HWND hwnd, MonitorFlags dwFlags); + + +value DWORD EnumDisplaySettingsIndex +{ +#define ENUM_CURRENT_SETTINGS -1 +#define ENUM_REGISTRY_SETTINGS -2 +}; + +FailOnFalse [gle] EnumDisplaySettingsA(LPCSTR lpszDeviceName, + EnumDisplaySettingsIndex iModeNum, + [out] LPDEVMODEA lpDevMode); + +FailOnFalse [gle] EnumDisplaySettingsW(LPCWSTR lpszDeviceName, + EnumDisplaySettingsIndex iModeNum, + [out] LPDEVMODEW lpDevMode); + +FailOnFalse [gle] EnumDisplaySettingsExA(LPCSTR lpszDeviceName, + EnumDisplaySettingsIndex iModeNum, + [out] LPDEVMODEA lpDevMode, + DWORD dwFlags); + +FailOnFalse [gle] EnumDisplaySettingsExW(LPCWSTR lpszDeviceName, + EnumDisplaySettingsIndex iModeNum, + [out] LPDEVMODEW lpDevMode, + DWORD dwFlags); + + + +HWND [gle] SetFocus(HWND hWnd); +HWND GetFocus(); + +FailOnFalse [gle] DrawFocusRect(HDC hDC, LPRECT* lprc); + +mask DWORD GetGUIThreadInfoFlags +{ +#define GUI_CARETBLINKING 0x00000001 +#define GUI_INMOVESIZE 0x00000002 +#define GUI_INMENUMODE 0x00000004 +#define GUI_SYSTEMMENUMODE 0x00000008 +#define GUI_POPUPMENUMODE 0x00000010 +}; + +typedef struct tagGUITHREADINFO +{ + DWORD cbSize; + GetGUIThreadInfoFlags flags; + HWND hwndActive; + HWND hwndFocus; + HWND hwndCapture; + HWND hwndMenuOwner; + HWND hwndMoveSize; + HWND hwndCaret; + RECT rcCaret; +} GUITHREADINFO, *PGUITHREADINFO, *LPGUITHREADINFO; + +FailOnFalse [gle] GetGUIThreadInfo(ThreadId idThread, [out] PGUITHREADINFO pgui); + +HWND SetCapture(HWND hWnd); +HWND GetCapture(); + +FailOnFalse [gle] ReleaseCapture(); + + +HRSRC SetCursor(HRSRC hCursor); + +value LPSTR CursorValueA +{ +#define IDC_ARROW 32512 +#define IDC_IBEAM 32513 +#define IDC_WAIT 32514 +#define IDC_CROSS 32515 +#define IDC_UPARROW 32516 +#define IDC_SIZE 32640 +#define IDC_ICON 32641 +#define IDC_SIZENWSE 32642 +#define IDC_SIZENESW 32643 +#define IDC_SIZEWE 32644 +#define IDC_SIZENS 32645 +#define IDC_SIZEALL 32646 +#define IDC_NO 32648 +#define IDC_HAND 32649 +#define IDC_APPSTARTING 32650 +#define IDC_HELP 32651 +}; + +value LPWSTR CursorValueW +{ +#define IDC_ARROW 32512 +#define IDC_IBEAM 32513 +#define IDC_WAIT 32514 +#define IDC_CROSS 32515 +#define IDC_UPARROW 32516 +#define IDC_SIZE 32640 +#define IDC_ICON 32641 +#define IDC_SIZENWSE 32642 +#define IDC_SIZENESW 32643 +#define IDC_SIZEWE 32644 +#define IDC_SIZENS 32645 +#define IDC_SIZEALL 32646 +#define IDC_NO 32648 +#define IDC_HAND 32649 +#define IDC_APPSTARTING 32650 +#define IDC_HELP 32651 +}; + +HRSRC [gle] LoadCursorA(HINSTANCE hInstance, CursorValueA lpCursorName); +HRSRC [gle] LoadCursorW(HINSTANCE hInstance, CursorValueW lpCursorName); + +value LPSTR IconValueA +{ +#define IDI_APPLICATION 32512 +#define IDI_HAND 32513 +#define IDI_QUESTION 32514 +#define IDI_EXCLAMATION 32515 +#define IDI_ASTERISK 32516 +#define IDI_WINLOGO 32517 +}; + +value LPWSTR IconValueW +{ +#define IDI_APPLICATION 32512 +#define IDI_HAND 32513 +#define IDI_QUESTION 32514 +#define IDI_EXCLAMATION 32515 +#define IDI_ASTERISK 32516 +#define IDI_WINLOGO 32517 +}; + +HRSRC [gle] LoadIconA(HINSTANCE hInstance, IconValueA lpIconName); +HRSRC [gle] LoadIconW(HINSTANCE hInstance, IconValueW lpIconName); + +mask DWORD ImageType +{ +#define IMAGE_BITMAP 0 +#define IMAGE_ICON 1 +#define IMAGE_CURSOR 2 +#define IMAGE_ENHMETAFILE 3 +}; + +mask DWORD LoadImageFlags +{ +#define LR_DEFAULTCOLOR 0x0000 +#define LR_MONOCHROME 0x0001 +#define LR_COLOR 0x0002 +#define LR_COPYRETURNORG 0x0004 +#define LR_COPYDELETEORG 0x0008 +#define LR_LOADFROMFILE 0x0010 +#define LR_LOADTRANSPARENT 0x0020 +#define LR_DEFAULTSIZE 0x0040 +#define LR_VGACOLOR 0x0080 +#define LR_LOADMAP3DCOLORS 0x1000 +#define LR_CREATEDIBSECTION 0x2000 +#define LR_COPYFROMRESOURCE 0x4000 +#define LR_SHARED 0x8000 +}; + +value LPSTR ImageValueA +{ +#define OBM_CLOSE 32754 +#define OBM_UPARROW 32753 +#define OBM_DNARROW 32752 +#define OBM_RGARROW 32751 +#define OBM_LFARROW 32750 +#define OBM_REDUCE 32749 +#define OBM_ZOOM 32748 +#define OBM_RESTORE 32747 +#define OBM_REDUCED 32746 +#define OBM_ZOOMD 32745 +#define OBM_RESTORED 32744 +#define OBM_UPARROWD 32743 +#define OBM_DNARROWD 32742 +#define OBM_RGARROWD 32741 +#define OBM_LFARROWD 32740 +#define OBM_MNARROW 32739 +#define OBM_COMBO 32738 +#define OBM_UPARROWI 32737 +#define OBM_DNARROWI 32736 +#define OBM_RGARROWI 32735 +#define OBM_LFARROWI 32734 + +#define OBM_OLD_CLOSE 32767 +#define OBM_SIZE 32766 +#define OBM_OLD_UPARROW 32765 +#define OBM_OLD_DNARROW 32764 +#define OBM_OLD_RGARROW 32763 +#define OBM_OLD_LFARROW 32762 +#define OBM_BTSIZE 32761 +#define OBM_CHECK 32760 +#define OBM_CHECKBOXES 32759 +#define OBM_BTNCORNERS 32758 +#define OBM_OLD_REDUCE 32757 +#define OBM_OLD_ZOOM 32756 +#define OBM_OLD_RESTORE 32755 + +#define OCR_NORMAL 32512 +#define OCR_IBEAM 32513 +#define OCR_WAIT 32514 +#define OCR_CROSS 32515 +#define OCR_UP 32516 +#define OCR_SIZE 32640 +#define OCR_ICON 32641 +#define OCR_SIZENWSE 32642 +#define OCR_SIZENESW 32643 +#define OCR_SIZEWE 32644 +#define OCR_SIZENS 32645 +#define OCR_SIZEALL 32646 +#define OCR_ICOCUR 32647 +#define OCR_NO 32648 +#define OCR_HAND 32649 +#define OCR_APPSTARTING 32650 + +#define OIC_WINLOGO 32517 +}; + +value LPWSTR ImageValueW +{ +#define OBM_CLOSE 32754 +#define OBM_UPARROW 32753 +#define OBM_DNARROW 32752 +#define OBM_RGARROW 32751 +#define OBM_LFARROW 32750 +#define OBM_REDUCE 32749 +#define OBM_ZOOM 32748 +#define OBM_RESTORE 32747 +#define OBM_REDUCED 32746 +#define OBM_ZOOMD 32745 +#define OBM_RESTORED 32744 +#define OBM_UPARROWD 32743 +#define OBM_DNARROWD 32742 +#define OBM_RGARROWD 32741 +#define OBM_LFARROWD 32740 +#define OBM_MNARROW 32739 +#define OBM_COMBO 32738 +#define OBM_UPARROWI 32737 +#define OBM_DNARROWI 32736 +#define OBM_RGARROWI 32735 +#define OBM_LFARROWI 32734 + +#define OBM_OLD_CLOSE 32767 +#define OBM_SIZE 32766 +#define OBM_OLD_UPARROW 32765 +#define OBM_OLD_DNARROW 32764 +#define OBM_OLD_RGARROW 32763 +#define OBM_OLD_LFARROW 32762 +#define OBM_BTSIZE 32761 +#define OBM_CHECK 32760 +#define OBM_CHECKBOXES 32759 +#define OBM_BTNCORNERS 32758 +#define OBM_OLD_REDUCE 32757 +#define OBM_OLD_ZOOM 32756 +#define OBM_OLD_RESTORE 32755 + +#define OCR_NORMAL 32512 +#define OCR_IBEAM 32513 +#define OCR_WAIT 32514 +#define OCR_CROSS 32515 +#define OCR_UP 32516 +#define OCR_SIZE 32640 +#define OCR_ICON 32641 +#define OCR_SIZENWSE 32642 +#define OCR_SIZENESW 32643 +#define OCR_SIZEWE 32644 +#define OCR_SIZENS 32645 +#define OCR_SIZEALL 32646 +#define OCR_ICOCUR 32647 +#define OCR_NO 32648 +#define OCR_HAND 32649 +#define OCR_APPSTARTING 32650 + +#define OIC_WINLOGO 32517 +}; + +HRSRC [gle] LoadImageA(HINSTANCE hInstance, + ImageValueA lpszName, + ImageType uType, + int cxDesired, + int cyDesired, + LoadImageFlags fuLoad); + +HRSRC [gle] LoadImageW(HINSTANCE hInstance, + ImageValueW lpszName, + ImageType uType, + int cxDesired, + int cyDesired, + LoadImageFlags fuLoad); + +HRSRC [gle] CopyImage(HRSRC hImage, + ImageType uType, + int x, + int y, + LoadImageFlags fuFlags); + +HRSRC [gle] CopyIcon(HRSRC hIcon); + +typedef struct _ICONINFO { + BOOL fIcon; + DWORD xHotspot; + DWORD yHotspot; + HRSRC hbmMask; + HRSRC hbmColor; +} ICONINFO; +typedef ICONINFO *PICONINFO; + +FailOnFalse [gle] GetIconInfo(HRSRC hIcon, [out] PICONINFO piconinfo); + +HRSRC [gle] CreateIcon(HINSTANCE hInstance, + int nWidth, + int nHeight, + BYTE cPlanes, + BYTE cBitsPixel, + BYTE* lpbANDbits, + BYTE* lpbXORbits); + +FailOnFalse [gle] OpenIcon(HWND hWnd); + +HRSRC [gle] CreateIconFromResource(PBYTE presbits, + DWORD dwResSize, + BOOL fIcon, + DWORD dwVer); + +HRSRC [gle] CreateIconFromResourceEx(PBYTE presbits, + DWORD dwResSize, + BOOL fIcon, + DWORD dwVer, + int cxDesired, + int cyDesired, + LoadImageFlags Flags); + +HRSRC [gle] CreateIconIndirect(PICONINFO piconinfo); + +DwordFailIfZero [gle] LookupIconIdFromDirectory(PBYTE presbits, BOOL fIcon); + +DwordFailIfZero [gle] LookupIconIdFromDirectoryEx(PBYTE presbits, + BOOL fIcon, + int cxDesired, + int cyDesired, + LoadImageFlags Flags); + +FailOnFalse [gle] DestroyCursor( [da] HRSRC hCursor); +FailOnFalse [gle] DestroyIcon( [da] HRSRC hIcon); + +value DWORD SystemMetric +{ +#define SM_CXSCREEN 0 +#define SM_CYSCREEN 1 +#define SM_CXVSCROLL 2 +#define SM_CYHSCROLL 3 +#define SM_CYCAPTION 4 +#define SM_CXBORDER 5 +#define SM_CYBORDER 6 +#define SM_CXDLGFRAME 7 +#define SM_CYDLGFRAME 8 +#define SM_CYVTHUMB 9 +#define SM_CXHTHUMB 10 +#define SM_CXICON 11 +#define SM_CYICON 12 +#define SM_CXCURSOR 13 +#define SM_CYCURSOR 14 +#define SM_CYMENU 15 +#define SM_CXFULLSCREEN 16 +#define SM_CYFULLSCREEN 17 +#define SM_CYKANJIWINDOW 18 +#define SM_MOUSEPRESENT 19 +#define SM_CYVSCROLL 20 +#define SM_CXHSCROLL 21 +#define SM_DEBUG 22 +#define SM_SWAPBUTTON 23 +#define SM_RESERVED1 24 +#define SM_RESERVED2 25 +#define SM_RESERVED3 26 +#define SM_RESERVED4 27 +#define SM_CXMIN 28 +#define SM_CYMIN 29 +#define SM_CXSIZE 30 +#define SM_CYSIZE 31 +#define SM_CXFRAME 32 +#define SM_CYFRAME 33 +#define SM_CXMINTRACK 34 +#define SM_CYMINTRACK 35 +#define SM_CXDOUBLECLK 36 +#define SM_CYDOUBLECLK 37 +#define SM_CXICONSPACING 38 +#define SM_CYICONSPACING 39 +#define SM_MENUDROPALIGNMENT 40 +#define SM_PENWINDOWS 41 +#define SM_DBCSENABLED 42 +#define SM_CMOUSEBUTTONS 43 + +#define SM_SECURE 44 +#define SM_CXEDGE 45 +#define SM_CYEDGE 46 +#define SM_CXMINSPACING 47 +#define SM_CYMINSPACING 48 +#define SM_CXSMICON 49 +#define SM_CYSMICON 50 +#define SM_CYSMCAPTION 51 +#define SM_CXSMSIZE 52 +#define SM_CYSMSIZE 53 +#define SM_CXMENUSIZE 54 +#define SM_CYMENUSIZE 55 +#define SM_ARRANGE 56 +#define SM_CXMINIMIZED 57 +#define SM_CYMINIMIZED 58 +#define SM_CXMAXTRACK 59 +#define SM_CYMAXTRACK 60 +#define SM_CXMAXIMIZED 61 +#define SM_CYMAXIMIZED 62 +#define SM_NETWORK 63 +#define SM_CLEANBOOT 67 +#define SM_CXDRAG 68 +#define SM_CYDRAG 69 +#define SM_SHOWSOUNDS 70 +#define SM_CXMENUCHECK 71 +#define SM_CYMENUCHECK 72 +#define SM_SLOWMACHINE 73 +#define SM_MIDEASTENABLED 74 + +#define SM_MOUSEWHEELPRESENT 75 +#define SM_XVIRTUALSCREEN 76 +#define SM_YVIRTUALSCREEN 77 +#define SM_CXVIRTUALSCREEN 78 +#define SM_CYVIRTUALSCREEN 79 +#define SM_CMONITORS 80 +#define SM_SAMEDISPLAYFORMAT 81 +#define SM_IMMENABLED 82 +#define SM_CXFOCUSBORDER 83 +#define SM_CYFOCUSBORDER 84 + +#define SM_REMOTESESSION 0x1000 +}; + +LongFailIfZero GetSystemMetrics(SystemMetric nIndex); + +HACCEL [gle] LoadAcceleratorsA(HINSTANCE hInstance, LPCSTR lpTableName); +HACCEL [gle] LoadAcceleratorsW(HINSTANCE hInstance, LPCWSTR lpTableName); + +FailOnFalse [gle] DestroyAcceleratorTable( [da] HACCEL hAccel); + +int TranslateAcceleratorA(HWND hWnd, HACCEL hAccTable, LPMSG lpMsg); +int TranslateAcceleratorW(HWND hWnd, HACCEL hAccTable, LPMSG lpMsg); + +mask DWORD ClassStyles +{ +#define CS_VREDRAW 0x0001 +#define CS_HREDRAW 0x0002 +#define CS_DBLCLKS 0x0008 +#define CS_OWNDC 0x0020 +#define CS_CLASSDC 0x0040 +#define CS_PARENTDC 0x0080 +#define CS_NOCLOSE 0x0200 +#define CS_SAVEBITS 0x0800 +#define CS_BYTEALIGNCLIENT 0x1000 +#define CS_BYTEALIGNWINDOW 0x2000 +#define CS_GLOBALCLASS 0x4000 +#define CS_IME 0x00010000 +#define CS_DROPSHADOW 0x00020000 +}; + +typedef struct tagWNDCLASSA { + ClassStyles style; + FILLER64 align1[4]; + WNDPROC lpfnWndProc; + int cbClsExtra; + int cbWndExtra; + HINSTANCE hInstance; + HRSRC hIcon; + HRSRC hCursor; + HRSRC hbrBackground; + LPCSTR lpszMenuName; + LPCSTR lpszClassName; +} WNDCLASSA, *PWNDCLASSA, *LPWNDCLASSA; + +typedef struct tagWNDCLASSW { + ClassStyles style; + FILLER64 align1[4]; + WNDPROC lpfnWndProc; + int cbClsExtra; + int cbWndExtra; + HINSTANCE hInstance; + HRSRC hIcon; + HRSRC hCursor; + HRSRC hbrBackground; + LPCWSTR lpszMenuName; + LPCWSTR lpszClassName; +} WNDCLASSW, *PWNDCLASSW, *LPWNDCLASSW; + + +DwordFailIfZero [gle] RegisterClassA(WNDCLASSA* lpWndClass); +DwordFailIfZero [gle] RegisterClassW(WNDCLASSW* lpWndClass); + +FailOnFalse [gle] UnregisterClassA(LPCSTR lpClassName, HINSTANCE hInstance); +FailOnFalse [gle] UnregisterClassW(LPCWSTR lpClassName, HINSTANCE hInstance); + +typedef struct tagWNDCLASSEXA { + UINT cbSize; + ClassStyles style; + WNDPROC lpfnWndProc; + int cbClsExtra; + int cbWndExtra; + HINSTANCE hInstance; + HRSRC hIcon; + HRSRC hCursor; + HRSRC hbrBackground; + LPCSTR lpszMenuName; + LPCSTR lpszClassName; + HRSRC hIconSm; +} WNDCLASSEXA, *PWNDCLASSEXA, *LPWNDCLASSEXA; + +typedef struct tagWNDCLASSEXW { + UINT cbSize; + ClassStyles style; + WNDPROC lpfnWndProc; + int cbClsExtra; + int cbWndExtra; + HINSTANCE hInstance; + HRSRC hIcon; + HRSRC hCursor; + HRSRC hbrBackground; + LPCWSTR lpszMenuName; + LPCWSTR lpszClassName; + HRSRC hIconSm; +} WNDCLASSEXW, *PWNDCLASSEXW, *LPWNDCLASSEXW; + +DwordFailIfZero [gle] RegisterClassExA(WNDCLASSEXA* lpWndClass); +DwordFailIfZero [gle] RegisterClassExW(WNDCLASSEXW* lpWndClass); + +typedef struct tagDLGTEMPLATE { + DWORD style; + DWORD dwExtendedStyle; + WORD cdit; + DWORD x_y; + DWORD cx_cy; +} DLGTEMPLATE; + +typedef DLGTEMPLATE *LPDLGTEMPLATEA; +typedef DLGTEMPLATE *LPDLGTEMPLATEW; + +HWND [gle] CreateDialogIndirectParamA(HINSTANCE hInstance, + LPDLGTEMPLATEA lpTemplate, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +HWND [gle] CreateDialogIndirectParamW(HINSTANCE hInstance, + LPDLGTEMPLATEA lpTemplate, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +HWND [gle] CreateDialogParamA(HINSTANCE hInstance, + LPCSTR lpTemplateName, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +HWND [gle] CreateDialogParamW(HINSTANCE hInstance, + LPCWSTR lpTemplateName, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +LongFailIfNeg1 [gle] DialogBoxIndirectParamA(HINSTANCE hInstance, + LPDLGTEMPLATEA hDialogTemplate, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +LongFailIfNeg1 [gle] DialogBoxIndirectParamW(HINSTANCE hInstance, + LPDLGTEMPLATEW hDialogTemplate, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +LongFailIfNeg1 [gle] DialogBoxParamA(HINSTANCE hInstance, + LPCSTR lpTemplateName, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +LongFailIfNeg1 [gle] DialogBoxParamW(HINSTANCE hInstance, + LPCWSTR lpTemplateName, + HWND hWndParent, + WNDPROC lpDialogFunc, + LPARAM dwInitParam); + +FailOnFalse [gle] EndDialog(HWND hDlg, int nResult); + +DWORD GetDialogBaseUnits(); + +FailOnFalse [gle] MapDialogRect(HWND hDlg, [out] LPRECT lpRect); + + +HWND [gle] CreateMDIWindowA(LPCSTR lpClassName, + LPCSTR lpWindowName, + WindowStyle dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + HINSTANCE hInstance, + LPARAM lParam); + +HWND [gle] CreateMDIWindowW(LPCWSTR lpClassName, + LPCWSTR lpWindowName, + WindowStyle dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + HINSTANCE hInstance, + LPARAM lParam); + +LRESULT DefMDIChildProcA(HWND hWnd, + WindowsMessage uMsg, + WPARAM wParam, + LPARAM lParam); + +LRESULT DefMDIChildProcW(HWND hWnd, + WindowsMessage uMsg, + WPARAM wParam, + LPARAM lParam); + +BOOL TranslateMDISysAccel(HWND hWndClient, + LPMSG lpMsg); + +value DWORD MessageBoxReturn +{ +#define ERROR 0 [fail] +#define IDOK 1 +#define IDCANCEL 2 +#define IDABORT 3 +#define IDRETRY 4 +#define IDIGNORE 5 +#define IDYES 6 +#define IDNO 7 +#define IDCLOSE 8 +#define IDHELP 9 +#define IDTRYAGAIN 10 +#define IDCONTINUE 11 +}; + +mask DWORD MessageBoxType +{ +#define MB_OK 0x00000000 +#define MB_OKCANCEL 0x00000001 +#define MB_ABORTRETRYIGNORE 0x00000002 +#define MB_YESNOCANCEL 0x00000003 +#define MB_YESNO 0x00000004 +#define MB_RETRYCANCEL 0x00000005 +#define MB_CANCELTRYCONTINUE 0x00000006 + +#define MB_ICONHAND 0x00000010 +#define MB_ICONQUESTION 0x00000020 +#define MB_ICONEXCLAMATION 0x00000030 +#define MB_ICONASTERISK 0x00000040 +#define MB_USERICON 0x00000080 + +#define MB_DEFBUTTON1 0x00000000 +#define MB_DEFBUTTON2 0x00000100 +#define MB_DEFBUTTON3 0x00000200 + +#define MB_SYSTEMMODAL 0x00001000 +#define MB_TASKMODAL 0x00002000 +#define MB_HELP 0x00004000 + +#define MB_NOFOCUS 0x00008000 +#define MB_SETFOREGROUND 0x00010000 +#define MB_DEFAULT_DESKTOP_ONLY 0x00020000 + +#define MB_TOPMOST 0x00040000 +#define MB_RIGHT 0x00080000 +#define MB_RTLREADING 0x00100000 +#define MB_SERVICE_NOTIFICATION 0x00200000 +}; + +MessageBoxReturn [gle] MessageBoxA(HWND hWnd, + LPCSTR lpText, + LPCSTR lpCaption, + MessageBoxType uType); + +MessageBoxReturn [gle] MessageBoxW(HWND hWnd, + LPCWSTR lpText, + LPCWSTR lpCaption, + MessageBoxType uType); + +value DWORD LanguageId +{ +#define LANG_NEUTRAL 0x00 +#define LANG_INVARIANT 0x7f + +#define LANG_AFRIKAANS 0x36 +#define LANG_ALBANIAN 0x1c +#define LANG_ARABIC 0x01 +#define LANG_ARMENIAN 0x2b +#define LANG_ASSAMESE 0x4d +#define LANG_AZERI 0x2c +#define LANG_BASQUE 0x2d +#define LANG_BELARUSIAN 0x23 +#define LANG_BENGALI 0x45 +#define LANG_BULGARIAN 0x02 +#define LANG_CATALAN 0x03 +#define LANG_CHINESE 0x04 +#define LANG_CROATIAN 0x1a +#define LANG_CZECH 0x05 +#define LANG_DANISH 0x06 +#define LANG_DUTCH 0x13 +#define LANG_ENGLISH 0x09 +#define LANG_ESTONIAN 0x25 +#define LANG_FAEROESE 0x38 +#define LANG_FARSI 0x29 +#define LANG_FINNISH 0x0b +#define LANG_FRENCH 0x0c +#define LANG_GEORGIAN 0x37 +#define LANG_GERMAN 0x07 +#define LANG_GREEK 0x08 +#define LANG_GUJARATI 0x47 +#define LANG_HEBREW 0x0d +#define LANG_HINDI 0x39 +#define LANG_HUNGARIAN 0x0e +#define LANG_ICELANDIC 0x0f +#define LANG_INDONESIAN 0x21 +#define LANG_ITALIAN 0x10 +#define LANG_JAPANESE 0x11 +#define LANG_KANNADA 0x4b +#define LANG_KASHMIRI 0x60 +#define LANG_KAZAK 0x3f +#define LANG_KONKANI 0x57 +#define LANG_KOREAN 0x12 +#define LANG_LATVIAN 0x26 +#define LANG_LITHUANIAN 0x27 +#define LANG_MACEDONIAN 0x2f +#define LANG_MALAY 0x3e +#define LANG_MALAYALAM 0x4c +#define LANG_MANIPURI 0x58 +#define LANG_MARATHI 0x4e +#define LANG_NEPALI 0x61 +#define LANG_NORWEGIAN 0x14 +#define LANG_ORIYA 0x48 +#define LANG_POLISH 0x15 +#define LANG_PORTUGUESE 0x16 +#define LANG_PUNJABI 0x46 +#define LANG_ROMANIAN 0x18 +#define LANG_RUSSIAN 0x19 +#define LANG_SANSKRIT 0x4f +#define LANG_SERBIAN 0x1a +#define LANG_SINDHI 0x59 +#define LANG_SLOVAK 0x1b +#define LANG_SLOVENIAN 0x24 +#define LANG_SPANISH 0x0a +#define LANG_SWAHILI 0x41 +#define LANG_SWEDISH 0x1d +#define LANG_TAMIL 0x49 +#define LANG_TATAR 0x44 +#define LANG_TELUGU 0x4a +#define LANG_THAI 0x1e +#define LANG_TURKISH 0x1f +#define LANG_UKRAINIAN 0x22 +#define LANG_URDU 0x20 +#define LANG_UZBEK 0x43 +#define LANG_VIETNAMESE 0x2a +}; + +MessageBoxReturn [gle] MessageBoxExA(HWND hWnd, + LPCSTR lpText, + LPCSTR lpCaption, + MessageBoxType uType, + LanguageId dwLanguageId); + +MessageBoxReturn [gle] MessageBoxExW(HWND hWnd, + LPCWSTR lpText, + LPCWSTR lpCaption, + MessageBoxType uType, + LanguageId dwLanguageId); + +typedef struct tagMSGBOXPARAMSA +{ + UINT cbSize; + HWND hwndOwner; + HINSTANCE hInstance; + LPCSTR lpszText; + LPCSTR lpszCaption; + MessageBoxType dwStyle; + LPCSTR lpszIcon; + DWORD dwContextHelpId; + WNDPROC lpfnMsgBoxCallback; + LanguageId dwLanguageId; +} MSGBOXPARAMSA, *PMSGBOXPARAMSA, *LPMSGBOXPARAMSA; + +typedef struct tagMSGBOXPARAMSW +{ + UINT cbSize; + HWND hwndOwner; + HINSTANCE hInstance; + LPCWSTR lpszText; + LPCWSTR lpszCaption; + MessageBoxType dwStyle; + LPCWSTR lpszIcon; + DWORD dwContextHelpId; + WNDPROC lpfnMsgBoxCallback; + LanguageId dwLanguageId; +} MSGBOXPARAMSW, *PMSGBOXPARAMSW, *LPMSGBOXPARAMSW; + + +MessageBoxReturn MessageBoxIndirectA(MSGBOXPARAMSA* lpMsgBoxParam); +MessageBoxReturn MessageBoxIndirectW(MSGBOXPARAMSW* lpMsgBoxParam); + +BOOL IsDialogMessageA(HWND hDlg, LPMSG lpMsg); +BOOL IsDialogMessageW(HWND hDlg, LPMSG lpMsg); + +BOOL IsChild(HWND hWndParent, HWND hWnd); + +BOOL IsIconic(HWND hWnd); + +BOOL IsMenu(HMENU hMenu); + +BOOL IsRectEmpty(LPRECT lprc); + +BOOL IntersectRect([out] LPRECT lprcDst, LPRECT lprcSrc1, LPRECT lprcSrc2); + +BOOL UnionRect([out] LPRECT lprcDst, LPRECT lprcSrc1, LPRECT lprcSrc2); + +FailOnFalse [gle] CopyRect([out] LPRECT lprcDst, LPRECT lprcSrc); + +BOOL SubtractRect([out] LPRECT lprcDst, LPRECT lprcSrc1, LPRECT lprcSrc2); + + +FailOnFalse [gle] InvertRect(HDC hDC, LPRECT lprc); + + +BOOL PtInRect(LPRECT lprc, int x, int y); + +FailOnFalse IsWindow(HWND hWnd); + +BOOL IsWindowEnabled(HWND hWnd); + +BOOL IsWindowUnicode(HWND hWnd); + +BOOL IsWindowVisible(HWND hWnd); + +BOOL IsZoomed(HWND hWnd); + +DwordFailIfZero [gle] RegisterWindowMessageA(LPCSTR lpString); +DwordFailIfZero [gle] RegisterWindowMessageW(LPCWSTR lpString); + +FailOnFalse [gle] SetPropA(HWND hWnd, LPCSTR lpString, HANDLE hData); +FailOnFalse [gle] SetPropW(HWND hWnd, LPCWSTR lpString, HANDLE hData); + +HANDLE GetPropA(HWND hWnd, LPCSTR lpString); +HANDLE GetPropW(HWND hWnd, LPCWSTR lpString); + +HANDLE RemovePropA(HWND hWnd, LPCSTR lpString); +HANDLE RemovePropW(HWND hWnd, LPCWSTR lpString); + +typedef LPVOID PROPENUMPROCA; +typedef LPVOID PROPENUMPROCW; +typedef LPVOID PROPENUMPROCEXA; +typedef LPVOID PROPENUMPROCEXW; + +LongFailIfNeg1 EnumPropsA(HWND hWnd, PROPENUMPROCA lpEnumFunc); +LongFailIfNeg1 EnumPropsW(HWND hWnd, PROPENUMPROCW lpEnumFunc); + +LongFailIfNeg1 EnumPropsExA(HWND hWnd, PROPENUMPROCEXA lpEnumFunc, LPARAM lParam); +LongFailIfNeg1 EnumPropsExW(HWND hWnd, PROPENUMPROCEXW lpEnumFunc, LPARAM lParam); + +mask DWORD QueueStates +{ +#define QS_KEY 0x0001 +#define QS_MOUSEMOVE 0x0002 +#define QS_MOUSEBUTTON 0x0004 +#define QS_POSTMESSAGE 0x0008 +#define QS_TIMER 0x0010 +#define QS_PAINT 0x0020 +#define QS_SENDMESSAGE 0x0040 +#define QS_HOTKEY 0x0080 +#define QS_ALLPOSTMESSAGE 0x0100 +#define QS_RAWINPUT 0x0400 +}; + +WaitReturnValues [gle] MsgWaitForMultipleObjects(DWORD nCount, + HANDLE* pHandles, + BOOL fWaitAll, + DWORD dwMilliseconds, + QueueStates dwWakeMask); + +mask DWORD MWMOFlags +{ +#define MWMO_WAITALL 0x0001 +#define MWMO_ALERTABLE 0x0002 +#define MWMO_INPUTAVAILABLE 0x0004 +}; + +WaitReturnValues [gle] MsgWaitForMultipleObjectsEx(DWORD nCount, + HANDLE* pHandles, + DWORD dwMilliseconds, + QueueStates dwWakeMask, + MWMOFlags dwFlags); + +WaitReturnValues [gle] WaitForInputIdle(HANDLE hProcess, DWORD dwMilliseconds); + +value DWORD GetWindowCmd +{ +#define GW_HWNDFIRST 0 +#define GW_HWNDLAST 1 +#define GW_HWNDNEXT 2 +#define GW_HWNDPREV 3 +#define GW_OWNER 4 +#define GW_CHILD 5 +#define GW_ENABLEDPOPUP 6 +}; + +HWND [gle] GetWindow(HWND hWnd, GetWindowCmd uCmd); + +DWORD GetWindowContextHelpId(HWND hwnd); + +FailOnFalse [gle] SetWindowContextHelpId(HWND hwnd, DWORD dwHelpId); + +typedef struct tagWINDOWINFO +{ + DWORD cbSize; + RECT rcWindow; + RECT rcClient; + DWORD dwStyle; + DWORD dwExStyle; + DWORD dwWindowStatus; + UINT cxWindowBorders; + UINT cyWindowBorders; + ATOM atomWindowType; + WORD wCreatorVersion; +} WINDOWINFO, *PWINDOWINFO, *LPWINDOWINFO; + + +FailOnFalse [gle] GetWindowInfo(HWND hwnd, [out] PWINDOWINFO pwi); + +UINT GetWindowModuleFileNameA(HWND hwnd, [out] LPSTR pszFileName, UINT cchFileNameMax); +UINT GetWindowModuleFileNameW(HWND hwnd, [out] LPWSTR pszFileName, UINT cchFileNameMax); + + +FailOnFalse [gle] GetWindowRect(HWND hWnd, [out] LPRECT lpRect); +FailOnFalse [gle] GetClientRect(HWND hwnd, [out] LPRECT lprect); + +FailOnFalse [gle] SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw); + +HRGN GetWindowRgn(HWND hWnd, HRGN hRgn); + +ThreadId GetWindowThreadProcessId(HWND hWnd, [out] ProcessId* lpdwProcessId); + +FailOnFalse [gle] EnableWindow(HWND hWnd, BOOL bEnable); + +value DWORD SPIValues +{ +#define SPI_GETBEEP 1 +#define SPI_SETBEEP 2 +#define SPI_GETMOUSE 3 +#define SPI_SETMOUSE 4 +#define SPI_GETBORDER 5 +#define SPI_SETBORDER 6 +#define SPI_GETKEYBOARDSPEED 10 +#define SPI_SETKEYBOARDSPEED 11 +#define SPI_LANGDRIVER 12 +#define SPI_ICONHORIZONTALSPACING 13 +#define SPI_GETSCREENSAVETIMEOUT 14 +#define SPI_SETSCREENSAVETIMEOUT 15 +#define SPI_GETSCREENSAVEACTIVE 16 +#define SPI_SETSCREENSAVEACTIVE 17 +#define SPI_GETGRIDGRANULARITY 18 +#define SPI_SETGRIDGRANULARITY 19 +#define SPI_SETDESKWALLPAPER 20 +#define SPI_SETDESKPATTERN 21 +#define SPI_GETKEYBOARDDELAY 22 +#define SPI_SETKEYBOARDDELAY 23 +#define SPI_ICONVERTICALSPACING 24 +#define SPI_GETICONTITLEWRAP 25 +#define SPI_SETICONTITLEWRAP 26 +#define SPI_GETMENUDROPALIGNMENT 27 +#define SPI_SETMENUDROPALIGNMENT 28 +#define SPI_SETDOUBLECLKWIDTH 29 +#define SPI_SETDOUBLECLKHEIGHT 30 +#define SPI_GETICONTITLELOGFONT 31 +#define SPI_SETDOUBLECLICKTIME 32 +#define SPI_SETMOUSEBUTTONSWAP 33 +#define SPI_SETICONTITLELOGFONT 34 +#define SPI_GETFASTTASKSWITCH 35 +#define SPI_SETFASTTASKSWITCH 36 +#define SPI_SETDRAGFULLWINDOWS 37 +#define SPI_GETDRAGFULLWINDOWS 38 +#define SPI_GETNONCLIENTMETRICS 41 +#define SPI_SETNONCLIENTMETRICS 42 +#define SPI_GETMINIMIZEDMETRICS 43 +#define SPI_SETMINIMIZEDMETRICS 44 +#define SPI_GETICONMETRICS 45 +#define SPI_SETICONMETRICS 46 +#define SPI_SETWORKAREA 47 +#define SPI_GETWORKAREA 48 +#define SPI_SETPENWINDOWS 49 + +#define SPI_GETFILTERKEYS 50 +#define SPI_SETFILTERKEYS 51 +#define SPI_GETTOGGLEKEYS 52 +#define SPI_SETTOGGLEKEYS 53 +#define SPI_GETMOUSEKEYS 54 +#define SPI_SETMOUSEKEYS 55 +#define SPI_GETSHOWSOUNDS 56 +#define SPI_SETSHOWSOUNDS 57 +#define SPI_GETSTICKYKEYS 58 +#define SPI_SETSTICKYKEYS 59 +#define SPI_GETACCESSTIMEOUT 60 +#define SPI_SETACCESSTIMEOUT 61 +#define SPI_GETSERIALKEYS 62 +#define SPI_SETSERIALKEYS 63 +#define SPI_GETSOUNDSENTRY 64 +#define SPI_SETSOUNDSENTRY 65 + +#define SPI_GETHIGHCONTRAST 66 +#define SPI_SETHIGHCONTRAST 67 +#define SPI_GETKEYBOARDPREF 68 +#define SPI_SETKEYBOARDPREF 69 +#define SPI_GETSCREENREADER 70 +#define SPI_SETSCREENREADER 71 +#define SPI_GETANIMATION 72 +#define SPI_SETANIMATION 73 +#define SPI_GETFONTSMOOTHING 74 +#define SPI_SETFONTSMOOTHING 75 +#define SPI_SETDRAGWIDTH 76 +#define SPI_SETDRAGHEIGHT 77 +#define SPI_SETHANDHELD 78 +#define SPI_GETLOWPOWERTIMEOUT 79 +#define SPI_GETPOWEROFFTIMEOUT 80 +#define SPI_SETLOWPOWERTIMEOUT 81 +#define SPI_SETPOWEROFFTIMEOUT 82 +#define SPI_GETLOWPOWERACTIVE 83 +#define SPI_GETPOWEROFFACTIVE 84 +#define SPI_SETLOWPOWERACTIVE 85 +#define SPI_SETPOWEROFFACTIVE 86 +#define SPI_SETCURSORS 87 +#define SPI_SETICONS 88 +#define SPI_GETDEFAULTINPUTLANG 89 +#define SPI_SETDEFAULTINPUTLANG 90 +#define SPI_SETLANGTOGGLE 91 +#define SPI_GETWINDOWSEXTENSION 92 +#define SPI_SETMOUSETRAILS 93 +#define SPI_GETMOUSETRAILS 94 + +#define SPI_GETSNAPTODEFBUTTON 95 +#define SPI_SETSNAPTODEFBUTTON 96 + +#define SPI_SETSCREENSAVERRUNNING 97 + +#define SPI_GETMOUSEHOVERWIDTH 98 +#define SPI_SETMOUSEHOVERWIDTH 99 +#define SPI_GETMOUSEHOVERHEIGHT 100 +#define SPI_SETMOUSEHOVERHEIGHT 101 +#define SPI_GETMOUSEHOVERTIME 102 +#define SPI_SETMOUSEHOVERTIME 103 +#define SPI_GETWHEELSCROLLLINES 104 +#define SPI_SETWHEELSCROLLLINES 105 +#define SPI_GETMENUSHOWDELAY 106 +#define SPI_SETMENUSHOWDELAY 107 + +#define SPI_GETSHOWIMEUI 110 +#define SPI_SETSHOWIMEUI 111 + + +#define SPI_GETMOUSESPEED 112 +#define SPI_SETMOUSESPEED 113 +#define SPI_GETSCREENSAVERRUNNING 114 +#define SPI_GETDESKWALLPAPER 115 + +#define SPI_GETACTIVEWINDOWTRACKING 0x1000 +#define SPI_SETACTIVEWINDOWTRACKING 0x1001 +#define SPI_GETMENUANIMATION 0x1002 +#define SPI_SETMENUANIMATION 0x1003 +#define SPI_GETCOMBOBOXANIMATION 0x1004 +#define SPI_SETCOMBOBOXANIMATION 0x1005 +#define SPI_GETLISTBOXSMOOTHSCROLLING 0x1006 +#define SPI_SETLISTBOXSMOOTHSCROLLING 0x1007 +#define SPI_GETGRADIENTCAPTIONS 0x1008 +#define SPI_SETGRADIENTCAPTIONS 0x1009 +#define SPI_GETKEYBOARDCUES 0x100A +#define SPI_SETKEYBOARDCUES 0x100B +#define SPI_GETACTIVEWNDTRKZORDER 0x100C +#define SPI_SETACTIVEWNDTRKZORDER 0x100D +#define SPI_GETHOTTRACKING 0x100E +#define SPI_SETHOTTRACKING 0x100F +#define SPI_GETMENUFADE 0x1012 +#define SPI_SETMENUFADE 0x1013 +#define SPI_GETSELECTIONFADE 0x1014 +#define SPI_SETSELECTIONFADE 0x1015 +#define SPI_GETTOOLTIPANIMATION 0x1016 +#define SPI_SETTOOLTIPANIMATION 0x1017 +#define SPI_GETTOOLTIPFADE 0x1018 +#define SPI_SETTOOLTIPFADE 0x1019 +#define SPI_GETCURSORSHADOW 0x101A +#define SPI_SETCURSORSHADOW 0x101B +#define SPI_GETMOUSESONAR 0x101C +#define SPI_SETMOUSESONAR 0x101D +#define SPI_GETMOUSECLICKLOCK 0x101E +#define SPI_SETMOUSECLICKLOCK 0x101F +#define SPI_GETMOUSEVANISH 0x1020 +#define SPI_SETMOUSEVANISH 0x1021 +#define SPI_GETFLATMENU 0x1022 +#define SPI_SETFLATMENU 0x1023 +#define SPI_GETDROPSHADOW 0x1024 +#define SPI_SETDROPSHADOW 0x1025 + +#define SPI_GETUIEFFECTS 0x103E +#define SPI_SETUIEFFECTS 0x103F + +#define SPI_GETFOREGROUNDLOCKTIMEOUT 0x2000 +#define SPI_SETFOREGROUNDLOCKTIMEOUT 0x2001 +#define SPI_GETACTIVEWNDTRKTIMEOUT 0x2002 +#define SPI_SETACTIVEWNDTRKTIMEOUT 0x2003 +#define SPI_GETFOREGROUNDFLASHCOUNT 0x2004 +#define SPI_SETFOREGROUNDFLASHCOUNT 0x2005 +#define SPI_GETCARETWIDTH 0x2006 +#define SPI_SETCARETWIDTH 0x2007 + +#define SPI_GETMOUSECLICKLOCKTIME 0x2008 +#define SPI_SETMOUSECLICKLOCKTIME 0x2009 +#define SPI_GETFONTSMOOTHINGTYPE 0x200A +#define SPI_SETFONTSMOOTHINGTYPE 0x200B + +#define SPI_GETFONTSMOOTHINGGAMMA 0x200C +#define SPI_SETFONTSMOOTHINGGAMMA 0x200D + +#define SPI_GETFOCUSBORDERWIDTH 0x200E +#define SPI_SETFOCUSBORDERWIDTH 0x200F +#define SPI_GETFOCUSBORDERHEIGHT 0x2010 +#define SPI_SETFOCUSBORDERHEIGHT 0x2011 +}; + +mask DWORD SPIWinini +{ +#define SPIF_UPDATEINIFILE 0x0001 +#define SPIF_SENDWININICHANGE 0x0002 +}; + +FailOnFalse [gle] SystemParametersInfoA(SPIValues uiAction, + UINT uiParam, + /* [out] */ LPVOID pvParam, + SPIWinini fWinIni); + +FailOnFalse [gle] SystemParametersInfoW(SPIValues uiAction, + UINT uiParam, + /* [out] */ LPVOID pvParam, + SPIWinini fWinIni); + +value DWORD ColorIndex +{ +#define CTLCOLOR_MSGBOX 0 +#define CTLCOLOR_EDIT 1 +#define CTLCOLOR_LISTBOX 2 +#define CTLCOLOR_BTN 3 +#define CTLCOLOR_DLG 4 +#define CTLCOLOR_SCROLLBAR 5 +#define CTLCOLOR_STATIC 6 +#define CTLCOLOR_MAX 7 + +#define COLOR_SCROLLBAR 0 +#define COLOR_BACKGROUND 1 +#define COLOR_ACTIVECAPTION 2 +#define COLOR_INACTIVECAPTION 3 +#define COLOR_MENU 4 +#define COLOR_WINDOW 5 +#define COLOR_WINDOWFRAME 6 +#define COLOR_MENUTEXT 7 +#define COLOR_WINDOWTEXT 8 +#define COLOR_CAPTIONTEXT 9 +#define COLOR_ACTIVEBORDER 10 +#define COLOR_INACTIVEBORDER 11 +#define COLOR_APPWORKSPACE 12 +#define COLOR_HIGHLIGHT 13 +#define COLOR_HIGHLIGHTTEXT 14 +#define COLOR_BTNFACE 15 +#define COLOR_BTNSHADOW 16 +#define COLOR_GRAYTEXT 17 +#define COLOR_BTNTEXT 18 +#define COLOR_INACTIVECAPTIONTEXT 19 +#define COLOR_BTNHIGHLIGHT 20 + +#define COLOR_3DDKSHADOW 21 +#define COLOR_3DLIGHT 22 +#define COLOR_INFOTEXT 23 +#define COLOR_INFOBK 24 + +#define COLOR_HOTLIGHT 26 +#define COLOR_GRADIENTACTIVECAPTION 27 +#define COLOR_GRADIENTINACTIVECAPTION 28 +#define COLOR_MENUHILIGHT 29 +#define COLOR_MENUBAR 30 +}; + +DWORD GetSysColor(ColorIndex nIndex); + +FailOnFalse [gle] SetSysColors(int cElements, + ColorIndex* lpaElements, + DWORD* lpaRgbValues); + +HRSRC GetSysColorBrush(ColorIndex nIndex); + +HWND [gle] SetParent(HWND hWndChild, HWND hWndNewParent); +HWND [gle] GetParent(HWND hWnd); + +value DWORD GetAncestorType +{ +#define GA_PARENT 1 +#define GA_ROOT 2 +#define GA_ROOTOWNER 3 +}; + +HWND GetAncestor(HWND hwnd, GetAncestorType gaFlags); + +HWND [gle] GetTopWindow(HWND hWnd); + +FailOnFalse [gle] ClipCursor(LPRECT lpRect); +FailOnFalse [gle] GetClipCursor([out] LPRECT lpRect); + +HRSRC [gle] CreateCursor(HINSTANCE hInst, + int xHotSpot, + int yHotSpot, + int nWidth, + int nHeight, + LPVOID pvANDPlane, + LPVOID pvXORPlane); + +HRSRC [gle] LoadCursorFromFileA(LPCSTR lpFileName); +HRSRC [gle] LoadCursorFromFileW(LPCWSTR lpFileName); + +HRSRC GetCursor(); + +value DWORD CursorInfoFlags +{ +#define CURSOR_HIDDEN 0x00000000 +#define CURSOR_SHOWING 0x00000001 +}; + +typedef struct tagCURSORINFO +{ + DWORD cbSize; + CursorInfoFlags flags; + HRSRC hCursor; + POINT ptScreenPos; +} CURSORINFO, *PCURSORINFO, *LPCURSORINFO; + +FailOnFalse [gle] GetCursorInfo([out] PCURSORINFO pci); + +FailOnFalse [gle] SetCursorPos(int X, int Y); +FailOnFalse [gle] GetCursorPos( [out] LPPOINT lpPoint); + +FailOnFalse [gle] SetSystemCursor(HRSRC hcur, ImageValueA id); + +int ShowCursor(BOOL bShow); + + +FailOnFalse [gle] GetClassInfoA(HINSTANCE hInstance, + LPCSTR lpClassName, + [out] LPWNDCLASSA lpWndClass); + +FailOnFalse [gle] GetClassInfoW(HINSTANCE hInstance, + LPCWSTR lpClassName, + [out] LPWNDCLASSW lpWndClass); + +FailOnFalse [gle] GetClassInfoExA(HINSTANCE hInstance, + LPCSTR lpClassName, + [out] LPWNDCLASSEXA lpWndClassEx); + +FailOnFalse [gle] GetClassInfoExW(HINSTANCE hInstance, + LPCWSTR lpClassName, + [out] LPWNDCLASSEXW lpWndClassEx); + +value DWORD ClassLongIndex +{ +#define GWLP_WNDPROC -4 +#define GWLP_HINSTANCE -6 +#define GWLP_HWNDPARENT -8 +#define GWLP_USERDATA -21 +#define GWLP_ID -12 + +#define GCL_MENUNAME -8 +#define GCL_HBRBACKGROUND -10 +#define GCL_HCURSOR -12 +#define GCL_HICON -14 +#define GCL_HMODULE -16 +#define GCL_CBWNDEXTRA -18 +#define GCL_CBCLSEXTRA -20 +#define GCL_WNDPROC -24 +#define GCL_STYLE -26 +#define GCW_ATOM -32 +}; + +DwordFailIfZero [gle] GetClassLongA(HWND hWnd, ClassLongIndex nIndex); +DwordFailIfZero [gle] GetClassLongW(HWND hWnd, ClassLongIndex nIndex); + +DwordFailIfZero [gle] SetClassLongA(HWND hWnd, ClassLongIndex nIndex, DWORD dwNewLong); +DwordFailIfZero [gle] SetClassLongW(HWND hWnd, ClassLongIndex nIndex, DWORD dwNewLong); + +LongFailIfZero [gle] GetClassNameA(HWND hWnd, [out] LPSTR lpClassName, int nMaxCount); +LongFailIfZero [gle] GetClassNameW(HWND hWnd, [out] LPWSTR lpClassName, int nMaxCount); + +HWND GetLastActivePopup(HWND hWnd); + +FailOnFalse [gle] ShowOwnedPopups(HWND hWnd, BOOL fShow); + +mask DWORD ExitWindowsExFlags +{ +#define EWX_LOGOFF 0x00000000 +#define EWX_SHUTDOWN 0x00000001 +#define EWX_REBOOT 0x00000002 +#define EWX_FORCE 0x00000004 +#define EWX_POWEROFF 0x00000008 +#define EWX_FORCEIFHUNG 0x00000010 +}; + +FailOnFalse [gle] ExitWindowsEx(ExitWindowsExFlags uFlags, DWORD dwReserved); + +LongFailIfZero [gle] FillRect(HDC hDC, LPRECT lprc, HRSRC hbr); + +HWND [gle] FindWindowA(LPCSTR lpClassName, LPCSTR lpWindowName); +HWND [gle] FindWindowW(LPCWSTR lpClassName, LPCWSTR lpWindowName); + +HWND [gle] FindWindowExA(HWND hwndParent, HWND hwndChildAfter, LPCSTR lpszClass, LPCSTR lpszWindow); +HWND [gle] FindWindowExW(HWND hwndParent, HWND hwndChildAfter, LPCWSTR lpszClass, LPCWSTR lpszWindow); + +BOOL FlashWindow(HWND hWnd, BOOL bInvert); + +mask DWORD FlashWindowExFlags +{ +#define FLASHW_STOP 0 +#define FLASHW_CAPTION 0x00000001 +#define FLASHW_TRAY 0x00000002 +#define FLASHW_TIMER 0x00000004 +#define FLASHW_TIMERNOFG 0x00000008 +}; + +typedef struct _FLASHWINFO { + UINT cbSize; + HWND hwnd; + FlashWindowExFlags dwFlags; + UINT uCount; + DWORD dwTimeout; +} FLASHWINFO, *PFLASHWINFO; + +BOOL FlashWindowEx(PFLASHWINFO pfwi); + +LongFailIfZero [gle] FrameRect(HDC hDC, LPRECT lprc, HRSRC hbr); + +FailOnFalse [gle] SetRect([out] LPRECT lprc, int xLeft, int yTop, int xRight, int yBottom); + +FailOnFalse [gle] SetRectEmpty([out] LPRECT lprc); + +FailOnFalse [gle] EqualRect(LPRECT lprc1, LPRECT lprc2); + +FailOnFalse [gle] OffsetRect([out] LPRECT lprc, int dx, int dy); + +FailOnFalse [gle] InflateRect([out] LPRECT lprc, int dx, int dy); + +value DWORD VirtualKey +{ +#define VK_LBUTTON 0x01 +#define VK_RBUTTON 0x02 +#define VK_CANCEL 0x03 +#define VK_MBUTTON 0x04 + +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 + +#define VK_BACK 0x08 +#define VK_TAB 0x09 + +#define VK_CLEAR 0x0C +#define VK_RETURN 0x0D + +#define VK_SHIFT 0x10 +#define VK_CONTROL 0x11 +#define VK_MENU 0x12 +#define VK_PAUSE 0x13 +#define VK_CAPITAL 0x14 + +#define VK_KANA 0x15 +#define VK_JUNJA 0x17 +#define VK_FINAL 0x18 +#define VK_KANJI 0x19 + +#define VK_ESCAPE 0x1B + +#define VK_CONVERT 0x1C +#define VK_NONCONVERT 0x1D +#define VK_ACCEPT 0x1E +#define VK_MODECHANGE 0x1F + +#define VK_SPACE 0x20 +#define VK_PRIOR 0x21 +#define VK_NEXT 0x22 +#define VK_END 0x23 +#define VK_HOME 0x24 +#define VK_LEFT 0x25 +#define VK_UP 0x26 +#define VK_RIGHT 0x27 +#define VK_DOWN 0x28 +#define VK_SELECT 0x29 +#define VK_PRINT 0x2A +#define VK_EXECUTE 0x2B +#define VK_SNAPSHOT 0x2C +#define VK_INSERT 0x2D +#define VK_DELETE 0x2E +#define VK_HELP 0x2F + +#define VK_0 0x30 +#define VK_1 0x31 +#define VK_2 0x32 +#define VK_3 0x33 +#define VK_4 0x34 +#define VK_5 0x35 +#define VK_6 0x36 +#define VK_7 0x37 +#define VK_8 0x38 +#define VK_9 0x39 + +#define VK_A 0x41 +#define VK_B 0x42 +#define VK_C 0x43 +#define VK_D 0x44 +#define VK_E 0x45 +#define VK_F 0x46 +#define VK_G 0x47 +#define VK_H 0x48 +#define VK_I 0x49 +#define VK_J 0x4A +#define VK_K 0x4B +#define VK_L 0x4C +#define VK_M 0x4D +#define VK_N 0x4E +#define VK_O 0x4F +#define VK_P 0x50 +#define VK_Q 0x51 +#define VK_R 0x52 +#define VK_S 0x53 +#define VK_T 0x54 +#define VK_U 0x55 +#define VK_V 0x56 +#define VK_W 0x57 +#define VK_X 0x58 +#define VK_Y 0x59 +#define VK_Z 0x5A + +#define VK_LWIN 0x5B +#define VK_RWIN 0x5C +#define VK_APPS 0x5D + +#define VK_SLEEP 0x5F + +#define VK_NUMPAD0 0x60 +#define VK_NUMPAD1 0x61 +#define VK_NUMPAD2 0x62 +#define VK_NUMPAD3 0x63 +#define VK_NUMPAD4 0x64 +#define VK_NUMPAD5 0x65 +#define VK_NUMPAD6 0x66 +#define VK_NUMPAD7 0x67 +#define VK_NUMPAD8 0x68 +#define VK_NUMPAD9 0x69 +#define VK_MULTIPLY 0x6A +#define VK_ADD 0x6B +#define VK_SEPARATOR 0x6C +#define VK_SUBTRACT 0x6D +#define VK_DECIMAL 0x6E +#define VK_DIVIDE 0x6F +#define VK_F1 0x70 +#define VK_F2 0x71 +#define VK_F3 0x72 +#define VK_F4 0x73 +#define VK_F5 0x74 +#define VK_F6 0x75 +#define VK_F7 0x76 +#define VK_F8 0x77 +#define VK_F9 0x78 +#define VK_F10 0x79 +#define VK_F11 0x7A +#define VK_F12 0x7B +#define VK_F13 0x7C +#define VK_F14 0x7D +#define VK_F15 0x7E +#define VK_F16 0x7F +#define VK_F17 0x80 +#define VK_F18 0x81 +#define VK_F19 0x82 +#define VK_F20 0x83 +#define VK_F21 0x84 +#define VK_F22 0x85 +#define VK_F23 0x86 +#define VK_F24 0x87 + +#define VK_NUMLOCK 0x90 +#define VK_SCROLL 0x91 + +#define VK_OEM_FJ_JISHO 0x92 +#define VK_OEM_FJ_MASSHOU 0x93 +#define VK_OEM_FJ_TOUROKU 0x94 +#define VK_OEM_FJ_LOYA 0x95 +#define VK_OEM_FJ_ROYA 0x96 + +#define VK_LSHIFT 0xA0 +#define VK_RSHIFT 0xA1 +#define VK_LCONTROL 0xA2 +#define VK_RCONTROL 0xA3 +#define VK_LMENU 0xA4 +#define VK_RMENU 0xA5 + +#define VK_BROWSER_BACK 0xA6 +#define VK_BROWSER_FORWARD 0xA7 +#define VK_BROWSER_REFRESH 0xA8 +#define VK_BROWSER_STOP 0xA9 +#define VK_BROWSER_SEARCH 0xAA +#define VK_BROWSER_FAVORITES 0xAB +#define VK_BROWSER_HOME 0xAC + +#define VK_VOLUME_MUTE 0xAD +#define VK_VOLUME_DOWN 0xAE +#define VK_VOLUME_UP 0xAF +#define VK_MEDIA_NEXT_TRACK 0xB0 +#define VK_MEDIA_PREV_TRACK 0xB1 +#define VK_MEDIA_STOP 0xB2 +#define VK_MEDIA_PLAY_PAUSE 0xB3 +#define VK_LAUNCH_MAIL 0xB4 +#define VK_LAUNCH_MEDIA_SELECT 0xB5 +#define VK_LAUNCH_APP1 0xB6 +#define VK_LAUNCH_APP2 0xB7 + +#define VK_OEM_1 0xBA +#define VK_OEM_PLUS 0xBB +#define VK_OEM_COMMA 0xBC +#define VK_OEM_MINUS 0xBD +#define VK_OEM_PERIOD 0xBE +#define VK_OEM_2 0xBF +#define VK_OEM_3 0xC0 + +#define VK_OEM_4 0xDB +#define VK_OEM_5 0xDC +#define VK_OEM_6 0xDD +#define VK_OEM_7 0xDE +#define VK_OEM_8 0xDF + +#define VK_OEM_AX 0xE1 +#define VK_OEM_102 0xE2 +#define VK_ICO_HELP 0xE3 +#define VK_ICO_00 0xE4 + +#define VK_PROCESSKEY 0xE5 + +#define VK_ICO_CLEAR 0xE6 + +#define VK_PACKET 0xE7 + +#define VK_OEM_RESET 0xE9 +#define VK_OEM_JUMP 0xEA +#define VK_OEM_PA1 0xEB +#define VK_OEM_PA2 0xEC +#define VK_OEM_PA3 0xED +#define VK_OEM_WSCTRL 0xEE +#define VK_OEM_CUSEL 0xEF +#define VK_OEM_ATTN 0xF0 +#define VK_OEM_FINISH 0xF1 +#define VK_OEM_COPY 0xF2 +#define VK_OEM_AUTO 0xF3 +#define VK_OEM_ENLW 0xF4 +#define VK_OEM_BACKTAB 0xF5 + +#define VK_ATTN 0xF6 +#define VK_CRSEL 0xF7 +#define VK_EXSEL 0xF8 +#define VK_EREOF 0xF9 +#define VK_PLAY 0xFA +#define VK_ZOOM 0xFB +#define VK_NONAME 0xFC +#define VK_PA1 0xFD +#define VK_OEM_CLEAR 0xFE +}; + +DWORD GetKeyState(VirtualKey nVirtKey); +DWORD GetAsyncKeyState(VirtualKey vKey); + +DwordFailIfNeg1 VkKeyScanA(char ch); +DwordFailIfNeg1 VkKeyScanW(WCHAR ch); + +DwordFailIfNeg1 VkKeyScanExA(char ch, HKL dwhkl); +DwordFailIfNeg1 VkKeyScanExW(WCHAR ch, HKL dwhkl); + + +mask DWORD KeyModifier +{ +#define MOD_ALT 0x0001 +#define MOD_CONTROL 0x0002 +#define MOD_SHIFT 0x0004 +#define MOD_WIN 0x0008 +}; + +FailOnFalse [gle] RegisterHotKey(HWND hWnd, + int id, + KeyModifier fsModifiers, + VirtualKey vk); + +FailOnFalse [gle] UnregisterHotKey(HWND hWnd, int id); + + +LongFailIfZero [gle] GetKeyNameTextA(LONG lParam, [out] LPSTR lpString, int nSize); +LongFailIfZero [gle] GetKeyNameTextW(LONG lParam, [out] LPWSTR lpString, int nSize); + +typedef struct tagKEYSTATE +{ + BYTE st[256]; +} KEYSTATE, *LPKEYSTATE; + +FailOnFalse [gle] GetKeyboardState([out] LPKEYSTATE lpKeyState); +FailOnFalse [gle] SetKeyboardState(LPKEYSTATE lpKeyState); + +LongFailIfZero [gle] GetKeyboardType(int nTypeFlag); + +typedef struct tagLASTINPUTINFO { + UINT cbSize; + DWORD dwTime; +} LASTINPUTINFO, * PLASTINPUTINFO; + +FailOnFalse GetLastInputInfo([out] PLASTINPUTINFO plii); + +typedef LPVOID WNDENUMPROC; + +FailOnFalse [gle] EnumThreadWindows(ThreadId dwThreadId, WNDENUMPROC lpfn, LPARAM lParam); + +FailOnFalse [gle] EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam); + +FailOnFalse [gle] EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc, LPARAM lParam); + +FailOnFalse [gle] EnumDesktopWindows(HDESK hDesktop, WNDENUMPROC lpfn, LPARAM lParam); + +HDESK [gle] GetThreadDesktop(ThreadId dwThreadId); + +FailOnFalse [gle] CloseDesktop(HDESK hDesktop); + +mask DWORD DesktopAccessMask +{ +#define DESKTOP_READOBJECTS 0x0001 +#define DESKTOP_CREATEWINDOW 0x0002 +#define DESKTOP_CREATEMENU 0x0004 +#define DESKTOP_HOOKCONTROL 0x0008 +#define DESKTOP_JOURNALRECORD 0x0010 +#define DESKTOP_JOURNALPLAYBACK 0x0020 +#define DESKTOP_ENUMERATE 0x0040 +#define DESKTOP_WRITEOBJECTS 0x0080 +#define DESKTOP_SWITCHDESKTOP 0x0100 +}; + +HDESK [gle] CreateDesktopA(LPCSTR lpszDesktop, + LPCSTR lpszDevice, + LPDEVMODEA pDevmode, + DWORD dwFlags, + DesktopAccessMask dwDesiredAccess, + LPSECURITY_ATTRIBUTES lpsa); + +HDESK [gle] CreateDesktopW(LPCWSTR lpszDesktop, + LPCWSTR lpszDevice, + LPDEVMODEW pDevmode, + DWORD dwFlags, + DesktopAccessMask dwDesiredAccess, + LPSECURITY_ATTRIBUTES lpsa); + +typedef LPVOID DESKTOPENUMPROCA; +typedef LPVOID DESKTOPENUMPROCW; + +FailOnFalse [gle] EnumDesktopsA(HWINSTA hwinsta, DESKTOPENUMPROCA lpEnumFunc, LPARAM lParam); +FailOnFalse [gle] EnumDesktopsW(HWINSTA hwinsta, DESKTOPENUMPROCW lpEnumFunc, LPARAM lParam); + +HDESK [gle] OpenInputDesktop(DWORD dwFlags, BOOL fInherit, DesktopAccessMask dwDesiredAccess); + +FailOnFalse [gle] SetThreadDesktop(HDESK hDesktop); + +HDESK [gle] OpenDesktopA(LPCSTR lpszDesktop, + DWORD dwFlags, + BOOL fInherit, + DesktopAccessMask dwDesiredAccess); + +HDESK [gle] OpenDesktopW(LPCWSTR lpszDesktop, + DWORD dwFlags, + BOOL fInherit, + DesktopAccessMask dwDesiredAccess); + +FailOnFalse [gle] SwitchDesktop(HDESK hDesktop); + +LongFailIfZero [gle] TileWindows(HWND hwndParent, + UINT wHow, + LPRECT lpRect, + UINT cKids, + HWND* lpKids); + +LongFailIfZero [gle] CascadeWindows(HWND hwndParent, + UINT wHow, + LPRECT lpRect, + UINT cKids, + HWND* lpKids); + +FailOnFalse [gle] BringWindowToTop(HWND hWnd); + +mask DWORD ButtonState +{ +#define BST_UNCHECKED 0x0000 +#define BST_CHECKED 0x0001 +#define BST_INDETERMINATE 0x0002 +#define BST_PUSHED 0x0004 +#define BST_FOCUS 0x0008 +}; + +FailOnFalse [gle] CheckDlgButton(HWND hDlg, int nIDButton, ButtonState uCheck); + +FailOnFalse [gle] CheckRadioButton(HWND hDlg, + int nIDFirstButton, + int nIDLastButton, + int nIDCheckButton); + +ButtonState IsDlgButtonChecked(HWND hDlg, int nIDButton); + +LongFailIfZero [gle] GetDlgCtrlID(HWND hWnd); + +HWND [gle] GetDlgItem(HWND hDlg, int nIDDlgItem); + +LongFailIfZero [gle] GetDlgItemInt(HWND hDlg, + int nIDDlgItem, + [out] BOOL* lpTranslated, + BOOL bSigned); + + +mask DWORD DrawTextFlags +{ +#define DT_TOP 0x00000000 +#define DT_LEFT 0x00000000 +#define DT_CENTER 0x00000001 +#define DT_RIGHT 0x00000002 +#define DT_VCENTER 0x00000004 +#define DT_BOTTOM 0x00000008 +#define DT_WORDBREAK 0x00000010 +#define DT_SINGLELINE 0x00000020 +#define DT_EXPANDTABS 0x00000040 +#define DT_TABSTOP 0x00000080 +#define DT_NOCLIP 0x00000100 +#define DT_EXTERNALLEADING 0x00000200 +#define DT_CALCRECT 0x00000400 +#define DT_NOPREFIX 0x00000800 +#define DT_INTERNAL 0x00001000 + +#define DT_EDITCONTROL 0x00002000 +#define DT_PATH_ELLIPSIS 0x00004000 +#define DT_END_ELLIPSIS 0x00008000 +#define DT_MODIFYSTRING 0x00010000 +#define DT_RTLREADING 0x00020000 +#define DT_WORD_ELLIPSIS 0x00040000 +#define DT_NOFULLWIDTHCHARBREAK 0x00080000 +#define DT_HIDEPREFIX 0x00100000 +#define DT_PREFIXONLY 0x00200000 +}; + +LongFailIfZero [gle] DrawTextA(HDC hDC, + LPCSTR lpString, + int nCount, + [out] LPRECT lpRect, + DrawTextFlags uFormat); + +LongFailIfZero [gle] DrawTextW(HDC hDC, + LPCWSTR lpString, + int nCount, + [out] LPRECT lpRect, + DrawTextFlags uFormat); + +typedef struct tagDRAWTEXTPARAMS +{ + UINT cbSize; + int iTabLength; + int iLeftMargin; + int iRightMargin; + UINT uiLengthDrawn; +} DRAWTEXTPARAMS, *LPDRAWTEXTPARAMS; + +LongFailIfZero [gle] DrawTextExA(HDC hDC, + [out] LPSTR lpString, + int nCount, + [out] LPRECT lpRect, + DrawTextFlags uFormat, + LPDRAWTEXTPARAMS lpDrawTextParams); + +LongFailIfZero [gle] DrawTextExW(HDC hDC, + [out] LPWSTR lpString, + int nCount, + [out] LPRECT lpRect, + DrawTextFlags uFormat, + LPDRAWTEXTPARAMS lpDrawTextParams); + +DwordFailIfZero [gle] GetTabbedTextExtentA(HDC hDC, + LPCSTR lpString, + int nCount, + int nTabPositions, + int* lpnTabStopPositions); + +DwordFailIfZero [gle] GetTabbedTextExtentW(HDC hDC, + LPCWSTR lpString, + int nCount, + int nTabPositions, + int* lpnTabStopPositions); + +DwordFailIfZero [gle] TabbedTextOutA(HDC hDC, + int X, + int Y, + LPCSTR lpString, + int nCount, + int nTabPositions, + int* lpnTabStopPositions, + int nTabOrigin); + +DwordFailIfZero [gle] TabbedTextOutW(HDC hDC, + int X, + int Y, + LPCWSTR lpString, + int nCount, + int nTabPositions, + int* lpnTabStopPositions, + int nTabOrigin); + + + +typedef struct tagALTTABINFO +{ + DWORD cbSize; + int cItems; + int cColumns; + int cRows; + int iColFocus; + int iRowFocus; + int cxItem; + int cyItem; + POINT ptStart; +} ALTTABINFO, *PALTTABINFO, *LPALTTABINFO; + +FailOnFalse [gle] GetAltTabInfoA(HWND hwnd, + int iItem, + [out] PALTTABINFO pati, + [out] LPSTR pszItemText, + UINT cchItemText); + +FailOnFalse [gle] GetAltTabInfoW(HWND hwnd, + int iItem, + [out] PALTTABINFO pati, + [out] LPWSTR pszItemText, + UINT cchItemText); + +LongFailIfZero [gle] RealGetWindowClassA(HWND hwnd, [out] LPSTR pszType, UINT cchType); +LongFailIfZero [gle] RealGetWindowClassW(HWND hwnd, [out] LPWSTR pszType, UINT cchType); + +DWORD GetQueueStatus(QueueStates flags); + +FailOnFalse [gle] BlockInput(BOOL fBlockIt); + +// not detailed +// export SendInput; + +mask DWORD KeybdEventFlags +{ +#define KEYEVENTF_EXTENDEDKEY 0x0001 +#define KEYEVENTF_KEYUP 0x0002 +#define KEYEVENTF_UNICODE 0x0004 +#define KEYEVENTF_SCANCODE 0x0008 +}; + +VOID keybd_event(VirtualKey bVk, BYTE bScan, KeybdEventFlags dwFlags, DWORD dwExtraInfo); + +mask DWORD MouseEventFlags +{ +#define MOUSEEVENTF_MOVE 0x0001 +#define MOUSEEVENTF_LEFTDOWN 0x0002 +#define MOUSEEVENTF_LEFTUP 0x0004 +#define MOUSEEVENTF_RIGHTDOWN 0x0008 +#define MOUSEEVENTF_RIGHTUP 0x0010 +#define MOUSEEVENTF_MIDDLEDOWN 0x0020 +#define MOUSEEVENTF_MIDDLEUP 0x0040 +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define MOUSEEVENTF_WHEEL 0x0800 +#define MOUSEEVENTF_VIRTUALDESK 0x4000 +#define MOUSEEVENTF_ABSOLUTE 0x8000 +}; + +VOID mouse_event(MouseEventFlags dwFlags, DWORD dx, DWORD dy, DWORD dwData, DWORD dwExtraInfo); + + +BOOL GetInputState(); + +mask DWORD DrawCaptionFlags +{ +#define DC_ACTIVE 0x0001 +#define DC_SMALLCAP 0x0002 +#define DC_ICON 0x0004 +#define DC_TEXT 0x0008 +#define DC_INBUTTON 0x0010 +#define DC_GRADIENT 0x0020 +}; + +FailOnFalse [gle] DrawCaption(HWND hwnd, HDC hdc, LPRECT lprc, DrawCaptionFlags dwFlags); + +FailOnFalse [gle] DrawAnimatedRects(HWND hwnd, + int idAni, + LPRECT lprcFrom, + LPRECT lprcTo); + +mask DWORD BorderStyle +{ +#define BDR_RAISEDOUTER 0x0001 +#define BDR_SUNKENOUTER 0x0002 +#define BDR_RAISEDINNER 0x0004 +#define BDR_SUNKENINNER 0x0008 +}; + +mask DWORD BorderType +{ +#define BF_LEFT 0x0001 +#define BF_TOP 0x0002 +#define BF_RIGHT 0x0004 +#define BF_BOTTOM 0x0008 + +#define BF_DIAGONAL 0x0010 + +#define BF_MIDDLE 0x0800 +#define BF_SOFT 0x1000 +#define BF_ADJUST 0x2000 +#define BF_FLAT 0x4000 +#define BF_MONO 0x8000 +}; + +FailOnFalse [gle] DrawEdge(HDC hdc, + [out] LPRECT lprc, + BorderStyle edge, + BorderType grfFlags); + +value DWORD DrawFrameControlType +{ +#define DFC_CAPTION 1 +#define DFC_MENU 2 +#define DFC_SCROLL 3 +#define DFC_BUTTON 4 +#define DFC_POPUPMENU 5 +}; + +mask DWORD DrawFrameControlState +{ +#define DFCS_INACTIVE 0x0100 +#define DFCS_PUSHED 0x0200 +#define DFCS_CHECKED 0x0400 + +#define DFCS_TRANSPARENT 0x0800 +#define DFCS_HOT 0x1000 + +#define DFCS_ADJUSTRECT 0x2000 +#define DFCS_FLAT 0x4000 +#define DFCS_MONO 0x8000 +}; + +FailOnFalse [gle] DrawFrameControl(HDC hDC, + [out] LPRECT lprc, + DrawFrameControlType type, + DrawFrameControlState state); + +FailOnFalse [gle] DrawIcon(HDC hDC, int X, int Y, HRSRC hIcon); + +value DWORD DrawIconExFlags +{ +#define DI_MASK 0x0001 +#define DI_IMAGE 0x0002 +#define DI_NORMAL 0x0003 +#define DI_COMPAT 0x0004 +#define DI_DEFAULTSIZE 0x0008 +}; + + +FailOnFalse [gle] DrawIconEx(HDC hdc, + int xLeft, + int yTop, + HRSRC hIcon, + int cxWidth, + int cyWidth, + UINT istepIfAniCur, + HRSRC hbrFlickerFreeDraw, + DrawIconExFlags diFlags); + +typedef LPVOID DRAWSTATEPROC; + +mask DWORD DrawStateFlags +{ +#define DSS_NORMAL 0x0000 +#define DSS_UNION 0x0010 +#define DSS_DISABLED 0x0020 +#define DSS_MONO 0x0080 +#define DSS_HIDEPREFIX 0x0200 +#define DSS_PREFIXONLY 0x0400 +#define DSS_RIGHT 0x8000 +}; + +FailOnFalse [gle] DrawStateA(HDC hDC, + HRSRC hBrush, + DRAWSTATEPROC pfnOutput, + LPARAM lParam, + WPARAM wParam, + int x, + int y, + int cx, + int cy, + DrawStateFlags flags); + +FailOnFalse [gle] DrawStateW(HDC hDC, + HRSRC hBrush, + DRAWSTATEPROC pfnOutput, + LPARAM lParam, + WPARAM wParam, + int x, + int y, + int cx, + int cy, + DrawStateFlags flags); + +typedef LPVOID GRAYSTRINGPROC; + +FailOnFalse [gle] GrayStringA(HDC hDC, + HRSRC hBrush, + GRAYSTRINGPROC lpOutputFunc, + LPARAM lpData, + int nCount, + int X, + int Y, + int nWidth, + int nHeight); + +FailOnFalse [gle] GrayStringW(HDC hDC, + HRSRC hBrush, + GRAYSTRINGPROC lpOutputFunc, + LPARAM lpData, + int nCount, + int X, + int Y, + int nWidth, + int nHeight); + +FailOnFalse [gle] CreateCaret(HWND hWnd, + HRSRC hBitmap, + int nWidth, + int nHeight); + +FailOnFalse [gle] DestroyCaret(); + +FailOnFalse [gle] SetCaretBlinkTime(UINT uMSeconds); + +LongFailIfZero [gle] GetCaretBlinkTime(); + +FailOnFalse [gle] SetCaretPos(int X, int Y); + +FailOnFalse [gle] GetCaretPos([out] LPPOINT lpPoint); + +FailOnFalse [gle] ShowCaret(HWND hWnd); + +FailOnFalse [gle] HideCaret(HWND hWnd); + +// WindowStation APIs + +mask DWORD WinstaAccessMask +{ +#define WINSTA_ENUMDESKTOPS 0x0001 +#define WINSTA_READATTRIBUTES 0x0002 +#define WINSTA_ACCESSCLIPBOARD 0x0004 +#define WINSTA_CREATEDESKTOP 0x0008 +#define WINSTA_WRITEATTRIBUTES 0x0010 +#define WINSTA_ACCESSGLOBALATOMS 0x0020 +#define WINSTA_EXITWINDOWS 0x0040 +#define WINSTA_ENUMERATE 0x0100 +#define WINSTA_READSCREEN 0x0200 +}; + +HWINSTA [gle] CreateWindowStationA(LPCSTR lpwinsta, + DWORD dwReserved, + WinstaAccessMask dwDesiredAccess, + LPSECURITY_ATTRIBUTES lpsa); + +HWINSTA [gle] CreateWindowStationW(LPCWSTR lpwinsta, + DWORD dwReserved, + WinstaAccessMask dwDesiredAccess, + LPSECURITY_ATTRIBUTES lpsa); + +FailOnFalse [gle] CloseWindowStation([da] HWINSTA hWinSta); + +typedef LPVOID WINSTAENUMPROCA; +typedef LPVOID WINSTAENUMPROCW; + +FailOnFalse [gle] EnumWindowStationsA(WINSTAENUMPROCA lpEnumFunc, LPARAM lParam); +FailOnFalse [gle] EnumWindowStationsW(WINSTAENUMPROCW lpEnumFunc, LPARAM lParam); + +HWINSTA [gle] GetProcessWindowStation(); + +FailOnFalse [gle] SetProcessWindowStation(HWINSTA hWinSta); + +HWINSTA [gle] OpenWindowStationA(LPCSTR lpszWinSta, BOOL fInherit, WinstaAccessMask dwDesiredAccess); +HWINSTA [gle] OpenWindowStationW(LPCWSTR lpszWinSta, BOOL fInherit, WinstaAccessMask dwDesiredAccess); + +// scrollbar + +value DWORD ScrollWhich +{ +#define SB_HORZ 0 +#define SB_VERT 1 +#define SB_CTL 2 +#define SB_BOTH 3 +}; + +mask DWORD ScrollEnableFlags +{ +#define ESB_ENABLE_BOTH 0x0000 +#define ESB_DISABLE_LEFT 0x0001 +#define ESB_DISABLE_RIGHT 0x0002 +}; + +FailOnFalse [gle] EnableScrollBar(HWND hWnd, ScrollWhich wSBflags, ScrollEnableFlags wArrows); + +mask DWORD ScrollInfoFlags +{ +#define SIF_RANGE 0x0001 +#define SIF_PAGE 0x0002 +#define SIF_POS 0x0004 +#define SIF_DISABLENOSCROLL 0x0008 +#define SIF_TRACKPOS 0x0010 +}; + +typedef struct tagSCROLLINFO +{ + UINT cbSize; + ScrollInfoFlags fMask; + int nMin; + int nMax; + UINT nPage; + int nPos; + int nTrackPos; +} SCROLLINFO, *LPSCROLLINFO; + +int SetScrollInfo(HWND hWnd, ScrollWhich nWhich, LPSCROLLINFO lpScrollInfo, BOOL fRedraw); + +LongFailIfZero [gle] SetScrollPos(HWND hWnd, ScrollWhich nBar, int nPos, BOOL bRedraw); + +FailOnFalse [gle] SetScrollRange(HWND hWnd, + ScrollWhich nBar, + int nMinPos, + int nMaxPos, + BOOL bRedraw); + +typedef struct tagSCROLLBARINFO +{ + DWORD cbSize; + RECT rcScrollBar; + int dxyLineButton; + int xyThumbTop; + int xyThumbBottom; + int reserved; + DWORD rgstate[6]; +} SCROLLBARINFO, *PSCROLLBARINFO, *LPSCROLLBARINFO; + +FailOnFalse [gle] GetScrollBarInfo(HWND hwnd, SystemObjectId idObject, [out] PSCROLLBARINFO psbi); + +FailOnFalse [gle] GetScrollInfo(HWND hwnd, ScrollWhich nBar, [out] LPSCROLLINFO lpScrollInfo); + +LongFailIfZero [gle] GetScrollPos(HWND hWnd, ScrollWhich nBar); + +FailOnFalse [gle] GetScrollRange(HWND hWnd, + ScrollWhich nBar, + [out] int* lpMinPos, + [out] int* lpMaxPos); + +FailOnFalse [gle] ShowScrollBar(HWND hWnd, ScrollWhich nBar, BOOL bShow); + +typedef struct tagCOMBOBOXINFO +{ + DWORD cbSize; + RECT rcItem; + RECT rcButton; + DWORD stateButton; + HWND hwndCombo; + HWND hwndItem; + HWND hwndList; +} COMBOBOXINFO, *PCOMBOBOXINFO, *LPCOMBOBOXINFO; + +FailOnFalse [gle] GetComboBoxInfo(HWND hwndCombo, [out] PCOMBOBOXINFO pcbi); + +int GetListBoxInfo(HWND hwnd); + +typedef struct tagTITLEBARINFO +{ + DWORD cbSize; + RECT rcTitleBar; + DWORD rgstate[6]; +} TITLEBARINFO, *PTITLEBARINFO, *LPTITLEBARINFO; + +BOOL GetTitleBarInfo(HWND hwnd, [out] PTITLEBARINFO pti); + +typedef LPVOID TIMERPROC; + +DwordFailIfZero [gle] SetTimer(HWND hWnd, DWORD nIDEvent, int uElapse, TIMERPROC lpTimerFunc); + +FailOnFalse [gle] KillTimer(HWND hWnd, DWORD uIDEvent); + +FailOnFalse [gle] SetDoubleClickTime(int nTimeout); + +int GetDoubleClickTime(); + +LPARAM SetMessageExtraInfo(LPARAM lParam); + +mask DWORD TrackMouseEventFlags +{ +#define TME_HOVER 0x00000001 +#define TME_LEAVE 0x00000002 +#define TME_NONCLIENT 0x00000010 +#define TME_QUERY 0x40000000 +#define TME_CANCEL 0x80000000 +}; + +typedef struct tagTRACKMOUSEEVENT { + DWORD cbSize; + TrackMouseEventFlags dwFlags; + HWND hwndTrack; + DWORD dwHoverTime; +} TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT; + + +// BUGBUG: this is [in] and [out] +FailOnFalse [gle] TrackMouseEvent([out] LPTRACKMOUSEEVENT lpEventTrack); + +BOOL SwapMouseButton(BOOL fSwap); + +LongFailIfZero [gle] MapWindowPoints(HWND hWndFrom, + HWND hWndTo, + [out] LPPOINT lpPoints, + int cPoints); + + +category User32StringExports: + +LongFailIfZero [gle] LoadStringA(HINSTANCE hInstance, UINT uID, [out] LPSTR lpBuffer, int nBufferMax); +LongFailIfZero [gle] LoadStringW(HINSTANCE hInstance, UINT uID, [out] LPWSTR lpBuffer, int nBufferMax); + +#include "clipboard.h" +#include "hook.h" diff --git a/tools/Debugging Tools for Windows/winext/manifest/uuids.h b/tools/Debugging Tools for Windows/winext/manifest/uuids.h new file mode 100644 index 0000000000..4a6fe77e45 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/uuids.h @@ -0,0 +1,404 @@ + +struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) IUnknown; +struct __declspec(uuid("00000001-0000-0000-C000-000000000046")) IClassFactory; +struct __declspec(uuid("00000002-0000-0000-C000-000000000046")) IMalloc; +struct __declspec(uuid("00020400-0000-0000-C000-000000000046")) IDispatch; +struct __declspec(uuid("0000010b-0000-0000-C000-000000000046")) IPersistFile; +struct __declspec(uuid("0000010c-0000-0000-C000-000000000046")) IPersist; +struct __declspec(uuid("0000010d-0000-0000-C000-000000000046")) IViewObject; +struct __declspec(uuid("00000127-0000-0000-C000-000000000046")) IViewObject2; +struct __declspec(uuid("0000013D-0000-0000-C000-000000000046")) IClientSecurity; +struct __declspec(uuid("0000013E-0000-0000-C000-000000000046")) IServerSecurity; +struct __declspec(uuid("00000140-0000-0000-C000-000000000046")) IClassActivator; +struct __declspec(uuid("00020d00-0000-0000-c000-000000000046")) IRichEditOle; +struct __declspec(uuid("00020d03-0000-0000-c000-000000000046")) IRichEditOleCallback; +struct __declspec(uuid("000214e1-0000-0000-c000-000000000046")) INewShortcutHookA; +struct __declspec(uuid("000214e2-0000-0000-c000-000000000046")) IShellBrowser; +struct __declspec(uuid("000214e3-0000-0000-c000-000000000046")) IShellView; +struct __declspec(uuid("000214e4-0000-0000-c000-000000000046")) IContextMenu; +struct __declspec(uuid("000214e5-0000-0000-c000-000000000046")) IShellIcon; +struct __declspec(uuid("000214e6-0000-0000-c000-000000000046")) IShellFolder; +struct __declspec(uuid("000214e8-0000-0000-c000-000000000046")) IShellExtInit; +struct __declspec(uuid("000214e9-0000-0000-c000-000000000046")) IShellPropSheetExt; +struct __declspec(uuid("000214ea-0000-0000-c000-000000000046")) IPersistFolder; +struct __declspec(uuid("000214eb-0000-0000-c000-000000000046")) IExtractIconA; +struct __declspec(uuid("000214ee-0000-0000-c000-000000000046")) IShellLinkA; +struct __declspec(uuid("000214f0-0000-0000-c000-000000000046")) IFileViewerA; +struct __declspec(uuid("000214f1-0000-0000-c000-000000000046")) ICommDlgBrowser; +struct __declspec(uuid("000214f2-0000-0000-c000-000000000046")) IEnumIDList; +struct __declspec(uuid("000214f3-0000-0000-c000-000000000046")) IFileViewerSite; +struct __declspec(uuid("000214f4-0000-0000-c000-000000000046")) IContextMenu2; +struct __declspec(uuid("000214f5-0000-0000-c000-000000000046")) IShellExecuteHookA; +struct __declspec(uuid("000214f7-0000-0000-c000-000000000046")) INewShortcutHookW; +struct __declspec(uuid("000214f8-0000-0000-c000-000000000046")) IFileViewerW; +struct __declspec(uuid("000214f9-0000-0000-c000-000000000046")) IShellLinkW; +struct __declspec(uuid("000214fa-0000-0000-c000-000000000046")) IExtractIconW; +struct __declspec(uuid("000214fb-0000-0000-c000-000000000046")) IShellExecuteHookW; +struct __declspec(uuid("00021500-0000-0000-c000-000000000046")) IQueryInfo; +struct __declspec(uuid("0002DF05-0000-0000-C000-000000000046")) IWebBrowserApp; +struct __declspec(uuid("0002E000-0000-0000-C000-000000000046")) IEnumGUID; +struct __declspec(uuid("0002E011-0000-0000-C000-000000000046")) IEnumCATEGORYINFO; +struct __declspec(uuid("0002E012-0000-0000-C000-000000000046")) ICatRegister; +struct __declspec(uuid("0002E013-0000-0000-C000-000000000046")) ICatInformation; +struct __declspec(uuid("012dd920-7b26-11d0-8ca9-00a0c92dbfe8")) IDockingWindow; +struct __declspec(uuid("3050f4e9-98b5-11cf-bb82-00aa00bdce0b")) IHTMLControlElement; +struct __declspec(uuid("085FB2C0-0DF8-11D1-8F4B-00A0C905413F")) ISubscriptionMgr; +struct __declspec(uuid("08EC3E00-50B0-11CF-960C-0080C7F4EE85")) FolderItemVerb; +struct __declspec(uuid("0c6c4200-c589-11d0-999a-00c04fd655e1")) IShellIconOverlayIdentifier; +struct __declspec(uuid("1008C4A0-7613-11CF-9AF1-0020AF6E72F4")) IChannelHook; +struct __declspec(uuid("163BB1E0-6E00-11CF-837A-48DC04C10000")) IHTMLLocation; +struct __declspec(uuid("1CFF0050-6FDD-11D0-9328-00A0C90DCAA9")) IActiveScriptParseProcedureOld; +struct __declspec(uuid("1F8352C0-50B0-11CF-960C-0080C7F4EE85")) FolderItemVerbs; +struct __declspec(uuid("1ac3d9f0-175c-11d1-95be-00609797ea4f")) IPersistFolder2; +struct __declspec(uuid("275C23E1-3747-11D0-9FEA-00AA003F8646")) IMultiLanguage; +struct __declspec(uuid("275C23E3-3747-11D0-9FEA-00AA003F8646")) IEnumCodePage; +struct __declspec(uuid("2a342fc2-7b26-11d0-8ca9-00a0c92dbfe8")) IDockingWindowSite; +struct __declspec(uuid("3050F1D8-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLBodyElement; +struct __declspec(uuid("3050F1D9-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFontElement; +struct __declspec(uuid("3050F1DA-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLAnchorElement; +struct __declspec(uuid("3050F1DD-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLUListElement; +struct __declspec(uuid("3050F1DE-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLOListElement; +struct __declspec(uuid("3050F1E0-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLLIElement; +struct __declspec(uuid("3050F1F0-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLBRElement; +struct __declspec(uuid("3050F1F1-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDListElement; +struct __declspec(uuid("3050F1F2-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDDElement; +struct __declspec(uuid("3050F1F3-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDTElement; +struct __declspec(uuid("3050F1F4-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLHRElement; +struct __declspec(uuid("3050F1F5-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLParaElement; +struct __declspec(uuid("3050F1F6-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLHeaderElement; +struct __declspec(uuid("3050F1F7-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFormElement; +struct __declspec(uuid("3050F1FF-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLElement; +struct __declspec(uuid("3050F200-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDivElement; +struct __declspec(uuid("3050F202-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLBaseFontElement; +struct __declspec(uuid("3050F203-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLMetaElement; +struct __declspec(uuid("3050F204-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLBaseElement; +struct __declspec(uuid("3050F205-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLLinkElement; +struct __declspec(uuid("3050F206-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLIsIndexElement; +struct __declspec(uuid("3050F207-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLNextIdElement; +struct __declspec(uuid("3050F208-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLBlockElement; +struct __declspec(uuid("3050F209-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLUnknownElement; +struct __declspec(uuid("3050F20A-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLPhraseElement; +struct __declspec(uuid("3050F20C-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLCommentElement; +struct __declspec(uuid("3050F20E-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLListElement; +struct __declspec(uuid("3050F211-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLOptionElement; +struct __declspec(uuid("3050F212-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDivPosition; +struct __declspec(uuid("3050F216-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDialog; +struct __declspec(uuid("3050F218-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTextElement; +struct __declspec(uuid("3050F21E-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTable; +struct __declspec(uuid("3050F21F-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLElementCollection; +struct __declspec(uuid("3050F220-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTxtRange; +struct __declspec(uuid("3050F230-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTextContainer; +struct __declspec(uuid("3050F23A-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTableCol; +struct __declspec(uuid("3050F23B-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTableSection; +struct __declspec(uuid("3050F23C-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTableRow; +struct __declspec(uuid("3050F23D-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTableCell; +struct __declspec(uuid("3050F240-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLImgElement; +struct __declspec(uuid("3050F244-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLSelectElement; +struct __declspec(uuid("3050F24F-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLObjectElement; +struct __declspec(uuid("3050F25A-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLSelectionObject; +struct __declspec(uuid("3050F25E-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyle; +struct __declspec(uuid("3050F25F-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLEmbedElement; +struct __declspec(uuid("3050F265-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLAreaElement; +struct __declspec(uuid("3050F266-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLMapElement; +struct __declspec(uuid("3050F28B-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLScriptElement; +struct __declspec(uuid("3050F29C-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLControlRange; +struct __declspec(uuid("3050F2A4-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLInputHiddenElement; +struct __declspec(uuid("3050F2A6-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLInputTextElement; +struct __declspec(uuid("3050F2AA-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTextAreaElement; +struct __declspec(uuid("3050F2AD-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLInputFileElement; +struct __declspec(uuid("3050F2B2-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLInputButtonElement; +struct __declspec(uuid("3050F2B5-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLMarqueeElement; +struct __declspec(uuid("3050F2BB-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLButtonElement; +struct __declspec(uuid("3050F2BC-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLOptionButtonElement; +struct __declspec(uuid("3050F2C2-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLInputImage; +struct __declspec(uuid("3050F2E3-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyleSheet; +struct __declspec(uuid("3050F2E5-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyleSheetRulesCollection; +struct __declspec(uuid("3050F2EB-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTableCaption; +struct __declspec(uuid("3050F2F4-98B5-11CF-BB82-00AA00BDCE0B")) IViewFilterSite; +struct __declspec(uuid("3050F311-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFrameBase; +struct __declspec(uuid("3050F313-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFrameElement; +struct __declspec(uuid("3050F315-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLIFrameElement; +struct __declspec(uuid("3050F319-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFrameSetElement; +struct __declspec(uuid("3050F322-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLTitleElement; +struct __declspec(uuid("3050F32A-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLLabelElement; +struct __declspec(uuid("3050F32D-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLEventObj; +struct __declspec(uuid("3050F357-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyleSheetRule; +struct __declspec(uuid("3050F35C-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLScreen; +struct __declspec(uuid("3050F35F-98B5-11CF-BB82-00AA00BDCE0B")) ITimerService; +struct __declspec(uuid("3050F360-98B5-11CF-BB82-00AA00BDCE0B")) ITimer; +struct __declspec(uuid("3050F361-98B5-11CF-BB82-00AA00BDCE0B")) ITimerSink; +struct __declspec(uuid("3050F369-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLBGsound; +struct __declspec(uuid("3050F372-98B5-11CF-BB82-00AA00BDCE0B")) IViewTransition; +struct __declspec(uuid("3050F373-98B5-11CF-BB82-00AA00BDCE0B")) IViewTransitionSite; +struct __declspec(uuid("3050F375-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyleElement; +struct __declspec(uuid("3050F376-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFontNamesCollection; +struct __declspec(uuid("3050F377-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFontSizesCollection; +struct __declspec(uuid("3050F378-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLOptionsHolder; +struct __declspec(uuid("3050F37E-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyleSheetsCollection; +struct __declspec(uuid("3050F383-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLAreasCollection; +struct __declspec(uuid("3050F38A-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLNoShowElement; +struct __declspec(uuid("3050F38C-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLOptionElementFactory; +struct __declspec(uuid("3050F38E-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLImageElementFactory; +struct __declspec(uuid("3050F3CF-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLRuleStyle; +struct __declspec(uuid("3050F3D5-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLStyleFontFace; +struct __declspec(uuid("3050F3E5-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLSpanFlow; +struct __declspec(uuid("3050F3E7-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFieldSetElement; +struct __declspec(uuid("3050F3EA-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLLegendElement; +struct __declspec(uuid("3050F3EC-98B5-11CF-BB82-00AA00BDCE0B")) ICSSFilter; +struct __declspec(uuid("3050F3ED-98B5-11CF-BB82-00AA00BDCE0B")) ICSSFilterSite; +struct __declspec(uuid("3050F3EE-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLFiltersCollection; +struct __declspec(uuid("3050F3F0-98B5-11CF-BB82-00AA00BDCE0B")) ICustomDoc; +struct __declspec(uuid("3050F3F2-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLDatabinding; +struct __declspec(uuid("3050F3F3-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLSpanElement; +struct __declspec(uuid("3050F3FC-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLMimeTypesCollection; +struct __declspec(uuid("3050F3FD-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLPluginsCollection; +struct __declspec(uuid("3050F401-98B5-11CF-BB82-00AA00BDCE0B")) IHTMLOpsProfile; +struct __declspec(uuid("3050f3d7-98b5-11cf-bb82-00aa00bdce0b")) IImgCtx; +struct __declspec(uuid("332C4425-26CB-11D0-B483-00C04FD90119")) IHTMLDocument2; +struct __declspec(uuid("332C4426-26CB-11D0-B483-00C04FD90119")) IHTMLFramesCollection2; +struct __declspec(uuid("332C4427-26CB-11D0-B483-00C04FD90119")) IHTMLWindow2; +struct __declspec(uuid("359F3441-BD4A-11D0-B188-00AA0038C969")) IMLangFontLink; +struct __declspec(uuid("359F3443-BD4A-11D0-B188-00AA0038C969")) IMLangCodePages; +struct __declspec(uuid("3AF24292-0C96-11CE-A0CF-00AA00600AB8")) IViewObjectEx; +struct __declspec(uuid("3C374A41-BAE4-11CF-BF7D-00AA006946EE")) IUrlHistoryStg; +struct __declspec(uuid("3C374A42-BAE4-11CF-BF7D-00AA006946EE")) IEnumSTATURL; +struct __declspec(uuid("3DC39D1D-C030-11D0-B81B-00C04FC9B31F")) IEnumRfc1766; +struct __declspec(uuid("47d2657a-7b27-11d0-8ca9-00a0c92dbfe8")) IDockingWindowFrame; +struct __declspec(uuid("539698A0-CDCA-11CF-A5EB-00AA0047A063")) IActiveScriptSiteInterruptPoll; +struct __declspec(uuid("618736e0-3c3d-11cf-810c-00aa00389b71")) IAccessible; +struct __declspec(uuid("626FC520-A41E-11CF-A731-00A0C9082637")) IHTMLDocument; +struct __declspec(uuid("63CDBCB0-C1B1-11D0-9336-00A0C90DCAA9")) IBindEventHandler; +struct __declspec(uuid("68284faa-6a48-11d0-8c78-00c04fd918b4")) IInputObject; +struct __declspec(uuid("71EE5B20-FB04-11d1-B3A8-00A0C911E8B2")) IActiveScriptParseProcedure2; +struct __declspec(uuid("729FE2F8-1EA8-11D1-8F85-00C04FC2FBE1")) IShellUIHelper; +struct __declspec(uuid("744129E0-CBE5-11CE-8350-444553540000")) FolderItems; +struct __declspec(uuid("79EAC9C2-BAF9-11CE-8C82-00AA004BA90B")) IHlinkSite; +struct __declspec(uuid("79EAC9C3-BAF9-11CE-8C82-00AA004BA90B")) IHlink; +struct __declspec(uuid("79EAC9C4-BAF9-11CE-8C82-00AA004BA90B")) IHlinkTarget; +struct __declspec(uuid("79EAC9C5-BAF9-11CE-8C82-00AA004BA90B")) IHlinkFrame; +struct __declspec(uuid("79EAC9C6-BAF9-11CE-8C82-00AA004BA90B")) IEnumHLITEM; +struct __declspec(uuid("79EAC9C7-BAF9-11CE-8C82-00AA004BA90B")) IHlinkBrowseContext; +struct __declspec(uuid("79EAC9CB-BAF9-11CE-8C82-00AA004BA90B")) IExtensionServices; +struct __declspec(uuid("7d688a70-c613-11d0-999b-00c04fd655e1")) IShellIconOverlay; +struct __declspec(uuid("85BD8E82-0FBA-11D1-90C3-00C04FC2F568")) IChannelMgr; +struct __declspec(uuid("85CB6900-4D95-11CF-960C-0080C7F4EE85")) IShellWindows; +struct __declspec(uuid("88A05C00-F000-11CE-8350-444553540000")) IShellLinkDual; +struct __declspec(uuid("88e39e80-3578-11cf-ae69-08002b2e1262")) IShellView2; +struct __declspec(uuid("89BCB740-6119-101A-BCB7-00DD010655AF")) IFilter; +struct __declspec(uuid("91A565C1-E38F-11D0-94BF-00A0C9055CBF")) IPersistHistory; +struct __declspec(uuid("9BA05970-F6A8-11CF-A442-00A0C90A8F39")) IFolderViewOC; +struct __declspec(uuid("A3CCEDF3-2DE2-11D0-86F4-00A0C913F750")) IImageDecodeFilter; +struct __declspec(uuid("A4C65425-0F82-11D1-90C3-00C04FC2F568")) IEnumChannels; +struct __declspec(uuid("A6EF9860-C720-11D0-9337-00A0C90DCAA9")) IDispatchEx; +struct __declspec(uuid("A6EF9861-C720-11D0-9337-00A0C90DCAA9")) IDispError; +struct __declspec(uuid("A6EF9862-C720-11D0-9337-00A0C90DCAA9")) IVariantChangeType; +struct __declspec(uuid("AA5B6A80-B834-11D0-932F-00A0C90DCAA9")) IActiveScriptParseProcedure; +struct __declspec(uuid("AFA0DC11-C313-11D0-831A-00C04FD5AE38")) IUrlHistoryStg2; +struct __declspec(uuid("B722BCC5-4E68-101B-A2BC-00AA00404770")) IOleDocument; +struct __declspec(uuid("B722BCC6-4E68-101B-A2BC-00AA00404770")) IOleDocumentView; +struct __declspec(uuid("B722BCC7-4E68-101B-A2BC-00AA00404770")) IOleDocumentSite; +struct __declspec(uuid("B722BCC8-4E68-101B-A2BC-00AA00404770")) IEnumOleDocumentViews; +struct __declspec(uuid("B722BCC9-4E68-101B-A2BC-00AA00404770")) IPrint; +struct __declspec(uuid("B722BCCA-4E68-101B-A2BC-00AA00404770")) IContinueCallback; +struct __declspec(uuid("B722BCCB-4E68-101B-A2BC-00AA00404770")) IOleCommandTarget; +struct __declspec(uuid("B8DA6310-E19B-11D0-933C-00A0C90DCAA9")) IActiveScriptStats; +struct __declspec(uuid("BAA342A0-2DED-11D0-86F4-00A0C913F750")) IImageDecodeEventSink; +struct __declspec(uuid("BB1A2AE1-A4F9-11CF-8F20-00805F2CD064")) IActiveScript; +struct __declspec(uuid("BB1A2AE2-A4F9-11CF-8F20-00805F2CD064")) IActiveScriptParse; +struct __declspec(uuid("BC40BEC1-C493-11D0-831B-00C04FD5AE38")) IUrlHistoryNotify; +struct __declspec(uuid("BD3F23C0-D43E-11CF-893B-00AA00BDCE1A")) IDocHostUIHandler; +struct __declspec(uuid("C04D65CE-B70D-11D0-B188-00AA0038C969")) IMLangString; +struct __declspec(uuid("C04D65D0-B70D-11D0-B188-00AA0038C969")) IMLangStringWStr; +struct __declspec(uuid("C04D65D2-B70D-11D0-B188-00AA0038C969")) IMLangStringAStr; +struct __declspec(uuid("C4D244B0-D43E-11CF-893B-00AA00BDCE1A")) IDocHostShowUI; +struct __declspec(uuid("CA04B7E6-0D21-11D1-8CC5-00C04FC2B085")) IObjectIdentity; +struct __declspec(uuid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")) IObjectSafety; +struct __declspec(uuid("D10F6761-83E9-11CF-8F20-00805F2CD064")) IActiveScriptSiteWindow; +struct __declspec(uuid("D24ACD21-BA72-11D0-B188-00AA0038C969")) IMLangStringBufW; +struct __declspec(uuid("D24ACD23-BA72-11D0-B188-00AA0038C969")) IMLangStringBufA; +struct __declspec(uuid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E")) IWebBrowser2; +struct __declspec(uuid("D66D6F98-CDAA-11D0-B822-00C04FC9B31F")) IMLangConvertCharset; +struct __declspec(uuid("D8F015C0-C278-11CE-A49E-444553540000")) IShellDispatch; +struct __declspec(uuid("D9E89500-30FA-11D0-B724-00AA006C1A01")) IMapMIMEToCLSID; +struct __declspec(uuid("DB01A1E3-A42B-11CF-8F20-00805F2CD064")) IActiveScriptSite; +struct __declspec(uuid("E0E270C0-C0BE-11D0-8FE4-00A0C90A6341")) OLEDBSimpleProvider; +struct __declspec(uuid("E0E270C1-C0BE-11D0-8FE4-00A0C90A6341")) OLEDBSimpleProviderListener; +struct __declspec(uuid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")) IShellFolderViewDual; +struct __declspec(uuid("EAB22AC1-30C1-11CF-A7EB-0000C05BAE0B")) IWebBrowser; +struct __declspec(uuid("EAE1BA61-A4ED-11CF-8F20-00805F2CD064")) IActiveScriptError; +struct __declspec(uuid("F5BE2EE1-BFD7-11D0-B188-00AA0038C969")) IMLangLineBreakConsole; +struct __declspec(uuid("F77459A0-BF9A-11CF-BA4E-00C04FD70816")) IMimeInfo; +struct __declspec(uuid("FAC32C80-CBE4-11CE-8350-444553540000")) FolderItem; +struct __declspec(uuid("FECEAAA2-8405-11CF-8BA1-00AA00476DA6")) IOmHistory; +struct __declspec(uuid("FECEAAA5-8405-11CF-8BA1-00AA00476DA6")) IOmNavigator; +struct __declspec(uuid("ac60f6a0-0fd9-11d0-99cb-00c04fd64497")) IURLSearchHook; +struct __declspec(uuid("bcfce0a0-ec17-11d0-8d10-00a0c90f2719")) IContextMenu3; +struct __declspec(uuid("cabb0da0-da57-11cf-9974-0020afd79762")) IUniformResourceLocatorW; +struct __declspec(uuid("eb0fe172-1a3a-11d0-89b3-00a0c90a90ac")) IDeskBand; +struct __declspec(uuid("f1db8392-7331-11d0-8c99-00a0c92dbfe8")) IInputObjectSite; +struct __declspec(uuid("f490eb00-1240-11d1-9888-006097deacf9")) IActiveDesktop; +struct __declspec(uuid("fbf23b80-e3f0-101b-8488-00aa003e56f8")) IUniformResourceLocatorA; + +// CoClasses: + +class __declspec(uuid("00000017-0000-0000-c000-000000000046")) StdMarshal; +class __declspec(uuid("0000001b-0000-0000-c000-000000000046")) IdentityUnmarshal; +class __declspec(uuid("0000001c-0000-0000-c000-000000000046")) InProcFreeMarshaler; +class __declspec(uuid("0000030c-0000-0000-c000-000000000046")) PSGenObject; +class __declspec(uuid("0000030d-0000-0000-c000-000000000046")) PSClientSite; +class __declspec(uuid("0000030e-0000-0000-c000-000000000046")) PSClassObject; +class __declspec(uuid("0000030f-0000-0000-c000-000000000046")) PSInPlaceActive; +class __declspec(uuid("00000310-0000-0000-c000-000000000046")) PSInPlaceFrame; +class __declspec(uuid("00000311-0000-0000-c000-000000000046")) PSDragDrop; +class __declspec(uuid("00000312-0000-0000-c000-000000000046")) PSBindCtx; +class __declspec(uuid("00000313-0000-0000-c000-000000000046")) PSEnumerators; +class __declspec(uuid("00000315-0000-0000-c000-000000000046")) Picture_Metafile; +class __declspec(uuid("00000315-0000-0000-c000-000000000046")) StaticMetafile; +class __declspec(uuid("00000316-0000-0000-c000-000000000046")) Picture_Dib; +class __declspec(uuid("00000316-0000-0000-c000-000000000046")) StaticDib; +class __declspec(uuid("00000319-0000-0000-c000-000000000046")) Picture_EnhMetafile; +class __declspec(uuid("0000031d-0000-0000-c000-000000000046")) DCOMAccessControl; +class __declspec(uuid("00021400-0000-0000-c000-000000000046")) ShellDesktop; +class __declspec(uuid("00021401-0000-0000-c000-000000000046")) ShellLink; +class __declspec(uuid("0002DF01-0000-0000-C000-000000000046")) InternetExplorer; +class __declspec(uuid("0002e005-0000-0000-c000-000000000046")) StdComponentCategoriesMgr; +class __declspec(uuid("08165ea0-e946-11cf-9c87-00aa005127ed")) WebCrawlerAgent; +class __declspec(uuid("0A89A860-D7B1-11CE-8350-444553540000")) ShellDispatchInproc; +class __declspec(uuid("0D04D285-6BEC-11CF-8B97-00AA00476DA6")) OldHTMLFormElement; +class __declspec(uuid("0be35200-8f91-11ce-9de3-00aa004bb851")) CFontPropPage; +class __declspec(uuid("0be35201-8f91-11ce-9de3-00aa004bb851")) CColorPropPage; +class __declspec(uuid("0be35202-8f91-11ce-9de3-00aa004bb851")) CPicturePropPage; +class __declspec(uuid("0be35203-8f91-11ce-9de3-00aa004bb851")) StdFont; +class __declspec(uuid("0be35204-8f91-11ce-9de3-00aa004bb851")) StdPicture; +class __declspec(uuid("11219420-1768-11D1-95BE-00609797EA4F")) ShellLinkObject; +class __declspec(uuid("163BB1E1-6E00-11CF-837A-48DC04C10000")) HTMLLocation; +class __declspec(uuid("1820FED0-473E-11D0-A96C-00C04FD705A2")) WebViewFolderContents; +class __declspec(uuid("25336920-03F9-11CF-8FD0-00AA00686F13")) HTMLDocument; +class __declspec(uuid("25336921-03f9-11cf-8fd0-00aa00686f13")) HTMLPluginDocument; +class __declspec(uuid("275C23E2-3747-11D0-9FEA-00AA003F8646")) CMultiLanguage; +class __declspec(uuid("3050F241-98B5-11CF-BB82-00AA00BDCE0B")) HTMLImg; +class __declspec(uuid("3050F245-98B5-11CF-BB82-00AA00BDCE0B")) HTMLSelectElement; +class __declspec(uuid("3050F246-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTableCell; +class __declspec(uuid("3050F248-98B5-11CF-BB82-00AA00BDCE0B")) HTMLAnchorElement; +class __declspec(uuid("3050F249-98B5-11CF-BB82-00AA00BDCE0B")) HTMLDivPosition; +class __declspec(uuid("3050F24A-98B5-11CF-BB82-00AA00BDCE0B")) HTMLBody; +class __declspec(uuid("3050F24D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLOptionElement; +class __declspec(uuid("3050F24E-98B5-11CF-BB82-00AA00BDCE0B")) HTMLObjectElement; +class __declspec(uuid("3050F251-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFormElement; +class __declspec(uuid("3050F252-98B5-11CF-BB82-00AA00BDCE0B")) HTMLHRElement; +class __declspec(uuid("3050F25D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLEmbed; +class __declspec(uuid("3050F268-98B5-11CF-BB82-00AA00BDCE0B")) HTMLUnknownElement; +class __declspec(uuid("3050F269-98B5-11CF-BB82-00AA00BDCE0B")) HTMLUListElement; +class __declspec(uuid("3050F26A-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTextElement; +class __declspec(uuid("3050F26B-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTable; +class __declspec(uuid("3050F26C-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTableCol; +class __declspec(uuid("3050F26D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTableRow; +class __declspec(uuid("3050F26E-98B5-11CF-BB82-00AA00BDCE0B")) HTMLPhraseElement; +class __declspec(uuid("3050F26F-98B5-11CF-BB82-00AA00BDCE0B")) HTMLParaElement; +class __declspec(uuid("3050F270-98B5-11CF-BB82-00AA00BDCE0B")) HTMLOListElement; +class __declspec(uuid("3050F271-98B5-11CF-BB82-00AA00BDCE0B")) HTMLMapElement; +class __declspec(uuid("3050F272-98B5-11CF-BB82-00AA00BDCE0B")) HTMLListElement; +class __declspec(uuid("3050F273-98B5-11CF-BB82-00AA00BDCE0B")) HTMLLIElement; +class __declspec(uuid("3050F275-98B5-11CF-BB82-00AA00BDCE0B")) HTMLMetaElement; +class __declspec(uuid("3050F276-98B5-11CF-BB82-00AA00BDCE0B")) HTMLBaseElement; +class __declspec(uuid("3050F277-98B5-11CF-BB82-00AA00BDCE0B")) HTMLLinkElement; +class __declspec(uuid("3050F278-98B5-11CF-BB82-00AA00BDCE0B")) HTMLIsIndexElement; +class __declspec(uuid("3050F279-98B5-11CF-BB82-00AA00BDCE0B")) HTMLNextIdElement; +class __declspec(uuid("3050F27A-98B5-11CF-BB82-00AA00BDCE0B")) HTMLHeaderElement; +class __declspec(uuid("3050F27B-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFontElement; +class __declspec(uuid("3050F27C-98B5-11CF-BB82-00AA00BDCE0B")) HTMLDTElement; +class __declspec(uuid("3050F27D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLDListElement; +class __declspec(uuid("3050F27E-98B5-11CF-BB82-00AA00BDCE0B")) HTMLDivElement; +class __declspec(uuid("3050F27F-98B5-11CF-BB82-00AA00BDCE0B")) HTMLDDElement; +class __declspec(uuid("3050F280-98B5-11CF-BB82-00AA00BDCE0B")) HTMLBRElement; +class __declspec(uuid("3050F281-98B5-11CF-BB82-00AA00BDCE0B")) HTMLBlockElement; +class __declspec(uuid("3050F282-98B5-11CF-BB82-00AA00BDCE0B")) HTMLBaseFontElement; +class __declspec(uuid("3050F283-98B5-11CF-BB82-00AA00BDCE0B")) HTMLAreaElement; +class __declspec(uuid("3050F284-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTitleElement; +class __declspec(uuid("3050F285-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyle; +class __declspec(uuid("3050F28A-98B5-11CF-BB82-00AA00BDCE0B")) HTMLDialog; +class __declspec(uuid("3050F28C-98B5-11CF-BB82-00AA00BDCE0B")) HTMLScriptElement; +class __declspec(uuid("3050F2AB-98B5-11CF-BB82-00AA00BDCE0B")) HTMLInputTextElement; +class __declspec(uuid("3050F2AC-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTextAreaElement; +class __declspec(uuid("3050F2AE-98B5-11CF-BB82-00AA00BDCE0B")) HTMLInputFileElement; +class __declspec(uuid("3050F2B4-98B5-11CF-BB82-00AA00BDCE0B")) HTMLInputButtonElement; +class __declspec(uuid("3050F2B9-98B5-11CF-BB82-00AA00BDCE0B")) HTMLMarqueeElement; +class __declspec(uuid("3050F2BE-98B5-11CF-BB82-00AA00BDCE0B")) HTMLOptionButtonElement; +class __declspec(uuid("3050F2C4-98B5-11CF-BB82-00AA00BDCE0B")) HTMLInputImage; +class __declspec(uuid("3050F2C6-98B5-11CF-BB82-00AA00BDCE0B")) HTMLButtonElement; +class __declspec(uuid("3050F2E4-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyleSheet; +class __declspec(uuid("3050F2E9-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTableSection; +class __declspec(uuid("3050F2EC-98B5-11CF-BB82-00AA00BDCE0B")) HTMLTableCaption; +class __declspec(uuid("3050F312-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFrameBase; +class __declspec(uuid("3050F314-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFrameElement; +class __declspec(uuid("3050F316-98B5-11CF-BB82-00AA00BDCE0B")) HTMLIFrame; +class __declspec(uuid("3050F317-98B5-11CF-BB82-00AA00BDCE0B")) HTMLCommentElement; +class __declspec(uuid("3050F31A-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFrameSetSite; +class __declspec(uuid("3050F32B-98B5-11CF-BB82-00AA00BDCE0B")) HTMLLabelElement; +class __declspec(uuid("3050F35D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLScreen; +class __declspec(uuid("3050F370-98B5-11CF-BB82-00AA00BDCE0B")) HTMLBGsound; +class __declspec(uuid("3050F37D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyleElement; +class __declspec(uuid("3050F37F-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyleSheetsCollection; +class __declspec(uuid("3050F38B-98B5-11CF-BB82-00AA00BDCE0B")) HTMLNoShowElement; +class __declspec(uuid("3050F38D-98B5-11CF-BB82-00AA00BDCE0B")) HTMLOptionElementFactory; +class __declspec(uuid("3050F38F-98B5-11CF-BB82-00AA00BDCE0B")) HTMLImageElementFactory; +class __declspec(uuid("3050F391-98B5-11CF-BB82-00AA00BDCE0B")) HTMLWindowProxy; +class __declspec(uuid("3050F3CD-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyleSheetRulesCollection; +class __declspec(uuid("3050F3CE-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyleSheetRule; +class __declspec(uuid("3050F3D0-98B5-11CF-BB82-00AA00BDCE0B")) HTMLRuleStyle; +class __declspec(uuid("3050F3D4-98B5-11CF-BB82-00AA00BDCE0B")) HTMLStyleFontFace; +class __declspec(uuid("3050F3E6-98B5-11CF-BB82-00AA00BDCE0B")) HTMLSpanFlow; +class __declspec(uuid("3050F3E8-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFieldSetElement; +class __declspec(uuid("3050F3E9-98B5-11CF-BB82-00AA00BDCE0B")) HTMLLegendElement; +class __declspec(uuid("3050F3EF-98B5-11CF-BB82-00AA00BDCE0B")) HTMLFiltersCollection; +class __declspec(uuid("3050F3F5-98B4-11CF-BB82-00AA00BDCE0B")) HTMLSpanElement; +class __declspec(uuid("3050F3FE-98B5-11CF-BB82-00AA00BDCE0B")) CMimeTypes; +class __declspec(uuid("3050F3FF-98B5-11CF-BB82-00AA00BDCE0B")) CPlugins; +class __declspec(uuid("3050F402-98B5-11CF-BB82-00AA00BDCE0B")) COpsProfile; +class __declspec(uuid("3050f3d9-98b5-11cf-bb82-00aa00bdce0b")) MHTMLDocument; +class __declspec(uuid("32b533bb-edae-11d0-bd5a-00aa00b92af1")) ClassInstallFilter; +class __declspec(uuid("3c374a40-bae4-11cf-bf7d-00aa006946ee")) CUrlHistory; +class __declspec(uuid("3dd53d40-7b8b-11d0-b013-00aa0059ce02")) CdlProtocol; +class __declspec(uuid("54c37cd0-d944-11d0-a9f4-006097942311")) StdEncodingFilterFac; +class __declspec(uuid("56fdf344-fd6d-11d0-958a-006097c9a090")) TaskbarList; +class __declspec(uuid("62112AA1-EBE4-11CF-A5FB-0020AFE7292D")) ShellFolderView; +class __declspec(uuid("63b51f81-c868-11d0-999c-00c04fd655e1")) CFSIconOverlayManager; +class __declspec(uuid("64AB4BB7-111E-11D1-8F79-00C04FC2FBE1")) ShellUIHelper; +class __declspec(uuid("75048700-ef1f-11d0-9888-006097deacf9")) ActiveDesktop; +class __declspec(uuid("79eac9d0-baf9-11ce-8c82-00aa004ba90b")) StdHlink; +class __declspec(uuid("79eac9d1-baf9-11ce-8c82-00aa004ba90b")) StdHlinkBrowseContext; +class __declspec(uuid("79eac9e0-baf9-11ce-8c82-00aa004ba90b")) StdURLMoniker; +class __declspec(uuid("79eac9e1-baf9-11ce-8c82-00aa004ba90b")) StdURLProtocol; +class __declspec(uuid("79eac9e2-baf9-11ce-8c82-00aa004ba90b")) HttpProtocol; +class __declspec(uuid("79eac9e3-baf9-11ce-8c82-00aa004ba90b")) FtpProtocol; +class __declspec(uuid("79eac9e4-baf9-11ce-8c82-00aa004ba90b")) GopherProtocol; +class __declspec(uuid("79eac9e5-baf9-11ce-8c82-00aa004ba90b")) HttpSProtocol; +class __declspec(uuid("79eac9e6-baf9-11ce-8c82-00aa004ba90b")) MkProtocol; +class __declspec(uuid("79eac9e7-baf9-11ce-8c82-00aa004ba90b")) FileProtocol; +class __declspec(uuid("79eac9f2-baf9-11ce-8c82-00aa004ba90b")) UrlMkBindCtx; +class __declspec(uuid("7b8a2d94-0ac9-11d1-896c-00c04fb6bfc4")) InternetSecurityManager; +class __declspec(uuid("7b8a2d95-0ac9-11d1-896c-00c04fb6bfc4")) InternetZoneManager; +class __declspec(uuid("7d559c10-9fe9-11d0-93f7-00aa0059ce02")) CDLAgent; +class __declspec(uuid("7d688a77-c613-11d0-999b-00c04fd655e1")) OverlayIdentifier_SlowFile; +class __declspec(uuid("7ebdaae0-8120-11cf-899f-00aa00688b10")) StockFontPage; +class __declspec(uuid("7ebdaae1-8120-11cf-899f-00aa00688b10")) StockColorPage; +class __declspec(uuid("7ebdaae2-8120-11cf-899f-00aa00688b10")) StockPicturePage; +class __declspec(uuid("8856F961-340A-11D0-A96B-00C04FD705A2")) WebBrowser; +class __declspec(uuid("8f6b0360-b80d-11d0-a9b3-006097942311")) DeCompMimeFilter; +class __declspec(uuid("9BA05971-F6A8-11CF-A442-00A0C90A8F39")) ShellFolderViewOC; +class __declspec(uuid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")) ShellWindows; +class __declspec(uuid("ABBE31D0-6DAE-11D0-BECA-00C04FD940BE")) SubscriptionMgr; +class __declspec(uuid("B3CDAE90-D170-11D0-802B-00C04FD75D13")) ChannelMgr; +class __declspec(uuid("C04D65CF-B70D-11D0-B188-00AA0038C969")) CMLangString; +class __declspec(uuid("D48A6EC6-6A4A-11CF-94A7-444553540000")) HTMLWindow2; +class __declspec(uuid("D48A6EC9-6A4A-11CF-94A7-444553540000")) OldHTMLDocument; +class __declspec(uuid("D66D6F99-CDAA-11D0-B822-00C04FC9B31F")) CMLangConvertCharset; +class __declspec(uuid("EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B")) WebBrowser_V1; +class __declspec(uuid("FECEAAA3-8405-11CF-8BA1-00AA00476DA6")) HTMLHistory; +class __declspec(uuid("FECEAAA6-8405-11CF-8BA1-00AA00476DA6")) HTMLNavigator; +class __declspec(uuid("b15b8dc0-c7e1-11d0-8680-00aa00bdcb71")) SoftDistExt; +class __declspec(uuid("cfbfae00-17a6-11d0-99cb-00c04fd64497")) CURLSearchHook; +class __declspec(uuid("e3a8bde6-abce-11d0-bc4b-00c04fd929db")) ChannelAgent; +class __declspec(uuid("fb8f0821-0164-101b-84ed-08002b2ec713")) PersistPropset; +class __declspec(uuid("fb8f0822-0164-101b-84ed-08002b2ec713")) ConvertVBX; +class __declspec(uuid("fbf23b40-e3f0-101b-8488-00aa003e56f8")) InternetShortcut; diff --git a/tools/Debugging Tools for Windows/winext/manifest/version.h b/tools/Debugging Tools for Windows/winext/manifest/version.h new file mode 100644 index 0000000000..10afaad37b --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/version.h @@ -0,0 +1,87 @@ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// Versioning Functions +// +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +category Version: +module VERSION.DLL: + + +/* +export VerFindFileA; +export VerFindFileW; +export VerInstallFileA; +export VerInstallFileW; +export VerQueryValueA; +export VerQueryValueIndexA; +export VerQueryValueIndexW; +export VerQueryValueW; +*/ + +DWORD +GetFileVersionInfoSizeA( + LPSTR lptstrFilename, /* Filename of version stamped file */ + [out] LPDWORD lpdwHandle + ); /* Information for use by GetFileVersionInfo */ +/* Returns size of version info in bytes */ +DWORD +GetFileVersionInfoSizeW( + LPWSTR lptstrFilename, /* Filename of version stamped file */ + [out] LPDWORD lpdwHandle + ); /* Information for use by GetFileVersionInfo */ + +/* Read version info into buffer */ +FailOnFalse +GetFileVersionInfoA( + LPSTR lptstrFilename, /* Filename of version stamped file */ + DWORD dwHandle, /* Information from GetFileVersionSize */ + DWORD dwLen, /* Length of buffer for info */ + [out] LPVOID lpData + ); /* Buffer to place the data structure */ +/* Read version info into buffer */ +FailOnFalse +GetFileVersionInfoW( + LPWSTR lptstrFilename, /* Filename of version stamped file */ + DWORD dwHandle, /* Information from GetFileVersionSize */ + DWORD dwLen, /* Length of buffer for info */ + [out] LPVOID lpData + ); /* Buffer to place the data structure */ + + +module KERNEL32.DLL: + +value LONG PlatformId +{ +#define VER_PLATFORM_WIN32s 0 +#define VER_PLATFORM_WIN32_WINDOWS 1 +#define VER_PLATFORM_WIN32_NT 2 +}; + +typedef struct _OSVERSIONINFOA{ + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + PlatformId dwPlatformId; + CHAR szCSDVersion[ 128 ]; +} OSVERSIONINFOA, *LPOSVERSIONINFOA; + +typedef struct _OSVERSIONINFOW{ + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + PlatformId dwPlatformId; + WCHAR szCSDVersion[ 128 ]; +} OSVERSIONINFOW, *LPOSVERSIONINFOW; + +DWORD GetVersion(); + +FailOnFalse [gle] GetVersionExA( + [out] LPOSVERSIONINFOA lpVersionInfo + ); + +FailOnFalse [gle] GetVersionExW( + [out] LPOSVERSIONINFOW lpVersionInfo + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/winerror.h b/tools/Debugging Tools for Windows/winext/manifest/winerror.h new file mode 100644 index 0000000000..db1605c427 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/winerror.h @@ -0,0 +1,5833 @@ +value LONG WinError +{ +#define ERROR_SUCCESS 0L +#define ERROR_INVALID_FUNCTION 1L [fail] +#define ERROR_FILE_NOT_FOUND 2L [fail] +#define ERROR_PATH_NOT_FOUND 3L [fail] +#define ERROR_TOO_MANY_OPEN_FILES 4L [fail] +#define ERROR_ACCESS_DENIED 5L [fail] +#define ERROR_INVALID_HANDLE 6L [fail] +#define ERROR_ARENA_TRASHED 7L [fail] +#define ERROR_NOT_ENOUGH_MEMORY 8L [fail] +#define ERROR_INVALID_BLOCK 9L [fail] +#define ERROR_BAD_ENVIRONMENT 10L [fail] +#define ERROR_BAD_FORMAT 11L [fail] +#define ERROR_INVALID_ACCESS 12L [fail] +#define ERROR_INVALID_DATA 13L [fail] +#define ERROR_OUTOFMEMORY 14L [fail] +#define ERROR_INVALID_DRIVE 15L [fail] +#define ERROR_CURRENT_DIRECTORY 16L [fail] +#define ERROR_NOT_SAME_DEVICE 17L [fail] +#define ERROR_NO_MORE_FILES 18L [fail] +#define ERROR_WRITE_PROTECT 19L [fail] +#define ERROR_BAD_UNIT 20L [fail] +#define ERROR_NOT_READY 21L [fail] +#define ERROR_BAD_COMMAND 22L [fail] +#define ERROR_CRC 23L [fail] +#define ERROR_BAD_LENGTH 24L [fail] +#define ERROR_SEEK 25L [fail] +#define ERROR_NOT_DOS_DISK 26L [fail] +#define ERROR_SECTOR_NOT_FOUND 27L [fail] +#define ERROR_OUT_OF_PAPER 28L [fail] +#define ERROR_WRITE_FAULT 29L [fail] +#define ERROR_READ_FAULT 30L [fail] +#define ERROR_GEN_FAILURE 31L [fail] +#define ERROR_SHARING_VIOLATION 32L [fail] +#define ERROR_LOCK_VIOLATION 33L [fail] +#define ERROR_WRONG_DISK 34L [fail] +#define ERROR_SHARING_BUFFER_EXCEEDED 36L [fail] +#define ERROR_HANDLE_EOF 38L [fail] +#define ERROR_HANDLE_DISK_FULL 39L [fail] +#define ERROR_NOT_SUPPORTED 50L [fail] +#define ERROR_REM_NOT_LIST 51L [fail] +#define ERROR_DUP_NAME 52L [fail] +#define ERROR_BAD_NETPATH 53L [fail] +#define ERROR_NETWORK_BUSY 54L [fail] +#define ERROR_DEV_NOT_EXIST 55L [fail] +#define ERROR_TOO_MANY_CMDS 56L [fail] +#define ERROR_ADAP_HDW_ERR 57L [fail] +#define ERROR_BAD_NET_RESP 58L [fail] +#define ERROR_UNEXP_NET_ERR 59L [fail] +#define ERROR_BAD_REM_ADAP 60L [fail] +#define ERROR_PRINTQ_FULL 61L [fail] +#define ERROR_NO_SPOOL_SPACE 62L [fail] +#define ERROR_PRINT_CANCELLED 63L [fail] +#define ERROR_NETNAME_DELETED 64L [fail] +#define ERROR_NETWORK_ACCESS_DENIED 65L [fail] +#define ERROR_BAD_DEV_TYPE 66L [fail] +#define ERROR_BAD_NET_NAME 67L [fail] +#define ERROR_TOO_MANY_NAMES 68L [fail] +#define ERROR_TOO_MANY_SESS 69L [fail] +#define ERROR_SHARING_PAUSED 70L [fail] +#define ERROR_REQ_NOT_ACCEP 71L [fail] +#define ERROR_REDIR_PAUSED 72L [fail] +#define ERROR_FILE_EXISTS 80L [fail] +#define ERROR_CANNOT_MAKE 82L [fail] +#define ERROR_FAIL_I24 83L [fail] +#define ERROR_OUT_OF_STRUCTURES 84L [fail] +#define ERROR_ALREADY_ASSIGNED 85L [fail] +#define ERROR_INVALID_PASSWORD 86L [fail] +#define ERROR_INVALID_PARAMETER 87L [fail] +#define ERROR_NET_WRITE_FAULT 88L [fail] +#define ERROR_NO_PROC_SLOTS 89L [fail] +#define ERROR_TOO_MANY_SEMAPHORES 100L [fail] +#define ERROR_EXCL_SEM_ALREADY_OWNED 101L [fail] +#define ERROR_SEM_IS_SET 102L [fail] +#define ERROR_TOO_MANY_SEM_REQUESTS 103L [fail] +#define ERROR_INVALID_AT_INTERRUPT_TIME 104L [fail] +#define ERROR_SEM_OWNER_DIED 105L [fail] +#define ERROR_SEM_USER_LIMIT 106L [fail] +#define ERROR_DISK_CHANGE 107L [fail] +#define ERROR_DRIVE_LOCKED 108L [fail] +#define ERROR_BROKEN_PIPE 109L [fail] +#define ERROR_OPEN_FAILED 110L [fail] +#define ERROR_BUFFER_OVERFLOW 111L [fail] +#define ERROR_DISK_FULL 112L [fail] +#define ERROR_NO_MORE_SEARCH_HANDLES 113L [fail] +#define ERROR_INVALID_TARGET_HANDLE 114L [fail] +#define ERROR_INVALID_CATEGORY 117L [fail] +#define ERROR_INVALID_VERIFY_SWITCH 118L [fail] +#define ERROR_BAD_DRIVER_LEVEL 119L [fail] +#define ERROR_CALL_NOT_IMPLEMENTED 120L [fail] +#define ERROR_SEM_TIMEOUT 121L [fail] +#define ERROR_INSUFFICIENT_BUFFER 122L [fail] +#define ERROR_INVALID_NAME 123L [fail] +#define ERROR_INVALID_LEVEL 124L [fail] +#define ERROR_NO_VOLUME_LABEL 125L [fail] +#define ERROR_MOD_NOT_FOUND 126L [fail] +#define ERROR_PROC_NOT_FOUND 127L [fail] +#define ERROR_WAIT_NO_CHILDREN 128L [fail] +#define ERROR_CHILD_NOT_COMPLETE 129L [fail] +#define ERROR_DIRECT_ACCESS_HANDLE 130L [fail] +#define ERROR_NEGATIVE_SEEK 131L [fail] +#define ERROR_SEEK_ON_DEVICE 132L [fail] +#define ERROR_IS_JOIN_TARGET 133L [fail] +#define ERROR_IS_JOINED 134L [fail] +#define ERROR_IS_SUBSTED 135L [fail] +#define ERROR_NOT_JOINED 136L [fail] +#define ERROR_NOT_SUBSTED 137L [fail] +#define ERROR_JOIN_TO_JOIN 138L [fail] +#define ERROR_SUBST_TO_SUBST 139L [fail] +#define ERROR_JOIN_TO_SUBST 140L [fail] +#define ERROR_SUBST_TO_JOIN 141L [fail] +#define ERROR_BUSY_DRIVE 142L [fail] +#define ERROR_SAME_DRIVE 143L [fail] +#define ERROR_DIR_NOT_ROOT 144L [fail] +#define ERROR_DIR_NOT_EMPTY 145L [fail] +#define ERROR_IS_SUBST_PATH 146L [fail] +#define ERROR_IS_JOIN_PATH 147L [fail] +#define ERROR_PATH_BUSY 148L [fail] +#define ERROR_IS_SUBST_TARGET 149L [fail] +#define ERROR_SYSTEM_TRACE 150L [fail] +#define ERROR_INVALID_EVENT_COUNT 151L [fail] +#define ERROR_TOO_MANY_MUXWAITERS 152L [fail] +#define ERROR_INVALID_LIST_FORMAT 153L [fail] +#define ERROR_LABEL_TOO_LONG 154L [fail] +#define ERROR_TOO_MANY_TCBS 155L [fail] +#define ERROR_SIGNAL_REFUSED 156L [fail] +#define ERROR_DISCARDED 157L [fail] +#define ERROR_NOT_LOCKED 158L [fail] +#define ERROR_BAD_THREADID_ADDR 159L [fail] +#define ERROR_BAD_ARGUMENTS 160L [fail] +#define ERROR_BAD_PATHNAME 161L [fail] +#define ERROR_SIGNAL_PENDING 162L [fail] +#define ERROR_MAX_THRDS_REACHED 164L [fail] +#define ERROR_LOCK_FAILED 167L [fail] +#define ERROR_BUSY 170L [fail] +#define ERROR_CANCEL_VIOLATION 173L [fail] +#define ERROR_ATOMIC_LOCKS_NOT_SUPPORTED 174L [fail] +#define ERROR_INVALID_SEGMENT_NUMBER 180L [fail] +#define ERROR_INVALID_ORDINAL 182L [fail] +#define ERROR_ALREADY_EXISTS 183L [fail] +#define ERROR_INVALID_FLAG_NUMBER 186L [fail] +#define ERROR_SEM_NOT_FOUND 187L [fail] +#define ERROR_INVALID_STARTING_CODESEG 188L [fail] +#define ERROR_INVALID_STACKSEG 189L [fail] +#define ERROR_INVALID_MODULETYPE 190L [fail] +#define ERROR_INVALID_EXE_SIGNATURE 191L [fail] +#define ERROR_EXE_MARKED_INVALID 192L [fail] +#define ERROR_BAD_EXE_FORMAT 193L [fail] +#define ERROR_ITERATED_DATA_EXCEEDS_64k 194L [fail] +#define ERROR_INVALID_MINALLOCSIZE 195L [fail] +#define ERROR_DYNLINK_FROM_INVALID_RING 196L [fail] +#define ERROR_IOPL_NOT_ENABLED 197L [fail] +#define ERROR_INVALID_SEGDPL 198L [fail] +#define ERROR_AUTODATASEG_EXCEEDS_64k 199L [fail] +#define ERROR_RING2SEG_MUST_BE_MOVABLE 200L [fail] +#define ERROR_RELOC_CHAIN_XEEDS_SEGLIM 201L [fail] +#define ERROR_INFLOOP_IN_RELOC_CHAIN 202L [fail] +#define ERROR_ENVVAR_NOT_FOUND 203L [fail] +#define ERROR_NO_SIGNAL_SENT 205L [fail] +#define ERROR_FILENAME_EXCED_RANGE 206L [fail] +#define ERROR_RING2_STACK_IN_USE 207L [fail] +#define ERROR_META_EXPANSION_TOO_LONG 208L [fail] +#define ERROR_INVALID_SIGNAL_NUMBER 209L [fail] +#define ERROR_THREAD_1_INACTIVE 210L [fail] +#define ERROR_LOCKED 212L [fail] +#define ERROR_TOO_MANY_MODULES 214L [fail] +#define ERROR_NESTING_NOT_ALLOWED 215L [fail] +#define ERROR_EXE_MACHINE_TYPE_MISMATCH 216L [fail] +#define ERROR_BAD_PIPE 230L [fail] +#define ERROR_PIPE_BUSY 231L [fail] +#define ERROR_NO_DATA 232L [fail] +#define ERROR_PIPE_NOT_CONNECTED 233L [fail] +#define ERROR_MORE_DATA 234L [fail] +#define ERROR_VC_DISCONNECTED 240L [fail] +#define ERROR_INVALID_EA_NAME 254L [fail] +#define ERROR_EA_LIST_INCONSISTENT 255L [fail] +#define ERROR_NO_MORE_ITEMS 259L [fail] +#define ERROR_CANNOT_COPY 266L [fail] +#define ERROR_DIRECTORY 267L [fail] +#define ERROR_EAS_DIDNT_FIT 275L [fail] +#define ERROR_EA_FILE_CORRUPT 276L [fail] +#define ERROR_EA_TABLE_FULL 277L [fail] +#define ERROR_INVALID_EA_HANDLE 278L [fail] +#define ERROR_EAS_NOT_SUPPORTED 282L [fail] +#define ERROR_NOT_OWNER 288L [fail] +#define ERROR_TOO_MANY_POSTS 298L [fail] +#define ERROR_PARTIAL_COPY 299L [fail] +#define ERROR_OPLOCK_NOT_GRANTED 300L [fail] +#define ERROR_INVALID_OPLOCK_PROTOCOL 301L [fail] +#define ERROR_MR_MID_NOT_FOUND 317L [fail] +#define ERROR_INVALID_ADDRESS 487L [fail] +#define ERROR_ARITHMETIC_OVERFLOW 534L [fail] +#define ERROR_PIPE_CONNECTED 535L [fail] +#define ERROR_PIPE_LISTENING 536L [fail] +#define ERROR_EA_ACCESS_DENIED 994L [fail] +#define ERROR_OPERATION_ABORTED 995L [fail] +#define ERROR_IO_INCOMPLETE 996L [fail] +#define ERROR_IO_PENDING 997L [fail] +#define ERROR_NOACCESS 998L [fail] +#define ERROR_SWAPERROR 999L [fail] +#define ERROR_STACK_OVERFLOW 1001L [fail] +#define ERROR_INVALID_MESSAGE 1002L [fail] +#define ERROR_CAN_NOT_COMPLETE 1003L [fail] +#define ERROR_INVALID_FLAGS 1004L [fail] +#define ERROR_UNRECOGNIZED_VOLUME 1005L [fail] +#define ERROR_FILE_INVALID 1006L [fail] +#define ERROR_FULLSCREEN_MODE 1007L [fail] +#define ERROR_NO_TOKEN 1008L [fail] +#define ERROR_BADDB 1009L [fail] +#define ERROR_BADKEY 1010L [fail] +#define ERROR_CANTOPEN 1011L [fail] +#define ERROR_CANTREAD 1012L [fail] +#define ERROR_CANTWRITE 1013L [fail] +#define ERROR_REGISTRY_RECOVERED 1014L [fail] +#define ERROR_REGISTRY_CORRUPT 1015L [fail] +#define ERROR_REGISTRY_IO_FAILED 1016L [fail] +#define ERROR_NOT_REGISTRY_FILE 1017L [fail] +#define ERROR_KEY_DELETED 1018L [fail] +#define ERROR_NO_LOG_SPACE 1019L [fail] +#define ERROR_KEY_HAS_CHILDREN 1020L [fail] +#define ERROR_CHILD_MUST_BE_VOLATILE 1021L [fail] +#define ERROR_NOTIFY_ENUM_DIR 1022L [fail] +#define ERROR_DEPENDENT_SERVICES_RUNNING 1051L [fail] +#define ERROR_INVALID_SERVICE_CONTROL 1052L [fail] +#define ERROR_SERVICE_REQUEST_TIMEOUT 1053L [fail] +#define ERROR_SERVICE_NO_THREAD 1054L [fail] +#define ERROR_SERVICE_DATABASE_LOCKED 1055L [fail] +#define ERROR_SERVICE_ALREADY_RUNNING 1056L [fail] +#define ERROR_INVALID_SERVICE_ACCOUNT 1057L [fail] +#define ERROR_SERVICE_DISABLED 1058L [fail] +#define ERROR_CIRCULAR_DEPENDENCY 1059L [fail] +#define ERROR_SERVICE_DOES_NOT_EXIST 1060L [fail] +#define ERROR_SERVICE_CANNOT_ACCEPT_CTRL 1061L [fail] +#define ERROR_SERVICE_NOT_ACTIVE 1062L [fail] +#define ERROR_FAILED_SERVICE_CONTROLLER_CONNECT 1063L [fail] +#define ERROR_EXCEPTION_IN_SERVICE 1064L [fail] +#define ERROR_DATABASE_DOES_NOT_EXIST 1065L [fail] +#define ERROR_SERVICE_SPECIFIC_ERROR 1066L [fail] +#define ERROR_PROCESS_ABORTED 1067L [fail] +#define ERROR_SERVICE_DEPENDENCY_FAIL 1068L [fail] +#define ERROR_SERVICE_LOGON_FAILED 1069L [fail] +#define ERROR_SERVICE_START_HANG 1070L [fail] +#define ERROR_INVALID_SERVICE_LOCK 1071L [fail] +#define ERROR_SERVICE_MARKED_FOR_DELETE 1072L [fail] +#define ERROR_SERVICE_EXISTS 1073L [fail] +#define ERROR_ALREADY_RUNNING_LKG 1074L [fail] +#define ERROR_SERVICE_DEPENDENCY_DELETED 1075L [fail] +#define ERROR_BOOT_ALREADY_ACCEPTED 1076L [fail] +#define ERROR_SERVICE_NEVER_STARTED 1077L [fail] +#define ERROR_DUPLICATE_SERVICE_NAME 1078L [fail] +#define ERROR_DIFFERENT_SERVICE_ACCOUNT 1079L [fail] +#define ERROR_CANNOT_DETECT_DRIVER_FAILURE 1080L [fail] +#define ERROR_CANNOT_DETECT_PROCESS_ABORT 1081L [fail] +#define ERROR_NO_RECOVERY_PROGRAM 1082L [fail] +#define ERROR_END_OF_MEDIA 1100L [fail] +#define ERROR_FILEMARK_DETECTED 1101L [fail] +#define ERROR_BEGINNING_OF_MEDIA 1102L [fail] +#define ERROR_SETMARK_DETECTED 1103L [fail] +#define ERROR_NO_DATA_DETECTED 1104L [fail] +#define ERROR_PARTITION_FAILURE 1105L [fail] +#define ERROR_INVALID_BLOCK_LENGTH 1106L [fail] +#define ERROR_DEVICE_NOT_PARTITIONED 1107L [fail] +#define ERROR_UNABLE_TO_LOCK_MEDIA 1108L [fail] +#define ERROR_UNABLE_TO_UNLOAD_MEDIA 1109L [fail] +#define ERROR_MEDIA_CHANGED 1110L [fail] +#define ERROR_BUS_RESET 1111L [fail] +#define ERROR_NO_MEDIA_IN_DRIVE 1112L [fail] +#define ERROR_NO_UNICODE_TRANSLATION 1113L [fail] +#define ERROR_DLL_INIT_FAILED 1114L [fail] +#define ERROR_SHUTDOWN_IN_PROGRESS 1115L [fail] +#define ERROR_NO_SHUTDOWN_IN_PROGRESS 1116L [fail] +#define ERROR_IO_DEVICE 1117L [fail] +#define ERROR_SERIAL_NO_DEVICE 1118L [fail] +#define ERROR_IRQ_BUSY 1119L [fail] +#define ERROR_MORE_WRITES 1120L [fail] +#define ERROR_COUNTER_TIMEOUT 1121L [fail] +#define ERROR_FLOPPY_ID_MARK_NOT_FOUND 1122L [fail] +#define ERROR_FLOPPY_WRONG_CYLINDER 1123L [fail] +#define ERROR_FLOPPY_UNKNOWN_ERROR 1124L [fail] +#define ERROR_FLOPPY_BAD_REGISTERS 1125L [fail] +#define ERROR_DISK_RECALIBRATE_FAILED 1126L [fail] +#define ERROR_DISK_OPERATION_FAILED 1127L [fail] +#define ERROR_DISK_RESET_FAILED 1128L [fail] +#define ERROR_EOM_OVERFLOW 1129L [fail] +#define ERROR_NOT_ENOUGH_SERVER_MEMORY 1130L [fail] +#define ERROR_POSSIBLE_DEADLOCK 1131L [fail] +#define ERROR_MAPPED_ALIGNMENT 1132L [fail] +#define ERROR_SET_POWER_STATE_VETOED 1140L [fail] +#define ERROR_SET_POWER_STATE_FAILED 1141L [fail] +#define ERROR_TOO_MANY_LINKS 1142L [fail] +#define ERROR_OLD_WIN_VERSION 1150L [fail] +#define ERROR_APP_WRONG_OS 1151L [fail] +#define ERROR_SINGLE_INSTANCE_APP 1152L [fail] +#define ERROR_RMODE_APP 1153L [fail] +#define ERROR_INVALID_DLL 1154L [fail] +#define ERROR_NO_ASSOCIATION 1155L [fail] +#define ERROR_DDE_FAIL 1156L [fail] +#define ERROR_DLL_NOT_FOUND 1157L [fail] +#define ERROR_NO_MORE_USER_HANDLES 1158L [fail] +#define ERROR_MESSAGE_SYNC_ONLY 1159L [fail] +#define ERROR_SOURCE_ELEMENT_EMPTY 1160L [fail] +#define ERROR_DESTINATION_ELEMENT_FULL 1161L [fail] +#define ERROR_ILLEGAL_ELEMENT_ADDRESS 1162L [fail] +#define ERROR_MAGAZINE_NOT_PRESENT 1163L [fail] +#define ERROR_DEVICE_REINITIALIZATION_NEEDED 1164L [fail] +#define ERROR_DEVICE_REQUIRES_CLEANING 1165L [fail] +#define ERROR_DEVICE_DOOR_OPEN 1166L [fail] +#define ERROR_DEVICE_NOT_CONNECTED 1167L [fail] +#define ERROR_NOT_FOUND 1168L [fail] +#define ERROR_NO_MATCH 1169L [fail] +#define ERROR_SET_NOT_FOUND 1170L [fail] +#define ERROR_POINT_NOT_FOUND 1171L [fail] +#define ERROR_NO_TRACKING_SERVICE 1172L [fail] +#define ERROR_NO_VOLUME_ID 1173L [fail] +#define ERROR_CONNECTED_OTHER_PASSWORD 2108L [fail] +#define ERROR_BAD_USERNAME 2202L [fail] +#define ERROR_NOT_CONNECTED 2250L [fail] +#define ERROR_OPEN_FILES 2401L [fail] +#define ERROR_ACTIVE_CONNECTIONS 2402L [fail] +#define ERROR_DEVICE_IN_USE 2404L [fail] +#define ERROR_BAD_DEVICE 1200L [fail] +#define ERROR_CONNECTION_UNAVAIL 1201L [fail] +#define ERROR_DEVICE_ALREADY_REMEMBERED 1202L [fail] +#define ERROR_NO_NET_OR_BAD_PATH 1203L [fail] +#define ERROR_BAD_PROVIDER 1204L [fail] +#define ERROR_CANNOT_OPEN_PROFILE 1205L [fail] +#define ERROR_BAD_PROFILE 1206L [fail] +#define ERROR_NOT_CONTAINER 1207L [fail] +#define ERROR_EXTENDED_ERROR 1208L [fail] +#define ERROR_INVALID_GROUPNAME 1209L [fail] +#define ERROR_INVALID_COMPUTERNAME 1210L [fail] +#define ERROR_INVALID_EVENTNAME 1211L [fail] +#define ERROR_INVALID_DOMAINNAME 1212L [fail] +#define ERROR_INVALID_SERVICENAME 1213L [fail] +#define ERROR_INVALID_NETNAME 1214L [fail] +#define ERROR_INVALID_SHARENAME 1215L [fail] +#define ERROR_INVALID_PASSWORDNAME 1216L [fail] +#define ERROR_INVALID_MESSAGENAME 1217L [fail] +#define ERROR_INVALID_MESSAGEDEST 1218L [fail] +#define ERROR_SESSION_CREDENTIAL_CONFLICT 1219L [fail] +#define ERROR_REMOTE_SESSION_LIMIT_EXCEEDED 1220L [fail] +#define ERROR_DUP_DOMAINNAME 1221L [fail] +#define ERROR_NO_NETWORK 1222L [fail] +#define ERROR_CANCELLED 1223L [fail] +#define ERROR_USER_MAPPED_FILE 1224L [fail] +#define ERROR_CONNECTION_REFUSED 1225L [fail] +#define ERROR_GRACEFUL_DISCONNECT 1226L [fail] +#define ERROR_ADDRESS_ALREADY_ASSOCIATED 1227L [fail] +#define ERROR_ADDRESS_NOT_ASSOCIATED 1228L [fail] +#define ERROR_CONNECTION_INVALID 1229L [fail] +#define ERROR_CONNECTION_ACTIVE 1230L [fail] +#define ERROR_NETWORK_UNREACHABLE 1231L [fail] +#define ERROR_HOST_UNREACHABLE 1232L [fail] +#define ERROR_PROTOCOL_UNREACHABLE 1233L [fail] +#define ERROR_PORT_UNREACHABLE 1234L [fail] +#define ERROR_REQUEST_ABORTED 1235L [fail] +#define ERROR_CONNECTION_ABORTED 1236L [fail] +#define ERROR_RETRY 1237L [fail] +#define ERROR_CONNECTION_COUNT_LIMIT 1238L [fail] +#define ERROR_LOGIN_TIME_RESTRICTION 1239L [fail] +#define ERROR_LOGIN_WKSTA_RESTRICTION 1240L [fail] +#define ERROR_INCORRECT_ADDRESS 1241L [fail] +#define ERROR_ALREADY_REGISTERED 1242L [fail] +#define ERROR_SERVICE_NOT_FOUND 1243L [fail] +#define ERROR_NOT_AUTHENTICATED 1244L [fail] +#define ERROR_NOT_LOGGED_ON 1245L [fail] +#define ERROR_CONTINUE 1246L [fail] +#define ERROR_ALREADY_INITIALIZED 1247L [fail] +#define ERROR_NO_MORE_DEVICES 1248L [fail] +#define ERROR_NO_SUCH_SITE 1249L [fail] +#define ERROR_DOMAIN_CONTROLLER_EXISTS 1250L [fail] +#define ERROR_DS_NOT_INSTALLED 1251L [fail] +#define ERROR_NOT_ALL_ASSIGNED 1300L [fail] +#define ERROR_SOME_NOT_MAPPED 1301L [fail] +#define ERROR_NO_QUOTAS_FOR_ACCOUNT 1302L [fail] +#define ERROR_LOCAL_USER_SESSION_KEY 1303L [fail] +#define ERROR_NULL_LM_PASSWORD 1304L [fail] +#define ERROR_UNKNOWN_REVISION 1305L [fail] +#define ERROR_REVISION_MISMATCH 1306L [fail] +#define ERROR_INVALID_OWNER 1307L [fail] +#define ERROR_INVALID_PRIMARY_GROUP 1308L [fail] +#define ERROR_NO_IMPERSONATION_TOKEN 1309L [fail] +#define ERROR_CANT_DISABLE_MANDATORY 1310L [fail] +#define ERROR_NO_LOGON_SERVERS 1311L [fail] +#define ERROR_NO_SUCH_LOGON_SESSION 1312L [fail] +#define ERROR_NO_SUCH_PRIVILEGE 1313L [fail] +#define ERROR_PRIVILEGE_NOT_HELD 1314L [fail] +#define ERROR_INVALID_ACCOUNT_NAME 1315L [fail] +#define ERROR_USER_EXISTS 1316L [fail] +#define ERROR_NO_SUCH_USER 1317L [fail] +#define ERROR_GROUP_EXISTS 1318L [fail] +#define ERROR_NO_SUCH_GROUP 1319L [fail] +#define ERROR_MEMBER_IN_GROUP 1320L [fail] +#define ERROR_MEMBER_NOT_IN_GROUP 1321L [fail] +#define ERROR_LAST_ADMIN 1322L [fail] +#define ERROR_WRONG_PASSWORD 1323L [fail] +#define ERROR_ILL_FORMED_PASSWORD 1324L [fail] +#define ERROR_PASSWORD_RESTRICTION 1325L [fail] +#define ERROR_LOGON_FAILURE 1326L [fail] +#define ERROR_ACCOUNT_RESTRICTION 1327L [fail] +#define ERROR_INVALID_LOGON_HOURS 1328L [fail] +#define ERROR_INVALID_WORKSTATION 1329L [fail] +#define ERROR_PASSWORD_EXPIRED 1330L [fail] +#define ERROR_ACCOUNT_DISABLED 1331L [fail] +#define ERROR_NONE_MAPPED 1332L [fail] +#define ERROR_TOO_MANY_LUIDS_REQUESTED 1333L [fail] +#define ERROR_LUIDS_EXHAUSTED 1334L [fail] +#define ERROR_INVALID_SUB_AUTHORITY 1335L [fail] +#define ERROR_INVALID_ACL 1336L [fail] +#define ERROR_INVALID_SID 1337L [fail] +#define ERROR_INVALID_SECURITY_DESCR 1338L [fail] +#define ERROR_BAD_INHERITANCE_ACL 1340L [fail] +#define ERROR_SERVER_DISABLED 1341L [fail] +#define ERROR_SERVER_NOT_DISABLED 1342L [fail] +#define ERROR_INVALID_ID_AUTHORITY 1343L [fail] +#define ERROR_ALLOTTED_SPACE_EXCEEDED 1344L [fail] +#define ERROR_INVALID_GROUP_ATTRIBUTES 1345L [fail] +#define ERROR_BAD_IMPERSONATION_LEVEL 1346L [fail] +#define ERROR_CANT_OPEN_ANONYMOUS 1347L [fail] +#define ERROR_BAD_VALIDATION_CLASS 1348L [fail] +#define ERROR_BAD_TOKEN_TYPE 1349L [fail] +#define ERROR_NO_SECURITY_ON_OBJECT 1350L [fail] +#define ERROR_CANT_ACCESS_DOMAIN_INFO 1351L [fail] +#define ERROR_INVALID_SERVER_STATE 1352L [fail] +#define ERROR_INVALID_DOMAIN_STATE 1353L [fail] +#define ERROR_INVALID_DOMAIN_ROLE 1354L [fail] +#define ERROR_NO_SUCH_DOMAIN 1355L [fail] +#define ERROR_DOMAIN_EXISTS 1356L [fail] +#define ERROR_DOMAIN_LIMIT_EXCEEDED 1357L [fail] +#define ERROR_INTERNAL_DB_CORRUPTION 1358L [fail] +#define ERROR_INTERNAL_ERROR 1359L [fail] +#define ERROR_GENERIC_NOT_MAPPED 1360L [fail] +#define ERROR_BAD_DESCRIPTOR_FORMAT 1361L [fail] +#define ERROR_NOT_LOGON_PROCESS 1362L [fail] +#define ERROR_LOGON_SESSION_EXISTS 1363L [fail] +#define ERROR_NO_SUCH_PACKAGE 1364L [fail] +#define ERROR_BAD_LOGON_SESSION_STATE 1365L [fail] +#define ERROR_LOGON_SESSION_COLLISION 1366L [fail] +#define ERROR_INVALID_LOGON_TYPE 1367L [fail] +#define ERROR_CANNOT_IMPERSONATE 1368L [fail] +#define ERROR_RXACT_INVALID_STATE 1369L [fail] +#define ERROR_RXACT_COMMIT_FAILURE 1370L [fail] +#define ERROR_SPECIAL_ACCOUNT 1371L [fail] +#define ERROR_SPECIAL_GROUP 1372L [fail] +#define ERROR_SPECIAL_USER 1373L [fail] +#define ERROR_MEMBERS_PRIMARY_GROUP 1374L [fail] +#define ERROR_TOKEN_ALREADY_IN_USE 1375L [fail] +#define ERROR_NO_SUCH_ALIAS 1376L [fail] +#define ERROR_MEMBER_NOT_IN_ALIAS 1377L [fail] +#define ERROR_MEMBER_IN_ALIAS 1378L [fail] +#define ERROR_ALIAS_EXISTS 1379L [fail] +#define ERROR_LOGON_NOT_GRANTED 1380L [fail] +#define ERROR_TOO_MANY_SECRETS 1381L [fail] +#define ERROR_SECRET_TOO_LONG 1382L [fail] +#define ERROR_INTERNAL_DB_ERROR 1383L [fail] +#define ERROR_TOO_MANY_CONTEXT_IDS 1384L [fail] +#define ERROR_LOGON_TYPE_NOT_GRANTED 1385L [fail] +#define ERROR_NT_CROSS_ENCRYPTION_REQUIRED 1386L [fail] +#define ERROR_NO_SUCH_MEMBER 1387L [fail] +#define ERROR_INVALID_MEMBER 1388L [fail] +#define ERROR_TOO_MANY_SIDS 1389L [fail] +#define ERROR_LM_CROSS_ENCRYPTION_REQUIRED 1390L [fail] +#define ERROR_NO_INHERITANCE 1391L [fail] +#define ERROR_FILE_CORRUPT 1392L [fail] +#define ERROR_DISK_CORRUPT 1393L [fail] +#define ERROR_NO_USER_SESSION_KEY 1394L [fail] +#define ERROR_LICENSE_QUOTA_EXCEEDED 1395L [fail] +#define ERROR_INVALID_WINDOW_HANDLE 1400L [fail] +#define ERROR_INVALID_MENU_HANDLE 1401L [fail] +#define ERROR_INVALID_CURSOR_HANDLE 1402L [fail] +#define ERROR_INVALID_ACCEL_HANDLE 1403L [fail] +#define ERROR_INVALID_HOOK_HANDLE 1404L [fail] +#define ERROR_INVALID_DWP_HANDLE 1405L [fail] +#define ERROR_TLW_WITH_WSCHILD 1406L [fail] +#define ERROR_CANNOT_FIND_WND_CLASS 1407L [fail] +#define ERROR_WINDOW_OF_OTHER_THREAD 1408L [fail] +#define ERROR_HOTKEY_ALREADY_REGISTERED 1409L [fail] +#define ERROR_CLASS_ALREADY_EXISTS 1410L [fail] +#define ERROR_CLASS_DOES_NOT_EXIST 1411L [fail] +#define ERROR_CLASS_HAS_WINDOWS 1412L [fail] +#define ERROR_INVALID_INDEX 1413L [fail] +#define ERROR_INVALID_ICON_HANDLE 1414L [fail] +#define ERROR_PRIVATE_DIALOG_INDEX 1415L [fail] +#define ERROR_LISTBOX_ID_NOT_FOUND 1416L [fail] +#define ERROR_NO_WILDCARD_CHARACTERS 1417L [fail] +#define ERROR_CLIPBOARD_NOT_OPEN 1418L [fail] +#define ERROR_HOTKEY_NOT_REGISTERED 1419L [fail] +#define ERROR_WINDOW_NOT_DIALOG 1420L [fail] +#define ERROR_CONTROL_ID_NOT_FOUND 1421L [fail] +#define ERROR_INVALID_COMBOBOX_MESSAGE 1422L [fail] +#define ERROR_WINDOW_NOT_COMBOBOX 1423L [fail] +#define ERROR_INVALID_EDIT_HEIGHT 1424L [fail] +#define ERROR_DC_NOT_FOUND 1425L [fail] +#define ERROR_INVALID_HOOK_FILTER 1426L [fail] +#define ERROR_INVALID_FILTER_PROC 1427L [fail] +#define ERROR_HOOK_NEEDS_HMOD 1428L [fail] +#define ERROR_GLOBAL_ONLY_HOOK 1429L [fail] +#define ERROR_JOURNAL_HOOK_SET 1430L [fail] +#define ERROR_HOOK_NOT_INSTALLED 1431L [fail] +#define ERROR_INVALID_LB_MESSAGE 1432L [fail] +#define ERROR_SETCOUNT_ON_BAD_LB 1433L [fail] +#define ERROR_LB_WITHOUT_TABSTOPS 1434L [fail] +#define ERROR_DESTROY_OBJECT_OF_OTHER_THREAD 1435L [fail] +#define ERROR_CHILD_WINDOW_MENU 1436L [fail] +#define ERROR_NO_SYSTEM_MENU 1437L [fail] +#define ERROR_INVALID_MSGBOX_STYLE 1438L [fail] +#define ERROR_INVALID_SPI_VALUE 1439L [fail] +#define ERROR_SCREEN_ALREADY_LOCKED 1440L [fail] +#define ERROR_HWNDS_HAVE_DIFF_PARENT 1441L [fail] +#define ERROR_NOT_CHILD_WINDOW 1442L [fail] +#define ERROR_INVALID_GW_COMMAND 1443L [fail] +#define ERROR_INVALID_THREAD_ID 1444L [fail] +#define ERROR_NON_MDICHILD_WINDOW 1445L [fail] +#define ERROR_POPUP_ALREADY_ACTIVE 1446L [fail] +#define ERROR_NO_SCROLLBARS 1447L [fail] +#define ERROR_INVALID_SCROLLBAR_RANGE 1448L [fail] +#define ERROR_INVALID_SHOWWIN_COMMAND 1449L [fail] +#define ERROR_NO_SYSTEM_RESOURCES 1450L [fail] +#define ERROR_NONPAGED_SYSTEM_RESOURCES 1451L [fail] +#define ERROR_PAGED_SYSTEM_RESOURCES 1452L [fail] +#define ERROR_WORKING_SET_QUOTA 1453L [fail] +#define ERROR_PAGEFILE_QUOTA 1454L [fail] +#define ERROR_COMMITMENT_LIMIT 1455L [fail] +#define ERROR_MENU_ITEM_NOT_FOUND 1456L [fail] +#define ERROR_INVALID_KEYBOARD_HANDLE 1457L [fail] +#define ERROR_HOOK_TYPE_NOT_ALLOWED 1458L [fail] +#define ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION 1459L[fail] +#define ERROR_TIMEOUT 1460L [fail] +#define ERROR_INVALID_MONITOR_HANDLE 1461L [fail] +#define ERROR_EVENTLOG_FILE_CORRUPT 1500L [fail] +#define ERROR_EVENTLOG_CANT_START 1501L [fail] +#define ERROR_LOG_FILE_FULL 1502L [fail] +#define ERROR_EVENTLOG_FILE_CHANGED 1503L [fail] +#define ERROR_INSTALL_SERVICE 1601L [fail] +#define ERROR_INSTALL_USEREXIT 1602L [fail] +#define ERROR_INSTALL_FAILURE 1603L [fail] +#define ERROR_INSTALL_SUSPEND 1604L [fail] +#define ERROR_UNKNOWN_PRODUCT 1605L [fail] +#define ERROR_UNKNOWN_FEATURE 1606L [fail] +#define ERROR_UNKNOWN_COMPONENT 1607L [fail] +#define ERROR_UNKNOWN_PROPERTY 1608L [fail] +#define ERROR_INVALID_HANDLE_STATE 1609L [fail] +#define ERROR_BAD_CONFIGURATION 1610L [fail] +#define ERROR_INDEX_ABSENT 1611L [fail] +#define ERROR_INSTALL_SOURCE_ABSENT 1612L [fail] +#define ERROR_BAD_DATABASE_VERSION 1613L [fail] +#define ERROR_PRODUCT_UNINSTALLED 1614L [fail] +#define ERROR_BAD_QUERY_SYNTAX 1615L [fail] +#define ERROR_INVALID_FIELD 1616L [fail] +#define RPC_S_INVALID_STRING_BINDING 1700L [fail] +#define RPC_S_WRONG_KIND_OF_BINDING 1701L [fail] +#define RPC_S_INVALID_BINDING 1702L [fail] +#define RPC_S_PROTSEQ_NOT_SUPPORTED 1703L [fail] +#define RPC_S_INVALID_RPC_PROTSEQ 1704L [fail] +#define RPC_S_INVALID_STRING_UUID 1705L [fail] +#define RPC_S_INVALID_ENDPOINT_FORMAT 1706L [fail] +#define RPC_S_INVALID_NET_ADDR 1707L [fail] +#define RPC_S_NO_ENDPOINT_FOUND 1708L [fail] +#define RPC_S_INVALID_TIMEOUT 1709L [fail] +#define RPC_S_OBJECT_NOT_FOUND 1710L [fail] +#define RPC_S_ALREADY_REGISTERED 1711L [fail] +#define RPC_S_TYPE_ALREADY_REGISTERED 1712L [fail] +#define RPC_S_ALREADY_LISTENING 1713L [fail] +#define RPC_S_NO_PROTSEQS_REGISTERED 1714L [fail] +#define RPC_S_NOT_LISTENING 1715L [fail] +#define RPC_S_UNKNOWN_MGR_TYPE 1716L [fail] +#define RPC_S_UNKNOWN_IF 1717L [fail] +#define RPC_S_NO_BINDINGS 1718L [fail] +#define RPC_S_NO_PROTSEQS 1719L [fail] +#define RPC_S_CANT_CREATE_ENDPOINT 1720L [fail] +#define RPC_S_OUT_OF_RESOURCES 1721L [fail] +#define RPC_S_SERVER_UNAVAILABLE 1722L [fail] +#define RPC_S_SERVER_TOO_BUSY 1723L [fail] +#define RPC_S_INVALID_NETWORK_OPTIONS 1724L [fail] +#define RPC_S_NO_CALL_ACTIVE 1725L [fail] +#define RPC_S_CALL_FAILED 1726L [fail] +#define RPC_S_CALL_FAILED_DNE 1727L [fail] +#define RPC_S_PROTOCOL_ERROR 1728L [fail] +#define RPC_S_UNSUPPORTED_TRANS_SYN 1730L [fail] +#define RPC_S_UNSUPPORTED_TYPE 1732L [fail] +#define RPC_S_INVALID_TAG 1733L [fail] +#define RPC_S_INVALID_BOUND 1734L [fail] +#define RPC_S_NO_ENTRY_NAME 1735L [fail] +#define RPC_S_INVALID_NAME_SYNTAX 1736L [fail] +#define RPC_S_UNSUPPORTED_NAME_SYNTAX 1737L [fail] +#define RPC_S_UUID_NO_ADDRESS 1739L [fail] +#define RPC_S_DUPLICATE_ENDPOINT 1740L [fail] +#define RPC_S_UNKNOWN_AUTHN_TYPE 1741L [fail] +#define RPC_S_MAX_CALLS_TOO_SMALL 1742L [fail] +#define RPC_S_STRING_TOO_LONG 1743L [fail] +#define RPC_S_PROTSEQ_NOT_FOUND 1744L [fail] +#define RPC_S_PROCNUM_OUT_OF_RANGE 1745L [fail] +#define RPC_S_BINDING_HAS_NO_AUTH 1746L [fail] +#define RPC_S_UNKNOWN_AUTHN_SERVICE 1747L [fail] +#define RPC_S_UNKNOWN_AUTHN_LEVEL 1748L [fail] +#define RPC_S_INVALID_AUTH_IDENTITY 1749L [fail] +#define RPC_S_UNKNOWN_AUTHZ_SERVICE 1750L [fail] +#define EPT_S_INVALID_ENTRY 1751L [fail] +#define EPT_S_CANT_PERFORM_OP 1752L [fail] +#define EPT_S_NOT_REGISTERED 1753L [fail] +#define RPC_S_NOTHING_TO_EXPORT 1754L [fail] +#define RPC_S_INCOMPLETE_NAME 1755L [fail] +#define RPC_S_INVALID_VERS_OPTION 1756L [fail] +#define RPC_S_NO_MORE_MEMBERS 1757L [fail] +#define RPC_S_NOT_ALL_OBJS_UNEXPORTED 1758L [fail] +#define RPC_S_INTERFACE_NOT_FOUND 1759L [fail] +#define RPC_S_ENTRY_ALREADY_EXISTS 1760L [fail] +#define RPC_S_ENTRY_NOT_FOUND 1761L [fail] +#define RPC_S_NAME_SERVICE_UNAVAILABLE 1762L [fail] +#define RPC_S_INVALID_NAF_ID 1763L [fail] +#define RPC_S_CANNOT_SUPPORT 1764L [fail] +#define RPC_S_NO_CONTEXT_AVAILABLE 1765L [fail] +#define RPC_S_INTERNAL_ERROR 1766L [fail] +#define RPC_S_ZERO_DIVIDE 1767L [fail] +#define RPC_S_ADDRESS_ERROR 1768L [fail] +#define RPC_S_FP_DIV_ZERO 1769L [fail] +#define RPC_S_FP_UNDERFLOW 1770L [fail] +#define RPC_S_FP_OVERFLOW 1771L [fail] +#define RPC_X_NO_MORE_ENTRIES 1772L [fail] +#define RPC_X_SS_CHAR_TRANS_OPEN_FAIL 1773L [fail] +#define RPC_X_SS_CHAR_TRANS_SHORT_FILE 1774L [fail] +#define RPC_X_SS_IN_NULL_CONTEXT 1775L [fail] +#define RPC_X_SS_CONTEXT_DAMAGED 1777L [fail] +#define RPC_X_SS_HANDLES_MISMATCH 1778L [fail] +#define RPC_X_SS_CANNOT_GET_CALL_HANDLE 1779L [fail] +#define RPC_X_NULL_REF_POINTER 1780L [fail] +#define RPC_X_ENUM_VALUE_OUT_OF_RANGE 1781L [fail] +#define RPC_X_BYTE_COUNT_TOO_SMALL 1782L [fail] +#define RPC_X_BAD_STUB_DATA 1783L [fail] +#define ERROR_INVALID_USER_BUFFER 1784L [fail] +#define ERROR_UNRECOGNIZED_MEDIA 1785L [fail] +#define ERROR_NO_TRUST_LSA_SECRET 1786L [fail] +#define ERROR_NO_TRUST_SAM_ACCOUNT 1787L [fail] +#define ERROR_TRUSTED_DOMAIN_FAILURE 1788L [fail] +#define ERROR_TRUSTED_RELATIONSHIP_FAILURE 1789L [fail] +#define ERROR_TRUST_FAILURE 1790L [fail] +#define RPC_S_CALL_IN_PROGRESS 1791L [fail] +#define ERROR_NETLOGON_NOT_STARTED 1792L [fail] +#define ERROR_ACCOUNT_EXPIRED 1793L [fail] +#define ERROR_REDIRECTOR_HAS_OPEN_HANDLES 1794L [fail] +#define ERROR_PRINTER_DRIVER_ALREADY_INSTALLED 1795L [fail] +#define ERROR_UNKNOWN_PORT 1796L [fail] +#define ERROR_UNKNOWN_PRINTER_DRIVER 1797L [fail] +#define ERROR_UNKNOWN_PRINTPROCESSOR 1798L [fail] +#define ERROR_INVALID_SEPARATOR_FILE 1799L [fail] +#define ERROR_INVALID_PRIORITY 1800L [fail] +#define ERROR_INVALID_PRINTER_NAME 1801L [fail] +#define ERROR_PRINTER_ALREADY_EXISTS 1802L [fail] +#define ERROR_INVALID_PRINTER_COMMAND 1803L [fail] +#define ERROR_INVALID_DATATYPE 1804L [fail] +#define ERROR_INVALID_ENVIRONMENT 1805L [fail] +#define RPC_S_NO_MORE_BINDINGS 1806L [fail] +#define ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT 1807L [fail] +#define ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT 1808L [fail] +#define ERROR_NOLOGON_SERVER_TRUST_ACCOUNT 1809L [fail] +#define ERROR_DOMAIN_TRUST_INCONSISTENT 1810L [fail] +#define ERROR_SERVER_HAS_OPEN_HANDLES 1811L [fail] +#define ERROR_RESOURCE_DATA_NOT_FOUND 1812L [fail] +#define ERROR_RESOURCE_TYPE_NOT_FOUND 1813L [fail] +#define ERROR_RESOURCE_NAME_NOT_FOUND 1814L [fail] +#define ERROR_RESOURCE_LANG_NOT_FOUND 1815L [fail] +#define ERROR_NOT_ENOUGH_QUOTA 1816L [fail] +#define RPC_S_NO_INTERFACES 1817L [fail] +#define RPC_S_CALL_CANCELLED 1818L [fail] +#define RPC_S_BINDING_INCOMPLETE 1819L [fail] +#define RPC_S_COMM_FAILURE 1820L [fail] +#define RPC_S_UNSUPPORTED_AUTHN_LEVEL 1821L [fail] +#define RPC_S_NO_PRINC_NAME 1822L [fail] +#define RPC_S_NOT_RPC_ERROR 1823L [fail] +#define RPC_S_UUID_LOCAL_ONLY 1824L [fail] +#define RPC_S_SEC_PKG_ERROR 1825L [fail] +#define RPC_S_NOT_CANCELLED 1826L [fail] +#define RPC_X_INVALID_ES_ACTION 1827L [fail] +#define RPC_X_WRONG_ES_VERSION 1828L [fail] +#define RPC_X_WRONG_STUB_VERSION 1829L [fail] +#define RPC_X_INVALID_PIPE_OBJECT 1830L [fail] +#define RPC_X_WRONG_PIPE_ORDER 1831L [fail] +#define RPC_X_WRONG_PIPE_VERSION 1832L [fail] +#define RPC_S_GROUP_MEMBER_NOT_FOUND 1898L [fail] +#define EPT_S_CANT_CREATE 1899L [fail] +#define RPC_S_INVALID_OBJECT 1900L [fail] +#define ERROR_INVALID_TIME 1901L [fail] +#define ERROR_INVALID_FORM_NAME 1902L [fail] +#define ERROR_INVALID_FORM_SIZE 1903L [fail] +#define ERROR_ALREADY_WAITING 1904L [fail] +#define ERROR_PRINTER_DELETED 1905L [fail] +#define ERROR_INVALID_PRINTER_STATE 1906L [fail] +#define ERROR_PASSWORD_MUST_CHANGE 1907L [fail] +#define ERROR_DOMAIN_CONTROLLER_NOT_FOUND 1908L [fail] +#define ERROR_ACCOUNT_LOCKED_OUT 1909L [fail] +#define OR_INVALID_OXID 1910L [fail] +#define OR_INVALID_OID 1911L [fail] +#define OR_INVALID_SET 1912L [fail] +#define RPC_S_SEND_INCOMPLETE 1913L [fail] +#define RPC_S_INVALID_ASYNC_HANDLE 1914L [fail] +#define RPC_S_INVALID_ASYNC_CALL 1915L [fail] +#define RPC_X_PIPE_CLOSED 1916L [fail] +#define RPC_X_PIPE_DISCIPLINE_ERROR 1917L [fail] +#define RPC_X_PIPE_EMPTY 1918L [fail] +#define ERROR_NO_SITENAME 1919L [fail] +#define ERROR_CANT_ACCESS_FILE 1920L [fail] +#define ERROR_CANT_RESOLVE_FILENAME 1921L [fail] +#define ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY 1922L [fail] +#define ERROR_DS_NO_ATTRIBUTE_OR_VALUE 1923L [fail] +#define ERROR_DS_INVALID_ATTRIBUTE_SYNTAX 1924L [fail] +#define ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED 1925L [fail] +#define ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS 1926L [fail] +#define ERROR_DS_BUSY 1927L [fail] +#define ERROR_DS_UNAVAILABLE 1928L [fail] +#define ERROR_DS_NO_RIDS_ALLOCATED 1929L [fail] +#define ERROR_DS_NO_MORE_RIDS 1930L [fail] +#define ERROR_DS_INCORRECT_ROLE_OWNER 1931L [fail] +#define ERROR_DS_RIDMGR_INIT_ERROR 1932L [fail] +#define ERROR_DS_OBJ_CLASS_VIOLATION 1933L [fail] +#define ERROR_DS_CANT_ON_NON_LEAF 1934L [fail] +#define ERROR_DS_CANT_ON_RDN 1935L [fail] +#define ERROR_DS_CANT_MOD_OBJ_CLASS 1936L [fail] +#define ERROR_DS_CROSS_DOM_MOVE_ERROR 1937L [fail] +#define ERROR_DS_GC_NOT_AVAILABLE 1938L [fail] +#define ERROR_NO_BROWSER_SERVERS_FOUND 6118L [fail] +#define ERROR_INVALID_PIXEL_FORMAT 2000L [fail] +#define ERROR_BAD_DRIVER 2001L [fail] +#define ERROR_INVALID_WINDOW_STYLE 2002L [fail] +#define ERROR_METAFILE_NOT_SUPPORTED 2003L [fail] +#define ERROR_TRANSFORM_NOT_SUPPORTED 2004L [fail] +#define ERROR_CLIPPING_NOT_SUPPORTED 2005L [fail] + +// End of OpenGL error codes + + +/////////////////////////////////////////// +// // +// Image Color Management Error Code // +// // +/////////////////////////////////////////// + + +#define ERROR_INVALID_CMM 2300L [fail] +#define ERROR_INVALID_PROFILE 2301L [fail] +#define ERROR_TAG_NOT_FOUND 2302L [fail] +#define ERROR_TAG_NOT_PRESENT 2303L [fail] +#define ERROR_DUPLICATE_TAG 2304L [fail] +#define ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE 2305L [fail] +#define ERROR_PROFILE_NOT_FOUND 2306L [fail] +#define ERROR_INVALID_COLORSPACE 2307L [fail] +#define ERROR_ICM_NOT_ENABLED 2308L [fail] +#define ERROR_DELETING_ICM_XFORM 2309L [fail] +#define ERROR_INVALID_TRANSFORM 2310L [fail] + + +//////////////////////////////////// +// // +// Win32 Spooler Error Codes // +// // +//////////////////////////////////// + + +#define ERROR_UNKNOWN_PRINT_MONITOR 3000L [fail] +#define ERROR_PRINTER_DRIVER_IN_USE 3001L [fail] +#define ERROR_SPOOL_FILE_NOT_FOUND 3002L [fail] +#define ERROR_SPL_NO_STARTDOC 3003L [fail] +#define ERROR_SPL_NO_ADDJOB 3004L [fail] +#define ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED 3005L [fail] +#define ERROR_PRINT_MONITOR_ALREADY_INSTALLED 3006L [fail] +#define ERROR_INVALID_PRINT_MONITOR 3007L [fail] +#define ERROR_PRINT_MONITOR_IN_USE 3008L [fail] +#define ERROR_PRINTER_HAS_JOBS_QUEUED 3009L [fail] +#define ERROR_SUCCESS_REBOOT_REQUIRED 3010L [fail] +#define ERROR_SUCCESS_RESTART_REQUIRED 3011L [fail] + +//////////////////////////////////// +// // +// Wins Error Codes // +// // +//////////////////////////////////// + +#define ERROR_WINS_INTERNAL 4000L [fail] +#define ERROR_CAN_NOT_DEL_LOCAL_WINS 4001L [fail] +#define ERROR_STATIC_INIT 4002L [fail] +#define ERROR_INC_BACKUP 4003L [fail] +#define ERROR_FULL_BACKUP 4004L [fail] +#define ERROR_REC_NON_EXISTENT 4005L [fail] +#define ERROR_RPL_NOT_ALLOWED 4006L [fail] + + +//////////////////////////////////// +// // +// DHCP Error Codes // +// // +//////////////////////////////////// + + +#define ERROR_DHCP_ADDRESS_CONFLICT 4100L [fail] + + +//////////////////////////////////// +// // +// WMI Error Codes // +// // +//////////////////////////////////// + +#define ERROR_WMI_GUID_NOT_FOUND 4200L [fail] +#define ERROR_WMI_INSTANCE_NOT_FOUND 4201L [fail] +#define ERROR_WMI_ITEMID_NOT_FOUND 4202L [fail] +#define ERROR_WMI_TRY_AGAIN 4203L [fail] +#define ERROR_WMI_DP_NOT_FOUND 4204L [fail] +#define ERROR_WMI_UNRESOLVED_INSTANCE_REF 4205L [fail] +#define ERROR_WMI_ALREADY_ENABLED 4206L [fail] +#define ERROR_WMI_GUID_DISCONNECTED 4207L [fail] +#define ERROR_WMI_SERVER_UNAVAILABLE 4208L [fail] +#define ERROR_WMI_DP_FAILED 4209L [fail] +#define ERROR_WMI_INVALID_MOF 4210L [fail] +#define ERROR_WMI_INVALID_REGINFO 4211L [fail] + +//////////////////////////////////// +// // +// NT Media Services Error Codes // +// // +//////////////////////////////////// + + +#define ERROR_INVALID_MEDIA 4300L [fail] +#define ERROR_INVALID_LIBRARY 4301L [fail] +#define ERROR_INVALID_MEDIA_POOL 4302L [fail] +#define ERROR_DRIVE_MEDIA_MISMATCH 4303L [fail] +#define ERROR_MEDIA_OFFLINE 4304L [fail] +#define ERROR_LIBRARY_OFFLINE 4305L [fail] +#define ERROR_EMPTY 4306L [fail] +#define ERROR_NOT_EMPTY 4307L [fail] +#define ERROR_MEDIA_UNAVAILABLE 4308L [fail] +#define ERROR_RESOURCE_DISABLED 4309L [fail] +#define ERROR_INVALID_CLEANER 4310L [fail] +#define ERROR_UNABLE_TO_CLEAN 4311L [fail] +#define ERROR_OBJECT_NOT_FOUND 4312L [fail] +#define ERROR_DATABASE_FAILURE 4313L [fail] +#define ERROR_DATABASE_FULL 4314L [fail] +#define ERROR_MEDIA_INCOMPATIBLE 4315L [fail] +#define ERROR_RESOURCE_NOT_PRESENT 4316L [fail] +#define ERROR_INVALID_OPERATION 4317L [fail] +#define ERROR_MEDIA_NOT_AVAILABLE 4318L [fail] +#define ERROR_DEVICE_NOT_AVAILABLE 4319L [fail] +#define ERROR_REQUEST_REFUSED 4320L [fail] + +//////////////////////////////////////////// +// // +// NT Remote Storage Service Error Codes // +// // +//////////////////////////////////////////// + + +#define ERROR_FILE_OFFLINE 4350L [fail] +#define ERROR_REMOTE_STORAGE_NOT_ACTIVE 4351L [fail] +#define ERROR_REMOTE_STORAGE_MEDIA_ERROR 4352L [fail] + +//////////////////////////////////////////// +// // +// NT Reparse Points Error Codes // +// // +//////////////////////////////////////////// + + +#define ERROR_NOT_A_REPARSE_POINT 4390L [fail] +#define ERROR_REPARSE_ATTRIBUTE_CONFLICT 4391L [fail] + +//////////////////////////////////// +// // +// Cluster Error Codes // +// // +//////////////////////////////////// + + +#define ERROR_DEPENDENT_RESOURCE_EXISTS 5001L [fail] +#define ERROR_DEPENDENCY_NOT_FOUND 5002L [fail] +#define ERROR_DEPENDENCY_ALREADY_EXISTS 5003L [fail] +#define ERROR_RESOURCE_NOT_ONLINE 5004L [fail] +#define ERROR_HOST_NODE_NOT_AVAILABLE 5005L [fail] +#define ERROR_RESOURCE_NOT_AVAILABLE 5006L [fail] +#define ERROR_RESOURCE_NOT_FOUND 5007L [fail] +#define ERROR_SHUTDOWN_CLUSTER 5008L [fail] +#define ERROR_CANT_EVICT_ACTIVE_NODE 5009L [fail] +#define ERROR_OBJECT_ALREADY_EXISTS 5010L [fail] +#define ERROR_OBJECT_IN_LIST 5011L [fail] +#define ERROR_GROUP_NOT_AVAILABLE 5012L [fail] +#define ERROR_GROUP_NOT_FOUND 5013L [fail] +#define ERROR_GROUP_NOT_ONLINE 5014L [fail] +#define ERROR_HOST_NODE_NOT_RESOURCE_OWNER 5015L [fail] +#define ERROR_HOST_NODE_NOT_GROUP_OWNER 5016L [fail] +#define ERROR_RESMON_CREATE_FAILED 5017L [fail] +#define ERROR_RESMON_ONLINE_FAILED 5018L [fail] +#define ERROR_RESOURCE_ONLINE 5019L [fail] +#define ERROR_QUORUM_RESOURCE 5020L [fail] +#define ERROR_NOT_QUORUM_CAPABLE 5021L [fail] +#define ERROR_CLUSTER_SHUTTING_DOWN 5022L [fail] +#define ERROR_INVALID_STATE 5023L [fail] +#define ERROR_RESOURCE_PROPERTIES_STORED 5024L [fail] +#define ERROR_NOT_QUORUM_CLASS 5025L [fail] +#define ERROR_CORE_RESOURCE 5026L [fail] +#define ERROR_QUORUM_RESOURCE_ONLINE_FAILED 5027L [fail] +#define ERROR_QUORUMLOG_OPEN_FAILED 5028L [fail] +#define ERROR_CLUSTERLOG_CORRUPT 5029L [fail] +#define ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE 5030L [fail] +#define ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE 5031L [fail] +#define ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND 5032L [fail] +#define ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE 5033L [fail] + +//////////////////////////////////// +// // +// EFS Error Codes // +// // +//////////////////////////////////// + + +#define ERROR_ENCRYPTION_FAILED 6000L [fail] +#define ERROR_DECRYPTION_FAILED 6001L [fail] +#define ERROR_FILE_ENCRYPTED 6002L [fail] +#define ERROR_NO_RECOVERY_POLICY 6003L [fail] +#define ERROR_NO_EFS 6004L [fail] +#define ERROR_WRONG_EFS 6005L [fail] +#define ERROR_NO_USER_KEYS 6006L [fail] +#define ERROR_FILE_NOT_ENCRYPTED 6007L [fail] +#define ERROR_NOT_EXPORT_FORMAT 6008L [fail] +}; + + +value DWORD HRESULT +{ +// +// Error definitions follow +// + +// +// Codes 0x4000-0x40ff are reserved for OLE +// +// +// Error codes +// +// +// MessageId: E_UNEXPECTED +// +// MessageText: +// +// Catastrophic failure +// +#define E_UNEXPECTED 0x8000FFFFL [fail] + +// +// MessageId: E_NOTIMPL +// +// MessageText: +// +// Not implemented +// +#define E_NOTIMPL 0x80004001L [fail] + +// +// MessageId: E_OUTOFMEMORY +// +// MessageText: +// +// Ran out of memory +// +#define E_OUTOFMEMORY 0x8007000EL [fail] + +// +// MessageId: E_INVALIDARG +// +// MessageText: +// +// One or more arguments are invalid +// +#define E_INVALIDARG 0x80070057L [fail] + +// +// MessageId: E_NOINTERFACE +// +// MessageText: +// +// No such interface supported +// +#define E_NOINTERFACE 0x80004002L [fail] + +// +// MessageId: E_POINTER +// +// MessageText: +// +// Invalid pointer +// +#define E_POINTER 0x80004003L [fail] + +// +// MessageId: E_HANDLE +// +// MessageText: +// +// Invalid handle +// +#define E_HANDLE 0x80070006L [fail] + +// +// MessageId: E_ABORT +// +// MessageText: +// +// Operation aborted +// +#define E_ABORT 0x80004004L [fail] + +// +// MessageId: E_FAIL +// +// MessageText: +// +// Unspecified error +// +#define E_FAIL 0x80004005L [fail] + +// +// MessageId: E_ACCESSDENIED +// +// MessageText: +// +// General access denied error +// +#define E_ACCESSDENIED 0x80070005L [fail] + +// +// MessageId: E_NOTIMPL +// +// MessageText: +// +// Not implemented +// +#define E_NOTIMPL 0x80000001L [fail] + +// +// MessageId: E_OUTOFMEMORY +// +// MessageText: +// +// Ran out of memory +// +#define E_OUTOFMEMORY 0x80000002L [fail] + +// +// MessageId: E_INVALIDARG +// +// MessageText: +// +// One or more arguments are invalid +// +#define E_INVALIDARG 0x80000003L [fail] + +// +// MessageId: E_NOINTERFACE +// +// MessageText: +// +// No such interface supported +// +#define E_NOINTERFACE 0x80000004L [fail] + +// +// MessageId: E_POINTER +// +// MessageText: +// +// Invalid pointer +// +#define E_POINTER 0x80000005L [fail] + +// +// MessageId: E_HANDLE +// +// MessageText: +// +// Invalid handle +// +#define E_HANDLE 0x80000006L [fail] + +// +// MessageId: E_ABORT +// +// MessageText: +// +// Operation aborted +// +#define E_ABORT 0x80000007L [fail] + +// +// MessageId: E_FAIL +// +// MessageText: +// +// Unspecified error +// +#define E_FAIL 0x80000008L [fail] + +// +// MessageId: E_ACCESSDENIED +// +// MessageText: +// +// General access denied error +// +#define E_ACCESSDENIED 0x80000009L [fail] + +// +// MessageId: E_PENDING +// +// MessageText: +// +// The data necessary to complete this operation is not yet available. +// +#define E_PENDING 0x8000000AL [fail] + +// +// MessageId: CO_E_INIT_TLS +// +// MessageText: +// +// Thread local storage failure +// +#define CO_E_INIT_TLS 0x80004006L [fail] + +// +// MessageId: CO_E_INIT_SHARED_ALLOCATOR +// +// MessageText: +// +// Get shared memory allocator failure +// +#define CO_E_INIT_SHARED_ALLOCATOR 0x80004007L [fail] + +// +// MessageId: CO_E_INIT_MEMORY_ALLOCATOR +// +// MessageText: +// +// Get memory allocator failure +// +#define CO_E_INIT_MEMORY_ALLOCATOR 0x80004008L [fail] + +// +// MessageId: CO_E_INIT_CLASS_CACHE +// +// MessageText: +// +// Unable to initialize class cache +// +#define CO_E_INIT_CLASS_CACHE 0x80004009L [fail] + +// +// MessageId: CO_E_INIT_RPC_CHANNEL +// +// MessageText: +// +// Unable to initialize RPC services +// +#define CO_E_INIT_RPC_CHANNEL 0x8000400AL [fail] + +// +// MessageId: CO_E_INIT_TLS_SET_CHANNEL_CONTROL +// +// MessageText: +// +// Cannot set thread local storage channel control +// +#define CO_E_INIT_TLS_SET_CHANNEL_CONTROL 0x8000400BL [fail] + +// +// MessageId: CO_E_INIT_TLS_CHANNEL_CONTROL +// +// MessageText: +// +// Could not allocate thread local storage channel control +// +#define CO_E_INIT_TLS_CHANNEL_CONTROL 0x8000400CL [fail] + +// +// MessageId: CO_E_INIT_UNACCEPTED_USER_ALLOCATOR +// +// MessageText: +// +// The user supplied memory allocator is unacceptable +// +#define CO_E_INIT_UNACCEPTED_USER_ALLOCATOR 0x8000400DL [fail] + +// +// MessageId: CO_E_INIT_SCM_MUTEX_EXISTS +// +// MessageText: +// +// The OLE service mutex already exists +// +#define CO_E_INIT_SCM_MUTEX_EXISTS 0x8000400EL [fail] + +// +// MessageId: CO_E_INIT_SCM_FILE_MAPPING_EXISTS +// +// MessageText: +// +// The OLE service file mapping already exists +// +#define CO_E_INIT_SCM_FILE_MAPPING_EXISTS 0x8000400FL [fail] + +// +// MessageId: CO_E_INIT_SCM_MAP_VIEW_OF_FILE +// +// MessageText: +// +// Unable to map view of file for OLE service +// +#define CO_E_INIT_SCM_MAP_VIEW_OF_FILE 0x80004010L [fail] + +// +// MessageId: CO_E_INIT_SCM_EXEC_FAILURE +// +// MessageText: +// +// Failure attempting to launch OLE service +// +#define CO_E_INIT_SCM_EXEC_FAILURE 0x80004011L [fail] + +// +// MessageId: CO_E_INIT_ONLY_SINGLE_THREADED +// +// MessageText: +// +// There was an attempt to call CoInitialize a second time while single threaded +// +#define CO_E_INIT_ONLY_SINGLE_THREADED 0x80004012L [fail] + +// +// MessageId: CO_E_CANT_REMOTE +// +// MessageText: +// +// A Remote activation was necessary but was not allowed +// +#define CO_E_CANT_REMOTE 0x80004013L [fail] + +// +// MessageId: CO_E_BAD_SERVER_NAME +// +// MessageText: +// +// A Remote activation was necessary but the server name provided was invalid +// +#define CO_E_BAD_SERVER_NAME 0x80004014L [fail] + +// +// MessageId: CO_E_WRONG_SERVER_IDENTITY +// +// MessageText: +// +// The class is configured to run as a security id different from the caller +// +#define CO_E_WRONG_SERVER_IDENTITY 0x80004015L [fail] + +// +// MessageId: CO_E_OLE1DDE_DISABLED +// +// MessageText: +// +// Use of Ole1 services requiring DDE windows is disabled +// +#define CO_E_OLE1DDE_DISABLED 0x80004016L [fail] + +// +// MessageId: CO_E_RUNAS_SYNTAX +// +// MessageText: +// +// A RunAs specification must be <domain name>\<user name> or simply <user name> +// +#define CO_E_RUNAS_SYNTAX 0x80004017L [fail] + +// +// MessageId: CO_E_CREATEPROCESS_FAILURE +// +// MessageText: +// +// The server process could not be started. The pathname may be incorrect. +// +#define CO_E_CREATEPROCESS_FAILURE 0x80004018L [fail] + +// +// MessageId: CO_E_RUNAS_CREATEPROCESS_FAILURE +// +// MessageText: +// +// The server process could not be started as the configured identity. The pathname may be incorrect or unavailable. +// +#define CO_E_RUNAS_CREATEPROCESS_FAILURE 0x80004019L [fail] + +// +// MessageId: CO_E_RUNAS_LOGON_FAILURE +// +// MessageText: +// +// The server process could not be started because the configured identity is incorrect. Check the username and password. +// +#define CO_E_RUNAS_LOGON_FAILURE 0x8000401AL [fail] + +// +// MessageId: CO_E_LAUNCH_PERMSSION_DENIED +// +// MessageText: +// +// The client is not allowed to launch this server. +// +#define CO_E_LAUNCH_PERMSSION_DENIED 0x8000401BL [fail] + +// +// MessageId: CO_E_START_SERVICE_FAILURE +// +// MessageText: +// +// The service providing this server could not be started. +// +#define CO_E_START_SERVICE_FAILURE 0x8000401CL [fail] + +// +// MessageId: CO_E_REMOTE_COMMUNICATION_FAILURE +// +// MessageText: +// +// This computer was unable to communicate with the computer providing the server. +// +#define CO_E_REMOTE_COMMUNICATION_FAILURE 0x8000401DL [fail] + +// +// MessageId: CO_E_SERVER_START_TIMEOUT +// +// MessageText: +// +// The server did not respond after being launched. +// +#define CO_E_SERVER_START_TIMEOUT 0x8000401EL [fail] + +// +// MessageId: CO_E_CLSREG_INCONSISTENT +// +// MessageText: +// +// The registration information for this server is inconsistent or incomplete. +// +#define CO_E_CLSREG_INCONSISTENT 0x8000401FL [fail] + +// +// MessageId: CO_E_IIDREG_INCONSISTENT +// +// MessageText: +// +// The registration information for this interface is inconsistent or incomplete. +// +#define CO_E_IIDREG_INCONSISTENT 0x80004020L [fail] + +// +// MessageId: CO_E_NOT_SUPPORTED +// +// MessageText: +// +// The operation attempted is not supported. +// +#define CO_E_NOT_SUPPORTED 0x80004021L [fail] + +// +// MessageId: CO_E_RELOAD_DLL +// +// MessageText: +// +// A dll must be loaded. +// +#define CO_E_RELOAD_DLL 0x80004022L [fail] + +// +// MessageId: CO_E_MSI_ERROR +// +// MessageText: +// +// A Microsoft Software Installer error was encountered. +// +#define CO_E_MSI_ERROR 0x80004023L [fail] + + +// +// Success codes +// +#define S_OK 0x00000000L +#define S_FALSE 0x00000001L + +// ****************** +// FACILITY_ITF +// ****************** + +// +// Codes 0x0-0x01ff are reserved for the OLE group of +// interfaces. +// + + +// +// Generic OLE errors that may be returned by many inerfaces +// + +#define OLE_E_FIRST 0x80040000L +#define OLE_E_LAST 0x800400FFL +#define OLE_S_FIRST 0x00040000L +#define OLE_S_LAST 0x000400FFL + +// +// Old OLE errors +// +// +// MessageId: OLE_E_OLEVERB +// +// MessageText: +// +// Invalid OLEVERB structure +// +#define OLE_E_OLEVERB 0x80040000L [fail] + +// +// MessageId: OLE_E_ADVF +// +// MessageText: +// +// Invalid advise flags +// +#define OLE_E_ADVF 0x80040001L [fail] + +// +// MessageId: OLE_E_ENUM_NOMORE +// +// MessageText: +// +// Can't enumerate any more, because the associated data is missing +// +#define OLE_E_ENUM_NOMORE 0x80040002L [fail] + +// +// MessageId: OLE_E_ADVISENOTSUPPORTED +// +// MessageText: +// +// This implementation doesn't take advises +// +#define OLE_E_ADVISENOTSUPPORTED 0x80040003L [fail] + +// +// MessageId: OLE_E_NOCONNECTION +// +// MessageText: +// +// There is no connection for this connection ID +// +#define OLE_E_NOCONNECTION 0x80040004L [fail] + +// +// MessageId: OLE_E_NOTRUNNING +// +// MessageText: +// +// Need to run the object to perform this operation +// +#define OLE_E_NOTRUNNING 0x80040005L [fail] + +// +// MessageId: OLE_E_NOCACHE +// +// MessageText: +// +// There is no cache to operate on +// +#define OLE_E_NOCACHE 0x80040006L [fail] + +// +// MessageId: OLE_E_BLANK +// +// MessageText: +// +// Uninitialized object +// +#define OLE_E_BLANK 0x80040007L [fail] + +// +// MessageId: OLE_E_CLASSDIFF +// +// MessageText: +// +// Linked object's source class has changed +// +#define OLE_E_CLASSDIFF 0x80040008L [fail] + +// +// MessageId: OLE_E_CANT_GETMONIKER +// +// MessageText: +// +// Not able to get the moniker of the object +// +#define OLE_E_CANT_GETMONIKER 0x80040009L [fail] + +// +// MessageId: OLE_E_CANT_BINDTOSOURCE +// +// MessageText: +// +// Not able to bind to the source +// +#define OLE_E_CANT_BINDTOSOURCE 0x8004000AL [fail] + +// +// MessageId: OLE_E_STATIC +// +// MessageText: +// +// Object is static; operation not allowed +// +#define OLE_E_STATIC 0x8004000BL [fail] + +// +// MessageId: OLE_E_PROMPTSAVECANCELLED +// +// MessageText: +// +// User canceled out of save dialog +// +#define OLE_E_PROMPTSAVECANCELLED 0x8004000CL [fail] + +// +// MessageId: OLE_E_INVALIDRECT +// +// MessageText: +// +// Invalid rectangle +// +#define OLE_E_INVALIDRECT 0x8004000DL [fail] + +// +// MessageId: OLE_E_WRONGCOMPOBJ +// +// MessageText: +// +// compobj.dll is too old for the ole2.dll initialized +// +#define OLE_E_WRONGCOMPOBJ 0x8004000EL [fail] + +// +// MessageId: OLE_E_INVALIDHWND +// +// MessageText: +// +// Invalid window handle +// +#define OLE_E_INVALIDHWND 0x8004000FL [fail] + +// +// MessageId: OLE_E_NOT_INPLACEACTIVE +// +// MessageText: +// +// Object is not in any of the inplace active states +// +#define OLE_E_NOT_INPLACEACTIVE 0x80040010L [fail] + +// +// MessageId: OLE_E_CANTCONVERT +// +// MessageText: +// +// Not able to convert object +// +#define OLE_E_CANTCONVERT 0x80040011L [fail] + +// +// MessageId: OLE_E_NOSTORAGE +// +// MessageText: +// +// Not able to perform the operation because object is not given storage yet +// +// +#define OLE_E_NOSTORAGE 0x80040012L [fail] + +// +// MessageId: DV_E_FORMATETC +// +// MessageText: +// +// Invalid FORMATETC structure +// +#define DV_E_FORMATETC 0x80040064L [fail] + +// +// MessageId: DV_E_DVTARGETDEVICE +// +// MessageText: +// +// Invalid DVTARGETDEVICE structure +// +#define DV_E_DVTARGETDEVICE 0x80040065L [fail] + +// +// MessageId: DV_E_STGMEDIUM +// +// MessageText: +// +// Invalid STDGMEDIUM structure +// +#define DV_E_STGMEDIUM 0x80040066L [fail] + +// +// MessageId: DV_E_STATDATA +// +// MessageText: +// +// Invalid STATDATA structure +// +#define DV_E_STATDATA 0x80040067L [fail] + +// +// MessageId: DV_E_LINDEX +// +// MessageText: +// +// Invalid lindex +// +#define DV_E_LINDEX 0x80040068L [fail] + +// +// MessageId: DV_E_TYMED +// +// MessageText: +// +// Invalid tymed +// +#define DV_E_TYMED 0x80040069L [fail] + +// +// MessageId: DV_E_CLIPFORMAT +// +// MessageText: +// +// Invalid clipboard format +// +#define DV_E_CLIPFORMAT 0x8004006AL [fail] + +// +// MessageId: DV_E_DVASPECT +// +// MessageText: +// +// Invalid aspect(s) +// +#define DV_E_DVASPECT 0x8004006BL [fail] + +// +// MessageId: DV_E_DVTARGETDEVICE_SIZE +// +// MessageText: +// +// tdSize parameter of the DVTARGETDEVICE structure is invalid +// +#define DV_E_DVTARGETDEVICE_SIZE 0x8004006CL [fail] + +// +// MessageId: DV_E_NOIVIEWOBJECT +// +// MessageText: +// +// Object doesn't support IViewObject interface +// +#define DV_E_NOIVIEWOBJECT 0x8004006DL [fail] + +#define DRAGDROP_E_FIRST 0x80040100L +#define DRAGDROP_E_LAST 0x8004010FL +#define DRAGDROP_S_FIRST 0x00040100L +#define DRAGDROP_S_LAST 0x0004010FL +// +// MessageId: DRAGDROP_E_NOTREGISTERED +// +// MessageText: +// +// Trying to revoke a drop target that has not been registered +// +#define DRAGDROP_E_NOTREGISTERED 0x80040100L [fail] + +// +// MessageId: DRAGDROP_E_ALREADYREGISTERED +// +// MessageText: +// +// This window has already been registered as a drop target +// +#define DRAGDROP_E_ALREADYREGISTERED 0x80040101L [fail] + +// +// MessageId: DRAGDROP_E_INVALIDHWND +// +// MessageText: +// +// Invalid window handle +// +#define DRAGDROP_E_INVALIDHWND 0x80040102L [fail] + +#define CLASSFACTORY_E_FIRST 0x80040110L +#define CLASSFACTORY_E_LAST 0x8004011FL +#define CLASSFACTORY_S_FIRST 0x00040110L +#define CLASSFACTORY_S_LAST 0x0004011FL +// +// MessageId: CLASS_E_NOAGGREGATION +// +// MessageText: +// +// Class does not support aggregation (or class object is remote) +// +#define CLASS_E_NOAGGREGATION 0x80040110L [fail] + +// +// MessageId: CLASS_E_CLASSNOTAVAILABLE +// +// MessageText: +// +// ClassFactory cannot supply requested class +// +#define CLASS_E_CLASSNOTAVAILABLE 0x80040111L [fail] + +// +// MessageId: CLASS_E_NOTLICENSED +// +// MessageText: +// +// Class is not licensed for use +// +#define CLASS_E_NOTLICENSED 0x80040112L [fail] + +#define MARSHAL_E_FIRST 0x80040120L +#define MARSHAL_E_LAST 0x8004012FL +#define MARSHAL_S_FIRST 0x00040120L +#define MARSHAL_S_LAST 0x0004012FL +#define DATA_E_FIRST 0x80040130L +#define DATA_E_LAST 0x8004013FL +#define DATA_S_FIRST 0x00040130L +#define DATA_S_LAST 0x0004013FL +#define VIEW_E_FIRST 0x80040140L +#define VIEW_E_LAST 0x8004014FL +#define VIEW_S_FIRST 0x00040140L +#define VIEW_S_LAST 0x0004014FL +// +// MessageId: VIEW_E_DRAW +// +// MessageText: +// +// Error drawing view +// +#define VIEW_E_DRAW 0x80040140L [fail] + +#define REGDB_E_FIRST 0x80040150L +#define REGDB_E_LAST 0x8004015FL +#define REGDB_S_FIRST 0x00040150L +#define REGDB_S_LAST 0x0004015FL +// +// MessageId: REGDB_E_READREGDB +// +// MessageText: +// +// Could not read key from registry +// +#define REGDB_E_READREGDB 0x80040150L [fail] + +// +// MessageId: REGDB_E_WRITEREGDB +// +// MessageText: +// +// Could not write key to registry +// +#define REGDB_E_WRITEREGDB 0x80040151L [fail] + +// +// MessageId: REGDB_E_KEYMISSING +// +// MessageText: +// +// Could not find the key in the registry +// +#define REGDB_E_KEYMISSING 0x80040152L [fail] + +// +// MessageId: REGDB_E_INVALIDVALUE +// +// MessageText: +// +// Invalid value for registry +// +#define REGDB_E_INVALIDVALUE 0x80040153L [fail] + +// +// MessageId: REGDB_E_CLASSNOTREG +// +// MessageText: +// +// Class not registered +// +#define REGDB_E_CLASSNOTREG 0x80040154L [fail] + +// +// MessageId: REGDB_E_IIDNOTREG +// +// MessageText: +// +// Interface not registered +// +#define REGDB_E_IIDNOTREG 0x80040155L [fail] + +#define CAT_E_FIRST 0x80040160L +#define CAT_E_LAST 0x80040161L +// +// MessageId: CAT_E_CATIDNOEXIST +// +// MessageText: +// +// CATID does not exist +// +#define CAT_E_CATIDNOEXIST 0x80040160L [fail] + +// +// MessageId: CAT_E_NODESCRIPTION +// +// MessageText: +// +// Description not found +// +#define CAT_E_NODESCRIPTION 0x80040161L [fail] + +//////////////////////////////////// +// // +// Class Store Error Codes // +// // +//////////////////////////////////// +#define CS_E_FIRST 0x80040164L +#define CS_E_LAST 0x80040168L +// +// MessageId: CS_E_PACKAGE_NOTFOUND +// +// MessageText: +// +// No package in Class Store meets this criteria +// +#define CS_E_PACKAGE_NOTFOUND 0x80040164L [fail] + +// +// MessageId: CS_E_NOT_DELETABLE +// +// MessageText: +// +// Deleting this will break referential integrity +// +#define CS_E_NOT_DELETABLE 0x80040165L [fail] + +// +// MessageId: CS_E_CLASS_NOTFOUND +// +// MessageText: +// +// No such CLSID in Class Store +// +#define CS_E_CLASS_NOTFOUND 0x80040166L [fail] + +// +// MessageId: CS_E_INVALID_VERSION +// +// MessageText: +// +// The Class Store is corrupted or has a version that is no more supported +// +#define CS_E_INVALID_VERSION 0x80040167L [fail] + +// +// MessageId: CS_E_NO_CLASSSTORE +// +// MessageText: +// +// No such Class Store +// +#define CS_E_NO_CLASSSTORE 0x80040168L [fail] + +#define CACHE_E_FIRST 0x80040170L +#define CACHE_E_LAST 0x8004017FL +#define CACHE_S_FIRST 0x00040170L +#define CACHE_S_LAST 0x0004017FL +// +// MessageId: CACHE_E_NOCACHE_UPDATED +// +// MessageText: +// +// Cache not updated +// +#define CACHE_E_NOCACHE_UPDATED 0x80040170L [fail] + +#define OLEOBJ_E_FIRST 0x80040180L +#define OLEOBJ_E_LAST 0x8004018FL +#define OLEOBJ_S_FIRST 0x00040180L +#define OLEOBJ_S_LAST 0x0004018FL +// +// MessageId: OLEOBJ_E_NOVERBS +// +// MessageText: +// +// No verbs for OLE object +// +#define OLEOBJ_E_NOVERBS 0x80040180L [fail] + +// +// MessageId: OLEOBJ_E_INVALIDVERB +// +// MessageText: +// +// Invalid verb for OLE object +// +#define OLEOBJ_E_INVALIDVERB 0x80040181L [fail] + +#define CLIENTSITE_E_FIRST 0x80040190L +#define CLIENTSITE_E_LAST 0x8004019FL +#define CLIENTSITE_S_FIRST 0x00040190L +#define CLIENTSITE_S_LAST 0x0004019FL +// +// MessageId: INPLACE_E_NOTUNDOABLE +// +// MessageText: +// +// Undo is not available +// +#define INPLACE_E_NOTUNDOABLE 0x800401A0L [fail] + +// +// MessageId: INPLACE_E_NOTOOLSPACE +// +// MessageText: +// +// Space for tools is not available +// +#define INPLACE_E_NOTOOLSPACE 0x800401A1L [fail] + +#define INPLACE_E_FIRST 0x800401A0L +#define INPLACE_E_LAST 0x800401AFL +#define INPLACE_S_FIRST 0x000401A0L +#define INPLACE_S_LAST 0x000401AFL +#define ENUM_E_FIRST 0x800401B0L +#define ENUM_E_LAST 0x800401BFL +#define ENUM_S_FIRST 0x000401B0L +#define ENUM_S_LAST 0x000401BFL +#define CONVERT10_E_FIRST 0x800401C0L +#define CONVERT10_E_LAST 0x800401CFL +#define CONVERT10_S_FIRST 0x000401C0L +#define CONVERT10_S_LAST 0x000401CFL +// +// MessageId: CONVERT10_E_OLESTREAM_GET +// +// MessageText: +// +// OLESTREAM Get method failed +// +#define CONVERT10_E_OLESTREAM_GET 0x800401C0L [fail] + +// +// MessageId: CONVERT10_E_OLESTREAM_PUT +// +// MessageText: +// +// OLESTREAM Put method failed +// +#define CONVERT10_E_OLESTREAM_PUT 0x800401C1L [fail] + +// +// MessageId: CONVERT10_E_OLESTREAM_FMT +// +// MessageText: +// +// Contents of the OLESTREAM not in correct format +// +#define CONVERT10_E_OLESTREAM_FMT 0x800401C2L [fail] + +// +// MessageId: CONVERT10_E_OLESTREAM_BITMAP_TO_DIB +// +// MessageText: +// +// There was an error in a Windows GDI call while converting the bitmap to a DIB +// +#define CONVERT10_E_OLESTREAM_BITMAP_TO_DIB 0x800401C3L [fail] + +// +// MessageId: CONVERT10_E_STG_FMT +// +// MessageText: +// +// Contents of the IStorage not in correct format +// +#define CONVERT10_E_STG_FMT 0x800401C4L [fail] + +// +// MessageId: CONVERT10_E_STG_NO_STD_STREAM +// +// MessageText: +// +// Contents of IStorage is missing one of the standard streams +// +#define CONVERT10_E_STG_NO_STD_STREAM 0x800401C5L [fail] + +// +// MessageId: CONVERT10_E_STG_DIB_TO_BITMAP +// +// MessageText: +// +// There was an error in a Windows GDI call while converting the DIB to a bitmap. +// +// +#define CONVERT10_E_STG_DIB_TO_BITMAP 0x800401C6L [fail] + +#define CLIPBRD_E_FIRST 0x800401D0L +#define CLIPBRD_E_LAST 0x800401DFL +#define CLIPBRD_S_FIRST 0x000401D0L +#define CLIPBRD_S_LAST 0x000401DFL +// +// MessageId: CLIPBRD_E_CANT_OPEN +// +// MessageText: +// +// OpenClipboard Failed +// +#define CLIPBRD_E_CANT_OPEN 0x800401D0L [fail] + +// +// MessageId: CLIPBRD_E_CANT_EMPTY +// +// MessageText: +// +// EmptyClipboard Failed +// +#define CLIPBRD_E_CANT_EMPTY 0x800401D1L [fail] + +// +// MessageId: CLIPBRD_E_CANT_SET +// +// MessageText: +// +// SetClipboard Failed +// +#define CLIPBRD_E_CANT_SET 0x800401D2L [fail] + +// +// MessageId: CLIPBRD_E_BAD_DATA +// +// MessageText: +// +// Data on clipboard is invalid +// +#define CLIPBRD_E_BAD_DATA 0x800401D3L [fail] + +// +// MessageId: CLIPBRD_E_CANT_CLOSE +// +// MessageText: +// +// CloseClipboard Failed +// +#define CLIPBRD_E_CANT_CLOSE 0x800401D4L [fail] + +#define MK_E_FIRST 0x800401E0L +#define MK_E_LAST 0x800401EFL +#define MK_S_FIRST 0x000401E0L +#define MK_S_LAST 0x000401EFL +// +// MessageId: MK_E_CONNECTMANUALLY +// +// MessageText: +// +// Moniker needs to be connected manually +// +#define MK_E_CONNECTMANUALLY 0x800401E0L [fail] + +// +// MessageId: MK_E_EXCEEDEDDEADLINE +// +// MessageText: +// +// Operation exceeded deadline +// +#define MK_E_EXCEEDEDDEADLINE 0x800401E1L [fail] + +// +// MessageId: MK_E_NEEDGENERIC +// +// MessageText: +// +// Moniker needs to be generic +// +#define MK_E_NEEDGENERIC 0x800401E2L [fail] + +// +// MessageId: MK_E_UNAVAILABLE +// +// MessageText: +// +// Operation unavailable +// +#define MK_E_UNAVAILABLE 0x800401E3L [fail] + +// +// MessageId: MK_E_SYNTAX +// +// MessageText: +// +// Invalid syntax +// +#define MK_E_SYNTAX 0x800401E4L [fail] + +// +// MessageId: MK_E_NOOBJECT +// +// MessageText: +// +// No object for moniker +// +#define MK_E_NOOBJECT 0x800401E5L [fail] + +// +// MessageId: MK_E_INVALIDEXTENSION +// +// MessageText: +// +// Bad extension for file +// +#define MK_E_INVALIDEXTENSION 0x800401E6L [fail] + +// +// MessageId: MK_E_INTERMEDIATEINTERFACENOTSUPPORTED +// +// MessageText: +// +// Intermediate operation failed +// +#define MK_E_INTERMEDIATEINTERFACENOTSUPPORTED 0x800401E7L [fail] + +// +// MessageId: MK_E_NOTBINDABLE +// +// MessageText: +// +// Moniker is not bindable +// +#define MK_E_NOTBINDABLE 0x800401E8L [fail] + +// +// MessageId: MK_E_NOTBOUND +// +// MessageText: +// +// Moniker is not bound +// +#define MK_E_NOTBOUND 0x800401E9L [fail] + +// +// MessageId: MK_E_CANTOPENFILE +// +// MessageText: +// +// Moniker cannot open file +// +#define MK_E_CANTOPENFILE 0x800401EAL [fail] + +// +// MessageId: MK_E_MUSTBOTHERUSER +// +// MessageText: +// +// User input required for operation to succeed +// +#define MK_E_MUSTBOTHERUSER 0x800401EBL [fail] + +// +// MessageId: MK_E_NOINVERSE +// +// MessageText: +// +// Moniker class has no inverse +// +#define MK_E_NOINVERSE 0x800401ECL [fail] + +// +// MessageId: MK_E_NOSTORAGE +// +// MessageText: +// +// Moniker does not refer to storage +// +#define MK_E_NOSTORAGE 0x800401EDL [fail] + +// +// MessageId: MK_E_NOPREFIX +// +// MessageText: +// +// No common prefix +// +#define MK_E_NOPREFIX 0x800401EEL [fail] + +// +// MessageId: MK_E_ENUMERATION_FAILED +// +// MessageText: +// +// Moniker could not be enumerated +// +#define MK_E_ENUMERATION_FAILED 0x800401EFL [fail] + +#define CO_E_FIRST 0x800401F0L +#define CO_E_LAST 0x800401FFL +#define CO_S_FIRST 0x000401F0L +#define CO_S_LAST 0x000401FFL +// +// MessageId: CO_E_NOTINITIALIZED +// +// MessageText: +// +// CoInitialize has not been called. +// +#define CO_E_NOTINITIALIZED 0x800401F0L [fail] + +// +// MessageId: CO_E_ALREADYINITIALIZED +// +// MessageText: +// +// CoInitialize has already been called. +// +#define CO_E_ALREADYINITIALIZED 0x800401F1L [fail] + +// +// MessageId: CO_E_CANTDETERMINECLASS +// +// MessageText: +// +// Class of object cannot be determined +// +#define CO_E_CANTDETERMINECLASS 0x800401F2L [fail] + +// +// MessageId: CO_E_CLASSSTRING +// +// MessageText: +// +// Invalid class string +// +#define CO_E_CLASSSTRING 0x800401F3L [fail] + +// +// MessageId: CO_E_IIDSTRING +// +// MessageText: +// +// Invalid interface string +// +#define CO_E_IIDSTRING 0x800401F4L [fail] + +// +// MessageId: CO_E_APPNOTFOUND +// +// MessageText: +// +// Application not found +// +#define CO_E_APPNOTFOUND 0x800401F5L [fail] + +// +// MessageId: CO_E_APPSINGLEUSE +// +// MessageText: +// +// Application cannot be run more than once +// +#define CO_E_APPSINGLEUSE 0x800401F6L [fail] + +// +// MessageId: CO_E_ERRORINAPP +// +// MessageText: +// +// Some error in application program +// +#define CO_E_ERRORINAPP 0x800401F7L [fail] + +// +// MessageId: CO_E_DLLNOTFOUND +// +// MessageText: +// +// DLL for class not found +// +#define CO_E_DLLNOTFOUND 0x800401F8L [fail] + +// +// MessageId: CO_E_ERRORINDLL +// +// MessageText: +// +// Error in the DLL +// +#define CO_E_ERRORINDLL 0x800401F9L [fail] + +// +// MessageId: CO_E_WRONGOSFORAPP +// +// MessageText: +// +// Wrong OS or OS version for application +// +#define CO_E_WRONGOSFORAPP 0x800401FAL [fail] + +// +// MessageId: CO_E_OBJNOTREG +// +// MessageText: +// +// Object is not registered +// +#define CO_E_OBJNOTREG 0x800401FBL [fail] + +// +// MessageId: CO_E_OBJISREG +// +// MessageText: +// +// Object is already registered +// +#define CO_E_OBJISREG 0x800401FCL [fail] + +// +// MessageId: CO_E_OBJNOTCONNECTED +// +// MessageText: +// +// Object is not connected to server +// +#define CO_E_OBJNOTCONNECTED 0x800401FDL [fail] + +// +// MessageId: CO_E_APPDIDNTREG +// +// MessageText: +// +// Application was launched but it didn't register a class factory +// +#define CO_E_APPDIDNTREG 0x800401FEL [fail] + +// +// MessageId: CO_E_RELEASED +// +// MessageText: +// +// Object has been released +// +#define CO_E_RELEASED 0x800401FFL [fail] + +// +// MessageId: CO_E_FAILEDTOIMPERSONATE +// +// MessageText: +// +// Unable to impersonate DCOM client +// +#define CO_E_FAILEDTOIMPERSONATE 0x80040200L [fail] + +// +// MessageId: CO_E_FAILEDTOGETSECCTX +// +// MessageText: +// +// Unable to obtain server's security context +// +#define CO_E_FAILEDTOGETSECCTX 0x80040201L [fail] + +// +// MessageId: CO_E_FAILEDTOOPENTHREADTOKEN +// +// MessageText: +// +// Unable to open the access token of the current thread +// +#define CO_E_FAILEDTOOPENTHREADTOKEN 0x80040202L [fail] + +// +// MessageId: CO_E_FAILEDTOGETTOKENINFO +// +// MessageText: +// +// Unable to obtain user info from an access token +// +#define CO_E_FAILEDTOGETTOKENINFO 0x80040203L [fail] + +// +// MessageId: CO_E_TRUSTEEDOESNTMATCHCLIENT +// +// MessageText: +// +// The client who called IAccessControl::IsAccessPermitted was the trustee provided tot he method +// +#define CO_E_TRUSTEEDOESNTMATCHCLIENT 0x80040204L [fail] + +// +// MessageId: CO_E_FAILEDTOQUERYCLIENTBLANKET +// +// MessageText: +// +// Unable to obtain the client's security blanket +// +#define CO_E_FAILEDTOQUERYCLIENTBLANKET 0x80040205L [fail] + +// +// MessageId: CO_E_FAILEDTOSETDACL +// +// MessageText: +// +// Unable to set a discretionary ACL into a security descriptor +// +#define CO_E_FAILEDTOSETDACL 0x80040206L [fail] + +// +// MessageId: CO_E_ACCESSCHECKFAILED +// +// MessageText: +// +// The system function, AccessCheck, returned false +// +#define CO_E_ACCESSCHECKFAILED 0x80040207L [fail] + +// +// MessageId: CO_E_NETACCESSAPIFAILED +// +// MessageText: +// +// Either NetAccessDel or NetAccessAdd returned an error code. +// +#define CO_E_NETACCESSAPIFAILED 0x80040208L [fail] + +// +// MessageId: CO_E_WRONGTRUSTEENAMESYNTAX +// +// MessageText: +// +// One of the trustee strings provided by the user did not conform to the <Domain>\<Name> syntax and it was not the "*" string +// +#define CO_E_WRONGTRUSTEENAMESYNTAX 0x80040209L [fail] + +// +// MessageId: CO_E_INVALIDSID +// +// MessageText: +// +// One of the security identifiers provided by the user was invalid +// +#define CO_E_INVALIDSID 0x8004020AL [fail] + +// +// MessageId: CO_E_CONVERSIONFAILED +// +// MessageText: +// +// Unable to convert a wide character trustee string to a multibyte trustee string +// +#define CO_E_CONVERSIONFAILED 0x8004020BL [fail] + +// +// MessageId: CO_E_NOMATCHINGSIDFOUND +// +// MessageText: +// +// Unable to find a security identifier that corresponds to a trustee string provided by the user +// +#define CO_E_NOMATCHINGSIDFOUND 0x8004020CL [fail] + +// +// MessageId: CO_E_LOOKUPACCSIDFAILED +// +// MessageText: +// +// The system function, LookupAccountSID, failed +// +#define CO_E_LOOKUPACCSIDFAILED 0x8004020DL [fail] + +// +// MessageId: CO_E_NOMATCHINGNAMEFOUND +// +// MessageText: +// +// Unable to find a trustee name that corresponds to a security identifier provided by the user +// +#define CO_E_NOMATCHINGNAMEFOUND 0x8004020EL [fail] + +// +// MessageId: CO_E_LOOKUPACCNAMEFAILED +// +// MessageText: +// +// The system function, LookupAccountName, failed +// +#define CO_E_LOOKUPACCNAMEFAILED 0x8004020FL [fail] + +// +// MessageId: CO_E_SETSERLHNDLFAILED +// +// MessageText: +// +// Unable to set or reset a serialization handle +// +#define CO_E_SETSERLHNDLFAILED 0x80040210L [fail] + +// +// MessageId: CO_E_FAILEDTOGETWINDIR +// +// MessageText: +// +// Unable to obtain the Windows directory +// +#define CO_E_FAILEDTOGETWINDIR 0x80040211L [fail] + +// +// MessageId: CO_E_PATHTOOLONG +// +// MessageText: +// +// Path too long +// +#define CO_E_PATHTOOLONG 0x80040212L [fail] + +// +// MessageId: CO_E_FAILEDTOGENUUID +// +// MessageText: +// +// Unable to generate a uuid. +// +#define CO_E_FAILEDTOGENUUID 0x80040213L [fail] + +// +// MessageId: CO_E_FAILEDTOCREATEFILE +// +// MessageText: +// +// Unable to create file +// +#define CO_E_FAILEDTOCREATEFILE 0x80040214L [fail] + +// +// MessageId: CO_E_FAILEDTOCLOSEHANDLE +// +// MessageText: +// +// Unable to close a serialization handle or a file handle. +// +#define CO_E_FAILEDTOCLOSEHANDLE 0x80040215L [fail] + +// +// MessageId: CO_E_EXCEEDSYSACLLIMIT +// +// MessageText: +// +// The number of ACEs in an ACL exceeds the system limit +// +#define CO_E_EXCEEDSYSACLLIMIT 0x80040216L [fail] + +// +// MessageId: CO_E_ACESINWRONGORDER +// +// MessageText: +// +// Not all the DENY_ACCESS ACEs are arranged in front of the GRANT_ACCESS ACEs in the stream +// +#define CO_E_ACESINWRONGORDER 0x80040217L [fail] + +// +// MessageId: CO_E_INCOMPATIBLESTREAMVERSION +// +// MessageText: +// +// The version of ACL format in the stream is not supported by this implementation of IAccessControl +// +#define CO_E_INCOMPATIBLESTREAMVERSION 0x80040218L [fail] + +// +// MessageId: CO_E_FAILEDTOOPENPROCESSTOKEN +// +// MessageText: +// +// Unable to open the access token of the server process +// +#define CO_E_FAILEDTOOPENPROCESSTOKEN 0x80040219L [fail] + +// +// MessageId: CO_E_DECODEFAILED +// +// MessageText: +// +// Unable to decode the ACL in the stream provided by the user +// +#define CO_E_DECODEFAILED 0x8004021AL [fail] + +// +// MessageId: CO_E_ACNOTINITIALIZED +// +// MessageText: +// +// The COM IAccessControl object is not initialized +// +#define CO_E_ACNOTINITIALIZED 0x8004021BL [fail] + +// +// Old OLE Success Codes +// +// +// MessageId: OLE_S_USEREG +// +// MessageText: +// +// Use the registry database to provide the requested information +// +#define OLE_S_USEREG 0x00040000L [fail] + +// +// MessageId: OLE_S_STATIC +// +// MessageText: +// +// Success, but static +// +#define OLE_S_STATIC 0x00040001L [fail] + +// +// MessageId: OLE_S_MAC_CLIPFORMAT +// +// MessageText: +// +// Macintosh clipboard format +// +#define OLE_S_MAC_CLIPFORMAT 0x00040002L [fail] + +// +// MessageId: DRAGDROP_S_DROP +// +// MessageText: +// +// Successful drop took place +// +#define DRAGDROP_S_DROP 0x00040100L [fail] + +// +// MessageId: DRAGDROP_S_CANCEL +// +// MessageText: +// +// Drag-drop operation canceled +// +#define DRAGDROP_S_CANCEL 0x00040101L [fail] + +// +// MessageId: DRAGDROP_S_USEDEFAULTCURSORS +// +// MessageText: +// +// Use the default cursor +// +#define DRAGDROP_S_USEDEFAULTCURSORS 0x00040102L [fail] + +// +// MessageId: DATA_S_SAMEFORMATETC +// +// MessageText: +// +// Data has same FORMATETC +// +#define DATA_S_SAMEFORMATETC 0x00040130L [fail] + +// +// MessageId: VIEW_S_ALREADY_FROZEN +// +// MessageText: +// +// View is already frozen +// +#define VIEW_S_ALREADY_FROZEN 0x00040140L [fail] + +// +// MessageId: CACHE_S_FORMATETC_NOTSUPPORTED +// +// MessageText: +// +// FORMATETC not supported +// +#define CACHE_S_FORMATETC_NOTSUPPORTED 0x00040170L [fail] + +// +// MessageId: CACHE_S_SAMECACHE +// +// MessageText: +// +// Same cache +// +#define CACHE_S_SAMECACHE 0x00040171L [fail] + +// +// MessageId: CACHE_S_SOMECACHES_NOTUPDATED +// +// MessageText: +// +// Some cache(s) not updated +// +#define CACHE_S_SOMECACHES_NOTUPDATED 0x00040172L [fail] + +// +// MessageId: OLEOBJ_S_INVALIDVERB +// +// MessageText: +// +// Invalid verb for OLE object +// +#define OLEOBJ_S_INVALIDVERB 0x00040180L [fail] + +// +// MessageId: OLEOBJ_S_CANNOT_DOVERB_NOW +// +// MessageText: +// +// Verb number is valid but verb cannot be done now +// +#define OLEOBJ_S_CANNOT_DOVERB_NOW 0x00040181L [fail] + +// +// MessageId: OLEOBJ_S_INVALIDHWND +// +// MessageText: +// +// Invalid window handle passed +// +#define OLEOBJ_S_INVALIDHWND 0x00040182L [fail] + +// +// MessageId: INPLACE_S_TRUNCATED +// +// MessageText: +// +// Message is too long; some of it had to be truncated before displaying +// +#define INPLACE_S_TRUNCATED 0x000401A0L [fail] + +// +// MessageId: CONVERT10_S_NO_PRESENTATION +// +// MessageText: +// +// Unable to convert OLESTREAM to IStorage +// +#define CONVERT10_S_NO_PRESENTATION 0x000401C0L [fail] + +// +// MessageId: MK_S_REDUCED_TO_SELF +// +// MessageText: +// +// Moniker reduced to itself +// +#define MK_S_REDUCED_TO_SELF 0x000401E2L [fail] + +// +// MessageId: MK_S_ME +// +// MessageText: +// +// Common prefix is this moniker +// +#define MK_S_ME 0x000401E4L [fail] + +// +// MessageId: MK_S_HIM +// +// MessageText: +// +// Common prefix is input moniker +// +#define MK_S_HIM 0x000401E5L [fail] + +// +// MessageId: MK_S_US +// +// MessageText: +// +// Common prefix is both monikers +// +#define MK_S_US 0x000401E6L [fail] + +// +// MessageId: MK_S_MONIKERALREADYREGISTERED +// +// MessageText: +// +// Moniker is already registered in running object table +// +#define MK_S_MONIKERALREADYREGISTERED 0x000401E7L [fail] + +// ****************** +// FACILITY_WINDOWS +// ****************** +// +// Codes 0x0-0x01ff are reserved for the OLE group of +// interfaces. +// +// +// MessageId: CO_E_CLASS_CREATE_FAILED +// +// MessageText: +// +// Attempt to create a class object failed +// +#define CO_E_CLASS_CREATE_FAILED 0x80080001L [fail] + +// +// MessageId: CO_E_SCM_ERROR +// +// MessageText: +// +// OLE service could not bind object +// +#define CO_E_SCM_ERROR 0x80080002L [fail] + +// +// MessageId: CO_E_SCM_RPC_FAILURE +// +// MessageText: +// +// RPC communication failed with OLE service +// +#define CO_E_SCM_RPC_FAILURE 0x80080003L [fail] + +// +// MessageId: CO_E_BAD_PATH +// +// MessageText: +// +// Bad path to object +// +#define CO_E_BAD_PATH 0x80080004L [fail] + +// +// MessageId: CO_E_SERVER_EXEC_FAILURE +// +// MessageText: +// +// Server execution failed +// +#define CO_E_SERVER_EXEC_FAILURE 0x80080005L [fail] + +// +// MessageId: CO_E_OBJSRV_RPC_FAILURE +// +// MessageText: +// +// OLE service could not communicate with the object server +// +#define CO_E_OBJSRV_RPC_FAILURE 0x80080006L [fail] + +// +// MessageId: MK_E_NO_NORMALIZED +// +// MessageText: +// +// Moniker path could not be normalized +// +#define MK_E_NO_NORMALIZED 0x80080007L [fail] + +// +// MessageId: CO_E_SERVER_STOPPING +// +// MessageText: +// +// Object server is stopping when OLE service contacts it +// +#define CO_E_SERVER_STOPPING 0x80080008L [fail] + +// +// MessageId: MEM_E_INVALID_ROOT +// +// MessageText: +// +// An invalid root block pointer was specified +// +#define MEM_E_INVALID_ROOT 0x80080009L [fail] + +// +// MessageId: MEM_E_INVALID_LINK +// +// MessageText: +// +// An allocation chain contained an invalid link pointer +// +#define MEM_E_INVALID_LINK 0x80080010L [fail] + +// +// MessageId: MEM_E_INVALID_SIZE +// +// MessageText: +// +// The requested allocation size was too large +// +#define MEM_E_INVALID_SIZE 0x80080011L [fail] + +// +// MessageId: CO_S_NOTALLINTERFACES +// +// MessageText: +// +// Not all the requested interfaces were available +// +#define CO_S_NOTALLINTERFACES 0x00080012L [fail] + +// ****************** +// FACILITY_DISPATCH +// ****************** +// +// MessageId: DISP_E_UNKNOWNINTERFACE +// +// MessageText: +// +// Unknown interface. +// +#define DISP_E_UNKNOWNINTERFACE 0x80020001L [fail] + +// +// MessageId: DISP_E_MEMBERNOTFOUND +// +// MessageText: +// +// Member not found. +// +#define DISP_E_MEMBERNOTFOUND 0x80020003L [fail] + +// +// MessageId: DISP_E_PARAMNOTFOUND +// +// MessageText: +// +// Parameter not found. +// +#define DISP_E_PARAMNOTFOUND 0x80020004L [fail] + +// +// MessageId: DISP_E_TYPEMISMATCH +// +// MessageText: +// +// Type mismatch. +// +#define DISP_E_TYPEMISMATCH 0x80020005L [fail] + +// +// MessageId: DISP_E_UNKNOWNNAME +// +// MessageText: +// +// Unknown name. +// +#define DISP_E_UNKNOWNNAME 0x80020006L [fail] + +// +// MessageId: DISP_E_NONAMEDARGS +// +// MessageText: +// +// No named arguments. +// +#define DISP_E_NONAMEDARGS 0x80020007L [fail] + +// +// MessageId: DISP_E_BADVARTYPE +// +// MessageText: +// +// Bad variable type. +// +#define DISP_E_BADVARTYPE 0x80020008L [fail] + +// +// MessageId: DISP_E_EXCEPTION +// +// MessageText: +// +// Exception occurred. +// +#define DISP_E_EXCEPTION 0x80020009L [fail] + +// +// MessageId: DISP_E_OVERFLOW +// +// MessageText: +// +// Out of present range. +// +#define DISP_E_OVERFLOW 0x8002000AL [fail] + +// +// MessageId: DISP_E_BADINDEX +// +// MessageText: +// +// Invalid index. +// +#define DISP_E_BADINDEX 0x8002000BL [fail] + +// +// MessageId: DISP_E_UNKNOWNLCID +// +// MessageText: +// +// Unknown language. +// +#define DISP_E_UNKNOWNLCID 0x8002000CL [fail] + +// +// MessageId: DISP_E_ARRAYISLOCKED +// +// MessageText: +// +// Memory is locked. +// +#define DISP_E_ARRAYISLOCKED 0x8002000DL [fail] + +// +// MessageId: DISP_E_BADPARAMCOUNT +// +// MessageText: +// +// Invalid number of parameters. +// +#define DISP_E_BADPARAMCOUNT 0x8002000EL [fail] + +// +// MessageId: DISP_E_PARAMNOTOPTIONAL +// +// MessageText: +// +// Parameter not optional. +// +#define DISP_E_PARAMNOTOPTIONAL 0x8002000FL [fail] + +// +// MessageId: DISP_E_BADCALLEE +// +// MessageText: +// +// Invalid callee. +// +#define DISP_E_BADCALLEE 0x80020010L [fail] + +// +// MessageId: DISP_E_NOTACOLLECTION +// +// MessageText: +// +// Does not support a collection. +// +#define DISP_E_NOTACOLLECTION 0x80020011L [fail] + +// +// MessageId: DISP_E_DIVBYZERO +// +// MessageText: +// +// Division by zero. +// +#define DISP_E_DIVBYZERO 0x80020012L [fail] + +// +// MessageId: TYPE_E_BUFFERTOOSMALL +// +// MessageText: +// +// Buffer too small. +// +#define TYPE_E_BUFFERTOOSMALL 0x80028016L [fail] + +// +// MessageId: TYPE_E_FIELDNOTFOUND +// +// MessageText: +// +// Field name not defined in the record. +// +#define TYPE_E_FIELDNOTFOUND 0x80028017L [fail] + +// +// MessageId: TYPE_E_INVDATAREAD +// +// MessageText: +// +// Old format or invalid type library. +// +#define TYPE_E_INVDATAREAD 0x80028018L [fail] + +// +// MessageId: TYPE_E_UNSUPFORMAT +// +// MessageText: +// +// Old format or invalid type library. +// +#define TYPE_E_UNSUPFORMAT 0x80028019L [fail] + +// +// MessageId: TYPE_E_REGISTRYACCESS +// +// MessageText: +// +// Error accessing the OLE registry. +// +#define TYPE_E_REGISTRYACCESS 0x8002801CL [fail] + +// +// MessageId: TYPE_E_LIBNOTREGISTERED +// +// MessageText: +// +// Library not registered. +// +#define TYPE_E_LIBNOTREGISTERED 0x8002801DL [fail] + +// +// MessageId: TYPE_E_UNDEFINEDTYPE +// +// MessageText: +// +// Bound to unknown type. +// +#define TYPE_E_UNDEFINEDTYPE 0x80028027L [fail] + +// +// MessageId: TYPE_E_QUALIFIEDNAMEDISALLOWED +// +// MessageText: +// +// Qualified name disallowed. +// +#define TYPE_E_QUALIFIEDNAMEDISALLOWED 0x80028028L [fail] + +// +// MessageId: TYPE_E_INVALIDSTATE +// +// MessageText: +// +// Invalid forward reference, or reference to uncompiled type. +// +#define TYPE_E_INVALIDSTATE 0x80028029L [fail] + +// +// MessageId: TYPE_E_WRONGTYPEKIND +// +// MessageText: +// +// Type mismatch. +// +#define TYPE_E_WRONGTYPEKIND 0x8002802AL [fail] + +// +// MessageId: TYPE_E_ELEMENTNOTFOUND +// +// MessageText: +// +// Element not found. +// +#define TYPE_E_ELEMENTNOTFOUND 0x8002802BL [fail] + +// +// MessageId: TYPE_E_AMBIGUOUSNAME +// +// MessageText: +// +// Ambiguous name. +// +#define TYPE_E_AMBIGUOUSNAME 0x8002802CL [fail] + +// +// MessageId: TYPE_E_NAMECONFLICT +// +// MessageText: +// +// Name already exists in the library. +// +#define TYPE_E_NAMECONFLICT 0x8002802DL [fail] + +// +// MessageId: TYPE_E_UNKNOWNLCID +// +// MessageText: +// +// Unknown LCID. +// +#define TYPE_E_UNKNOWNLCID 0x8002802EL [fail] + +// +// MessageId: TYPE_E_DLLFUNCTIONNOTFOUND +// +// MessageText: +// +// Function not defined in specified DLL. +// +#define TYPE_E_DLLFUNCTIONNOTFOUND 0x8002802FL [fail] + +// +// MessageId: TYPE_E_BADMODULEKIND +// +// MessageText: +// +// Wrong module kind for the operation. +// +#define TYPE_E_BADMODULEKIND 0x800288BDL [fail] + +// +// MessageId: TYPE_E_SIZETOOBIG +// +// MessageText: +// +// Size may not exceed 64K. +// +#define TYPE_E_SIZETOOBIG 0x800288C5L [fail] + +// +// MessageId: TYPE_E_DUPLICATEID +// +// MessageText: +// +// Duplicate ID in inheritance hierarchy. +// +#define TYPE_E_DUPLICATEID 0x800288C6L [fail] + +// +// MessageId: TYPE_E_INVALIDID +// +// MessageText: +// +// Incorrect inheritance depth in standard OLE hmember. +// +#define TYPE_E_INVALIDID 0x800288CFL [fail] + +// +// MessageId: TYPE_E_TYPEMISMATCH +// +// MessageText: +// +// Type mismatch. +// +#define TYPE_E_TYPEMISMATCH 0x80028CA0L [fail] + +// +// MessageId: TYPE_E_OUTOFBOUNDS +// +// MessageText: +// +// Invalid number of arguments. +// +#define TYPE_E_OUTOFBOUNDS 0x80028CA1L [fail] + +// +// MessageId: TYPE_E_IOERROR +// +// MessageText: +// +// I/O Error. +// +#define TYPE_E_IOERROR 0x80028CA2L [fail] + +// +// MessageId: TYPE_E_CANTCREATETMPFILE +// +// MessageText: +// +// Error creating unique tmp file. +// +#define TYPE_E_CANTCREATETMPFILE 0x80028CA3L [fail] + +// +// MessageId: TYPE_E_CANTLOADLIBRARY +// +// MessageText: +// +// Error loading type library/DLL. +// +#define TYPE_E_CANTLOADLIBRARY 0x80029C4AL [fail] + +// +// MessageId: TYPE_E_INCONSISTENTPROPFUNCS +// +// MessageText: +// +// Inconsistent property functions. +// +#define TYPE_E_INCONSISTENTPROPFUNCS 0x80029C83L [fail] + +// +// MessageId: TYPE_E_CIRCULARTYPE +// +// MessageText: +// +// Circular dependency between types/modules. +// +#define TYPE_E_CIRCULARTYPE 0x80029C84L [fail] + +// ****************** +// FACILITY_STORAGE +// ****************** +// +// MessageId: STG_E_INVALIDFUNCTION +// +// MessageText: +// +// Unable to perform requested operation. +// +#define STG_E_INVALIDFUNCTION 0x80030001L [fail] + +// +// MessageId: STG_E_FILENOTFOUND +// +// MessageText: +// +// %1 could not be found. +// +#define STG_E_FILENOTFOUND 0x80030002L [fail] + +// +// MessageId: STG_E_PATHNOTFOUND +// +// MessageText: +// +// The path %1 could not be found. +// +#define STG_E_PATHNOTFOUND 0x80030003L [fail] + +// +// MessageId: STG_E_TOOMANYOPENFILES +// +// MessageText: +// +// There are insufficient resources to open another file. +// +#define STG_E_TOOMANYOPENFILES 0x80030004L [fail] + +// +// MessageId: STG_E_ACCESSDENIED +// +// MessageText: +// +// Access Denied. +// +#define STG_E_ACCESSDENIED 0x80030005L [fail] + +// +// MessageId: STG_E_INVALIDHANDLE +// +// MessageText: +// +// Attempted an operation on an invalid object. +// +#define STG_E_INVALIDHANDLE 0x80030006L [fail] + +// +// MessageId: STG_E_INSUFFICIENTMEMORY +// +// MessageText: +// +// There is insufficient memory available to complete operation. +// +#define STG_E_INSUFFICIENTMEMORY 0x80030008L [fail] + +// +// MessageId: STG_E_INVALIDPOINTER +// +// MessageText: +// +// Invalid pointer error. +// +#define STG_E_INVALIDPOINTER 0x80030009L [fail] + +// +// MessageId: STG_E_NOMOREFILES +// +// MessageText: +// +// There are no more entries to return. +// +#define STG_E_NOMOREFILES 0x80030012L [fail] + +// +// MessageId: STG_E_DISKISWRITEPROTECTED +// +// MessageText: +// +// Disk is write-protected. +// +#define STG_E_DISKISWRITEPROTECTED 0x80030013L [fail] + +// +// MessageId: STG_E_SEEKERROR +// +// MessageText: +// +// An error occurred during a seek operation. +// +#define STG_E_SEEKERROR 0x80030019L [fail] + +// +// MessageId: STG_E_WRITEFAULT +// +// MessageText: +// +// A disk error occurred during a write operation. +// +#define STG_E_WRITEFAULT 0x8003001DL [fail] + +// +// MessageId: STG_E_READFAULT +// +// MessageText: +// +// A disk error occurred during a read operation. +// +#define STG_E_READFAULT 0x8003001EL [fail] + +// +// MessageId: STG_E_SHAREVIOLATION +// +// MessageText: +// +// A share violation has occurred. +// +#define STG_E_SHAREVIOLATION 0x80030020L [fail] + +// +// MessageId: STG_E_LOCKVIOLATION +// +// MessageText: +// +// A lock violation has occurred. +// +#define STG_E_LOCKVIOLATION 0x80030021L [fail] + +// +// MessageId: STG_E_FILEALREADYEXISTS +// +// MessageText: +// +// %1 already exists. +// +#define STG_E_FILEALREADYEXISTS 0x80030050L [fail] + +// +// MessageId: STG_E_INVALIDPARAMETER +// +// MessageText: +// +// Invalid parameter error. +// +#define STG_E_INVALIDPARAMETER 0x80030057L [fail] + +// +// MessageId: STG_E_MEDIUMFULL +// +// MessageText: +// +// There is insufficient disk space to complete operation. +// +#define STG_E_MEDIUMFULL 0x80030070L [fail] + +// +// MessageId: STG_E_PROPSETMISMATCHED +// +// MessageText: +// +// Illegal write of non-simple property to simple property set. +// +#define STG_E_PROPSETMISMATCHED 0x800300F0L [fail] + +// +// MessageId: STG_E_ABNORMALAPIEXIT +// +// MessageText: +// +// An API call exited abnormally. +// +#define STG_E_ABNORMALAPIEXIT 0x800300FAL [fail] + +// +// MessageId: STG_E_INVALIDHEADER +// +// MessageText: +// +// The file %1 is not a valid compound file. +// +#define STG_E_INVALIDHEADER 0x800300FBL [fail] + +// +// MessageId: STG_E_INVALIDNAME +// +// MessageText: +// +// The name %1 is not valid. +// +#define STG_E_INVALIDNAME 0x800300FCL [fail] + +// +// MessageId: STG_E_UNKNOWN +// +// MessageText: +// +// An unexpected error occurred. +// +#define STG_E_UNKNOWN 0x800300FDL [fail] + +// +// MessageId: STG_E_UNIMPLEMENTEDFUNCTION +// +// MessageText: +// +// That function is not implemented. +// +#define STG_E_UNIMPLEMENTEDFUNCTION 0x800300FEL [fail] + +// +// MessageId: STG_E_INVALIDFLAG +// +// MessageText: +// +// Invalid flag error. +// +#define STG_E_INVALIDFLAG 0x800300FFL [fail] + +// +// MessageId: STG_E_INUSE +// +// MessageText: +// +// Attempted to use an object that is busy. +// +#define STG_E_INUSE 0x80030100L [fail] + +// +// MessageId: STG_E_NOTCURRENT +// +// MessageText: +// +// The storage has been changed since the last commit. +// +#define STG_E_NOTCURRENT 0x80030101L [fail] + +// +// MessageId: STG_E_REVERTED +// +// MessageText: +// +// Attempted to use an object that has ceased to exist. +// +#define STG_E_REVERTED 0x80030102L [fail] + +// +// MessageId: STG_E_CANTSAVE +// +// MessageText: +// +// Can't save. +// +#define STG_E_CANTSAVE 0x80030103L [fail] + +// +// MessageId: STG_E_OLDFORMAT +// +// MessageText: +// +// The compound file %1 was produced with an incompatible version of storage. +// +#define STG_E_OLDFORMAT 0x80030104L [fail] + +// +// MessageId: STG_E_OLDDLL +// +// MessageText: +// +// The compound file %1 was produced with a newer version of storage. +// +#define STG_E_OLDDLL 0x80030105L [fail] + +// +// MessageId: STG_E_SHAREREQUIRED +// +// MessageText: +// +// Share.exe or equivalent is required for operation. +// +#define STG_E_SHAREREQUIRED 0x80030106L [fail] + +// +// MessageId: STG_E_NOTFILEBASEDSTORAGE +// +// MessageText: +// +// Illegal operation called on non-file based storage. +// +#define STG_E_NOTFILEBASEDSTORAGE 0x80030107L [fail] + +// +// MessageId: STG_E_EXTANTMARSHALLINGS +// +// MessageText: +// +// Illegal operation called on object with extant marshallings. +// +#define STG_E_EXTANTMARSHALLINGS 0x80030108L [fail] + +// +// MessageId: STG_E_DOCFILECORRUPT +// +// MessageText: +// +// The docfile has been corrupted. +// +#define STG_E_DOCFILECORRUPT 0x80030109L [fail] + +// +// MessageId: STG_E_BADBASEADDRESS +// +// MessageText: +// +// OLE32.DLL has been loaded at the wrong address. +// +#define STG_E_BADBASEADDRESS 0x80030110L [fail] + +// +// MessageId: STG_E_INCOMPLETE +// +// MessageText: +// +// The file download was aborted abnormally. The file is incomplete. +// +#define STG_E_INCOMPLETE 0x80030201L [fail] + +// +// MessageId: STG_E_TERMINATED +// +// MessageText: +// +// The file download has been terminated. +// +#define STG_E_TERMINATED 0x80030202L [fail] + +// +// MessageId: STG_S_CONVERTED +// +// MessageText: +// +// The underlying file was converted to compound file format. +// +#define STG_S_CONVERTED 0x00030200L [fail] + +// +// MessageId: STG_S_BLOCK +// +// MessageText: +// +// The storage operation should block until more data is available. +// +#define STG_S_BLOCK 0x00030201L [fail] + +// +// MessageId: STG_S_RETRYNOW +// +// MessageText: +// +// The storage operation should retry immediately. +// +#define STG_S_RETRYNOW 0x00030202L [fail] + +// +// MessageId: STG_S_MONITORING +// +// MessageText: +// +// The notified event sink will not influence the storage operation. +// +#define STG_S_MONITORING 0x00030203L [fail] + +// +// MessageId: STG_S_MULTIPLEOPENS +// +// MessageText: +// +// Multiple opens prevent consolidated. (commit succeeded). +// +#define STG_S_MULTIPLEOPENS 0x00030204L [fail] + +// +// MessageId: STG_S_CONSOLIDATIONFAILED +// +// MessageText: +// +// Consolidation of the storage file failed. (commit succeeded). +// +#define STG_S_CONSOLIDATIONFAILED 0x00030205L [fail] + +// +// MessageId: STG_S_CANNOTCONSOLIDATE +// +// MessageText: +// +// Consolidation of the storage file is inappropriate. (commit succeeded). +// +#define STG_S_CANNOTCONSOLIDATE 0x00030206L [fail] + +// ****************** +// FACILITY_RPC +// ****************** +// +// Codes 0x0-0x11 are propagated from 16 bit OLE. +// +// +// MessageId: RPC_E_CALL_REJECTED +// +// MessageText: +// +// Call was rejected by callee. +// +#define RPC_E_CALL_REJECTED 0x80010001L [fail] + +// +// MessageId: RPC_E_CALL_CANCELED +// +// MessageText: +// +// Call was canceled by the message filter. +// +#define RPC_E_CALL_CANCELED 0x80010002L [fail] + +// +// MessageId: RPC_E_CANTPOST_INSENDCALL +// +// MessageText: +// +// The caller is dispatching an intertask SendMessage call and +// cannot call out via PostMessage. +// +#define RPC_E_CANTPOST_INSENDCALL 0x80010003L [fail] + +// +// MessageId: RPC_E_CANTCALLOUT_INASYNCCALL +// +// MessageText: +// +// The caller is dispatching an asynchronous call and cannot +// make an outgoing call on behalf of this call. +// +#define RPC_E_CANTCALLOUT_INASYNCCALL 0x80010004L [fail] + +// +// MessageId: RPC_E_CANTCALLOUT_INEXTERNALCALL +// +// MessageText: +// +// It is illegal to call out while inside message filter. +// +#define RPC_E_CANTCALLOUT_INEXTERNALCALL 0x80010005L [fail] + +// +// MessageId: RPC_E_CONNECTION_TERMINATED +// +// MessageText: +// +// The connection terminated or is in a bogus state +// and cannot be used any more. Other connections +// are still valid. +// +#define RPC_E_CONNECTION_TERMINATED 0x80010006L [fail] + +// +// MessageId: RPC_E_SERVER_DIED +// +// MessageText: +// +// The callee (server [not server application]) is not available +// and disappeared; all connections are invalid. The call may +// have executed. +// +#define RPC_E_SERVER_DIED 0x80010007L [fail] + +// +// MessageId: RPC_E_CLIENT_DIED +// +// MessageText: +// +// The caller (client) disappeared while the callee (server) was +// processing a call. +// +#define RPC_E_CLIENT_DIED 0x80010008L [fail] + +// +// MessageId: RPC_E_INVALID_DATAPACKET +// +// MessageText: +// +// The data packet with the marshalled parameter data is incorrect. +// +#define RPC_E_INVALID_DATAPACKET 0x80010009L [fail] + +// +// MessageId: RPC_E_CANTTRANSMIT_CALL +// +// MessageText: +// +// The call was not transmitted properly; the message queue +// was full and was not emptied after yielding. +// +#define RPC_E_CANTTRANSMIT_CALL 0x8001000AL [fail] + +// +// MessageId: RPC_E_CLIENT_CANTMARSHAL_DATA +// +// MessageText: +// +// The client (caller) cannot marshall the parameter data - low memory, etc. +// +#define RPC_E_CLIENT_CANTMARSHAL_DATA 0x8001000BL [fail] + +// +// MessageId: RPC_E_CLIENT_CANTUNMARSHAL_DATA +// +// MessageText: +// +// The client (caller) cannot unmarshall the return data - low memory, etc. +// +#define RPC_E_CLIENT_CANTUNMARSHAL_DATA 0x8001000CL [fail] + +// +// MessageId: RPC_E_SERVER_CANTMARSHAL_DATA +// +// MessageText: +// +// The server (callee) cannot marshall the return data - low memory, etc. +// +#define RPC_E_SERVER_CANTMARSHAL_DATA 0x8001000DL [fail] + +// +// MessageId: RPC_E_SERVER_CANTUNMARSHAL_DATA +// +// MessageText: +// +// The server (callee) cannot unmarshall the parameter data - low memory, etc. +// +#define RPC_E_SERVER_CANTUNMARSHAL_DATA 0x8001000EL [fail] + +// +// MessageId: RPC_E_INVALID_DATA +// +// MessageText: +// +// Received data is invalid; could be server or client data. +// +#define RPC_E_INVALID_DATA 0x8001000FL [fail] + +// +// MessageId: RPC_E_INVALID_PARAMETER +// +// MessageText: +// +// A particular parameter is invalid and cannot be (un)marshalled. +// +#define RPC_E_INVALID_PARAMETER 0x80010010L [fail] + +// +// MessageId: RPC_E_CANTCALLOUT_AGAIN +// +// MessageText: +// +// There is no second outgoing call on same channel in DDE conversation. +// +#define RPC_E_CANTCALLOUT_AGAIN 0x80010011L [fail] + +// +// MessageId: RPC_E_SERVER_DIED_DNE +// +// MessageText: +// +// The callee (server [not server application]) is not available +// and disappeared; all connections are invalid. The call did not execute. +// +#define RPC_E_SERVER_DIED_DNE 0x80010012L [fail] + +// +// MessageId: RPC_E_SYS_CALL_FAILED +// +// MessageText: +// +// System call failed. +// +#define RPC_E_SYS_CALL_FAILED 0x80010100L [fail] + +// +// MessageId: RPC_E_OUT_OF_RESOURCES +// +// MessageText: +// +// Could not allocate some required resource (memory, events, ...) +// +#define RPC_E_OUT_OF_RESOURCES 0x80010101L [fail] + +// +// MessageId: RPC_E_ATTEMPTED_MULTITHREAD +// +// MessageText: +// +// Attempted to make calls on more than one thread in single threaded mode. +// +#define RPC_E_ATTEMPTED_MULTITHREAD 0x80010102L [fail] + +// +// MessageId: RPC_E_NOT_REGISTERED +// +// MessageText: +// +// The requested interface is not registered on the server object. +// +#define RPC_E_NOT_REGISTERED 0x80010103L [fail] + +// +// MessageId: RPC_E_FAULT +// +// MessageText: +// +// RPC could not call the server or could not return the results of calling the server. +// +#define RPC_E_FAULT 0x80010104L [fail] + +// +// MessageId: RPC_E_SERVERFAULT +// +// MessageText: +// +// The server threw an exception. +// +#define RPC_E_SERVERFAULT 0x80010105L [fail] + +// +// MessageId: RPC_E_CHANGED_MODE +// +// MessageText: +// +// Cannot change thread mode after it is set. +// +#define RPC_E_CHANGED_MODE 0x80010106L [fail] + +// +// MessageId: RPC_E_INVALIDMETHOD +// +// MessageText: +// +// The method called does not exist on the server. +// +#define RPC_E_INVALIDMETHOD 0x80010107L [fail] + +// +// MessageId: RPC_E_DISCONNECTED +// +// MessageText: +// +// The object invoked has disconnected from its clients. +// +#define RPC_E_DISCONNECTED 0x80010108L [fail] + +// +// MessageId: RPC_E_RETRY +// +// MessageText: +// +// The object invoked chose not to process the call now. Try again later. +// +#define RPC_E_RETRY 0x80010109L [fail] + +// +// MessageId: RPC_E_SERVERCALL_RETRYLATER +// +// MessageText: +// +// The message filter indicated that the application is busy. +// +#define RPC_E_SERVERCALL_RETRYLATER 0x8001010AL [fail] + +// +// MessageId: RPC_E_SERVERCALL_REJECTED +// +// MessageText: +// +// The message filter rejected the call. +// +#define RPC_E_SERVERCALL_REJECTED 0x8001010BL [fail] + +// +// MessageId: RPC_E_INVALID_CALLDATA +// +// MessageText: +// +// A call control interfaces was called with invalid data. +// +#define RPC_E_INVALID_CALLDATA 0x8001010CL [fail] + +// +// MessageId: RPC_E_CANTCALLOUT_ININPUTSYNCCALL +// +// MessageText: +// +// An outgoing call cannot be made since the application is dispatching an input-synchronous call. +// +#define RPC_E_CANTCALLOUT_ININPUTSYNCCALL 0x8001010DL [fail] + +// +// MessageId: RPC_E_WRONG_THREAD +// +// MessageText: +// +// The application called an interface that was marshalled for a different thread. +// +#define RPC_E_WRONG_THREAD 0x8001010EL [fail] + +// +// MessageId: RPC_E_THREAD_NOT_INIT +// +// MessageText: +// +// CoInitialize has not been called on the current thread. +// +#define RPC_E_THREAD_NOT_INIT 0x8001010FL [fail] + +// +// MessageId: RPC_E_VERSION_MISMATCH +// +// MessageText: +// +// The version of OLE on the client and server machines does not match. +// +#define RPC_E_VERSION_MISMATCH 0x80010110L [fail] + +// +// MessageId: RPC_E_INVALID_HEADER +// +// MessageText: +// +// OLE received a packet with an invalid header. +// +#define RPC_E_INVALID_HEADER 0x80010111L [fail] + +// +// MessageId: RPC_E_INVALID_EXTENSION +// +// MessageText: +// +// OLE received a packet with an invalid extension. +// +#define RPC_E_INVALID_EXTENSION 0x80010112L [fail] + +// +// MessageId: RPC_E_INVALID_IPID +// +// MessageText: +// +// The requested object or interface does not exist. +// +#define RPC_E_INVALID_IPID 0x80010113L [fail] + +// +// MessageId: RPC_E_INVALID_OBJECT +// +// MessageText: +// +// The requested object does not exist. +// +#define RPC_E_INVALID_OBJECT 0x80010114L [fail] + +// +// MessageId: RPC_S_CALLPENDING +// +// MessageText: +// +// OLE has sent a request and is waiting for a reply. +// +#define RPC_S_CALLPENDING 0x80010115L [fail] + +// +// MessageId: RPC_S_WAITONTIMER +// +// MessageText: +// +// OLE is waiting before retrying a request. +// +#define RPC_S_WAITONTIMER 0x80010116L [fail] + +// +// MessageId: RPC_E_CALL_COMPLETE +// +// MessageText: +// +// Call context cannot be accessed after call completed. +// +#define RPC_E_CALL_COMPLETE 0x80010117L [fail] + +// +// MessageId: RPC_E_UNSECURE_CALL +// +// MessageText: +// +// Impersonate on unsecure calls is not supported. +// +#define RPC_E_UNSECURE_CALL 0x80010118L [fail] + +// +// MessageId: RPC_E_TOO_LATE +// +// MessageText: +// +// Security must be initialized before any interfaces are marshalled or +// unmarshalled. It cannot be changed once initialized. +// +#define RPC_E_TOO_LATE 0x80010119L [fail] + +// +// MessageId: RPC_E_NO_GOOD_SECURITY_PACKAGES +// +// MessageText: +// +// No security packages are installed on this machine or the user is not logged +// on or there are no compatible security packages between the client and server. +// +#define RPC_E_NO_GOOD_SECURITY_PACKAGES 0x8001011AL [fail] + +// +// MessageId: RPC_E_ACCESS_DENIED +// +// MessageText: +// +// Access is denied. +// +#define RPC_E_ACCESS_DENIED 0x8001011BL [fail] + +// +// MessageId: RPC_E_REMOTE_DISABLED +// +// MessageText: +// +// Remote calls are not allowed for this process. +// +#define RPC_E_REMOTE_DISABLED 0x8001011CL [fail] + +// +// MessageId: RPC_E_INVALID_OBJREF +// +// MessageText: +// +// The marshaled interface data packet (OBJREF) has an invalid or unknown format. +// +#define RPC_E_INVALID_OBJREF 0x8001011DL [fail] + +// +// MessageId: RPC_E_NO_CONTEXT +// +// MessageText: +// +// No context is associated with this call. This happens for some custom +// marshalled calls and on the client side of the call. +// +#define RPC_E_NO_CONTEXT 0x8001011EL [fail] + +// +// MessageId: RPC_E_TIMEOUT +// +// MessageText: +// +// This operation returned because the timeout period expired. +// +#define RPC_E_TIMEOUT 0x8001011FL [fail] + +// +// MessageId: RPC_E_NO_SYNC +// +// MessageText: +// +// There are no synchronize objects to wait on. +// +#define RPC_E_NO_SYNC 0x80010120L [fail] + +// +// MessageId: RPC_E_UNEXPECTED +// +// MessageText: +// +// An internal error occurred. +// +#define RPC_E_UNEXPECTED 0x8001FFFFL [fail] + + + ///////////////// + // + // FACILITY_SSPI + // + ///////////////// + +// +// MessageId: NTE_BAD_UID +// +// MessageText: +// +// Bad UID. +// +#define NTE_BAD_UID 0x80090001L [fail] + +// +// MessageId: NTE_BAD_HASH +// +// MessageText: +// +// Bad Hash. +// +#define NTE_BAD_HASH 0x80090002L [fail] + +// +// MessageId: NTE_BAD_KEY +// +// MessageText: +// +// Bad Key. +// +#define NTE_BAD_KEY 0x80090003L [fail] + +// +// MessageId: NTE_BAD_LEN +// +// MessageText: +// +// Bad Length. +// +#define NTE_BAD_LEN 0x80090004L [fail] + +// +// MessageId: NTE_BAD_DATA +// +// MessageText: +// +// Bad Data. +// +#define NTE_BAD_DATA 0x80090005L [fail] + +// +// MessageId: NTE_BAD_SIGNATURE +// +// MessageText: +// +// Invalid Signature. +// +#define NTE_BAD_SIGNATURE 0x80090006L [fail] + +// +// MessageId: NTE_BAD_VER +// +// MessageText: +// +// Bad Version of provider. +// +#define NTE_BAD_VER 0x80090007L [fail] + +// +// MessageId: NTE_BAD_ALGID +// +// MessageText: +// +// Invalid algorithm specified. +// +#define NTE_BAD_ALGID 0x80090008L [fail] + +// +// MessageId: NTE_BAD_FLAGS +// +// MessageText: +// +// Invalid flags specified. +// +#define NTE_BAD_FLAGS 0x80090009L [fail] + +// +// MessageId: NTE_BAD_TYPE +// +// MessageText: +// +// Invalid type specified. +// +#define NTE_BAD_TYPE 0x8009000AL [fail] + +// +// MessageId: NTE_BAD_KEY_STATE +// +// MessageText: +// +// Key not valid for use in specified state. +// +#define NTE_BAD_KEY_STATE 0x8009000BL [fail] + +// +// MessageId: NTE_BAD_HASH_STATE +// +// MessageText: +// +// Hash not valid for use in specified state. +// +#define NTE_BAD_HASH_STATE 0x8009000CL [fail] + +// +// MessageId: NTE_NO_KEY +// +// MessageText: +// +// Key does not exist. +// +#define NTE_NO_KEY 0x8009000DL [fail] + +// +// MessageId: NTE_NO_MEMORY +// +// MessageText: +// +// Insufficient memory available for the operation. +// +#define NTE_NO_MEMORY 0x8009000EL [fail] + +// +// MessageId: NTE_EXISTS +// +// MessageText: +// +// Object already exists. +// +#define NTE_EXISTS 0x8009000FL [fail] + +// +// MessageId: NTE_PERM +// +// MessageText: +// +// Access denied. +// +#define NTE_PERM 0x80090010L [fail] + +// +// MessageId: NTE_NOT_FOUND +// +// MessageText: +// +// Object was not found. +// +#define NTE_NOT_FOUND 0x80090011L [fail] + +// +// MessageId: NTE_DOUBLE_ENCRYPT +// +// MessageText: +// +// Data already encrypted. +// +#define NTE_DOUBLE_ENCRYPT 0x80090012L [fail] + +// +// MessageId: NTE_BAD_PROVIDER +// +// MessageText: +// +// Invalid provider specified. +// +#define NTE_BAD_PROVIDER 0x80090013L [fail] + +// +// MessageId: NTE_BAD_PROV_TYPE +// +// MessageText: +// +// Invalid provider type specified. +// +#define NTE_BAD_PROV_TYPE 0x80090014L [fail] + +// +// MessageId: NTE_BAD_PUBLIC_KEY +// +// MessageText: +// +// Provider's public key is invalid. +// +#define NTE_BAD_PUBLIC_KEY 0x80090015L [fail] + +// +// MessageId: NTE_BAD_KEYSET +// +// MessageText: +// +// Keyset does not exist +// +#define NTE_BAD_KEYSET 0x80090016L [fail] + +// +// MessageId: NTE_PROV_TYPE_NOT_DEF +// +// MessageText: +// +// Provider type not defined. +// +#define NTE_PROV_TYPE_NOT_DEF 0x80090017L [fail] + +// +// MessageId: NTE_PROV_TYPE_ENTRY_BAD +// +// MessageText: +// +// Provider type as registered is invalid. +// +#define NTE_PROV_TYPE_ENTRY_BAD 0x80090018L [fail] + +// +// MessageId: NTE_KEYSET_NOT_DEF +// +// MessageText: +// +// The keyset is not defined. +// +#define NTE_KEYSET_NOT_DEF 0x80090019L [fail] + +// +// MessageId: NTE_KEYSET_ENTRY_BAD +// +// MessageText: +// +// Keyset as registered is invalid. +// +#define NTE_KEYSET_ENTRY_BAD 0x8009001AL [fail] + +// +// MessageId: NTE_PROV_TYPE_NO_MATCH +// +// MessageText: +// +// Provider type does not match registered value. +// +#define NTE_PROV_TYPE_NO_MATCH 0x8009001BL [fail] + +// +// MessageId: NTE_SIGNATURE_FILE_BAD +// +// MessageText: +// +// The digital signature file is corrupt. +// +#define NTE_SIGNATURE_FILE_BAD 0x8009001CL [fail] + +// +// MessageId: NTE_PROVIDER_DLL_FAIL +// +// MessageText: +// +// Provider DLL failed to initialize correctly. +// +#define NTE_PROVIDER_DLL_FAIL 0x8009001DL [fail] + +// +// MessageId: NTE_PROV_DLL_NOT_FOUND +// +// MessageText: +// +// Provider DLL could not be found. +// +#define NTE_PROV_DLL_NOT_FOUND 0x8009001EL [fail] + +// +// MessageId: NTE_BAD_KEYSET_PARAM +// +// MessageText: +// +// The Keyset parameter is invalid. +// +#define NTE_BAD_KEYSET_PARAM 0x8009001FL [fail] + +// +// MessageId: NTE_FAIL +// +// MessageText: +// +// An internal error occurred. +// +#define NTE_FAIL 0x80090020L [fail] + +// +// MessageId: NTE_SYS_ERR +// +// MessageText: +// +// A base error occurred. +// +#define NTE_SYS_ERR 0x80090021L [fail] + +// +// MessageId: CRYPT_E_MSG_ERROR +// +// MessageText: +// +// An error was encountered doing a cryptographic message operation. +// +#define CRYPT_E_MSG_ERROR 0x80091001L [fail] + +// +// MessageId: CRYPT_E_UNKNOWN_ALGO +// +// MessageText: +// +// The cryptographic algorithm is unknown. +// +#define CRYPT_E_UNKNOWN_ALGO 0x80091002L [fail] + +// +// MessageId: CRYPT_E_OID_FORMAT +// +// MessageText: +// +// The object identifier is badly formatted. +// +#define CRYPT_E_OID_FORMAT 0x80091003L [fail] + +// +// MessageId: CRYPT_E_INVALID_MSG_TYPE +// +// MessageText: +// +// The message type is invalid. +// +#define CRYPT_E_INVALID_MSG_TYPE 0x80091004L [fail] + +// +// MessageId: CRYPT_E_UNEXPECTED_ENCODING +// +// MessageText: +// +// The message is not encoded as expected. +// +#define CRYPT_E_UNEXPECTED_ENCODING 0x80091005L [fail] + +// +// MessageId: CRYPT_E_AUTH_ATTR_MISSING +// +// MessageText: +// +// The message does not contain an expected authenticated attribute. +// +#define CRYPT_E_AUTH_ATTR_MISSING 0x80091006L [fail] + +// +// MessageId: CRYPT_E_HASH_VALUE +// +// MessageText: +// +// The hash value is not correct. +// +#define CRYPT_E_HASH_VALUE 0x80091007L [fail] + +// +// MessageId: CRYPT_E_INVALID_INDEX +// +// MessageText: +// +// The index value is not valid. +// +#define CRYPT_E_INVALID_INDEX 0x80091008L [fail] + +// +// MessageId: CRYPT_E_ALREADY_DECRYPTED +// +// MessageText: +// +// The message content has already been decrypted. +// +#define CRYPT_E_ALREADY_DECRYPTED 0x80091009L [fail] + +// +// MessageId: CRYPT_E_NOT_DECRYPTED +// +// MessageText: +// +// The message content has not been decrypted yet. +// +#define CRYPT_E_NOT_DECRYPTED 0x8009100AL [fail] + +// +// MessageId: CRYPT_E_RECIPIENT_NOT_FOUND +// +// MessageText: +// +// The enveloped-data message does not contain the specified recipient. +// +#define CRYPT_E_RECIPIENT_NOT_FOUND 0x8009100BL [fail] + +// +// MessageId: CRYPT_E_CONTROL_TYPE +// +// MessageText: +// +// The control type is not valid. +// +#define CRYPT_E_CONTROL_TYPE 0x8009100CL [fail] + +// +// MessageId: CRYPT_E_ISSUER_SERIALNUMBER +// +// MessageText: +// +// The issuer and/or serial number are/is not valid. +// +#define CRYPT_E_ISSUER_SERIALNUMBER 0x8009100DL [fail] + +// +// MessageId: CRYPT_E_SIGNER_NOT_FOUND +// +// MessageText: +// +// The original signer is not found. +// +#define CRYPT_E_SIGNER_NOT_FOUND 0x8009100EL [fail] + +// +// MessageId: CRYPT_E_ATTRIBUTES_MISSING +// +// MessageText: +// +// The message does not contain the requested attributes. +// +#define CRYPT_E_ATTRIBUTES_MISSING 0x8009100FL [fail] + +// +// MessageId: CRYPT_E_STREAM_MSG_NOT_READY +// +// MessageText: +// +// The steamed message is note yet able to return the requested data. +// +#define CRYPT_E_STREAM_MSG_NOT_READY 0x80091010L [fail] + +// +// MessageId: CRYPT_E_STREAM_INSUFFICIENT_DATA +// +// MessageText: +// +// The streamed message needs more data before the decode can complete. +// +#define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011L [fail] + +// +// MessageId: CRYPT_E_BAD_LEN +// +// MessageText: +// +// The length specified for the output data was insufficient. +// +#define CRYPT_E_BAD_LEN 0x80092001L [fail] + +// +// MessageId: CRYPT_E_BAD_ENCODE +// +// MessageText: +// +// An error was encountered while encoding or decoding. +// +#define CRYPT_E_BAD_ENCODE 0x80092002L [fail] + +// +// MessageId: CRYPT_E_FILE_ERROR +// +// MessageText: +// +// An error occurred while reading or writing to the file +// +#define CRYPT_E_FILE_ERROR 0x80092003L [fail] + +// +// MessageId: CRYPT_E_NOT_FOUND +// +// MessageText: +// +// The object or property wasn't found +// +#define CRYPT_E_NOT_FOUND 0x80092004L [fail] + +// +// MessageId: CRYPT_E_EXISTS +// +// MessageText: +// +// The object or property already exists +// +#define CRYPT_E_EXISTS 0x80092005L [fail] + +// +// MessageId: CRYPT_E_NO_PROVIDER +// +// MessageText: +// +// No provider was specified for the store or object +// +#define CRYPT_E_NO_PROVIDER 0x80092006L [fail] + +// +// MessageId: CRYPT_E_SELF_SIGNED +// +// MessageText: +// +// The specified certificate is self signed. +// +#define CRYPT_E_SELF_SIGNED 0x80092007L [fail] + +// +// MessageId: CRYPT_E_DELETED_PREV +// +// MessageText: +// +// The previous certificate or CRL context was deleted. +// +#define CRYPT_E_DELETED_PREV 0x80092008L [fail] + +// +// MessageId: CRYPT_E_NO_MATCH +// +// MessageText: +// +// No match when trying to find the object. +// +#define CRYPT_E_NO_MATCH 0x80092009L [fail] + +// +// MessageId: CRYPT_E_UNEXPECTED_MSG_TYPE +// +// MessageText: +// +// The type of the cryptographic message being decoded is different than what was expected. +// +#define CRYPT_E_UNEXPECTED_MSG_TYPE 0x8009200AL [fail] + +// +// MessageId: CRYPT_E_NO_KEY_PROPERTY +// +// MessageText: +// +// The certificate doesn't have a private key property +// +#define CRYPT_E_NO_KEY_PROPERTY 0x8009200BL [fail] + +// +// MessageId: CRYPT_E_NO_DECRYPT_CERT +// +// MessageText: +// +// No certificate was found having a private key property to use for decrypting. +// +#define CRYPT_E_NO_DECRYPT_CERT 0x8009200CL [fail] + +// +// MessageId: CRYPT_E_BAD_MSG +// +// MessageText: +// +// Either, not a cryptographic message or incorrectly formatted. +// +#define CRYPT_E_BAD_MSG 0x8009200DL [fail] + +// +// MessageId: CRYPT_E_NO_SIGNER +// +// MessageText: +// +// The signed message doesn't have a signer for the specified signer index +// +#define CRYPT_E_NO_SIGNER 0x8009200EL [fail] + +// +// MessageId: CRYPT_E_PENDING_CLOSE +// +// MessageText: +// +// Final closure is pending until additional frees or closes. +// +#define CRYPT_E_PENDING_CLOSE 0x8009200FL [fail] + +// +// MessageId: CRYPT_E_REVOKED +// +// MessageText: +// +// The certificate or signature has been revoked +// +#define CRYPT_E_REVOKED 0x80092010L [fail] + +// +// MessageId: CRYPT_E_NO_REVOCATION_DLL +// +// MessageText: +// +// No Dll or exported function was found to verify revocation. +// +#define CRYPT_E_NO_REVOCATION_DLL 0x80092011L [fail] + +// +// MessageId: CRYPT_E_NO_REVOCATION_CHECK +// +// MessageText: +// +// The called function wasn't able to do a revocation check on the certificate or signature. +// +#define CRYPT_E_NO_REVOCATION_CHECK 0x80092012L [fail] + +// +// MessageId: CRYPT_E_REVOCATION_OFFLINE +// +// MessageText: +// +// Since the revocation server was offline, the called function wasn't able to complete the revocation check. +// +#define CRYPT_E_REVOCATION_OFFLINE 0x80092013L [fail] + +// +// MessageId: CRYPT_E_NOT_IN_REVOCATION_DATABASE +// +// MessageText: +// +// The certificate or signature to be checked was not found in the revocation servers database. +// +#define CRYPT_E_NOT_IN_REVOCATION_DATABASE 0x80092014L [fail] + +// +// MessageId: CRYPT_E_INVALID_NUMERIC_STRING +// +// MessageText: +// +// The string contains a non-numeric character. +// +#define CRYPT_E_INVALID_NUMERIC_STRING 0x80092020L [fail] + +// +// MessageId: CRYPT_E_INVALID_PRINTABLE_STRING +// +// MessageText: +// +// The string contains a non-printable character. +// +#define CRYPT_E_INVALID_PRINTABLE_STRING 0x80092021L [fail] + +// +// MessageId: CRYPT_E_INVALID_IA5_STRING +// +// MessageText: +// +// The string contains a character not in the 7 bit ASCII character set. +// +#define CRYPT_E_INVALID_IA5_STRING 0x80092022L [fail] + +// +// MessageId: CRYPT_E_INVALID_X500_STRING +// +// MessageText: +// +// The string contains an invalid X500 name attribute key, oid, value or delimiter. +// +#define CRYPT_E_INVALID_X500_STRING 0x80092023L [fail] + +// +// MessageId: CRYPT_E_NOT_CHAR_STRING +// +// MessageText: +// +// The dwValueType for the CERT_NAME_VALUE is not one of the character strings. Most likely it is either a CERT_RDN_ENCODED_BLOB or CERT_TDN_OCTED_STRING. +// +#define CRYPT_E_NOT_CHAR_STRING 0x80092024L [fail] + +// +// MessageId: CRYPT_E_FILERESIZED +// +// MessageText: +// +// The Put operation can not continue. The file needs to be resized. However, there is already a signature present. A complete signing operation must be done. +// +#define CRYPT_E_FILERESIZED 0x80092025L [fail] + +// +// MessageId: CRYPT_E_SECURITY_SETTINGS +// +// MessageText: +// +// The cryptography operation has failed due to a local security option setting. +// +#define CRYPT_E_SECURITY_SETTINGS 0x80092026L [fail] + +// +// MessageId: CRYPT_E_NO_VERIFY_USAGE_DLL +// +// MessageText: +// +// No DLL or exported function was found to verify subject usage. +// +#define CRYPT_E_NO_VERIFY_USAGE_DLL 0x80092027L [fail] + +// +// MessageId: CRYPT_E_NO_VERIFY_USAGE_CHECK +// +// MessageText: +// +// The called function wasn't able to do a usage check on the subject. +// +#define CRYPT_E_NO_VERIFY_USAGE_CHECK 0x80092028L [fail] + +// +// MessageId: CRYPT_E_VERIFY_USAGE_OFFLINE +// +// MessageText: +// +// Since the server was offline, the called function wasn't able to complete the usage check. +// +#define CRYPT_E_VERIFY_USAGE_OFFLINE 0x80092029L [fail] + +// +// MessageId: CRYPT_E_NOT_IN_CTL +// +// MessageText: +// +// The subject was not found in a Certificate Trust List (CTL [fail]. +// +#define CRYPT_E_NOT_IN_CTL 0x8009202AL [fail] + +// +// MessageId: CRYPT_E_NO_TRUSTED_SIGNER +// +// MessageText: +// +// No trusted signer was found to verify the signature of the message or trust list. +// +#define CRYPT_E_NO_TRUSTED_SIGNER 0x8009202BL [fail] + +// +// MessageId: CRYPT_E_OSS_ERROR +// +// MessageText: +// +// OSS Certificate encode/decode error code base +// +// See asn1code.h for a definition of the OSS runtime errors. The OSS +// error values are offset by CRYPT_E_OSS_ERROR. +// +#define CRYPT_E_OSS_ERROR 0x80093000L [fail] + +// +// MessageId: CERTSRV_E_BAD_REQUESTSUBJECT +// +// MessageText: +// +// The request subject name is invalid or too long. +// +#define CERTSRV_E_BAD_REQUESTSUBJECT 0x80094001L [fail] + +// +// MessageId: CERTSRV_E_NO_REQUEST +// +// MessageText: +// +// The request does not exist. +// +#define CERTSRV_E_NO_REQUEST 0x80094002L [fail] + +// +// MessageId: CERTSRV_E_BAD_REQUESTSTATUS +// +// MessageText: +// +// The request's current status does not allow this operation. +// +#define CERTSRV_E_BAD_REQUESTSTATUS 0x80094003L [fail] + +// +// MessageId: CERTSRV_E_PROPERTY_EMPTY +// +// MessageText: +// +// The requested property value is empty. +// +#define CERTSRV_E_PROPERTY_EMPTY 0x80094004L [fail] + +// +// MessageId: CERTDB_E_JET_ERROR +// +// MessageText: +// +// Jet error code base +// +// See jet.h for a definition of the Jet runtime errors. +// Negative Jet error values are masked to three digits and offset by CERTDB_E_JET_ERROR. +// +#define CERTDB_E_JET_ERROR 0x80095000L [fail] + +// +// MessageId: TRUST_E_SYSTEM_ERROR +// +// MessageText: +// +// A system-level error occured while verifying trust. +// +#define TRUST_E_SYSTEM_ERROR 0x80096001L [fail] + +// +// MessageId: TRUST_E_NO_SIGNER_CERT +// +// MessageText: +// +// The certificate for the signer of the message is invalid or not found. +// +#define TRUST_E_NO_SIGNER_CERT 0x80096002L [fail] + +// +// MessageId: TRUST_E_COUNTER_SIGNER +// +// MessageText: +// +// One of the counter signers was invalid. +// +#define TRUST_E_COUNTER_SIGNER 0x80096003L [fail] + +// +// MessageId: TRUST_E_CERT_SIGNATURE +// +// MessageText: +// +// The signature of the certificate can not be verified. +// +#define TRUST_E_CERT_SIGNATURE 0x80096004L [fail] + +// +// MessageId: TRUST_E_TIME_STAMP +// +// MessageText: +// +// The time stamp signer and or certificate could not be verified or is malformed. +// +#define TRUST_E_TIME_STAMP 0x80096005L [fail] + +// +// MessageId: TRUST_E_BAD_DIGEST +// +// MessageText: +// +// The objects digest did not verify. +// +#define TRUST_E_BAD_DIGEST 0x80096010L [fail] + +// +// MessageId: TRUST_E_BASIC_CONSTRAINTS +// +// MessageText: +// +// The cerficates basic constraints are invalid or missing. +// +#define TRUST_E_BASIC_CONSTRAINTS 0x80096019L [fail] + +// +// MessageId: TRUST_E_FINANCIAL_CRITERIA +// +// MessageText: +// +// The certificate does not meet or contain the Authenticode financial extensions. +// +#define TRUST_E_FINANCIAL_CRITERIA 0x8009601EL [fail] + +#define NTE_OP_OK 0 + +// +// Note that additional FACILITY_SSPI errors are in issperr.h +// +// ****************** +// FACILITY_CERT +// ****************** +// +// MessageId: TRUST_E_PROVIDER_UNKNOWN +// +// MessageText: +// +// The specified trust provider is not known on this system. +// +#define TRUST_E_PROVIDER_UNKNOWN 0x800B0001L [fail] + +// +// MessageId: TRUST_E_ACTION_UNKNOWN +// +// MessageText: +// +// The trust verification action specified is not supported by the specified trust provider. +// +#define TRUST_E_ACTION_UNKNOWN 0x800B0002L [fail] + +// +// MessageId: TRUST_E_SUBJECT_FORM_UNKNOWN +// +// MessageText: +// +// The form specified for the subject is not one supported or known by the specified trust provider. +// +#define TRUST_E_SUBJECT_FORM_UNKNOWN 0x800B0003L [fail] + +// +// MessageId: TRUST_E_SUBJECT_NOT_TRUSTED +// +// MessageText: +// +// The subject is not trusted for the specified action. +// +#define TRUST_E_SUBJECT_NOT_TRUSTED 0x800B0004L [fail] + +// +// MessageId: DIGSIG_E_ENCODE +// +// MessageText: +// +// Error due to problem in ASN.1 encoding process. +// +#define DIGSIG_E_ENCODE 0x800B0005L [fail] + +// +// MessageId: DIGSIG_E_DECODE +// +// MessageText: +// +// Error due to problem in ASN.1 decoding process. +// +#define DIGSIG_E_DECODE 0x800B0006L [fail] + +// +// MessageId: DIGSIG_E_EXTENSIBILITY +// +// MessageText: +// +// Reading / writing Extensions where Attributes are appropriate, and visa versa. +// +#define DIGSIG_E_EXTENSIBILITY 0x800B0007L [fail] + +// +// MessageId: DIGSIG_E_CRYPTO +// +// MessageText: +// +// Unspecified cryptographic failure. +// +#define DIGSIG_E_CRYPTO 0x800B0008L [fail] + +// +// MessageId: PERSIST_E_SIZEDEFINITE +// +// MessageText: +// +// The size of the data could not be determined. +// +#define PERSIST_E_SIZEDEFINITE 0x800B0009L [fail] + +// +// MessageId: PERSIST_E_SIZEINDEFINITE +// +// MessageText: +// +// The size of the indefinite-sized data could not be determined. +// +#define PERSIST_E_SIZEINDEFINITE 0x800B000AL [fail] + +// +// MessageId: PERSIST_E_NOTSELFSIZING +// +// MessageText: +// +// This object does not read and write self-sizing data. +// +#define PERSIST_E_NOTSELFSIZING 0x800B000BL [fail] + +// +// MessageId: TRUST_E_NOSIGNATURE +// +// MessageText: +// +// No signature was present in the subject. +// +#define TRUST_E_NOSIGNATURE 0x800B0100L [fail] + +// +// MessageId: CERT_E_EXPIRED +// +// MessageText: +// +// A required certificate is not within its validity period. +// +#define CERT_E_EXPIRED 0x800B0101L [fail] + +// +// MessageId: CERT_E_VALIDITYPERIODNESTING +// +// MessageText: +// +// The validity periods of the certification chain do not nest correctly. +// +#define CERT_E_VALIDITYPERIODNESTING 0x800B0102L [fail] + +// +// MessageId: CERT_E_ROLE +// +// MessageText: +// +// A certificate that can only be used as an end-entity is being used as a CA or visa versa. +// +#define CERT_E_ROLE 0x800B0103L [fail] + +// +// MessageId: CERT_E_PATHLENCONST +// +// MessageText: +// +// A path length constraint in the certification chain has been violated. +// +#define CERT_E_PATHLENCONST 0x800B0104L [fail] + +// +// MessageId: CERT_E_CRITICAL +// +// MessageText: +// +// An extension of unknown type that is labeled 'critical' is present in a certificate. +// +#define CERT_E_CRITICAL 0x800B0105L [fail] + +// +// MessageId: CERT_E_PURPOSE +// +// MessageText: +// +// A certificate is being used for a purpose other than that for which it is permitted. +// +#define CERT_E_PURPOSE 0x800B0106L [fail] + +// +// MessageId: CERT_E_ISSUERCHAINING +// +// MessageText: +// +// A parent of a given certificate in fact did not issue that child certificate. +// +#define CERT_E_ISSUERCHAINING 0x800B0107L [fail] + +// +// MessageId: CERT_E_MALFORMED +// +// MessageText: +// +// A certificate is missing or has an empty value for an important field, such as a subject or issuer name. +// +#define CERT_E_MALFORMED 0x800B0108L [fail] + +// +// MessageId: CERT_E_UNTRUSTEDROOT +// +// MessageText: +// +// A certification chain processed correctly, but terminated in a root certificate which isn't trusted by the trust provider. +// +#define CERT_E_UNTRUSTEDROOT 0x800B0109L [fail] + +// +// MessageId: CERT_E_CHAINING +// +// MessageText: +// +// A chain of certs didn't chain as they should in a certain application of chaining. +// +#define CERT_E_CHAINING 0x800B010AL [fail] + +// +// MessageId: TRUST_E_FAIL +// +// MessageText: +// +// Generic Trust Failure. +// +#define TRUST_E_FAIL 0x800B010BL [fail] + +// +// MessageId: CERT_E_REVOKED +// +// MessageText: +// +// A certificate was explicitly revoked by its issuer. +// +#define CERT_E_REVOKED 0x800B010CL [fail] + +// +// MessageId: CERT_E_UNTRUSTEDTESTROOT +// +// MessageText: +// +// The root certificate is a testing certificate and the policy settings disallow test certificates. +// +#define CERT_E_UNTRUSTEDTESTROOT 0x800B010DL [fail] + +// +// MessageId: CERT_E_REVOCATION_FAILURE +// +// MessageText: +// +// The revocation process could not continue - the certificate(s) could not be checked. +// +#define CERT_E_REVOCATION_FAILURE 0x800B010EL [fail] + +// +// MessageId: CERT_E_CN_NO_MATCH +// +// MessageText: +// +// The certificate's CN name does not match the passed value. +// +#define CERT_E_CN_NO_MATCH 0x800B010FL [fail] + +// +// MessageId: CERT_E_WRONG_USAGE +// +// MessageText: +// +// The certificate is not valid for the requested usage. +// +#define CERT_E_WRONG_USAGE 0x800B0110L [fail] + +// ***************** +// FACILITY_SETUPAPI +// ***************** +// +// +// MessageId: SPAPI_E_EXPECTED_SECTION_NAME +// +// MessageText: +// +// A non-empty line was encountered in the INF before the start of a section. +// +#define SPAPI_E_EXPECTED_SECTION_NAME 0x800F0000L [fail] + +// +// MessageId: SPAPI_E_BAD_SECTION_NAME_LINE +// +// MessageText: +// +// A section name marker in the INF is not complete, or does not exist on a line by itself. +// +#define SPAPI_E_BAD_SECTION_NAME_LINE 0x800F0001L [fail] + +// +// MessageId: SPAPI_E_SECTION_NAME_TOO_LONG +// +// MessageText: +// +// An INF section was encountered whose name exceeds the maximum section name length. +// +#define SPAPI_E_SECTION_NAME_TOO_LONG 0x800F0002L [fail] + +// +// MessageId: SPAPI_E_GENERAL_SYNTAX +// +// MessageText: +// +// The syntax of the INF is invalid. +// +#define SPAPI_E_GENERAL_SYNTAX 0x800F0003L [fail] + +// +// MessageId: SPAPI_E_WRONG_INF_STYLE +// +// MessageText: +// +// The style of the INF is different than what was requested. +// +#define SPAPI_E_WRONG_INF_STYLE 0x800F0100L [fail] + +// +// MessageId: SPAPI_E_SECTION_NOT_FOUND +// +// MessageText: +// +// The required section was not found in the INF. +// +#define SPAPI_E_SECTION_NOT_FOUND 0x800F0101L [fail] + +// +// MessageId: SPAPI_E_LINE_NOT_FOUND +// +// MessageText: +// +// The required line was not found in the INF. +// +#define SPAPI_E_LINE_NOT_FOUND 0x800F0102L [fail] + +// +// MessageId: SPAPI_E_NO_ASSOCIATED_CLASS +// +// MessageText: +// +// The INF or the device information set or element does not have an associated install class. +// +#define SPAPI_E_NO_ASSOCIATED_CLASS 0x800F0200L [fail] + +// +// MessageId: SPAPI_E_CLASS_MISMATCH +// +// MessageText: +// +// The INF or the device information set or element does not match the specified install class. +// +#define SPAPI_E_CLASS_MISMATCH 0x800F0201L [fail] + +// +// MessageId: SPAPI_E_DUPLICATE_FOUND +// +// MessageText: +// +// An existing device was found that is a duplicate of the device being manually installed. +// +#define SPAPI_E_DUPLICATE_FOUND 0x800F0202L [fail] + +// +// MessageId: SPAPI_E_NO_DRIVER_SELECTED +// +// MessageText: +// +// There is no driver selected for the device information set or element. +// +#define SPAPI_E_NO_DRIVER_SELECTED 0x800F0203L [fail] + +// +// MessageId: SPAPI_E_KEY_DOES_NOT_EXIST +// +// MessageText: +// +// The requested device registry key does not exist. +// +#define SPAPI_E_KEY_DOES_NOT_EXIST 0x800F0204L [fail] + +// +// MessageId: SPAPI_E_INVALID_DEVINST_NAME +// +// MessageText: +// +// The device instance name is invalid. +// +#define SPAPI_E_INVALID_DEVINST_NAME 0x800F0205L [fail] + +// +// MessageId: SPAPI_E_INVALID_CLASS +// +// MessageText: +// +// The install class is not present or is invalid. +// +#define SPAPI_E_INVALID_CLASS 0x800F0206L [fail] + +// +// MessageId: SPAPI_E_DEVINST_ALREADY_EXISTS +// +// MessageText: +// +// The device instance cannot be created because it already exists. +// +#define SPAPI_E_DEVINST_ALREADY_EXISTS 0x800F0207L [fail] + +// +// MessageId: SPAPI_E_DEVINFO_NOT_REGISTERED +// +// MessageText: +// +// The operation cannot be performed on a device information element that has not been registered. +// +#define SPAPI_E_DEVINFO_NOT_REGISTERED 0x800F0208L [fail] + +// +// MessageId: SPAPI_E_INVALID_REG_PROPERTY +// +// MessageText: +// +// The device property code is invalid. +// +#define SPAPI_E_INVALID_REG_PROPERTY 0x800F0209L [fail] + +// +// MessageId: SPAPI_E_NO_INF +// +// MessageText: +// +// The INF from which a driver list is to be built does not exist. +// +#define SPAPI_E_NO_INF 0x800F020AL [fail] + +// +// MessageId: SPAPI_E_NO_SUCH_DEVINST +// +// MessageText: +// +// The device instance does not exist in the hardware tree. +// +#define SPAPI_E_NO_SUCH_DEVINST 0x800F020BL [fail] + +// +// MessageId: SPAPI_E_CANT_LOAD_CLASS_ICON +// +// MessageText: +// +// The icon representing this install class cannot be loaded. +// +#define SPAPI_E_CANT_LOAD_CLASS_ICON 0x800F020CL [fail] + +// +// MessageId: SPAPI_E_INVALID_CLASS_INSTALLER +// +// MessageText: +// +// The class installer registry entry is invalid. +// +#define SPAPI_E_INVALID_CLASS_INSTALLER 0x800F020DL [fail] + +// +// MessageId: SPAPI_E_DI_DO_DEFAULT +// +// MessageText: +// +// The class installer has indicated that the default action should be performed for this installation request. +// +#define SPAPI_E_DI_DO_DEFAULT 0x800F020EL [fail] + +// +// MessageId: SPAPI_E_DI_NOFILECOPY +// +// MessageText: +// +// The operation does not require any files to be copied. +// +#define SPAPI_E_DI_NOFILECOPY 0x800F020FL [fail] + +// +// MessageId: SPAPI_E_INVALID_HWPROFILE +// +// MessageText: +// +// The specified hardware profile does not exist. +// +#define SPAPI_E_INVALID_HWPROFILE 0x800F0210L [fail] + +// +// MessageId: SPAPI_E_NO_DEVICE_SELECTED +// +// MessageText: +// +// There is no device information element currently selected for this device information set. +// +#define SPAPI_E_NO_DEVICE_SELECTED 0x800F0211L [fail] + +// +// MessageId: SPAPI_E_DEVINFO_LIST_LOCKED +// +// MessageText: +// +// The operation cannot be performed because the device information set is locked. +// +#define SPAPI_E_DEVINFO_LIST_LOCKED 0x800F0212L [fail] + +// +// MessageId: SPAPI_E_DEVINFO_DATA_LOCKED +// +// MessageText: +// +// The operation cannot be performed because the device information element is locked. +// +#define SPAPI_E_DEVINFO_DATA_LOCKED 0x800F0213L [fail] + +// +// MessageId: SPAPI_E_DI_BAD_PATH +// +// MessageText: +// +// The specified path does not contain any applicable device INFs. +// +#define SPAPI_E_DI_BAD_PATH 0x800F0214L [fail] + +// +// MessageId: SPAPI_E_NO_CLASSINSTALL_PARAMS +// +// MessageText: +// +// No class installer parameters have been set for the device information set or element. +// +#define SPAPI_E_NO_CLASSINSTALL_PARAMS 0x800F0215L [fail] + +// +// MessageId: SPAPI_E_FILEQUEUE_LOCKED +// +// MessageText: +// +// The operation cannot be performed because the file queue is locked. +// +#define SPAPI_E_FILEQUEUE_LOCKED 0x800F0216L [fail] + +// +// MessageId: SPAPI_E_BAD_SERVICE_INSTALLSECT +// +// MessageText: +// +// A service installation section in this INF is invalid. +// +#define SPAPI_E_BAD_SERVICE_INSTALLSECT 0x800F0217L [fail] + +// +// MessageId: SPAPI_E_NO_CLASS_DRIVER_LIST +// +// MessageText: +// +// There is no class driver list for the device information element. +// +#define SPAPI_E_NO_CLASS_DRIVER_LIST 0x800F0218L [fail] + +// +// MessageId: SPAPI_E_NO_ASSOCIATED_SERVICE +// +// MessageText: +// +// The installation failed because a function driver was not specified for this device instance. +// +#define SPAPI_E_NO_ASSOCIATED_SERVICE 0x800F0219L [fail] + +// +// MessageId: SPAPI_E_NO_DEFAULT_DEVICE_INTERFACE +// +// MessageText: +// +// There is presently no default device interface designated for this interface class. +// +#define SPAPI_E_NO_DEFAULT_DEVICE_INTERFACE 0x800F021AL [fail] + +// +// MessageId: SPAPI_E_DEVICE_INTERFACE_ACTIVE +// +// MessageText: +// +// The operation cannot be performed because the device interface is currently active. +// +#define SPAPI_E_DEVICE_INTERFACE_ACTIVE 0x800F021BL [fail] + +// +// MessageId: SPAPI_E_DEVICE_INTERFACE_REMOVED +// +// MessageText: +// +// The operation cannot be performed because the device interface has been removed from the system. +// +#define SPAPI_E_DEVICE_INTERFACE_REMOVED 0x800F021CL [fail] + +// +// MessageId: SPAPI_E_BAD_INTERFACE_INSTALLSECT +// +// MessageText: +// +// An interface installation section in this INF is invalid. +// +#define SPAPI_E_BAD_INTERFACE_INSTALLSECT 0x800F021DL [fail] + +// +// MessageId: SPAPI_E_NO_SUCH_INTERFACE_CLASS +// +// MessageText: +// +// This interface class does not exist in the system. +// +#define SPAPI_E_NO_SUCH_INTERFACE_CLASS 0x800F021EL [fail] + +// +// MessageId: SPAPI_E_INVALID_REFERENCE_STRING +// +// MessageText: +// +// The reference string supplied for this interface device is invalid. +// +#define SPAPI_E_INVALID_REFERENCE_STRING 0x800F021FL [fail] + +// +// MessageId: SPAPI_E_INVALID_MACHINENAME +// +// MessageText: +// +// The specified machine name does not conform to UNC naming conventions. +// +#define SPAPI_E_INVALID_MACHINENAME 0x800F0220L [fail] + +// +// MessageId: SPAPI_E_REMOTE_COMM_FAILURE +// +// MessageText: +// +// A general remote communication error occurred. +// +#define SPAPI_E_REMOTE_COMM_FAILURE 0x800F0221L [fail] + +// +// MessageId: SPAPI_E_MACHINE_UNAVAILABLE +// +// MessageText: +// +// The machine selected for remote communication is not available at this time. +// +#define SPAPI_E_MACHINE_UNAVAILABLE 0x800F0222L [fail] + +// +// MessageId: SPAPI_E_NO_CONFIGMGR_SERVICES +// +// MessageText: +// +// The Plug and Play service is not available on the remote machine. +// +#define SPAPI_E_NO_CONFIGMGR_SERVICES 0x800F0223L [fail] + +// +// MessageId: SPAPI_E_INVALID_PROPPAGE_PROVIDER +// +// MessageText: +// +// The property page provider registry entry is invalid. +// +#define SPAPI_E_INVALID_PROPPAGE_PROVIDER 0x800F0224L [fail] + +// +// MessageId: SPAPI_E_NO_SUCH_DEVICE_INTERFACE +// +// MessageText: +// +// The requested device interface is not present in the system. +// +#define SPAPI_E_NO_SUCH_DEVICE_INTERFACE 0x800F0225L [fail] + +// +// MessageId: SPAPI_E_DI_POSTPROCESSING_REQUIRED +// +// MessageText: +// +// The device's co-installer has additional work to perform after installation is complete. +// +#define SPAPI_E_DI_POSTPROCESSING_REQUIRED 0x800F0226L [fail] + +// +// MessageId: SPAPI_E_INVALID_COINSTALLER +// +// MessageText: +// +// The device's co-installer is invalid. +// +#define SPAPI_E_INVALID_COINSTALLER 0x800F0227L [fail] + +// +// MessageId: SPAPI_E_NO_COMPAT_DRIVERS +// +// MessageText: +// +// There are no compatible drivers for this device. +// +#define SPAPI_E_NO_COMPAT_DRIVERS 0x800F0228L [fail] + +// +// MessageId: SPAPI_E_NO_DEVICE_ICON +// +// MessageText: +// +// There is no icon that represents this device or device type. +// +#define SPAPI_E_NO_DEVICE_ICON 0x800F0229L [fail] + +// +// MessageId: SPAPI_E_INVALID_INF_LOGCONFIG +// +// MessageText: +// +// A logical configuration specified in this INF is invalid. +// +#define SPAPI_E_INVALID_INF_LOGCONFIG 0x800F022AL [fail] + +// +// MessageId: SPAPI_E_DI_DONT_INSTALL +// +// MessageText: +// +// The class installer has denied the request to install or upgrade this device. +// +#define SPAPI_E_DI_DONT_INSTALL 0x800F022BL [fail] + +// +// MessageId: SPAPI_E_INVALID_FILTER_DRIVER +// +// MessageText: +// +// One of the filter drivers installed for this device is invalid. +// +#define SPAPI_E_INVALID_FILTER_DRIVER 0x800F022CL [fail] + +// +// MessageId: SPAPI_E_ERROR_NOT_INSTALLED +// +// MessageText: +// +// No installed components were detected. +// + +#define SPAPI_E_ERROR_NOT_INSTALLED 0x800F1000L [fail] + + +}; diff --git a/tools/Debugging Tools for Windows/winext/manifest/winmm.h b/tools/Debugging Tools for Windows/winext/manifest/winmm.h new file mode 100644 index 0000000000..ba5070b0c6 --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/winmm.h @@ -0,0 +1,706 @@ + +mask DWORD PlaySoundFlags +{ +#define SND_SYNC 0x00000000L /* play synchronously (default) */ +#define SND_ASYNC 0x00000001L /* play asynchronously */ +#define SND_NODEFAULT 0x00000002L /* silence (!default) if sound not found */ +#define SND_MEMORY 0x00000004L /* pszSound points to a memory file */ +#define SND_LOOP 0x00000008L /* loop the sound until next sndPlaySound */ +#define SND_NOSTOP 0x00000010L /* don't stop any currently playing sound */ + +#define SND_PURGE 0x00000040L /* purge non-static events for task */ +#define SND_APPLICATION 0x00000080L /* look for application specific association */ + +#define SND_NOWAIT 0x00002000L /* don't wait if the driver is busy */ +#define SND_ALIAS 0x00010000L /* name is a registry alias */ +#define SND_FILENAME 0x00020000L /* name is file name */ +#define SND_RESOURCE 0x00040000L /* name is resource name or atom */ + +#define SND_ALIAS_ID 0x00100000L /* alias is a predefined ID */ +}; + +mask DWORD WaveOutCapsFlags +{ +#define WAVECAPS_PITCH 0x0001 /* supports pitch control */ +#define WAVECAPS_PLAYBACKRATE 0x0002 /* supports playback rate control */ +#define WAVECAPS_VOLUME 0x0004 /* supports volume control */ +#define WAVECAPS_LRVOLUME 0x0008 /* separate left-right volume control */ +#define WAVECAPS_SYNC 0x0010 +#define WAVECAPS_SAMPLEACCURATE 0x0020 +#define WAVECAPS_DIRECTSOUND 0x0040 +}; + +mask DWORD WaveCapsFormatFlags +{ +#define WAVE_INVALIDFORMAT 0x00000000 /* invalid format */ +#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ +#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ +}; + +mask DWORD WAVE_OUT_OPEN_FLAGS +{ +/* flags for dwFlags parameter in waveOutOpen() and waveInOpen() */ +#define WAVE_FORMAT_QUERY 0x0001 +#define WAVE_ALLOWSYNC 0x0002 +#define WAVE_MAPPED 0x0004 +#define WAVE_FORMAT_DIRECT 0x0008 +#define CALLBACK_WINDOW 0x000100000 /* dwCallback is a HWND */ +#define CALLBACK_TASK 0x000200000 /* dwCallback is a HTASK */ +#define CALLBACK_FUNCTION 0x000300000 /* dwCallback is a FARPROC */ +}; + +value DWORD MMRESULT_VALUES +{ +#define MMSYSERR_NOERROR 0 +#define MMSYSERR_ERROR 1 [fail] +#define MMSYSERR_BADDEVICEID 2 [fail] +#define MMSYSERR_NOTENABLED 3 [fail] +#define MMSYSERR_ALLOCATED 4 [fail] +#define MMSYSERR_INVALHANDLE 5 [fail] +#define MMSYSERR_NODRIVER 6 [fail] +#define MMSYSERR_NOMEM 7 [fail] +#define MMSYSERR_NOTSUPPORTED 8 [fail] +#define MMSYSERR_BADERRNUM 9 [fail] +#define MMSYSERR_INVALFLAG 10 [fail] +#define MMSYSERR_INVALPARAM 11 [fail] +#define MMSYSERR_HANDLEBUSY 12 [fail] +#define MMSYSERR_INVALIDALIAS 13 [fail] +#define MMSYSERR_BADDB 14 [fail] +#define MMSYSERR_KEYNOTFOUND 15 [fail] +#define MMSYSERR_READERROR 16 [fail] +#define MMSYSERR_WRITEERROR 17 [fail] +#define MMSYSERR_DELETEERROR 18 [fail] +#define MMSYSERR_VALNOTFOUND 19 [fail] +#define MMSYSERR_NODRIVERCB 20 [fail] +#define MMSYSERR_LASTERROR 20 [fail] +}; + +value UINT PRODUCT_ID_VALUE +{ +/* MM_MICROSOFT product IDs */ +#define MM_MIDI_MAPPER 1 /* Midi Mapper */ +#define MM_WAVE_MAPPER 2 /* Wave Mapper */ +#define MM_SNDBLST_MIDIOUT 3 /* Sound Blaster MIDI output port */ +#define MM_SNDBLST_MIDIIN 4 /* Sound Blaster MIDI input port */ +#define MM_SNDBLST_SYNTH 5 /* Sound Blaster internal synth */ +#define MM_SNDBLST_WAVEOUT 6 /* Sound Blaster waveform output */ +#define MM_SNDBLST_WAVEIN 7 /* Sound Blaster waveform input */ +#define MM_ADLIB 9 /* Ad Lib Compatible synth */ +#define MM_MPU401_MIDIOUT 10 /* MPU 401 compatible MIDI output port */ +#define MM_MPU401_MIDIIN 11 /* MPU 401 compatible MIDI input port */ +#define MM_PC_JOYSTICK 12 /* Joystick adapter */ +#define MM_PCSPEAKER_WAVEOUT 13 /* PC speaker waveform output */ +#define MM_MSFT_WSS_WAVEIN 14 /* MS Audio Board waveform input */ +#define MM_MSFT_WSS_WAVEOUT 15 /* MS Audio Board waveform output */ +#define MM_MSFT_WSS_FMSYNTH_STEREO 16 /* MS Audio Board Stereo FM synth */ +#define MM_MSFT_WSS_MIXER 17 /* MS Audio Board Mixer Driver */ +#define MM_MSFT_WSS_OEM_WAVEIN 18 /* MS OEM Audio Board waveform input */ +#define MM_MSFT_WSS_OEM_WAVEOUT 19 /* MS OEM Audio Board waveform output */ +#define MM_MSFT_WSS_OEM_FMSYNTH_STEREO 20 /* MS OEM Audio Board Stereo FM Synth */ +#define MM_MSFT_WSS_AUX 21 /* MS Audio Board Aux. Port */ +#define MM_MSFT_WSS_OEM_AUX 22 /* MS OEM Audio Aux Port */ +#define MM_MSFT_GENERIC_WAVEIN 23 /* MS Vanilla driver waveform input */ +#define MM_MSFT_GENERIC_WAVEOUT 24 /* MS Vanilla driver wavefrom output */ +#define MM_MSFT_GENERIC_MIDIIN 25 /* MS Vanilla driver MIDI in */ +#define MM_MSFT_GENERIC_MIDIOUT 26 /* MS Vanilla driver MIDI external out */ +#define MM_MSFT_GENERIC_MIDISYNTH 27 /* MS Vanilla driver MIDI synthesizer */ +#define MM_MSFT_GENERIC_AUX_LINE 28 /* MS Vanilla driver aux (line in) */ +#define MM_MSFT_GENERIC_AUX_MIC 29 /* MS Vanilla driver aux (mic) */ +#define MM_MSFT_GENERIC_AUX_CD 30 /* MS Vanilla driver aux (CD) */ +#define MM_MSFT_WSS_OEM_MIXER 31 /* MS OEM Audio Board Mixer Driver */ +#define MM_MSFT_MSACM 32 /* MS Audio Compression Manager */ +#define MM_MSFT_ACM_MSADPCM 33 /* MS ADPCM Codec */ +#define MM_MSFT_ACM_IMAADPCM 34 /* IMA ADPCM Codec */ +#define MM_MSFT_ACM_MSFILTER 35 /* MS Filter */ +#define MM_MSFT_ACM_GSM610 36 /* GSM 610 codec */ +#define MM_MSFT_ACM_G711 37 /* G.711 codec */ +#define MM_MSFT_ACM_PCM 38 /* PCM converter */ + + // Microsoft Windows Sound System drivers + +#define MM_WSS_SB16_WAVEIN 39 /* Sound Blaster 16 waveform input */ +#define MM_WSS_SB16_WAVEOUT 40 /* Sound Blaster 16 waveform output */ +#define MM_WSS_SB16_MIDIIN 41 /* Sound Blaster 16 midi-in */ +#define MM_WSS_SB16_MIDIOUT 42 /* Sound Blaster 16 midi out */ +#define MM_WSS_SB16_SYNTH 43 /* Sound Blaster 16 FM Synthesis */ +#define MM_WSS_SB16_AUX_LINE 44 /* Sound Blaster 16 aux (line in) */ +#define MM_WSS_SB16_AUX_CD 45 /* Sound Blaster 16 aux (CD) */ +#define MM_WSS_SB16_MIXER 46 /* Sound Blaster 16 mixer device */ +#define MM_WSS_SBPRO_WAVEIN 47 /* Sound Blaster Pro waveform input */ +#define MM_WSS_SBPRO_WAVEOUT 48 /* Sound Blaster Pro waveform output */ +#define MM_WSS_SBPRO_MIDIIN 49 /* Sound Blaster Pro midi in */ +#define MM_WSS_SBPRO_MIDIOUT 50 /* Sound Blaster Pro midi out */ +#define MM_WSS_SBPRO_SYNTH 51 /* Sound Blaster Pro FM synthesis */ +#define MM_WSS_SBPRO_AUX_LINE 52 /* Sound Blaster Pro aux (line in ) */ +#define MM_WSS_SBPRO_AUX_CD 53 /* Sound Blaster Pro aux (CD) */ +#define MM_WSS_SBPRO_MIXER 54 /* Sound Blaster Pro mixer */ + +#define MM_MSFT_WSS_NT_WAVEIN 55 /* WSS NT wave in */ +#define MM_MSFT_WSS_NT_WAVEOUT 56 /* WSS NT wave out */ +#define MM_MSFT_WSS_NT_FMSYNTH_STEREO 57 /* WSS NT FM synth */ +#define MM_MSFT_WSS_NT_MIXER 58 /* WSS NT mixer */ +#define MM_MSFT_WSS_NT_AUX 59 /* WSS NT aux */ + +#define MM_MSFT_SB16_WAVEIN 60 /* Sound Blaster 16 waveform input */ +#define MM_MSFT_SB16_WAVEOUT 61 /* Sound Blaster 16 waveform output */ +#define MM_MSFT_SB16_MIDIIN 62 /* Sound Blaster 16 midi-in */ +#define MM_MSFT_SB16_MIDIOUT 63 /* Sound Blaster 16 midi out */ +#define MM_MSFT_SB16_SYNTH 64 /* Sound Blaster 16 FM Synthesis */ +#define MM_MSFT_SB16_AUX_LINE 65 /* Sound Blaster 16 aux (line in) */ +#define MM_MSFT_SB16_AUX_CD 66 /* Sound Blaster 16 aux (CD) */ +#define MM_MSFT_SB16_MIXER 67 /* Sound Blaster 16 mixer device */ +#define MM_MSFT_SBPRO_WAVEIN 68 /* Sound Blaster Pro waveform input */ +#define MM_MSFT_SBPRO_WAVEOUT 69 /* Sound Blaster Pro waveform output */ +#define MM_MSFT_SBPRO_MIDIIN 70 /* Sound Blaster Pro midi in */ +#define MM_MSFT_SBPRO_MIDIOUT 71 /* Sound Blaster Pro midi out */ +#define MM_MSFT_SBPRO_SYNTH 72 /* Sound Blaster Pro FM synthesis */ +#define MM_MSFT_SBPRO_AUX_LINE 73 /* Sound Blaster Pro aux (line in ) */ +#define MM_MSFT_SBPRO_AUX_CD 74 /* Sound Blaster Pro aux (CD) */ +#define MM_MSFT_SBPRO_MIXER 75 /* Sound Blaster Pro mixer */ + +#define MM_MSFT_MSOPL_SYNTH 76 /* Yamaha OPL2/OPL3 compatible FM synthesis */ + +#define MM_MSFT_VMDMS_LINE_WAVEIN 80 /* Voice Modem Serial Line Wave Input */ +#define MM_MSFT_VMDMS_LINE_WAVEOUT 81 /* Voice Modem Serial Line Wave Output */ +#define MM_MSFT_VMDMS_HANDSET_WAVEIN 82 /* Voice Modem Serial Handset Wave Input */ +#define MM_MSFT_VMDMS_HANDSET_WAVEOUT 83 /* Voice Modem Serial Handset Wave Output */ +#define MM_MSFT_VMDMW_LINE_WAVEIN 84 /* Voice Modem Wrapper Line Wave Input */ +#define MM_MSFT_VMDMW_LINE_WAVEOUT 85 /* Voice Modem Wrapper Line Wave Output */ +#define MM_MSFT_VMDMW_HANDSET_WAVEIN 86 /* Voice Modem Wrapper Handset Wave Input */ +#define MM_MSFT_VMDMW_HANDSET_WAVEOUT 87 /* Voice Modem Wrapper Handset Wave Output */ +#define MM_MSFT_VMDMW_MIXER 88 /* Voice Modem Wrapper Mixer */ +#define MM_MSFT_VMDM_GAME_WAVEOUT 89 /* Voice Modem Game Compatible Wave Device */ +#define MM_MSFT_VMDM_GAME_WAVEIN 90 /* Voice Modem Game Compatible Wave Device */ + +#define MM_MSFT_ACM_MSNAUDIO 91 /* */ +#define MM_MSFT_ACM_MSG723 92 /* */ + +#define MM_MSFT_WDMAUDIO_WAVEOUT 100 /* Generic id for WDM Audio drivers */ +#define MM_MSFT_WDMAUDIO_WAVEIN 101 /* Generic id for WDM Audio drivers */ +#define MM_MSFT_WDMAUDIO_MIDIOUT 102 /* Generic id for WDM Audio drivers */ +#define MM_MSFT_WDMAUDIO_MIDIIN 103 /* Generic id for WDM Audio drivers */ +#define MM_MSFT_WDMAUDIO_MIXER 104 /* Generic id for WDM Audio drivers */ +}; + +typedef UINT MMVERSION; /* major (high byte), minor (low byte) */ +typedef UINT *LPUINT; +typedef HANDLE HWAVEOUT; +typedef HANDLE HWAVEIN; +typedef UINT MMRESULT; /* error return code, 0 means no error */ + +typedef struct mmtime_tag +{ + UINT wType; /* indicates the contents of the union */ + DWORD ms; /* milliseconds */ + DWORD sample; /* samples */ + DWORD cb; /* byte count */ + DWORD ticks; /* ticks in MIDI stream */ + +} MMTIME; +typedef MMTIME *PMMTIME; +typedef MMTIME *NPMMTIME; +typedef MMTIME *LPMMTIME; + +typedef struct wavehdr_tag { + LPSTR lpData; /* pointer to locked data buffer */ + DWORD dwBufferLength; /* length of data buffer */ + DWORD dwBytesRecorded; /* used for input only */ + DWORD dwUser; /* for client's use */ + DWORD dwFlags; /* assorted flags (see defines) */ + DWORD dwLoops; /* loop control counter */ + DWORD lpNext; /* reserved for driver */ + DWORD reserved; /* reserved for driver */ +} WAVEHDR; +typedef WAVEHDR *PWAVEHDR; +typedef WAVEHDR *NPWAVEHDR; +typedef WAVEHDR *LPWAVEHDR; + +typedef struct tagWAVEOUTCAPSA { + WORD wMid; /* manufacturer ID */ + WORD wPid; /* product ID */ + MMVERSION vDriverVersion; /* version of the driver */ + CHAR szPname[32]; /* product name (NULL terminated string) */ + DWORD dwFormats; /* formats supported */ + WORD wChannels; /* number of sources supported */ + WORD wReserved1; /* packing */ + DWORD dwSupport; /* functionality supported by driver */ +} WAVEOUTCAPSA; +typedef WAVEOUTCAPSA *PWAVEOUTCAPSA; +typedef WAVEOUTCAPSA *NPWAVEOUTCAPSA; +typedef WAVEOUTCAPSA *LPWAVEOUTCAPSA; + + +typedef struct tagWAVEOUTCAPSW { + WORD wMid; /* manufacturer ID */ + WORD wPid; /* product ID */ + MMVERSION vDriverVersion; /* version of the driver */ + WCHAR szPname[32]; /* product name (NULL terminated string) */ + DWORD dwFormats; /* formats supported */ + WORD wChannels; /* number of sources supported */ + WORD wReserved1; /* packing */ + DWORD dwSupport; /* functionality supported by driver */ +} WAVEOUTCAPSW; +typedef WAVEOUTCAPSW *PWAVEOUTCAPSW; +typedef WAVEOUTCAPSW *NPWAVEOUTCAPSW; +typedef WAVEOUTCAPSW *LPWAVEOUTCAPSW; + + +//typedef void (CALLBACK DRVCALLBACK)(HDRVR hdrvr, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); +typedef DWORD DRVCALLBACK; + + +typedef HWAVEIN *LPHWAVEIN; +typedef HWAVEOUT *LPHWAVEOUT; +typedef DRVCALLBACK WAVECALLBACK; +typedef WAVECALLBACK *LPWAVECALLBACK; + +value WORD FORMAT_TAG_VALUE +{ +#define WAVE_FORMAT_UNKNOWN 0x0000 /* Microsoft Corporation */ +#define WAVE_FORMAT_ADPCM 0x0002 /* Microsoft Corporation */ +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 /* Microsoft Corporation */ + /* IEEE754: range (+1, -1] */ + /* 32-bit/64-bit format as defined by */ + /* MSVC++ float/double type */ +#define WAVE_FORMAT_IBM_CVSD 0x0005 /* IBM Corporation */ +#define WAVE_FORMAT_ALAW 0x0006 /* Microsoft Corporation */ +#define WAVE_FORMAT_MULAW 0x0007 /* Microsoft Corporation */ +#define WAVE_FORMAT_OKI_ADPCM 0x0010 /* OKI */ +#define WAVE_FORMAT_DVI_ADPCM 0x0011 /* Intel Corporation */ +#define WAVE_FORMAT_IMA_ADPCM 0x0011 /* Intel Corporation */ +#define WAVE_FORMAT_MEDIASPACE_ADPCM 0x0012 /* Videologic */ +#define WAVE_FORMAT_SIERRA_ADPCM 0x0013 /* Sierra Semiconductor Corp */ +#define WAVE_FORMAT_G723_ADPCM 0x0014 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_DIGISTD 0x0015 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_DIGIFIX 0x0016 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_DIALOGIC_OKI_ADPCM 0x0017 /* Dialogic Corporation */ +#define WAVE_FORMAT_MEDIAVISION_ADPCM 0x0018 /* Media Vision, Inc. */ +#define WAVE_FORMAT_YAMAHA_ADPCM 0x0020 /* Yamaha Corporation of America */ +#define WAVE_FORMAT_SONARC 0x0021 /* Speech Compression */ +#define WAVE_FORMAT_DSPGROUP_TRUESPEECH 0x0022 /* DSP Group, Inc */ +#define WAVE_FORMAT_ECHOSC1 0x0023 /* Echo Speech Corporation */ +#define WAVE_FORMAT_AUDIOFILE_AF36 0x0024 /* */ +#define WAVE_FORMAT_APTX 0x0025 /* Audio Processing Technology */ +#define WAVE_FORMAT_AUDIOFILE_AF10 0x0026 /* */ +#define WAVE_FORMAT_DOLBY_AC2 0x0030 /* Dolby Laboratories */ +#define WAVE_FORMAT_GSM610 0x0031 /* Microsoft Corporation */ +#define WAVE_FORMAT_MSNAUDIO 0x0032 /* Microsoft Corporation */ +#define WAVE_FORMAT_ANTEX_ADPCME 0x0033 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_CONTROL_RES_VQLPC 0x0034 /* Control Resources Limited */ +#define WAVE_FORMAT_DIGIREAL 0x0035 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_DIGIADPCM 0x0036 /* DSP Solutions, Inc. */ +#define WAVE_FORMAT_CONTROL_RES_CR10 0x0037 /* Control Resources Limited */ +#define WAVE_FORMAT_NMS_VBXADPCM 0x0038 /* Natural MicroSystems */ +#define WAVE_FORMAT_CS_IMAADPCM 0x0039 /* Crystal Semiconductor IMA ADPCM */ +#define WAVE_FORMAT_ECHOSC3 0x003A /* Echo Speech Corporation */ +#define WAVE_FORMAT_ROCKWELL_ADPCM 0x003B /* Rockwell International */ +#define WAVE_FORMAT_ROCKWELL_DIGITALK 0x003C /* Rockwell International */ +#define WAVE_FORMAT_XEBEC 0x003D /* Xebec Multimedia Solutions Limited */ +#define WAVE_FORMAT_G721_ADPCM 0x0040 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_G728_CELP 0x0041 /* Antex Electronics Corporation */ +#define WAVE_FORMAT_MPEG 0x0050 /* Microsoft Corporation */ +#define WAVE_FORMAT_MPEGLAYER3 0x0055 /* ISO/MPEG Layer3 Format Tag */ +#define WAVE_FORMAT_CIRRUS 0x0060 /* Cirrus Logic */ +#define WAVE_FORMAT_ESPCM 0x0061 /* ESS Technology */ +#define WAVE_FORMAT_VOXWARE 0x0062 /* Voxware Inc */ +#define WAVEFORMAT_CANOPUS_ATRAC 0x0063 /* Canopus, co., Ltd. */ +#define WAVE_FORMAT_G726_ADPCM 0x0064 /* APICOM */ +#define WAVE_FORMAT_G722_ADPCM 0x0065 /* APICOM */ +#define WAVE_FORMAT_DSAT 0x0066 /* Microsoft Corporation */ +#define WAVE_FORMAT_DSAT_DISPLAY 0x0067 /* Microsoft Corporation */ +#define WAVE_FORMAT_SOFTSOUND 0x0080 /* Softsound, Ltd. */ +#define WAVE_FORMAT_RHETOREX_ADPCM 0x0100 /* Rhetorex Inc */ +#define WAVE_FORMAT_CREATIVE_ADPCM 0x0200 /* Creative Labs, Inc */ +#define WAVE_FORMAT_CREATIVE_FASTSPEECH8 0x0202 /* Creative Labs, Inc */ +#define WAVE_FORMAT_CREATIVE_FASTSPEECH10 0x0203 /* Creative Labs, Inc */ +#define WAVE_FORMAT_QUARTERDECK 0x0220 /* Quarterdeck Corporation */ +#define WAVE_FORMAT_FM_TOWNS_SND 0x0300 /* Fujitsu Corp. */ +#define WAVE_FORMAT_BTV_DIGITAL 0x0400 /* Brooktree Corporation */ +#define WAVE_FORMAT_OLIGSM 0x1000 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLIADPCM 0x1001 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLICELP 0x1002 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLISBC 0x1003 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_OLIOPR 0x1004 /* Ing C. Olivetti & C., S.p.A. */ +#define WAVE_FORMAT_LH_CODEC 0x1100 /* Lernout & Hauspie */ +#define WAVE_FORMAT_NORRIS 0x1400 /* Norris Communications, Inc. */ + +}; + +typedef struct waveformat_tag { + FORMAT_TAG_VALUE wFormatTag; /* format type */ + WORD nChannels; /* number of channels (i.e. mono, stereo, etc.) */ + DWORD nSamplesPerSec; /* sample rate */ + DWORD nAvgBytesPerSec; /* for buffer estimation */ + WORD nBlockAlign; /* block size of data */ +} WAVEFORMAT; +typedef WAVEFORMAT *PWAVEFORMAT; +typedef WAVEFORMAT *NPWAVEFORMAT; +typedef WAVEFORMAT *LPWAVEFORMAT; + +typedef struct pcmwaveformat_tag { + WAVEFORMAT wf; + WORD wBitsPerSample; +} PCMWAVEFORMAT; +typedef PCMWAVEFORMAT *PPCMWAVEFORMAT; +typedef PCMWAVEFORMAT *NPPCMWAVEFORMAT; +typedef PCMWAVEFORMAT *LPPCMWAVEFORMAT; + +typedef struct tWAVEFORMATEX +{ + WORD wFormatTag; /* format type */ + WORD nChannels; /* number of channels (i.e. mono, stereo...) */ + DWORD nSamplesPerSec; /* sample rate */ + DWORD nAvgBytesPerSec; /* for buffer estimation */ + WORD nBlockAlign; /* block size of data */ + WORD wBitsPerSample; /* number of bits per sample of mono data */ + WORD cbSize; /* the count in bytes of the size of */ + /* extra information (after cbSize) */ +} WAVEFORMATEX; +typedef WAVEFORMATEX *PWAVEFORMATEX; +typedef WAVEFORMATEX *NPWAVEFORMATEX; +typedef WAVEFORMATEX *LPWAVEFORMATEX; +typedef WAVEFORMATEX *LPCWAVEFORMATEX; + +typedef struct tagWAVEINCAPSA { + WORD wMid; /* manufacturer ID */ + WORD wPid; /* product ID */ + MMVERSION vDriverVersion; /* version of the driver */ + CHAR szPname[32]; /* product name (NULL terminated string) */ + DWORD dwFormats; /* formats supported */ + WORD wChannels; /* number of channels supported */ + WORD wReserved1; /* structure packing */ +} WAVEINCAPSA, *PWAVEINCAPSA, *NPWAVEINCAPSA, *LPWAVEINCAPSA; +typedef struct tagWAVEINCAPSW { + WORD wMid; /* manufacturer ID */ + WORD wPid; /* product ID */ + MMVERSION vDriverVersion; /* version of the driver */ + WCHAR szPname[32]; /* product name (NULL terminated string) */ + DWORD dwFormats; /* formats supported */ + WORD wChannels; /* number of channels supported */ + WORD wReserved1; /* structure packing */ +} WAVEINCAPSW; + +typedef WAVEINCAPSW *PWAVEINCAPSW; +typedef WAVEINCAPSW *NPWAVEINCAPSW; +typedef WAVEINCAPSW *LPWAVEINCAPSW; + +category Multimedia: +module WINMM.DLL: + +BOOL sndPlaySoundA(LPCSTR pszSound, PlaySoundFlags fuSound); +BOOL sndPlaySoundW(LPCWSTR pszSound, PlaySoundFlags fuSound); +BOOL PlaySound(LPCSTR pszSound, HMODULE hmod, PlaySoundFlags fdwSound); +BOOL PlaySoundA(LPCSTR pszSound, HMODULE hmod, PlaySoundFlags fdwSound); +BOOL PlaySoundW(LPCWSTR pszSound, HMODULE hmod, PlaySoundFlags fdwSound); + +MMRESULT waveOutGetDevCapsA(PRODUCT_ID_VALUE uDeviceID, [out] LPWAVEOUTCAPSA pwoc, UINT cbwoc); +MMRESULT_VALUES waveOutGetDevCapsW(PRODUCT_ID_VALUE uDeviceID, [out] LPWAVEOUTCAPSW pwoc, UINT cbwoc); +MMRESULT_VALUES waveOutGetVolume(HWAVEOUT hwo, [out] LPDWORD pdwVolume); +MMRESULT_VALUES waveOutSetVolume(HWAVEOUT hwo, DWORD dwVolume); +MMRESULT_VALUES waveOutGetErrorTextA(MMRESULT_VALUES mmrError, [out] LPSTR pszText, UINT cchText); +MMRESULT_VALUES waveOutGetErrorTextW(MMRESULT_VALUES mmrError, [out] LPWSTR pszText, UINT cchText); +MMRESULT_VALUES waveOutOpen(LPHWAVEOUT phwo, PRODUCT_ID_VALUE uDeviceID, LPCWAVEFORMATEX pwfx, DWORD dwCallback, DWORD dwInstance, WAVE_OUT_OPEN_FLAGS fdwOpen); +UINT waveOutGetNumDevs(); +MMRESULT_VALUES waveOutClose(HWAVEOUT hwo); +MMRESULT_VALUES waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +MMRESULT_VALUES waveOutUnprepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +MMRESULT_VALUES waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +MMRESULT_VALUES waveOutPause(HWAVEOUT hwo); +MMRESULT_VALUES waveOutRestart(HWAVEOUT hwo); +MMRESULT_VALUES waveOutReset(HWAVEOUT hwo); +MMRESULT_VALUES waveOutBreakLoop(HWAVEOUT hwo); +MMRESULT_VALUES waveOutGetPosition(HWAVEOUT hwo, [out] LPMMTIME pmmt, UINT cbmmt); +MMRESULT_VALUES waveOutGetPitch(HWAVEOUT hwo, [out] LPDWORD pdwPitch); +MMRESULT_VALUES waveOutSetPitch(HWAVEOUT hwo, DWORD dwPitch); +MMRESULT_VALUES waveOutGetPlaybackRate(HWAVEOUT hwo, [out] LPDWORD pdwRate); +MMRESULT_VALUES waveOutSetPlaybackRate(HWAVEOUT hwo, DWORD dwRate); +MMRESULT_VALUES waveOutGetID(HWAVEOUT hwo, [out] LPUINT puDeviceID); +MMRESULT_VALUES waveOutMessage(HWAVEOUT hwo, UINT uMsg, DWORD dw1, DWORD dw2); + + +MMRESULT_VALUES waveInGetDevCapsA(PRODUCT_ID_VALUE uDeviceID, [out] LPWAVEINCAPSA pwic, UINT cbwic); +MMRESULT_VALUES waveInGetDevCapsW(PRODUCT_ID_VALUE uDeviceID, [out] LPWAVEINCAPSW pwic, UINT cbwic); +MMRESULT_VALUES waveInGetErrorTextA(MMRESULT_VALUES mmrError, [out] LPSTR pszText, UINT cchText); +MMRESULT_VALUES waveInGetErrorTextW(MMRESULT_VALUES mmrError, [out] LPWSTR pszText, UINT cchText); +MMRESULT_VALUES waveInOpen(LPHWAVEIN phwi, PRODUCT_ID_VALUE uDeviceID, LPCWAVEFORMATEX pwfx, DWORD dwCallback, DWORD dwInstance, WAVE_OUT_OPEN_FLAGS fdwOpen); +MMRESULT_VALUES waveInClose(HWAVEIN hwi); +MMRESULT_VALUES waveInPrepareHeader(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +MMRESULT_VALUES waveInUnprepareHeader(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +MMRESULT_VALUES waveInAddBuffer(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +MMRESULT_VALUES waveInStart(HWAVEIN hwi); +MMRESULT_VALUES waveInStop(HWAVEIN hwi); +MMRESULT_VALUES waveInReset(HWAVEIN hwi); +MMRESULT_VALUES waveInGetPosition(HWAVEIN hwi, [out] LPMMTIME pmmt, UINT cbmmt); +MMRESULT_VALUES waveInGetID(HWAVEIN hwi, [out] LPUINT puDeviceID); + +value DWORD MCIERROR +{ +#define MCIERR_NO_ERROR 0 +#define MCIERR_UNRECOGNIZED_KEYWORD 259 +#define MCIERR_UNRECOGNIZED_COMMAND 261 +#define MCIERR_HARDWARE 262 +#define MCIERR_INVALID_DEVICE_NAME 263 +#define MCIERR_OUT_OF_MEMORY 264 +#define MCIERR_DEVICE_OPEN 265 +#define MCIERR_CANNOT_LOAD_DRIVER 266 +#define MCIERR_MISSING_COMMAND_STRING 267 +#define MCIERR_PARAM_OVERFLOW 268 +#define MCIERR_MISSING_STRING_ARGUMENT 269 +#define MCIERR_BAD_INTEGER 270 +#define MCIERR_PARSER_INTERNAL 271 +#define MCIERR_DRIVER_INTERNAL 272 +#define MCIERR_MISSING_PARAMETER 273 +#define MCIERR_UNSUPPORTED_FUNCTION 274 +#define MCIERR_FILE_NOT_FOUND 275 +#define MCIERR_DEVICE_NOT_READY 276 +#define MCIERR_INTERNAL 277 +#define MCIERR_DRIVER 278 +#define MCIERR_CANNOT_USE_ALL 279 +#define MCIERR_MULTIPLE 280 +#define MCIERR_EXTENSION_NOT_FOUND 281 +#define MCIERR_OUTOFRANGE 282 +#define MCIERR_FLAGS_NOT_COMPATIBLE 284 +#define MCIERR_FILE_NOT_SAVED 286 +#define MCIERR_DEVICE_TYPE_REQUIRED 287 +#define MCIERR_DEVICE_LOCKED 288 +#define MCIERR_DUPLICATE_ALIAS 289 +#define MCIERR_BAD_CONSTANT 290 +#define MCIERR_MUST_USE_SHAREABLE 291 +#define MCIERR_MISSING_DEVICE_NAME 292 +#define MCIERR_BAD_TIME_FORMAT 293 +#define MCIERR_NO_CLOSING_QUOTE 294 +#define MCIERR_DUPLICATE_FLAGS 295 +#define MCIERR_INVALID_FILE 296 +#define MCIERR_NULL_PARAMETER_BLOCK 297 +#define MCIERR_UNNAMED_RESOURCE 298 +#define MCIERR_NEW_REQUIRES_ALIAS 299 +#define MCIERR_NOTIFY_ON_AUTO_OPEN 300 +#define MCIERR_NO_ELEMENT_ALLOWED 301 +#define MCIERR_NONAPPLICABLE_FUNCTION 302 +#define MCIERR_ILLEGAL_FOR_AUTO_OPEN 303 +#define MCIERR_FILENAME_REQUIRED 304 +#define MCIERR_EXTRA_CHARACTERS 305 +#define MCIERR_DEVICE_NOT_INSTALLED 306 +#define MCIERR_GET_CD 307 +#define MCIERR_SET_CD 308 +#define MCIERR_SET_DRIVE 309 +#define MCIERR_DEVICE_LENGTH 310 +#define MCIERR_DEVICE_ORD_LENGTH 311 +#define MCIERR_NO_INTEGER 312 +#define MCIERR_WAVE_OUTPUTSINUSE 320 +#define MCIERR_WAVE_SETOUTPUTINUSE 321 +#define MCIERR_WAVE_INPUTSINUSE 322 +#define MCIERR_WAVE_SETINPUTINUSE 323 +#define MCIERR_WAVE_OUTPUTUNSPECIFIED 324 +#define MCIERR_WAVE_INPUTUNSPECIFIED 325 +#define MCIERR_WAVE_OUTPUTSUNSUITABLE 326 +#define MCIERR_WAVE_SETOUTPUTUNSUITABLE 327 +#define MCIERR_WAVE_INPUTSUNSUITABLE 328 +#define MCIERR_WAVE_SETINPUTUNSUITABLE 329 +#define MCIERR_SEQ_DIV_INCOMPATIBLE 336 +#define MCIERR_SEQ_PORT_INUSE 337 +#define MCIERR_SEQ_PORT_NONEXISTENT 338 +#define MCIERR_SEQ_PORT_MAPNODEVICE 339 +#define MCIERR_SEQ_PORT_MISCERROR 340 +#define MCIERR_SEQ_TIMER 341 +#define MCIERR_SEQ_PORTUNSPECIFIED 342 +#define MCIERR_SEQ_NOMIDIPRESENT 343 +#define MCIERR_NO_WINDOW 346 +#define MCIERR_CREATEWINDOW 347 +#define MCIERR_FILE_READ 348 +#define MCIERR_FILE_WRITE 349 +#define MCIERR_NO_IDENTITY 350 +#define MCIERR_CUSTOM_DRIVER_BASE 512 +}; + +typedef UINT MCIDEVICEID; /* MCI device ID type */ +typedef HANDLE HTASK; + +value UINT MCI_COMMAND_MESSAGE_VALUE +{ +#define MCI_OPEN 0x0803 +#define MCI_CLOSE 0x0804 +#define MCI_ESCAPE 0x0805 +#define MCI_PLAY 0x0806 +#define MCI_SEEK 0x0807 +#define MCI_STOP 0x0808 +#define MCI_PAUSE 0x0809 +#define MCI_INFO 0x080A +#define MCI_GETDEVCAPS 0x080B +#define MCI_SPIN 0x080C +#define MCI_SET 0x080D +#define MCI_STEP 0x080E +#define MCI_RECORD 0x080F +#define MCI_SYSINFO 0x0810 +#define MCI_BREAK 0x0811 +#define MCI_SAVE 0x0813 +#define MCI_STATUS 0x0814 +#define MCI_CUE 0x0830 +#define MCI_REALIZE 0x0840 +#define MCI_WINDOW 0x0841 +#define MCI_PUT 0x0842 +#define MCI_WHERE 0x0843 +#define MCI_FREEZE 0x0844 +#define MCI_UNFREEZE 0x0845 +#define MCI_LOAD 0x0850 +#define MCI_CUT 0x0851 +#define MCI_COPY 0x0852 +#define MCI_PASTE 0x0853 +#define MCI_UPDATE 0x0854 +#define MCI_RESUME 0x0855 +#define MCI_DELETE 0x0856 +}; + +mask DWORD MCI_SEND_COMMAND_MASK +{ +#define MCI_NOTIFY 0x00000001L +#define MCI_WAIT 0x00000002L +#define MCI_FROM 0x00000004L +#define MCI_TO 0x00000008L +#define MCI_TRACK 0x00000010L +#define MCI_COMMAND1 0x00000020L +#define MCI_COMMAND2 0x00000040L +#define MCI_COMMAND3 0x00000080L +#define MCI_COMMAND4 0x00000100L +#define MCI_COMMAND5 0x00000200L +#define MCI_COMMAND6 0x00000400L +#define MCI_COMMAND7 0x00000800L +#define MCI_COMMAND8 0x00001000L +#define MCI_COMMAND9 0x00002000L +#define MCI_COMMAND10 0x00004000L +#define MCI_COMMAND11 0x00008000L +}; + + +MCIERROR mciSendCommandA(MCIDEVICEID mciId, MCI_COMMAND_MESSAGE_VALUE uMsg, MCI_SEND_COMMAND_MASK dwParam1, DWORD dwParam2); +MCIERROR mciSendCommandW(MCIDEVICEID mciId, MCI_COMMAND_MESSAGE_VALUE uMsg, MCI_SEND_COMMAND_MASK dwParam1, DWORD dwParam2); +MCIERROR mciSendStringA(LPCSTR lpstrCommand, [out] LPSTR lpstrReturnString, UINT uReturnLength, HWND hwndCallback); +MCIERROR mciSendStringW(LPCWSTR lpstrCommand, [out] LPWSTR lpstrReturnString, UINT uReturnLength, HWND hwndCallback); +MCIDEVICEID mciGetDeviceIDA(LPCSTR pszDevice); +MCIDEVICEID mciGetDeviceIDW(LPCWSTR pszDevice); +MCIDEVICEID mciGetDeviceIDFromElementIDA(DWORD dwElementID, LPCSTR lpstrType ); +MCIDEVICEID mciGetDeviceIDFromElementIDW(DWORD dwElementID, LPCWSTR lpstrType ); +BOOL mciGetErrorStringA(MCIERROR mcierr, LPSTR pszText, UINT cchText); +BOOL mciGetErrorStringW(MCIERROR mcierr, LPWSTR pszText, UINT cchText); + +BOOL mciSetYieldProc(MCIDEVICEID mciId, DWORD fpYieldProc, DWORD dwYieldData); +HTASK mciGetCreatorTask(MCIDEVICEID mciId); +DWORD mciGetYieldProc(MCIDEVICEID mciId, LPDWORD pdwYieldData); +BOOL mciExecute(LPCSTR pszCommand); + + +/**************************************************************************** + + Multimedia File I/O support + +****************************************************************************/ +typedef DWORD FOURCC; /* a four character code */ +typedef char HPSTR; /* a huge version of LPSTR */ +typedef DWORD LPMMIOPROC; +typedef HANDLE HMMIO; + +typedef struct _MMIOINFO +{ + /* general fields */ + DWORD dwFlags; /* general status flags */ + FOURCC fccIOProc; /* pointer to I/O procedure */ + LPMMIOPROC pIOProc; /* pointer to I/O procedure */ + UINT wErrorRet; /* place for error to be returned */ + HTASK htask; /* alternate local task */ + + /* fields maintained by MMIO functions during buffered I/O */ + LONG cchBuffer; /* size of I/O buffer (or 0L) */ + HPSTR pchBuffer; /* start of I/O buffer (or NULL) */ + HPSTR pchNext; /* pointer to next byte to read/write */ + HPSTR pchEndRead; /* pointer to last valid byte to read */ + HPSTR pchEndWrite; /* pointer to last byte to write */ + LONG lBufOffset; /* disk offset of start of buffer */ + + /* fields maintained by I/O procedure */ + LONG lDiskOffset; /* disk offset of next read or write */ + DWORD adwInfo[3]; /* data specific to type of MMIOPROC */ + + /* other fields maintained by MMIO */ + DWORD dwReserved1; /* reserved for MMIO use */ + DWORD dwReserved2; /* reserved for MMIO use */ + HMMIO hmmio; /* handle to open file */ +} MMIOINFO; + +typedef MMIOINFO *PMMIOINFO; +typedef MMIOINFO *NPMMIOINFO; +typedef MMIOINFO *LPMMIOINFO; +typedef MMIOINFO *LPCMMIOINFO; + +/* RIFF chunk information data structure */ +typedef struct _MMCKINFO +{ + FOURCC ckid; /* chunk ID */ + DWORD cksize; /* chunk size */ + FOURCC fccType; /* form type or list type */ + DWORD dwDataOffset; /* offset of data portion of chunk */ + DWORD dwFlags; /* flags used by MMIO functions */ +} MMCKINFO; +typedef MMCKINFO *PMMCKINFO; +typedef MMCKINFO *NPMMCKINFO; +typedef MMCKINFO *LPMMCKINFO; +typedef MMCKINFO *LPCMMCKINFO; + +mask DWORD MMIOINFO_MASK +{ +#define MMIO_EXCLUSIVE 0x00000010 /* exclusive-access mode */ +#define MMIO_DENYWRITE 0x00000020 /* deny writing to other processes */ +#define MMIO_DENYREAD 0x00000030 /* deny reading to other processes */ +#define MMIO_DENYNONE 0x00000040 /* deny nothing to other processes */ +#define MMIO_CREATE 0x00001000 /* create new file (or truncate file) */ +#define MMIO_PARSE 0x00000100 /* parse new file returning path */ +#define MMIO_DELETE 0x00000200 /* create new file (or truncate file) */ +#define MMIO_EXIST 0x00004000 /* checks for existence of file */ +#define MMIO_ALLOCBUF 0x00010000 /* mmioOpen() should allocate a buffer */ +#define MMIO_GETTEMP 0x00020000 /* mmioOpen() should retrieve temp name */ +}; + +value DWORD MMIO_SEEK_VALUE +{ +#define SEEK_SET 0 /* seek to an absolute position */ +#define SEEK_CUR 1 /* seek relative to current position */ +#define SEEK_END 2 /* seek relative to end of file */ +}; + +value DWORD MMIO_RW_VALUE +{ +#define MMIO_READ 0x00000000 /* open file for reading only */ +#define MMIO_WRITE 0x00000001 /* open file for writing only */ +#define MMIO_READWRITE 0x00000002 /* open file for reading and writing */ +}; + +FOURCC mmioStringToFOURCCA(LPCSTR sz, UINT uFlags); +FOURCC mmioStringToFOURCCW(LPCWSTR sz, UINT uFlags); +LPMMIOPROC mmioInstallIOProcA(FOURCC fccIOProc, LPMMIOPROC pIOProc, DWORD dwFlags); +LPMMIOPROC mmioInstallIOProcW(FOURCC fccIOProc, LPMMIOPROC pIOProc, DWORD dwFlags); +HMMIO mmioOpenA(LPSTR pszFileName, LPMMIOINFO pmmioinfo, MMIOINFO_MASK fdwOpen); +HMMIO mmioOpenW(LPWSTR pszFileName, LPMMIOINFO pmmioinfo, MMIOINFO_MASK fdwOpen); +MMRESULT mmioRenameA(LPCSTR pszFileName, LPCSTR pszNewFileName, LPCMMIOINFO pmmioinfo, DWORD fdwRename); +MMRESULT mmioRenameW(LPCWSTR pszFileName, LPCWSTR pszNewFileName, LPCMMIOINFO pmmioinfo, DWORD fdwRename); +MMRESULT mmioClose(HMMIO hmmio, UINT fuClose); +LONG mmioRead(HMMIO hmmio, HPSTR pch, LONG cch); +LONG mmioWrite(HMMIO hmmio, char * pch, LONG cch); +LONG mmioSeek(HMMIO hmmio, LONG lOffset, MMIO_SEEK_VALUE iOrigin); +MMRESULT mmioGetInfo(HMMIO hmmio, [out] LPMMIOINFO pmmioinfo, UINT fuInfo); +MMRESULT mmioSetInfo(HMMIO hmmio, LPCMMIOINFO pmmioinfo, UINT fuInfo); +MMRESULT mmioSetBuffer(HMMIO hmmio, LPSTR pchBuffer, LONG cchBuffer, UINT fuBuffer); +MMRESULT mmioFlush(HMMIO hmmio, UINT fuFlush); +MMRESULT mmioAdvance(HMMIO hmmio, LPMMIOINFO pmmioinfo, MMIO_RW_VALUE fuAdvance); +LRESULT mmioSendMessage(HMMIO hmmio, UINT uMsg, LPARAM lParam1, LPARAM lParam2); +MMRESULT mmioDescend(HMMIO hmmio, LPMMCKINFO pmmcki, MMCKINFO * pmmckiParent, UINT fuDescend); +MMRESULT mmioAscend(HMMIO hmmio, LPMMCKINFO pmmcki, UINT fuAscend); +MMRESULT mmioCreateChunk(HMMIO hmmio, LPMMCKINFO pmmcki, UINT fuCreate); diff --git a/tools/Debugging Tools for Windows/winext/manifest/winsock2.h b/tools/Debugging Tools for Windows/winext/manifest/winsock2.h new file mode 100644 index 0000000000..541244802d --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/winsock2.h @@ -0,0 +1,281 @@ +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +// WinSock 2 API Set +// +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +category WinSock2: +module WS2_32.DLL: + + +typedef UINT* SOCKET; + +typedef struct sockaddr +{ + short sa_family; /* address family */ + char sa_data[14]; /* up to 14 bytes of direct address */ +}sockaddr; +typedef struct hostent { + LPSTR h_name; /* official name of host */ + LPSTR * h_aliases; /* alias list */ + short h_addrtype; /* host address type */ + short h_length; /* length of address */ + LPSTR * h_addr_list; /* list of addresses */ +}hostent; +typedef struct servent { + LPSTR s_name; /* official service name */ + LPSTR * s_aliases; /* alias list */ + short s_port; /* port # */ + LPSTR s_proto; /* protocol to use */ +}servent; +typedef struct protoent { + LPSTR p_name; /* official protocol name */ + LPSTR * p_aliases; /* alias list */ + short p_proto; /* protocol # */ +}protoent; +typedef struct WSAData { + WORD wVersion; + WORD wHighVersion; + char szDescription; + char szSystemStatus; + USHORT iMaxSockets; + USHORT iMaxUdpDg; + LPSTR lpVendorInfo; +} WSADATA, * LPWSADATA; +SOCKET + +accept( + SOCKET s, + [out] sockaddr * addr, + [out] int * addrlen + ); + + +int + +bind( + SOCKET s, + sockaddr * name, + int namelen + ); + + + +int + +closesocket( + SOCKET s + ); + + + +int + +connect( + SOCKET s, + sockaddr * name, + int namelen + ); + + + +int + +ioctlsocket( + SOCKET s, + long cmd, + [out] int * argp + ); + + +int + +getpeername( + SOCKET s, + [out] sockaddr * name, + [out] int * namelen + ); + + +int + +getsockname( + SOCKET s, + [out] sockaddr * name, + [out] int * namelen + ); + + + +int + +getsockopt( + SOCKET s, + int level, + int optname, + [out] LPSTR optval, + [out] int * optlen + ); + + +int + +htonl( + int hostlong + ); + + +int + +htons( + int hostshort + ); + +int + +inet_addr( + LPSTR cp + ); + +int + +listen( + SOCKET s, + int backlog + ); + + +int + +ntohl( + int netlong + ); + +int + +ntohs( + int netshort + ); + + +int + +recv( + SOCKET s, + [out] LPSTR buf, + int len, + int flags + ); + + +int + +recvfrom( + SOCKET s, + [out] LPSTR buf, + int len, + int flags, + [out] sockaddr * from, + [out] int * fromlen + ); + + +int + +send( + SOCKET s, + LPSTR buf, + int len, + int flags + ); + + +int + +sendto( + SOCKET s, + LPSTR buf, + int len, + int flags, + sockaddr * to, + int tolen + ); + + +int + +setsockopt( + SOCKET s, + int level, + int optname, + LPSTR optval, + int optlen + ); + + + + +int + +shutdown( + SOCKET s, + int how + ); + + + + +SOCKET + +socket( + int af, + int type, + int protocol + ); + + +/* Database function prototypes */ + +hostent * +gethostbyaddr( + LPSTR addr, + int len, + int type + ); +hostent * +gethostbyname( + LPSTR name + ); + +int +gethostname( + [out] LPSTR name, + int namelen + ); + + +servent * +getservbyport( + int port, + LPSTR proto + ); + + + +servent * +getservbyname( + LPSTR name, + LPSTR proto + ); + +protoent * +getprotobynumber( + int number + ); + +protoent * +getprotobyname( + LPSTR name + ); diff --git a/tools/Debugging Tools for Windows/winext/manifest/winspool.h b/tools/Debugging Tools for Windows/winext/manifest/winspool.h new file mode 100644 index 0000000000..663112bf9c --- /dev/null +++ b/tools/Debugging Tools for Windows/winext/manifest/winspool.h @@ -0,0 +1,949 @@ + +category Printing: +module WINSPOOL.DRV: + +mask DWORD PrinterEnum +{ +#define PRINTER_ENUM_DEFAULT 0x00000001 +#define PRINTER_ENUM_LOCAL 0x00000002 +#define PRINTER_ENUM_CONNECTIONS 0x00000004 +#define PRINTER_ENUM_FAVORITE 0x00000004 +#define PRINTER_ENUM_NAME 0x00000008 +#define PRINTER_ENUM_REMOTE 0x00000010 +#define PRINTER_ENUM_SHARED 0x00000020 +#define PRINTER_ENUM_NETWORK 0x00000040 + +#define PRINTER_ENUM_EXPAND 0x00004000 +#define PRINTER_ENUM_CONTAINER 0x00008000 + +#define PRINTER_ENUM_ICON1 0x00010000 +#define PRINTER_ENUM_ICON2 0x00020000 +#define PRINTER_ENUM_ICON3 0x00040000 +#define PRINTER_ENUM_ICON4 0x00080000 +#define PRINTER_ENUM_ICON5 0x00100000 +#define PRINTER_ENUM_ICON6 0x00200000 +#define PRINTER_ENUM_ICON7 0x00400000 +#define PRINTER_ENUM_ICON8 0x00800000 +#define PRINTER_ENUM_HIDE 0x01000000 +}; + +FailOnFalse [gle] EnumPrintersA(PrinterEnum Flags, // printer object types + LPSTR Name, // name of printer object + int Level, // information level + LPBYTE pPrinterEnum, // printer information buffer + int cbBuf, // size of printer information buffer + [out] int* pcbNeeded, // bytes received or required + [out] int* pcReturned); // number of printers enumerated + +FailOnFalse [gle] EnumPrintersW(PrinterEnum Flags, // printer object types + LPWSTR Name, // name of printer object + int Level, // information level + LPBYTE pPrinterEnum, // printer information buffer + int cbBuf, // size of printer information buffer + [out] int* pcbNeeded, // bytes received or required + [out] int* pcReturned); // number of printers enumerated + +FailOnFalse [gle] GetPrinterA(HPRINTER hPrinter, + int Level, + [out] LPBYTE pPrinter, + int cbBuf, + [out] int* pcbNeeded); + +FailOnFalse [gle] GetPrinterW(HPRINTER hPrinter, + int Level, + [out] LPBYTE pPrinter, + int cbBuf, + [out] int* pcbNeeded); + +mask DWORD PrinterAccessMode +{ +#define PRINTER_ACCESS_ADMINISTER 0x00000004 +#define PRINTER_ACCESS_USE 0x00000008 +#define STANDARD_RIGHTS_REQUIRED 0x000F000 + +#define DELETE 0x00010000 +#define READ_CONTROL 0x00020000 +#define WRITE_DAC 0x00040000 +#define WRITE_OWNER 0x00080000 +#define SYNCHRONIZE 0x00100000 +}; + +value DWORD SetPrinterCommand +{ +#define PRINTER_CONTROL_PAUSE 1 +#define PRINTER_CONTROL_RESUME 2 +#define PRINTER_CONTROL_PURGE 3 +#define PRINTER_CONTROL_SET_STATUS 4 +}; + +FailOnFalse [gle] SetPrinterA(HPRINTER hPrinter, + int Level, + LPBYTE pPrinter, + SetPrinterCommand Command); + +FailOnFalse [gle] SetPrinterW(HPRINTER hPrinter, + int Level, + LPBYTE pPrinter, + SetPrinterCommand Command); + +HPRINTER [gle] AddPrinterA(LPSTR pName, + int Level, + LPBYTE pPrinter); + +HPRINTER [gle] AddPrinterW(LPWSTR pName, + int Level, + LPBYTE pPrinter); + +FailOnFalse [gle] AbortPrinter(HPRINTER hPrinter); + +FailOnFalse [gle] AddFormA(HPRINTER hPrinter, + int Level, + LPBYTE pForm); + +FailOnFalse [gle] AddFormW(HPRINTER hPrinter, + int Level, + LPBYTE pForm); + +typedef struct _ADDJOB_INFO_1A { + LPSTR Path; + DWORD JobId; +} ADDJOB_INFO_1A, *LPADDJOB_INFO_1A; + +typedef struct _ADDJOB_INFO_1W { + LPWSTR Path; + DWORD JobId; +} ADDJOB_INFO_1W, *LPADDJOB_INFO_1W; + +FailOnFalse [gle] AddJobA(HPRINTER hPrinter, + int Level, + [out] ADDJOB_INFO_1A* pData, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] AddJobW(HPRINTER hPrinter, + int Level, + [out] ADDJOB_INFO_1W* pData, + int cbBuf, + [out] LPDWORD pcbNeeded); + +typedef struct _MONITOR_INFO_2A{ + LPSTR pName; + LPSTR pEnvironment; + LPSTR pDLLName; +} MONITOR_INFO_2A, *LPMONITOR_INFO_2A; + +typedef struct _MONITOR_INFO_2W{ + LPWSTR pName; + LPWSTR pEnvironment; + LPWSTR pDLLName; +} MONITOR_INFO_2W, *LPMONITOR_INFO_2W; + +FailOnFalse [gle] AddMonitorA(LPSTR pName, + int Level, + MONITOR_INFO_2A* pMonitors); + +FailOnFalse [gle] AddMonitorW(LPSTR pName, + int Level, + MONITOR_INFO_2A* pMonitors); + +FailOnFalse [gle] AddPortA(LPSTR pName, + HWND hWnd, + LPSTR pMonitorName); + +FailOnFalse [gle] AddPortW(LPWSTR pName, + HWND hWnd, + LPWSTR pMonitorName); + +FailOnFalse [gle] AddPrinterConnectionA(LPSTR pName); +FailOnFalse [gle] AddPrinterConnectionW(LPWSTR pName); + +FailOnFalse [gle] AddPrinterDriverA(LPSTR pName, + int Level, + [out] LPBYTE pDriverInfo); + +FailOnFalse [gle] AddPrinterDriverW(LPWSTR pName, + int Level, + [out] LPBYTE pDriverInfo); + +mask DWORD AddPrinterDriverExFlags +{ +#define APD_STRICT_UPGRADE 0x00000001 +#define APD_STRICT_DOWNGRADE 0x00000002 +#define APD_COPY_ALL_FILES 0x00000004 +#define APD_COPY_NEW_FILES 0x00000008 +#define APD_COPY_FROM_DIRECTORY 0x00000010 +}; + +FailOnFalse [gle] AddPrinterDriverExA(LPSTR pName, + int Level, + [out] LPBYTE pDriverInfo, + AddPrinterDriverExFlags dwFileCopyFlags); + +FailOnFalse [gle] AddPrinterDriverExW(LPWSTR pName, + int Level, + [out] LPBYTE pDriverInfo, + AddPrinterDriverExFlags dwFileCopyFlags); + +FailOnFalse [gle] AddPrintProcessorA(LPSTR pName, + LPSTR pEnvironment, + LPSTR pPathName, + LPSTR pPrintProcessorName); + +FailOnFalse [gle] AddPrintProcessorW(LPWSTR pName, + LPWSTR pEnvironment, + LPWSTR pPathName, + LPWSTR pPrintProcessorName); + +FailOnFalse [gle] AddPrintProvidorA(LPSTR pName, + int level, + LPBYTE pProvidorInfo); + +FailOnFalse [gle] AddPrintProvidorW(LPWSTR pName, + int level, + LPBYTE pProvidorInfo); + +FailOnFalse [gle] AdvancedDocumentPropertiesA(HWND hWnd, + HPRINTER hPrinter, + LPSTR pDeviceName, + [out] PDEVMODEA pDevModeOutput, + PDEVMODEA pDevModeInput); + +FailOnFalse [gle] AdvancedDocumentPropertiesW(HWND hWnd, + HPRINTER hPrinter, + LPWSTR pDeviceName, + [out] PDEVMODEW pDevModeOutput, + PDEVMODEW pDevModeInput); + +FailOnFalse [gle] ClosePrinter([da] HPRINTER hPrinter); + +FailOnFalse [gle] ConfigurePortA(LPSTR pName, + HWND hWnd, + LPSTR pPortName); + +FailOnFalse [gle] ConfigurePortW(LPWSTR pName, + HWND hWnd, + LPWSTR pPortName); + +HPRINTER ConnectToPrinterDlg(HWND hwnd, + DWORD Flags); + +FailOnFalse [gle] DeleteFormA(HPRINTER hPrinter, + LPSTR pFormName); + +FailOnFalse [gle] DeleteFormW(HPRINTER hPrinter, + LPWSTR pFormName); + +FailOnFalse [gle] DeleteMonitorA(LPSTR pName, + LPSTR pEnvironment, + LPSTR pMonitorName); + +FailOnFalse [gle] DeleteMonitorW(LPWSTR pName, + LPWSTR pEnvironment, + LPWSTR pMonitorName); + +FailOnFalse [gle] DeletePortA(LPSTR pName, + HWND hWnd, + LPSTR pPortName); + +FailOnFalse [gle] DeletePortW(LPWSTR pName, + HWND hWnd, + LPWSTR pPortName); + +FailOnFalse [gle] DeletePrinter(HPRINTER hPrinter); + +FailOnFalse [gle] DeletePrinterConnectionA(LPSTR pName); +FailOnFalse [gle] DeletePrinterConnectionW(LPWSTR pName); + +FailOnFalse [gle] DeletePrinterDataA(HPRINTER hPrinter, + LPSTR pValueName); + +FailOnFalse [gle] DeletePrinterDataW(HPRINTER hPrinter, + LPWSTR pValueName); + +FailOnFalse [gle] DeletePrinterDataExA(HPRINTER hPrinter, + LPSTR pKeyName, + LPSTR pValueName); + +FailOnFalse [gle] DeletePrinterDataExW(HPRINTER hPrinter, + LPWSTR pKeyName, + LPWSTR pValueName); + +FailOnFalse [gle] DeletePrinterDriverA(LPSTR pName, + LPSTR pEnvironment, + LPSTR pDriverName); + +FailOnFalse [gle] DeletePrinterDriverW(LPWSTR pName, + LPWSTR pEnvironment, + LPWSTR pDriverName); +mask DWORD DPDFlags +{ +// FLAGS for DeletePrinterDriverEx. +#define DPD_DELETE_UNUSED_FILES 0x00000001 +#define DPD_DELETE_SPECIFIC_VERSION 0x00000002 +#define DPD_DELETE_ALL_FILES 0x00000004 +}; + +FailOnFalse [gle] DeletePrinterDriverExA(LPSTR pName, + LPSTR pEnvironment, + LPSTR pDriverName, + DPDFlags dwDeleteFlag, + DWORD dwVersionFlag); + +FailOnFalse [gle] DeletePrinterDriverExW(LPWSTR pName, + LPWSTR pEnvironment, + LPWSTR pDriverName, + DPDFlags dwDeleteFlag, + DWORD dwVersionFlag); + +WinError DeletePrinterKeyA(HPRINTER hPrinter, LPSTR pKeyName); +WinError DeletePrinterKeyW(HPRINTER hPrinter, LPWSTR pKeyName); + +FailOnFalse [gle] DeletePrintProcessorA(LPSTR pName, + LPSTR pEnvironment, + LPSTR pPrintProcessorName); + +FailOnFalse [gle] DeletePrintProcessorW(LPWSTR pName, + LPWSTR pEnvironment, + LPWSTR pPrintProcessorName); + +FailOnFalse [gle] DeletePrintProvidorA(LPSTR pName, + LPSTR pEnvironment, + LPSTR pPrintProvidorName); + +FailOnFalse [gle] DeletePrintProvidorW(LPWSTR pName, + LPWSTR pEnvironment, + LPWSTR pPrintProvidorName); + +mask DWORD DPFlags +{ +#define DM_IN_BUFFER 8 +#define DM_IN_PROMPT 4 +#define DM_OUT_BUFFER 2 +#define DM_OUT_DEFAULT 1 +}; + +LongFailIfNeg1 [gle] DocumentPropertiesA(HWND hWnd, + HPRINTER hPrinter, + LPSTR pDeviceName, + [out] PDEVMODEA pDevModeOutput, + PDEVMODEA pDevModeInput, + DPFlags fMode); + +LongFailIfNeg1 [gle] DocumentPropertiesW(HWND hWnd, + HPRINTER hPrinter, + LPWSTR pDeviceName, + [out] PDEVMODEW pDevModeOutput, + PDEVMODEW pDevModeInput, + DPFlags fMode); + +FailOnFalse [gle] EndDocPrinter(HPRINTER hPrinter); + +FailOnFalse [gle] EndPagePrinter(HPRINTER hPrinter); + + +mask DWORD FormInfoFlags +{ +#define FORM_USER 0x00000000 +#define FORM_BUILTIN 0x00000001 +#define FORM_PRINTER 0x00000002 +}; + +typedef struct _FORM_INFO_1A { + FormInfoFlags Flags; + LPSTR pName; + SIZE Size; + RECT ImageableArea; +} FORM_INFO_1A, *LPFORM_INFO_1A, *PFORM_INFO_1A; + +typedef struct _FORM_INFO_1W { + FormInfoFlags Flags; + LPWSTR pName; + SIZE Size; + RECT ImageableArea; +} FORM_INFO_1W, *LPFORM_INFO_1W, *PFORM_INFO_1W; + +FailOnFalse [gle] EnumFormsA(HPRINTER hPrinter, + DWORD Level, + [out] LPFORM_INFO_1A pForm, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumFormsW(HPRINTER hPrinter, + DWORD Level, + [out] LPFORM_INFO_1W pForm, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumJobsA(HPRINTER hPrinter, + int FirstJob, + int NoJobs, + int Level, + [out] LPBYTE pJob, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumJobsW(HPRINTER hPrinter, + int FirstJob, + int NoJobs, + int Level, + [out] LPBYTE pJob, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumMonitorsA(LPSTR pName, + int Level, + [out] LPBYTE pMonitors, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumMonitorsW(LPWSTR pName, + int Level, + [out] LPBYTE pMonitors, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumPortsA(LPSTR pName, + int Level, + [out] LPBYTE pPorts, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumPortsW(LPWSTR pName, + int Level, + [out] LPBYTE pPorts, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumPrinterDataA(HPRINTER hPrinter, + int dwIndex, + [out] LPSTR pValueName, + int cbValueName, + [out] int* pcbValueName, + [out] RegistryType* pType, + [out] LPBYTE pData, + int cbData, + [out] int* pcbData); + +FailOnFalse [gle] EnumPrinterDataW(HPRINTER hPrinter, + int dwIndex, + [out] LPWSTR pValueName, + int cbValueName, + [out] int* pcbValueName, + [out] RegistryType* pType, + [out] LPBYTE pData, + int cbData, + [out] int* pcbData); + +FailOnFalse [gle] EnumPrinterDataExA(HPRINTER hPrinter, + LPSTR pKeyName, + [out] LPBYTE pEnumValues, + int cbEnumValues, + [out] int* pcbEnumValues, + [out] int* pnEnumValues); + +FailOnFalse [gle] EnumPrinterDataExW(HPRINTER hPrinter, + LPWSTR pKeyName, + [out] LPBYTE pEnumValues, + int cbEnumValues, + [out] int* pcbEnumValues, + [out] int* pnEnumValues); + +FailOnFalse [gle] EnumPrinterDriversA(LPSTR pName, + LPSTR pEnvironment, + int Level, + [out] LPBYTE pDriverInfo, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +FailOnFalse [gle] EnumPrinterDriversW(LPWSTR pName, + LPWSTR pEnvironment, + int Level, + [out] LPBYTE pDriverInfo, + int cbBuf, + [out] int* pcbNeeded, + [out] int* pcReturned); + +WinError EnumPrinterKeyA(HPRINTER hPrinter, + LPSTR pKeyName, + [out] LPSTR pSubkey, + int cbSubkey, + [out] int* pcbSubkey); + +WinError EnumPrinterKeyW(HPRINTER hPrinter, + LPWSTR pKeyName, + [out] LPWSTR pSubkey, + int cbSubkey, + [out] int* pcbSubkey); + +WinError GetPrinterDataA(HPRINTER hPrinter, + LPSTR pValueName, + [out] RegistryType* pType, + [out] LPBYTE pData, + int nSize, + [out] int* pcbNeeded); + +WinError GetPrinterDataW(HPRINTER hPrinter, + LPWSTR pValueName, + [out] RegistryType* pType, + [out] LPBYTE pData, + int nSize, + [out] int* pcbNeeded); + +WinError SetPrinterDataA(HPRINTER hPrinter, + LPSTR pValueName, + RegistryType Type, + LPBYTE pData, + int cbData); + +WinError SetPrinterDataW(HPRINTER hPrinter, + LPWSTR pValueName, + RegistryType Type, + LPBYTE pData, + int cbData); + +WinError SetPrinterDataExA(HPRINTER hPrinter, + LPSTR pKeyName, + LPSTR pValueName, + RegistryType Type, + LPBYTE pData, + int cbData); + +WinError SetPrinterDataExW(HPRINTER hPrinter, + LPWSTR pKeyName, + LPWSTR pValueName, + RegistryType Type, + LPBYTE pData, + int cbData); + +FailOnFalse [gle] GetDefaultPrinterA([out] LPSTR pszBuffer, + [out] int* pcchBuffer); + +FailOnFalse [gle] GetDefaultPrinterW([out] LPWSTR pszBuffer, + [out] int* pcchBuffer); + +value DWORD DILevel +{ +#define DRIVER_INFO_1 1 +#define DRIVER_INFO_2 2 +#define DRIVER_INFO_3 3 +#define DRIVER_INFO_4 4 +#define DRIVER_INFO_5 5 +#define DRIVER_INFO_6 6 +}; + +FailOnFalse [gle] GetPrinterDriverA( HPRINTER hPrinter, + LPSTR pEnvironment, + DILevel Level, + [out] LPBYTE pDriverInfo, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetPrinterDriverW( HPRINTER hPrinter, + LPWSTR pEnvironment, + DILevel Level, + [out] LPBYTE pDriverInfo, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] EnumPrintProcessorDatatypesA( LPSTR pName, + LPSTR pPrintProcessorName, + int Level, + [out] LPSTR pDatatypes, + int cbBuf, + [out] LPDWORD pcbNeeded, + [out] LPDWORD pcReturned); + +FailOnFalse [gle] EnumPrintProcessorDatatypesW( LPWSTR pName, + LPWSTR pPrintProcessorName, + int Level, + [out] LPWSTR pDatatypes, + int cbBuf, + [out] LPDWORD pcbNeeded, + [out] LPDWORD pcReturned); + +FailOnFalse [gle] EnumPrintProcessorsA( LPSTR pName, + LPSTR pEnvironment, + int Level, + [out] LPSTR pPrintProcessorInfo, + int cbBuf, + [out] LPDWORD pcbNeeded, + [out] LPDWORD pcReturned); + +FailOnFalse [gle] EnumPrintProcessorsW( LPWSTR pName, + LPWSTR pEnvironment, + int Level, + [out] LPWSTR pPrintProcessorInfo, + int cbBuf, + [out] LPDWORD pcbNeeded, + [out] LPDWORD pcReturned); + +FailOnFalse [gle] FindClosePrinterChangeNotification( HPRINTER hChange ); + +typedef struct _PRINTER_NOTIFY_OPTIONS_TYPE { + WORD Type; + WORD Reserved0; + DWORD Reserved1; + DWORD Reserved2; + DWORD Count; + WORD *pFields; +} PRINTER_NOTIFY_OPTIONS_TYPE, *PPRINTER_NOTIFY_OPTIONS_TYPE; + +typedef struct _PRINTER_NOTIFY_OPTIONS { + DWORD Version; + DWORD Flags; + DWORD Count; + PPRINTER_NOTIFY_OPTIONS_TYPE pTypes; +} PRINTER_NOTIFY_OPTIONS, *PPRINTER_NOTIFY_OPTIONS; + +mask DWORD PCFlags +{ +#define PRINTER_CHANGE_ADD_PRINTER 0x00000001 +#define PRINTER_CHANGE_SET_PRINTER 0x00000002 +#define PRINTER_CHANGE_DELETE_PRINTER 0x00000004 +#define PRINTER_CHANGE_FAILED_CONNECTION_PRINTER 0x00000008 +#define PRINTER_CHANGE_PRINTER 0x000000FF +#define PRINTER_CHANGE_ADD_JOB 0x00000100 +#define PRINTER_CHANGE_SET_JOB 0x00000200 +#define PRINTER_CHANGE_DELETE_JOB 0x00000400 +#define PRINTER_CHANGE_WRITE_JOB 0x00000800 +#define PRINTER_CHANGE_JOB 0x0000FF00 +#define PRINTER_CHANGE_ADD_FORM 0x00010000 +#define PRINTER_CHANGE_SET_FORM 0x00020000 +#define PRINTER_CHANGE_DELETE_FORM 0x00040000 +#define PRINTER_CHANGE_FORM 0x00070000 +#define PRINTER_CHANGE_ADD_PORT 0x00100000 +#define PRINTER_CHANGE_CONFIGURE_PORT 0x00200000 +#define PRINTER_CHANGE_DELETE_PORT 0x00400000 +#define PRINTER_CHANGE_PORT 0x00700000 +#define PRINTER_CHANGE_ADD_PRINT_PROCESSOR 0x01000000 +#define PRINTER_CHANGE_DELETE_PRINT_PROCESSOR 0x04000000 +#define PRINTER_CHANGE_PRINT_PROCESSOR 0x07000000 +#define PRINTER_CHANGE_ADD_PRINTER_DRIVER 0x10000000 +#define PRINTER_CHANGE_SET_PRINTER_DRIVER 0x20000000 +#define PRINTER_CHANGE_DELETE_PRINTER_DRIVER 0x40000000 +#define PRINTER_CHANGE_PRINTER_DRIVER 0x70000000 +#define PRINTER_CHANGE_TIMEOUT 0x80000000 +#define PRINTER_CHANGE_ALL 0x7777FFFF +}; + +HPRINTER FindFirstPrinterChangeNotification( HPRINTER hPrinter, + PCFlags fdwFlags, + int fdwOptions, + PPRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions); + +FailOnFalse [gle] FindNextPrinterChangeNotification( HPRINTER hChange, + PDWORD pdwChange, + LPVOID pPrinterNotifyOptions, + [out] LPVOID *ppPrinterNotifyInfo); + +FailOnFalse [gle] FlushPrinter( HPRINTER hPrinter, + LPVOID pBuf, + int cbBuf, + [out] LPDWORD pcWritten, + DWORD cSleep); + +typedef struct _PRINTER_NOTIFY_INFO_DATA { + WORD Type; + WORD Field; + DWORD Reserved; + DWORD Id; + int cbBuf; + LPVOID pBuf; +} PRINTER_NOTIFY_INFO_DATA, *PPRINTER_NOTIFY_INFO_DATA; + +typedef struct _PRINTER_NOTIFY_INFO { + DWORD Version; + DWORD Flags; + DWORD Count; + PRINTER_NOTIFY_INFO_DATA aData[1]; +} PRINTER_NOTIFY_INFO, *PPRINTER_NOTIFY_INFO; + +FailOnFalse [gle] FreePrinterNotifyInfo( PPRINTER_NOTIFY_INFO pPrinterNotifyInfo ); + +FailOnFalse [gle] GetFormA( HPRINTER hPrinter, + LPSTR pFormName, + int Level, + [out] PFORM_INFO_1A pForm, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetFormW( HPRINTER hPrinter, + LPWSTR pFormName, + int Level, + [out] PFORM_INFO_1W pForm, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetJobA( HPRINTER hPrinter, + DWORD JobId, + int Level, + [out] LPBYTE pJob, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetJobW( HPRINTER hPrinter, + DWORD JobId, + int Level, + [out] LPBYTE pJob, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetPrinterDriverDirectoryA( LPSTR pName, + LPSTR pEnvironment, + int Level, + [out] LPBYTE pDriverDirectory, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetPrinterDriverDirectoryW( LPWSTR pName, + LPWSTR pEnvironment, + int Level, + [out] LPBYTE pDriverDirectory, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetPrintProcessorDirectoryA( LPSTR pName, + LPSTR pEnvironment, + int Level, + [out] LPBYTE pPrintProcessorInfo, + int cbBuf, + [out] LPDWORD pcbNeeded); + +FailOnFalse [gle] GetPrintProcessorDirectoryW( LPWSTR pName, + LPWSTR pEnvironment, + int Level, + [out] LPBYTE pPrintProcessorInfo, + int cbBuf, + [out] LPDWORD pcbNeeded); + +typedef struct _PRINTER_DEFAULTSA{ + LPSTR pDatatype; + LPDEVMODEA pDevMode; + PrinterAccessMode DesiredAccess; +} PRINTER_DEFAULTSA, *PPRINTER_DEFAULTSA, *LPPRINTER_DEFAULTSA; + +typedef struct _PRINTER_DEFAULTSW{ + LPWSTR pDatatype; + LPDEVMODEW pDevMode; + PrinterAccessMode DesiredAccess; +} PRINTER_DEFAULTSW, *PPRINTER_DEFAULTSW, *LPPRINTER_DEFAULTSW; + +FailOnFalse [gle] OpenPrinterA(LPSTR pPrinterName, + [out] HPRINTER* phPrinter, + LPPRINTER_DEFAULTSA pDefault); + +FailOnFalse [gle] OpenPrinterW(LPWSTR pPrinterName, + [out] HPRINTER* phPrinter, + LPPRINTER_DEFAULTSW pDefault); + +FailOnFalse [gle] PrinterProperties( HWND hWnd, + HPRINTER hPrinter); + +FailOnFalse [gle] ReadPrinter( HPRINTER hPrinter, + [out] LPVOID pBuf, + int cbBuf, + [out] LPDWORD pNoBytesRead); + +FailOnFalse [gle] ResetPrinterA( HPRINTER hPrinter, + PPRINTER_DEFAULTSA pDefault); + +FailOnFalse [gle] ResetPrinterW( HPRINTER hPrinter, + PPRINTER_DEFAULTSW pDefault); + +FailOnFalse [gle] ScheduleJob( HPRINTER hPrinter, + DWORD dwJobID); + +FailOnFalse [gle] SetDefaultPrinterA( LPCSTR pszPrinter ); + +FailOnFalse [gle] SetDefaultPrinterW( LPCWSTR pszPrinter ); + +FailOnFalse [gle] SetFormA( HPRINTER hPrinter, + LPSTR pFormName, + int Level, + PFORM_INFO_1A pForm); + +FailOnFalse [gle] SetFormW( HPRINTER hPrinter, + LPWSTR pFormName, + int Level, + PFORM_INFO_1W pForm); + +FailOnFalse [gle] SetJobA( HPRINTER hPrinter, + DWORD JobId, + int Level, + LPBYTE pJob, + DWORD Command); + +FailOnFalse [gle] SetJobW( HPRINTER hPrinter, + DWORD JobId, + int Level, + LPBYTE pJob, + DWORD Command); + +typedef struct _PORT_INFO_3A { + DWORD dwStatus; + LPSTR pszStatus; + DWORD dwSeverity; +} PORT_INFO_3A, *PPORT_INFO_3A; + +typedef struct _PORT_INFO_3W { + DWORD dwStatus; + LPWSTR pszStatus; + DWORD dwSeverity; +} PORT_INFO_3W, *PPORT_INFO_3W; + +FailOnFalse [gle] SetPortA( LPSTR pName, + LPSTR pPortName, + DWORD dwLevel, + PPORT_INFO_3A pPortInfo); + +FailOnFalse [gle] SetPortW( LPWSTR pName, + LPWSTR pPortName, + DWORD dwLevel, + PPORT_INFO_3W pPortInfo); + +typedef struct _DOC_INFO_1A { + LPSTR pDocName; + LPSTR pOutputFile; + LPSTR pDatatype; +} DOC_INFO_1A, *PDOC_INFO_1A; + +typedef struct _DOC_INFO_1W { + LPWSTR pDocName; + LPWSTR pOutputFile; + LPWSTR pDatatype; +} DOC_INFO_1W, *PDOC_INFO_1W; + +FailOnFalse [gle] StartDocPrinterA( HPRINTER hPrinter, + int Level, + PDOC_INFO_1A pDocInfo); + +FailOnFalse [gle] StartDocPrinterW( HPRINTER hPrinter, + int Level, + PDOC_INFO_1W pDocInfo); + +FailOnFalse [gle] StartPagePrinter( HPRINTER hPrinter ); + +FailOnFalse [gle] WritePrinter( HPRINTER hPrinter, + LPVOID pBuf, + int cbBuf, + [out] LPDWORD pcWritten); + + +// +// The following functions are used to print +// + + +value DWORD DeviceCapabilitiesEnum +{ +#define DC_FIELDS 1 +#define DC_PAPERS 2 +#define DC_PAPERSIZE 3 +#define DC_MINEXTENT 4 +#define DC_MAXEXTENT 5 +#define DC_BINS 6 +#define DC_DUPLEX 7 +#define DC_SIZE 8 +#define DC_EXTRA 9 +#define DC_VERSION 10 +#define DC_DRIVER 11 +#define DC_BINNAMES 12 +#define DC_ENUMRESOLUTIONS 13 +#define DC_FILEDEPENDENCIES 14 +#define DC_TRUETYPE 15 +#define DC_PAPERNAMES 16 +#define DC_ORIENTATION 17 +#define DC_COPIES 18 +#define DC_BINADJUST 19 +#define DC_EMF_COMPLIANT 20 +#define DC_DATATYPE_PRODUCED 21 +#define DC_COLLATE 22 +#define DC_MANUFACTURER 23 +#define DC_MODEL 24 +#define DC_PERSONALITY 25 +#define DC_PRINTRATE 26 +#define DC_PRINTRATEUNIT 27 +#define DC_PRINTERMEM 28 +#define DC_MEDIAREADY 29 +#define DC_STAPLE 30 +#define DC_PRINTRATEPPM 31 +#define DC_COLORDEVICE 32 +#define DC_NUP 33 +}; + +SpoolerError [gle] DeviceCapabilitiesA(LPSTR pszPrinterName, + LPSTR pszPortName, + DeviceCapabilitiesEnum capabilities, + [out] LPSTR pszOutput, + DEVMODEA* pDevMode); + +SpoolerError [gle] DeviceCapabilitiesW(LPWSTR pszPrinterName, + LPWSTR pszPortName, + DeviceCapabilitiesEnum capabilities, + [out] LPWSTR pszOutput, + DEVMODEW* pDevMode); + +module GDI32.DLL: + +SpoolerError [gle] AbortDoc(HDC hdc); +SpoolerError [gle] EndDoc(HDC hdc); +SpoolerError [gle] EndPage(HDC hdc); + + +SpoolerError [gle] Escape(HDC hdc, + GdiEscapeCode escapeCode, + int cbSize, + LPSTR pszInData, + [out] LPVOID pOutData); + +SpoolerError [gle] ExtEscape(HDC hdc, + GdiEscapeCode escapeCode, + int cbInput, + LPSTR pszInData, + int cbOutput, + [out] LPSTR lpszOutData); + +typedef LPVOID ABORTPROC; + +SpoolerError [gle] SetAbortProc(HDC hdc, + ABORTPROC pfnAbort); + +value DWORD DocInfoType +{ +#define DI_NONE 0x00000000 +#define DI_APPBANDING 0x00000001 +#define DI_ROPS_READ_DESTINATION 0x00000002 +}; + +typedef struct _DOCINFOA { + int cbSize; + LPCSTR lpszDocName; + LPCSTR lpszOutput; + LPCSTR lpszDatatype; + DocInfoType fwType; +} DOCINFOA, *LPDOCINFOA; + +typedef struct _DOCINFOW { + int cbSize; + LPCWSTR lpszDocName; + LPCWSTR lpszOutput; + LPCWSTR lpszDatatype; + DocInfoType fwType; +} DOCINFOW, *LPDOCINFOW; + +SpoolerError [gle] StartDocA(HDC hdc, + DOCINFOA* pDocInfo); + +SpoolerError [gle] StartDocW(HDC hdc, + DOCINFOW* pDocInfo); + +SpoolerError [gle] StartPage(HDC hdc); diff --git a/tools/Debugging Tools for Windows/winext/rcdrkd.dll b/tools/Debugging Tools for Windows/winext/rcdrkd.dll new file mode 100644 index 0000000000..7599b21cb2 Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/rcdrkd.dll differ diff --git a/tools/Debugging Tools for Windows/winext/uext.dll b/tools/Debugging Tools for Windows/winext/uext.dll new file mode 100644 index 0000000000..14a5d7acaa Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/uext.dll differ diff --git a/tools/Debugging Tools for Windows/winext/usb3kd.dll b/tools/Debugging Tools for Windows/winext/usb3kd.dll new file mode 100644 index 0000000000..3e9e071ee3 Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/usb3kd.dll differ diff --git a/tools/Debugging Tools for Windows/winext/wdfkd.dll b/tools/Debugging Tools for Windows/winext/wdfkd.dll new file mode 100644 index 0000000000..d8b0887b50 Binary files /dev/null and b/tools/Debugging Tools for Windows/winext/wdfkd.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/acpikd.dll b/tools/Debugging Tools for Windows/winxp/acpikd.dll new file mode 100644 index 0000000000..9fc6c53da8 Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/acpikd.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/default.tmf b/tools/Debugging Tools for Windows/winxp/default.tmf new file mode 100644 index 0000000000..6582184b19 --- /dev/null +++ b/tools/Debugging Tools for Windows/winxp/default.tmf @@ -0,0 +1,123 @@ +//****************************************** +// Copyright (c) 1997-2001 Microsoft Corporation +// +// Default TMF definitions +// This file is handbuilt to reflect the hardware rlated descriptive trace 'events' +// that are included with every trace log file. +//****************************************** + +// The #typev statement may be used to convert +// messages into user readable forms. +// Wherever possible parameters are processed as their native format +// and the %x!x! style of FormatMessage should be used. +// (The #type statement is obsolete) +// +// Note Parameter %1 through %9 are predefined +// Parameter is #typev +// %1 GUID Friendly Name string +// %2 GUID SubType Name string +// %3 Thread ID LONG +// %4 System Time String +// %5 Kernel Time or User Time String +// %6 User Time or NULL String +// %7 Sequence Number LONG +// %8 Process Id LONG +// %9 CPU Number LONG +// %10 and above are the user parameters +// %254 Is Reserved +// %255 Is reserved +// +// Note these parameters are always present, but may not be valid +// depending on the source. +// +// User defined messages always start at message number 10 +// Messages 0 through 9 are reserved for system use. +// Message number 255 is reserved. +// +// Available formats for user arguments are - +// +//Name Description Format +//ItemChar CHAR +//ItemUChar UCHAR +//ItemCharShort USHORT +//ItemCharSign SHORT +//ItemShort Signed Short SHORT +//ItemUShort Unsigned Short USHORT +//ItemLong Signed Long, decoded as decimal LONG +//ItemULong Unsigned Long, decoded as decimal ULONG +//ItemULongX Unsigned Long, seen as hexadecimal ULONG +//ItemLongLong Signed 64 Bit value LONGLONG +//ItemULongLong Unsigned 64 Bit value ULONGLONG +//ItemWString Unicode String, null terminated String +//ItemPString Counted Ascii String String +//ItemPWString Counted Unicode String String +//ItemUnknown String + + + +68fdd900-4a3e-11d1-84f4-0000f80464e3 EventTrace +#typev Header 0 "EventTrace" +{ + BufferSize, ItemULong //10 + Version, ItemULong //11 + BuildNumber, ItemULong //12 + NumProc, ItemULong //13 + EndTime, ItemULongLong //14 + TimerResolution,ItemULong //15 + MaxFileSize, ItemULong //16 + LogFileMode, ItemULongX //17 + BuffersWritten, ItemULong //18 + StartBuffers, ItemULong //19 + PointerSize, ItemULong //20 + EventsLost, ItemULong //21 + CPUSpeed, ItemULong //22 + LoggerName, ItemPtr //23 + LogFileName, ItemPtr //24 + TimeZone, ItemCharHidden[176] //25 + BootTime, ItemULongLong //26 + PerfFrequency, ItemULongLong //27 + StartTime, ItemULongLong //28 + ReservedFlags, ItemULongX //29 + BuffersLost, ItemULong //30 +} + +01853a65-418f-4f36-aefc-dc0f1d2fd235 HWConfig +#typev CPU 10 "%15!s! :: CPU # %11!d!, Speed %10!d!Mhz, Memory %12!d!K, PageSize %13!d!K, AllocationGranularity %14!d!" +{ + MHz, ItemULong // 10 + NumberOfProcessors, ItemULong // 11 + MemSize, ItemULong // 12 + PageSize, ItemULong // 13 + AllocationGranularity, ItemULong // 14 + ComputerName, ItemWString // 15 +} +#typev PhyDisk 11 "Phsical Disk %10!d!(%19!s!), SectorSize: %11!d!, SectorsperTrack: %12!d!, TracksPerCylinder %13!d! Cylinders %14!d!, SCSI (Port=%15!d!, Path %16!d!, Target=%17!d!, Lun=%18!d!)" +{ + DiskNumber, ItemULong // 10 + BytesPerSector, ItemULong // 11 + SectorsPerTrack, ItemULong // 12 + TracksPerCylinder, ItemULong // 13 + Cylinders, ItemULongLong // 14 + SCSIPort, ItemULong // 15 + SCSIPath, ItemULong // 16 + SCSITarget, ItemULong // 17 + SCSILun, ItemULong // 18 + Manufacturer, ItemWString // 19 +} +#typev LogDisk 12 "Logical Disk %10!d! StartOffset: %11!d!, Size: %12!d!" +{ + DiskNumber, ItemULong // 10 + Pad, ItemULong // 11 + StartOffset, ItemULongLong // 12 + PartitionSize, ItemULongLong // 13 +} +#typev NIC 13 "NIC %10!s!" +{ + NICName, ItemWString // 10 +} + +b4955bf0-3af1-4740-b475-99055d3fe9aa CSharp +#typev CSharp1 14 "%0:%10!s!" +{ + dummyarg,ItemWString +} \ No newline at end of file diff --git a/tools/Debugging Tools for Windows/winxp/exts.dll b/tools/Debugging Tools for Windows/winxp/exts.dll new file mode 100644 index 0000000000..09c3eaecab Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/exts.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/fltkd.dll b/tools/Debugging Tools for Windows/winxp/fltkd.dll new file mode 100644 index 0000000000..bc6006160a Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/fltkd.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/kdexts.dll b/tools/Debugging Tools for Windows/winxp/kdexts.dll new file mode 100644 index 0000000000..8d8ef8ca5b Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/kdexts.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/ks.dll b/tools/Debugging Tools for Windows/winxp/ks.dll new file mode 100644 index 0000000000..0d24f14c3c Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/ks.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/minipkd.dll b/tools/Debugging Tools for Windows/winxp/minipkd.dll new file mode 100644 index 0000000000..6c70d25382 Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/minipkd.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/ndiskd.dll b/tools/Debugging Tools for Windows/winxp/ndiskd.dll new file mode 100644 index 0000000000..b2d66322ff Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/ndiskd.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/ntsdexts.dll b/tools/Debugging Tools for Windows/winxp/ntsdexts.dll new file mode 100644 index 0000000000..3ec9152b91 Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/ntsdexts.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/rpcexts.dll b/tools/Debugging Tools for Windows/winxp/rpcexts.dll new file mode 100644 index 0000000000..aec71a618b Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/rpcexts.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/scsikd.dll b/tools/Debugging Tools for Windows/winxp/scsikd.dll new file mode 100644 index 0000000000..b6984d59bd Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/scsikd.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/system.tmf b/tools/Debugging Tools for Windows/winxp/system.tmf new file mode 100644 index 0000000000..f578756c0f --- /dev/null +++ b/tools/Debugging Tools for Windows/winxp/system.tmf @@ -0,0 +1,929 @@ +//****************************************** +// System Trace Definitions +// Version 0006.1 22th Sep 2004 +//****************************************** + +// The #typev statement may be used to convert +// messages into user readable forms. +// Syntax: +// +// Guid EventName +// #version value +// #level value // Not Supported +// #typeV name1 value1 FormatString +// { +// MofFields +// } +// #typeV name2 value2 FormatString +// { +// MofFields +// } +// +// With #typev all parameters are processed as strings and the default string +// processing of FormTMessage is used +// With #typev wherever possible parameters are processed as their native format +// and the %x!x! style of FormatMessage should be used. +// +// Note Parameter %1 through %9 are predefined +// Parameter is #typev +// %1 GUID Friendly Name string +// %2 GUID SubType Name string +// %3 Thread ID ULONG_PTR +// %4 System Time String +// %5 Kernel Time or User Time String +// %6 User Time or NULL String +// %7 Sequence Number LONG +// %8 Unused String +// %9 CPU Number LONG +// %10 and above are the user parameters +// %255 Is reserved +// +// Note these parameters are always present, but may not be valid +// depending on the source. +// +// User defined messages always start at message number 10 +// Messages 0 through 9 are reserved for system use. +// Message number 255 is reserved. +// +// Available formats for user arguments are - +// +//Name Description #typev Format +//ItemChar CHAR +//ItemUChar UCHAR +//ItemCharShort USHORT +//ItemCharSign SHORT +//ItemShort Signed Short SHORT +//ItemUShort Unsigned Short USHORT +//ItemLong Signed Long, decoded as decimal LONG +//ItemULong Unsigned Long, decoded as decimal ULONG +//ItemULongX Unsigned Long, seen as hexadecimal ULONG +//ItemLongLong Signed 64 Bit value LONGLONG +//ItemULongLong Unsigned 64 Bit value ULONGLONG +//ItemRString Reduced Ascii String String +// (\t, \n, \r, \,, converted to space, trailing sp removed) +//ItemWString Unicode String, null terminated String +//ItemPString Counted Ascii String String +//ItemPWString Counted Unicode String String +//ItemMLString Multi-Line Ascii String String +//ItemKSid Security identifier String +//ItemChar4 CHAR4 +//ItemIPAddr IP Address String (If needed raw, use ItemUlong) +// (string of form xxx.xxx.xxx.xxx) +//ItemPort String (If needed raw use ItemUshort) +//ItemNWString Non-null terminated Wide Char String String +//ItemListByte (element1,element2,....) String +// byte index into a list of strings +//ItemListShort(element1,element2,....) String +// short index into a list of strings +//ItemListLong (element1,element2,....) String +// Long index into a list of strings +//ItemGUID Normal GUID format String +//ItemNTerror Translates a ULONG error code to the String +// NT Error Text +//ItemNTSTATUS Converts NTSTATUS to symbolic name String +//ItemWINERROR Converts WINERROR to symbolic name String +//ItemNETEVENT Converts NETEVENT to symbolic name String +//ItemMerror module.ext String +// Translates a ULONG error code using the +// module specified. +//ItemTimeStamp Treats a LONGLONG as a timestamp String +//ItemUnknown String + + + +68fdd900-4a3e-11d1-84f4-0000f80464e3 EventTrace +#typev LogFileHeader 0 "%0EventTrace Header" +{ + BufferSize, ItemULong + Version, ItemULong + BuildNumber, ItemULong + NumProc, ItemULong + EndTime, ItemULongLong + TimerResolution,ItemULong + MaxFileSize, ItemULong + LogFileMode, ItemULongX + BuffersWritten, ItemULong + StartBuffers, ItemULong + PointerSize, ItemULong + EventsLost, ItemULong + CPUSpeed, ItemULong + LoggerName, ItemPtr + LogFileName, ItemPtr + TimeZone, ItemCharHidden[176] + BootTime, ItemULongLong + PerfFrequency, ItemULongLong + StartTime, ItemULongLong + ReservedFlags, ItemULongX + BuffersLost, ItemULong +} +#typev Extension 5 "extension" +{ + GroupMaks1, ItemULongX + GroupMaks2, ItemULongX + GroupMaks3, ItemULongX + GroupMaks4, ItemULongX + GroupMaks5, ItemULongX + GroupMaks6, ItemULongX + GroupMaks7, ItemULongX + GroupMaks8, ItemULongX +} +#typev RDComplete 8 "rdcomplete" +{ +} +3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c Process +#version 0 +#typev Start 1 "%0Started Process %10!04X!.%11!04X! "%13!s!" :: %12!s!" +#typev End 2 "%0Ended Process %10!04X!.%11!04X! "%13!s!" :: %12!s!" +#typev DCStart 3 "%0Data Collection Started of %10!04X!.%11!04X! "%13!s!" :: %12!s!" +#typev DCEnd 4 "%0Data Colection Ended for %10!04X!.%11!04X! "%13!s!" :: %12!s!" +{ + ProcessId, ItemPtr + ParentId, ItemPtr + UserSID, ItemKSid + ImageFileName, ItemString +} +#version 1 +#typev Start 1 "%0Started Process %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!) " +#typev End 2 "%0Ended Process %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!) Exit Status %14!X!" +#typev DCStart 3 "%0Data Collection Started of %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!)" +#typev DCEnd 4 "%0Data Colection Ended for %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!)" +{ + PageDirectoryBase, ItemPtr + Process Id, ItemULong + Parent Id, ItemULong + Session Id, ItemULong + Exit Status, ItemUlong + User SID, ItemKSid + Image FileName, ItemString +} +#version 2 +#typev Start 1 "%0Started Process %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!) " +#typev End 2 "%0Ended Process %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!) Exit Status %14!X!" +#typev DCStart 3 "%0Data Collection Started of %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!)" +#typev DCEnd 4 "%0Data Colection Ended for %11!04X!.%12!04X! "%16!s!" :: %15!s! (Session=%13!d!)" +{ + UniqueProcessKey, ItemPtr + Process Id, ItemULong + Parent Id, ItemULong + Session Id, ItemULong + Exit Status, ItemUlong + User SID, ItemKSid + Image FileName, ItemString +} +#version 3 +#typev Start 1 "%0Started Process %11!04X!.%12!04X! %17!s! :: %16!s! (Session=%13!d!) " +#typev End 2 "%0Ended Process %11!04X!.%12!04X! %17!s! :: %16!s! (Session=%13!d!) Exit Status %14!X!" +#typev DCStart 3 "%0Data Collection Started of %11!04X!.%12!04X! %17!s! :: %16!s! (Session=%13!d!)" +#typev DCEnd 4 "%0Data Colection Ended for %11!04X!.%12!04X! %17!s! :: %16!s! (Session=%13!d!)" +{ + UniqueProcessKey, ItemPtr + Process Id, ItemULong + Parent Id, ItemULong + Session Id, ItemULong + Exit Status, ItemUlong + PageTable, ItemPtr + User SID, ItemKSid + Image FileName, ItemString +} + +#version 3 +#typev Start 1 "%0Started Process %11!04X!.%12!04X! "%17!s!" :: %16!s! (Session=%13!d!) " +#typev End 2 "%0Ended Process %11!04X!.%12!04X! "%17!s!" :: %16!s! (Session=%13!d!) Exit Status %14!X!" +#typev DCStart 3 "%0Data Collection Started of %11!04X!.%12!04X! "%17!s!" :: %16!s! (Session=%13!d!)" +#typev DCEnd 4 "%0Data Colection Ended for %11!04X!.%12!04X! "%17!s!" :: %16!s! (Session=%13!d!)" +{ + UniqueProcessKey, ItemPtr + Process Id, ItemULong + Parent Id, ItemULong + Session Id, ItemULong + Exit Status, ItemUlong + PageTable, ItemPtr + User SID, ItemKSid + Image FileName, ItemString +} + + +3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c Thread +#version 0 +#typev Start 1 "%0Started Thread %10!04X!.%11!04X!" +#typev End 2 "%0Ended Thread %10!04X!.%11!04X!" +#typev DCStart 3 "%0Data Collection Started for %10!04X!.%11!04X!" +#typev DCEnd 4 "%0Data Collection Ended for %10!04X!.%11!04X!" +{ + Process Id, ItemULong + Thread Id, ItemULong +} +#version 1 +#typev Start 1 "%0Started Thread %10!04X!.%11!04X!" +#typev DCStart 3 "%0Data Collection Started for %10!04X!.%11!04X!" +{ + Process Id, ItemULong + Thread Id, ItemULong + StackBase, ItemPtr + StackLimit, ItemPtr + UserStackBase, ItemPtr + UserStackLimit, ItemPtr + StartAddr, ItemPtr + Win32StartAddr, ItemPtr + WaitMode, ItemChar +} +#version 1 +#typev End 2 "%0Ended Thread %10!04X!.%11!04X!" +#typev DCEnd 4 "%0Data Collection Ended for %10!04X!.%11!04X!" +{ + Process Id, ItemULong + Thread Id, ItemULong +} +#version 1 +#typev CSwitch 36 "%0 Context Switch from %11!d!. to %10!d!." +{ + NewThreadId, ItemULongX + OldThreadId, ItemULongX + NewThreadPriority, ItemCharShort + OldThreadPriority, ItemCharShort + NewThreadQuantum, ItemCharShort + OldThreadQuantum, ItemCharShort + OldThreadWaitReason, ItemCharShort + OldThreadWaitMode, ItemCharShort + OldThreadState, ItemCharShort + OldThreadWaitIdealProcessor, ItemCharShort + NewThreadWaitTime, ItemULongX +} +#version 1 +#typev CompCS 37 "%0 Compressed CS Event." +{ +} +#version 1 +#typev WorkerThread 57 "%0 WorkerThread ThreadId = %10!d! :: ThreadRoutine %12!d!" +{ + ThreadId , ItemULongX + StartTime, ItemULongLong + ThreadRoutine, ItemULongX +} +#version 2 +#typev Start 1 "%0Started Thread %10!04X!.%11!04X! " +#typev End 2 "%0Ended Thread %10!04X!.%11!04X! " +#typev DCStart 3 "%0Data Collection Started of %10!04X!.%11!04X! " +#typev DCEnd 4 "%0Data Colection Ended for %10!04X!.%11!04X! " +{ + ProcessId, ItemULong + ThreadId, ItemULong + StackBase, ItemPtr + StackLimit, ItemPtr + UserStackBase, ItemPtr + UserStackLimit, ItemPtr + StartAddr, ItemPtr + Win32StartAddr, ItemPtr + TebBase, ItemPtr + WaitMode, ItemChar +} + +#version 2 +#typev CSwitch 36 "%0 Context Switch from %11!d!. to %10!d!. +{ + NewThreadId, ItemULongX + OldThreadId, ItemULongX + NewThreadPriority, ItemCharShort + OldThreadPriority, ItemCharShort + PreviousCState, ItemCharShort + SpareByte, ItemCharShort + OldThreadWaitReason, ItemCharShort + OldThreadWaitMode, ItemCharShort + OldThreadState, ItemCharShort + OldThreadWaitIdealProcessor, ItemCharShort + NewThreadWaitTime, ItemULongX +} +#version 2 +#typev CompCS 37 "%0 Compressed CS Event." +{ +} +#version 2 +#typev WorkerThread 57 "%0 WorkerThread ThreadId = %10!d! :: ThreadRoutine %12!d!" +{ + ThreadId , ItemULongX + StartTime, ItemULongLong + ThreadRoutine, ItemULongX +} +#version 2 +#typev ReserveCreate 48 "%0 ReserveCreate Reserve =%10!p! ::Period %11!d! :: ObjectFlags = %13!d! :: Processor %14!d!" +{ + Reserve , ItemPtr + Period, ItemULong + Budget, ItemULong + ObjectFlags, ItemULong + Processor,ItemChar +} +#version 2 +#typev ReserveDelete 49 "%0 ReserveDelete Reserve =%10!p!" +{ + Reserve , ItemPtr +} +#version 2 +#typev ReserveJoinThread 52 "%0 ReserveJoinThread Reserve =%10!p! :: ThreadId = %11!d!" +{ + Reserve , ItemPtr + ThreadId, ItemULong +} +#version 2 +#typev ReserveDisjoinThread 53 "%0 ReserveDisjoinThread Reserve =%10!p! :: ThreadId = %11!d!" +{ + Reserve , ItemPtr + ThreadId, ItemULong +} +#version 2 +#typev ReserveState 54 "%0 ReserveState Reserve =%10!p! :: DispatchState = %11!d!" +{ + Reserve , ItemPtr + DispatchState, ItemChar + Replenished,ItemChar //was ItemBool +} +#version 2 +#typev ReserveBandwidth 55 "%0 ReserveBandwidth Reserve =%10!p! :: Period = %11!d!" +{ + Reserve , ItemPtr + Period, ItemULong + Budget, ItemULong +} +#version 2 +#typev ReserveLateCount 56 "%0 ReserveLateCount Reserve =%10!p! :: LateCountIncrement = %11!d!" +{ + Reserve , ItemPtr + LateCountIncrement, ItemULong +} + + +3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c PageFault +#typev TransitionFault 10 "%0Pagefault Transition VA=0x%10!08X!, PC=0x%11!08X!" +#typev DemandZeroFault 11 "%0Pagefault DemandZero VA=0x%10!08X!, PC=0x%11!08X!" +#typev CopyOnWrite 12 "%0Pagefault CopyOnWrite VA=0x%10!08X!, PC=0x%11!08X!" +#typev GlobalPageFault 13 "%0Pagefault GuardPageFault VA=0x%10!08X!, PC=0x%11!08X!" +#typev Hard 14 "%0Pagefault Hard VA=0x%10!08X!, PC=0x%11!08X!" +{ + Virtual Address,ItemULongX + Program Counter,ItemUlongX +} +#typev HardPageFault 32 "PageFault Hard VA=0x%12!X! TID=0x%14!08X! ReadOffset=0x%11!I64X! Fileobject=0x%13!x! ByteCount=0x%15!08X!" +{ + InitialTime,ItemULongLong //10 + ReadOffset,ItemULongLong //11 + VirtualAddress,ItemULong //12 + FileObject,ItemULong //13 + ThreadId,ItemUlong //14 + ByteCount,ItemUlong //15 +} +01853a65-418f-4f36-aefc-dc0f1d2fd235 Config +#typev CPU 10 "%0%15!s!(%16!s!) :: CPU # %11!d!, HyperThreadingFlag %17!d!, Speed %10!d!Mhz, Memory %12!d!K, PageSize %13!d!K, AllocationGranularity %14!d!" +{ + MHz, ItemULong //10 + NumberOfProcessors, ItemULong //11 + MemSize, ItemULong //12 + PageSize, ItemULong //13 + AllocationGranularity, ItemULong //14 + ComputerName, ItemWChar[256] //15 + DomainName, ItemWChar[132] //16 + HyperThreadingFlag, ItemULong //17 + +} +#typev PhyDisk 11 "%0Phsical Disk %10!d!(%19!s!), SectorSize: %11!d!, SectorsperTrack: %12!d!, TracksPerCylinder %13!d! Cylinders %14!I64X!, SCSI (Port=%15!d!, Path %16!d!, Target=%17!d!, Lun=%18!d!)" +{ + DiskNumber, ItemULong //10 + BytesPerSector, ItemULong //11 + SectorsPerTrack, ItemULong //12 + TracksPerCylinder, ItemULong //13 + Cylinders, ItemULongLong //14 + SCSIPort, ItemULong //15 + SCSIPath, ItemULong //16 + SCSITarget, ItemULong //17 + SCSILun, ItemULong //18 + Manufacturer, ItemWChar[256] //19 + PartitionCount, ItemULong //20 + WriteCacheEnabled, ItemChar //was ItemBool //21 + BootDriveLetter, ItemWChar[3] //22 +} +#typev LogDisk 12 "%0Logical Disk %12!d! %15!s! " +{ + StartOffset, ItemULongLong //10 + PartitionSize, ItemULongLong //11 + DiskNumber, ItemULong //12 + Size, ItemULong //13 + DriveType, ItemULong //14 + DriveLetterString, ItemWChar[4] //15 + Pad, ItemULong //16 + PartitionNumber, ItemULong //17 + SectorsPerCluster, ItemULong //18 + BytesPerSector, ItemULong //19 + NumberOfFreeClusters, ItemLongLong //20 + TotalNumberOfClusters, ItemLongLong //21 + FileSystem, ItemWChar[16] //22 + VolumeExt, ItemULong + +} +#typev NIC 13 "%0NIC %12!d! Name = %10!s! " +{ + NICName, ItemWChar[256] //10 + Index, ItemULong //11 + PhysicalAddrLen, ItemULong //12 + PhysicalAddr, ItemWChar[8] //13 + Size, ItemULong //14 + IpAddress, ItemLong //15 + SubnetMask, ItemLong //16 + DhcpServer, ItemLong //17 + Gateway, ItemLong //18 + PrimaryWinsServer, ItemLong //19 + SecondaryWinsServer, ItemLong //20 + DnsServer1, ItemLong //21 + DnsServer2, ItemLong //21 + DnsServer3, ItemLong //23 + DnsServer4, ItemLong //24 + Data, ItemULong +} +#typev Video 14 "%0Video %17!s!" +{ + MemorySize, ItemULong //10 + XResolution, ItemULong //11 + YResolution, ItemULong //12 + BitsPerPixel, ItemULong //13 + VRefresh, ItemULong //14 + ChipType, ItemWCHAR[256] //15 + DACType, ItemWCHAR[256] //16 + AdapterString, ItemWCHAR[256] //17 + BiosString, ItemWCHAR[256] //18 + DeviceId, ItemWCHAR[256] //19 + StateFlags, ItemULong +} +#typev Services 15 "%0Service (PID=%13!d!) %10!s! %11!s! %12!s!" +{ + ServiceName, ItemWCHAR[34] + DisplayName, ItemWCHAR[256] + ProcessName, ItemWCHAR[34] + ProcessId, ItemULong +} +#typev Power 16 "%0Power Configuration" +{ + S1, ItemChar //was ItemBool + S2, ItemChar //was ItemBool + S3, ItemChar //was ItemBool + S4, ItemChar //was ItemBool + S5, ItemChar //was ItemBool + Pad1, ItemChar + Pad2, ItemChar + Pad3, ItemChar +} +#typev IRQ 21 "%0IRQ Affinity : 0x%10!016I64X! :: IRQNum = %11!d! :: %13!s!" +{ + IRQAffinity, ItemLongLong + IRQNum, ItemULong + DeviceDescriptionLen, ItemULong + DeviceDescription, ItemWString +} +#typev PnP 22 "%0PnP DeviceId: %13!s! :: Descrition: %14!s! :: Name: %15!s!" +{ + IDLength, ItemULong + DescriptionLength, ItemULong + FriendlyNameLength, ItemULong + DeviceID, ItemWString + DeviceDescription, ItemWString + FriendlyName, ItemWString +} +#version 3 +#typev Services 15 "%0Service (PID=%10!d!) %13!s! %14!s! %15!s!" +{ + ProcessId, ItemULong + ServiceState, ItemULong + ServiceTag, ItemULong + ServiceName, ItemWString + DisplayName, ItemWString + ProcessName, ItemWString + LoadOrderGroup, ItemWString + SvchostrGroup, ItemWString +} + +3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c DiskIo +#version 0 +#typev Read 10 "%0Read of %12!5d! bytes (FileObj=0x%15!p!)" +#typev Write 11 "%0Write of %12!5d! bytes (FileObj=0x%15!p!)" +{ + Disk Number, ItemULong + Irp Flags, ItemULongX + Transfer Size, ItemULong + ResponseTime, ItemULong + Byte Offset, ItemLongLong + File Object, ItemPtr + HighResResponseTime, ItemLongLong +} +#version 1 +#typev Read 10 "%0Read of %12!5d! bytes (FileObj=0x%15!p!)" +#typev Write 11 "%0Write of %12!5d! bytes (FileObj=0x%15!p!)" +{ + Disk Number, ItemULong + Irp Flags, ItemULongX + Transfer Size, ItemULong + ResponseTime, ItemULong + Byte Offset, ItemLongLong + File Object, ItemPtr + HighResResponseTime, ItemLongLong +} +#version 2 +#typev Read 10 "%0Read of %12!5d! bytes (FileObj=0x%15!p! ByteOffset=0x%14!I64X! IRP=0x%16!p! IRPFlags=0x%11!X!)" +#typev Write 11 "%0Write of %12!5d! bytes (FileObj=0x%15!p! ByteOffset=0x%14!I64X! IRP=0x%16!p! IRPFlags=0x%11!X!)" +{ + Disk Number, ItemULong //10 + Irp Flags, ItemULongX //11 + Transfer Size, ItemULong //12 + ResponseTime, ItemULong //13 + Byte Offset, ItemLongLong //14 + File Object, ItemPtr //15 + IRP, ItemPtr //16 + HighResResponseTime, ItemLongLong //17 +} +#version 2 +#typev Read 12 "%0ReadInit of (IRP=0x%15!08X!)" +#typev Write 13 "%0WriteInit of (IRP=0x%15!08X!)" +{ + IRP, ItemPtr +} +AE53722E-C863-11d2-8659-00C04FA321A1 Registry +#version 0 +#typev Create 10 "%0Create of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Open 11 "%0Open of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Delete 12 "%0Delete of Handle = 0x%11!08X!(%14!s!) Status = %10!0X!" +#typev Query 13 "%0Query of (%13!s!) Handle = 0x%11!08X! Status = %10!0X!" +#typev SetValue 14 "%0SetValue of %13!s! Handle = 0x%11!08X!(%14!s!)8X! Status = %10!0X! (TID =%3!0X!)" +#typev QueryValue 16 "%0QueryValue of (%13!s!) Handle = 0x%11!08X! Status = %10!0X!" +#typev EnumerateKey 17 "%0EnumerateKey of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev EnumerateValueKey 18 "%0EnumerateValueKey of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev QueryMultipleValue 19 "%0QueryMultiple of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev SetInformation 20 "%0SetInformation of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Flush 21 "%0Flush of %13!s! Handle = 0x%11!08X! Status = %10!0X!" +{ + Status,ItemUlongX + Key Handle, ItemULongX + Elapsed Time, ItemLongLong + KeyName, ItemWString +} +#version 1 +#typev Create 10 "%0Create of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Open 11 "%0Open of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Delete 12 "%0Delete of Handle = 0x%11!08X!(%14!s!) Status = %10!0X!" +#typev Query 13 "%0Query of (%14!s!) Handle = 0x%11!08X! Status = %10!0X!" +#typev SetValue 14 "%0SetValue of %14!s! Handle = 0x%11!08X!(%14!s!)8X! Status = %10!0X! (TID =%3!0X!)" +#typev QueryValue 16 "%0QueryValue of (%14!s!) Handle = 0x%11!08X! Status = %10!0X!" +#typev EnumerateKey 17 "%0EnumerateKey of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev EnumerateValueKey 18 "%0EnumerateValueKey of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev QueryMultipleValue 19 "%0QueryMultiple of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev SetInformation 20 "%0SetInformation of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Flush 21 "%0Flush of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev RunDown 22 "%0Rundown" +{ + Status,ItemUlongX + Key Handle, ItemULongX + Elapsed Time, ItemLongLong + Index, ItemULong + KeyName, ItemWString +} +#version 2 +#typev Create 10 "%0Create of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Open 11 "%0Open of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Delete 12 "%0Delete of Handle = 0x%11!08X!(%14!s!) Status = %10!0X!" +#typev Query 13 "%0Query of (%14!s!) Handle = 0x%11!08X! Status = %10!0X!" +#typev SetValue 14 "%0SetValue of %14!s! Handle = 0x%11!08X!(%14!s!)8X! Status = %10!0X! (TID =%3!0X!)" +#typev QueryValue 16 "%0QueryValue of (%14!s!) Handle = 0x%11!08X! Status = %10!0X!" +#typev EnumerateKey 17 "%0EnumerateKey of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev EnumerateValueKey 18 "%0EnumerateValueKey of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev QueryMultipleValue 19 "%0QueryMultiple of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev SetInformation 20 "%0SetInformation of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev Flush 21 "%0Flush of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev KCBCreate 22 "%0KCBCreate" +#typev KCBDelete 23 "%0KCBDelete" +#typev KCVBRundownBegin 24 "%0KCBRundown Begin" +#typev KCBRundownEnd 25 "%0KCBRundown End" +#typev SetSecurity 28 "%0SetSecurity of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +#typev QuerySecurity 28 "%0QuerySecurity of %14!s! Handle = 0x%11!08X! Status = %10!0X!" +{ + Status,ItemUlongX + Key Handle, ItemULongX + Elapsed Time, ItemLongLong + Index, ItemULong + KeyName, ItemWString +} +90cbdc39-4a3e-11d1-84f4-0000f80464e3 FileIo +#version 0 +#typev Name 0 "%0Filio for %11 (FileObj=0x%10!X!)" +{ + FileObject, ItemPtr + FileName, ItemWString +} +#version 1 +#typev Name 0 "%0Filio for %11 (FileObj=0x%10!X!)" +#typev FileCreate 32 "%0File Create of %11 (FileObj=0x%10!X!)" +{ + FileObject, ItemUlong + FileName, ItemWString +} +#version 2 +#typev Name 0 "%0Filio for %11 (FileObj=0x%10!X!)" +#typev FileCreate 32 "%0File Create of %11 (FileObj=0x%10!X!)" +#typev FileCreate 35 "%0File Delete of %11 (FileObj=0x%10!X!)" +#typev FileCreate 36 "%0File Rundown of %11 (FileObj=0x%10!X!)" +{ + FileObject, ItemUlong + FileName, ItemWString +} + +9a280ac0-c8e0-11d1-84e2-00c04fb998a2 TcpIp +#version 0 +#typev Send 10 "%0TCPIP Send to %10!13s!:%12!05d! from %11!13s!:%13!05d! of %14!5d! bytes" +#typev Recv 11 "%0TCPIP Receive from %10!13s!:%12!05d! to %11!13s!:%13!05d! of %14!5d! bytes" +#typev Connect 12 "%0TCPIP Connect to %10:%12!05d! from %11:%13!05d!" +#typev Disconnect 13 "%0TCPIP Discon From %10:%12!05d! to %11:%13!05d!" +#typev Retransmit 14 "%0TCPIP Retransmit to %10:%12!05d!" +#typev Accept 15 "%0TCPIP Accept From %10:%12!05d!" +{ + daddr, ItemIPAddr /10 + saddr, ItemIPAddr /11 + dport, ItemUshort /12 + sport, ItemUshort /13 + size, ItemULong /14 + PID, ItemUlong /15 +} +#version 1 +#typev Send 10 "%0TCPIP Send to %12!13s!:%14!05d! from %13!13s!:%15!05d! of %11!5d! bytes" +#typev Recv 11 "%0TCPIP Receive from %12!13s!:%14!05d! to %13!13s!:%15!05d! of %11!5d! bytes" +#typev Connect 12 "%0TCPIP Connect to %12:%14!05d! from %13:%15!05d!" +#typev Disconnect 13 "%0TCPIP Discon From %12:%14!05d! to %13:%15!05d!" +#typev Retransmit 14 "%0TCPIP Retransmit to %12:%14!05d!" +#typev Accept 15 "%0TCPIP Accept From %12:%14!05d!" +#typev Reconnect 16 "%0TCPIP Reconnect To %12:%14!05d!" +{ + PID, ItemULong /10 + size, ItemULong /11 + daddr, ItemIPAddr /12 + saddr, ItemIPAddr /13 + dport, ItemUshort /14 + sport, ItemUshort /15 +} +#version 2 +#typev Send 10 "%0TCPIP Send to %12!13s!:%14!05d! from %13!13s!:%15!05d! of %11!5d! bytes" +#typev Recv 11 "%0TCPIP Receive from %12!13s!:%14!05d! to %13!13s!:%15!05d! of %11!5d! bytes" +#typev Connect 12 "%0TCPIP Connect to %12:%14!05d! from %13:%15!05d!" +#typev Disconnect 13 "%0TCPIP Discon From %12:%14!05d! to %13:%15!05d!" +#typev Retransmit 14 "%0TCPIP Retransmit to %12:%14!05d!" +#typev Accept 15 "%0TCPIP Accept From %12:%14!05d!" +#typev Reconnect 16 "%0TCPIP Reconnect To %12:%14!05d!" +{ + PID, ItemULong /10 + size, ItemULong /11 + daddr, ItemIPAddr /12 + saddr, ItemIPAddr /13 + dport, ItemUshort /14 + sport, ItemUshort /15 +} + +bf3a50c5-a9c9-4988-a005-2df0b7c80f80 UdpIp +#version 0 +#typev Send 10 "%0UDP Send to %11!13s!:%12!05d! of %16!5d! bytes" +#typev Recv 11 "%0UDP Receive from %11!13s!:%12!05d! of %16!5d! bytes" +{ + context, ItemPtr /10 + saddr, ItemIPAddr /11 + sport, ItemPort /12 + size, ItemUshort /13 + daddr, ItemIPAddr /14 + dport, ItemPort /15 + dsize, ItemUshort /16 +} +#version 1 +#typev Send 10 "%0UDP Send to %12!13s!:%14!05d! from %13!13s!:%15!05d! of %11!5d! bytes (Pid= %10!08X!)" +#typev Recv 11 "%0UDP Receive from %12!13s!:%14!05d! to %13!13s!:%15!05d! of %11!5d! bytes (Pid= %10!08X!)" +{ + PID, ItemULong /10 + size, ItemUlong /11 + daddr, ItemIPAddr /12 + saddr, ItemIPAddr /13 + dport, ItemUshort /14 + sport, ItemUshort /15 +} +#version 2 +#typev Send 10 "%0UDP Send to %12!13s!:%14!05d! from %13!13s!:%15!05d! of %11!5d! bytes (Pid= %10!08X!)" +#typev Recv 11 "%0UDP Receive from %12!13s!:%14!05d! to %13!13s!:%15!05d! of %11!5d! bytes (Pid= %10!08X!)" +{ + PID, ItemULong /10 + size, ItemUlong /11 + daddr, ItemIPAddr /12 + saddr, ItemIPAddr /13 + dport, ItemUshort /14 + sport, ItemUshort /15 +} + +2cb15d1d-5fc1-11d2-abe1-00a0c911f518 Image +#version 0 +#typev Load 10 "%0ImageLoad of %12!s! (Base=0x%10!X!,size=0x%11!X!)" +{ + BaseAddress, ItemPtr /10 + ModuleSize, ItemPtr /11 + ImageFilename, ItemWString /12 +} +#version 1 +#typev Load 10 "%0ImageLoad of %13!s! (Process= %12!d!, Base=0x%10!X!,size=0x%11!X!)" +{ + BaseAddress, ItemPtr /10 + ModuleSize, ItemPtr /11 + ProcessId, ItemUlong /12 + ImageFilename, ItemWString /13 +} +#version 2 +#typev Load 10 "%0ImageLoad of %21!s! (Process= %12!d!, Base=0x%10!X!,size=0x%11!X!)" +#typev UnLoad 2 "%0Image UnLoad of %21!s! (Process= %12!d!, Base=0x%10!X!,size=0x%11!X!)" +{ + BaseAddress, ItemPtr /10 + ModuleSize, ItemPtr /11 + ProcessId, ItemUlong /12 + ImageChecksum, ItemUlong /13 + TimeDateStamp, ItemUlong /14 + Reserved0, ItemUlong /15 + DefaultBase, ItemPtr /16 + Reserved1, ItemUlong /17 + Reserved2, ItemUlong /18 + Reserved3, ItemUlong /19 + Reserved4, ItemUlong /20 + ImageFilename, ItemWString /21 +} + +222962ab-6180-4b88-a825-346b75f2a24a Heap +#typev HeapCreate 32 "Heap Create - Handle = %10!p! :: flags = %11!d!" +{ + HeapHandle, ItemPtr /10 + HeapFlags, ItemUlong /11 +} +#typev HeapTrace 33 "Heap Trace - Handle = %10!p! :: Alloc Size = %11!d! Address = %12!p!, Source Id = %13!d!" +{ + HeapHandle, ItemPtr /10 + AllocSize, ItemUlong /11 + AllocAddress, ItemPtr /12 + SourceId, ItemUlong /13 +} +#typev ReAlloc 34 "ReAlloc - Handle %10!p! :: new Size = %13!d! :: old size = %14!d!, Source Id = %15!d!" +{ + HeapHandle, ItemPtr /10 + NewAllocAddress, ItemPtr /11 + OldAllocAddress, ItemPtr /12 + NewAllocSize, ItemUlong /13 + OldAllocSize, ItemUlong + SourceId, ItemUlong +} +#typev Free 36 "Heap Free - Handle = %10!p! :: Address = %11!p!, Source Id = %12!d" +{ + HeapHandle, ItemPtr /10 + AllocAddress, ItemPtr /11 + SourceId, ItemUlong /12 +} +#typev Expand 37 "Expand - Handle %10!p!" +{ + HeapHandle, ItemPtr /10 + CommittedSize, ItemUlong + CommitAddress, ItemPtr + FreeSpace, ItemUlong + CommittedSpace, ItemUlong + ReservedSpace, ItemUlong + NoOfUCRs, ItemUlong +} +#typev SnapShot 38 "SnapShot - Handle %10!p! :: flags = %11!d! :: free space = %12!d!, committed = %13!d!, reserverd %14!d!" +{ + HeapHandle, ItemPtr /10 + HeapFlags, ItemUlong + FreeSpace, ItemUlong + CommittedSpace, ItemUlong + ReservedSpace, ItemUlong +} +#typev Walk 42 "Heap Walk - Handle = %10!p!" +{ + HeapHandle, ItemPtr /10 + DeCommittedSize, ItemUlong + DeCommitAddress, ItemPtr + FreeSpace, ItemUlong + CommittedSpace, ItemUlong + ReservedSpace, ItemUlong + NoOfUCRs, ItemUlong +} +#typev Destroy 35 "Heap Destroy - Handle = %10!p!" +#typev Lock 43 "Heap Lock - Handle = %10!p!" +#typev Unlock 44 "Heap Unlock - Handle = %10!p!" +#typev Validate 45 "Heap Validate - Handle = %10!p!" +#typev Walk 46 "Heap Walk - Handle = %10!p!" +{ + HeapHandle, ItemPtr /10 +} +3AC66736-CC59-4cff-8115-8DF50E39816B CritSec +#typev Collision 34 "CritSec - LockCount = %10!d!, OwningThread = %12!p!" +{ + LockCount, ItemUlong + SpinCount, ItemUlong + OwningThread, ItemPtr + CirtSecAddr, ItemPtr +} +#typev Initialize 35 "Initialize - SpinCount %10!d!, CirtSecAddr = %11!p!" +{ + SpinCount, ItemUlong + CirtSecAddr, ItemPtr +} +ce1dbfb4-137e-4da6-87b0-3f59aa102cbc PerfInfoEvent + +#typev SampleProf 46 "InstructionPointer %10!p! ThreadId= 0x%11!X! Count=%12!d!" +{ + InstructionPointer,ItemPtr + ThreadID,ItemUlong + Count,ItemUlong +} +#typev SysClEnter 51 "System Call Enter SysCallAddress=0x%10!p!" +{ + SysCallAddress,ItemPtr +} +#typev SysClExit 52 "System Call Exit NtStatus = %10!s!" +{ + SysCallNtStatus,ItemNTSTATUS +} +#typev DBGEnabled 58 "Debugger Enabled" +{ +} + +#version 1 +#typev ISR 67 "ISR Initial Time=%10!I64d!. Routine=0x%11!p! Return Value=0x%12!d!." +{ + InitialTime,ItemULongLong + Routine,ItemPtr + ReturnValue,ItemUchar +} + +#version 2 +#typev ISR 67 "ISR Initial Time=%10!I64d!. Routine=0x%11!p! Return Value=0x%12!d! Vector=0x%13!d!" +{ + InitialTime,ItemULongLong + Routine,ItemPtr + ReturnValue,ItemUchar + Vector,ItemUchar +} + + +#typev DPC 68 "DPC Initial Time=%10!I64d!. Routine=0x%11!p!" +#typev TimerDPC 69 "TimerDPC Initial Time=%10!I64d!. Routine=0x%11!p!" +#typev ThreadedDPC 66 "ThreadedDPC Initial Time=%10!I64d!. Routine=0x%11!p!" +{ + InitialTime,ItemULongLong + Routine,ItemPtr +} +//****************************************** +// RAC Events +//****************************************** +22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716 PSProvider +#version 0 +#typev ProcessStart 1 "Process Start Pid=0x%10!04x! Created=%11!s! ParentPid=0x%12!04x! SessionId=%13!d!. Image=%14!s!" +{ + ProcessId,ItemUlong + CreateTime,ItemTimeStamp + ParentProcessID,ItemUlong + SessionID,ItemUlong + ImageName,ItemWString +} +#version 0 +#typev ProcessStop 2 "Process Stop Pid=0x%10!04x! Created=%11!s! Exited=%12!s! ExitCode=0x%13!x! Image=%18!s!" +{ + ProcessId,ItemUlong /10 + CreateTime,ItemTimeStamp /11 + ExitTime,ItemTimeStamp /12 + ExitCode,ItemULong /13 + TokenElevationType,ItemUlong /14 + HandleCount,ItemUlong /15 + CommitCharge,ItemUlongLong /16 + CommitPeak,ItemUlongLong /17 + ImageName,ItemString /18 +} +#version 1 +#typev ProcessStop 2 "Process Stop Pid=0x%10!04x! Created=%11!s! Exited=%12!s! ExitCode=0x%13!x! Image=%24!s!" +{ + ProcessId,ItemUlong /10 + CreateTime,ItemTimeStamp /11 + ExitTime,ItemTimeStamp /12 + ExitCode,ItemULong /13 + TokenElevationType,ItemUlong /14 + HandleCount,ItemUlong /15 + CommitCharge,ItemUlongLong /16 + CommitPeak,ItemUlongLong /17 + CycleTime,ItemUlongLong /18 + ReadCount,ItemUlong /19 + WriteCount,ItemUlong /20 + ReadSize,ItemUlong /21 + WriteSize,ItemUlong /22 + HardFaultCount,ItemUlong /23 + ImageName,ItemString /24 +} + + +9c205a39-1250-487d-abd7-e831c6290539 PNP +#typev PNP 1 "%0pnp start %10!s! 0x%11!x! %12!s! 0x%13!x!" +#typev PNP 2 "%0pnp stop %10!s! 0x%11!x! %12!s! 0x%13!x!" +{ + Name,ItemCWString + Unknown,ItemUlong + file,ItemCWString + Unknown2,ItemUlong +} diff --git a/tools/Debugging Tools for Windows/winxp/vdmexts.dll b/tools/Debugging Tools for Windows/winxp/vdmexts.dll new file mode 100644 index 0000000000..3766d99096 Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/vdmexts.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/wmitrace.dll b/tools/Debugging Tools for Windows/winxp/wmitrace.dll new file mode 100644 index 0000000000..78b3e96c39 Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/wmitrace.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/wow64exts.dll b/tools/Debugging Tools for Windows/winxp/wow64exts.dll new file mode 100644 index 0000000000..1b5ffb261c Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/wow64exts.dll differ diff --git a/tools/Debugging Tools for Windows/winxp/wudfext.dll b/tools/Debugging Tools for Windows/winxp/wudfext.dll new file mode 100644 index 0000000000..d42a8f0880 Binary files /dev/null and b/tools/Debugging Tools for Windows/winxp/wudfext.dll differ diff --git a/tools/VsixUtil/VsixUtil.exe b/tools/VsixUtil/VsixUtil.exe new file mode 100644 index 0000000000..2d20167910 Binary files /dev/null and b/tools/VsixUtil/VsixUtil.exe differ diff --git a/tools/nuget/NuGet.exe b/tools/nuget/NuGet.exe new file mode 100644 index 0000000000..9552e30597 Binary files /dev/null and b/tools/nuget/NuGet.exe differ