├── .config └── tsaoptions.json ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── ThemeConverter ├── FinalPublicKey.snk ├── ThemeConverter.sln ├── ThemeConverter │ ├── CategoryGuid.json │ ├── ColorCompiler │ │ ├── CategoryCollectionRecord.cs │ │ ├── CategoryRecord.cs │ │ ├── CategoryThemeKey.cs │ │ ├── ColorCategory.cs │ │ ├── ColorEntry.cs │ │ ├── ColorManager.cs │ │ ├── ColorName.cs │ │ ├── ColorRecord.cs │ │ ├── ColorRow.cs │ │ ├── ColorTheme.cs │ │ ├── FileWriter.cs │ │ ├── OwnershipCollection.cs │ │ ├── PkgDefConstants.cs │ │ ├── PkgDefFileWriter.cs │ │ ├── PkgDefItem.cs │ │ ├── PkgDefValueType.cs │ │ ├── PkgDefWriter.cs │ │ ├── VersionedBinaryWriter.cs │ │ ├── XmlFileReader.cs │ │ ├── XmlFileWriter.cs │ │ └── __VSCOLORTYPE.cs │ ├── Converter.cs │ ├── JSON │ │ ├── RuleContract.cs │ │ ├── SettingsContract.cs │ │ └── ThemeContract.cs │ ├── OverlayMapping.json │ ├── ParseMapping.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── ThemeConverter.csproj │ ├── TokenMappings.json │ ├── VSCTokenFallback.json │ └── VSTokens.json ├── ThemeConverterTests │ ├── ConversionTest.cs │ ├── DataValidationTest.cs │ ├── TestFiles │ │ ├── Complete_Dark.json │ │ ├── Complete_Light.json │ │ ├── Incomplete_MissingCriticalColors.json │ │ └── Incomplete_NoColors.json │ └── ThemeConverterTests.csproj └── ThemeTests │ ├── .editorconfig │ ├── AllExtensions.cs │ ├── BaseTest.cs │ ├── BaseThemeTest.cs │ ├── GetToCodeThemeTest.cs │ ├── Logger.cs │ ├── MainShellThemeTest.cs │ ├── ManualTestFiles │ └── BasicTests.md │ ├── README.md │ ├── TestFiles │ └── CSharpApp │ │ ├── CSharpApp.csproj │ │ ├── CSharpApp.sln │ │ ├── Languages │ │ ├── cplusplus.cpp │ │ ├── cplusplus.h │ │ ├── csharp.cs │ │ ├── css.css │ │ ├── html.html │ │ ├── javascript.js │ │ ├── json.json │ │ ├── markdown.md │ │ ├── visualbasic.vb │ │ └── xml.xml │ │ └── Program.cs │ ├── ThemeTestFixture.cs │ ├── ThemeTests.csproj │ └── xunit.runner.json └── azure-pipelines.yml /.config/tsaoptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "tsaVersion": "TsaV2", 3 | "codebase": "NewOrUpdate", 4 | "codebaseName": "Theme Converter for VS", 5 | "tsaStamp": "DevDiv", 6 | "tsaEnvironment": "PROD", 7 | "areaPath": "DevDiv\\VS Core\\IDE Experience\\Theming", 8 | "instanceUrl": "https://devdiv.visualstudio.com/", 9 | "instanceUrlDevDiv": "DEVDIV", 10 | "instanceUrlForTsaV2": "DEVDIV", 11 | "projectName": "DevDiv", 12 | "projectNameDEVDIV": "DEVDIV", 13 | "iterationPath": "DevDiv", 14 | "tools": [ 15 | "CredScan", 16 | "PoliCheck", 17 | "Roslyn" 18 | ], 19 | "notificationAliases": [ 20 | "vspersonalization@microsoft.com" 21 | ], 22 | "codebaseAdmins": [ 23 | "REDMOND\\jekelly", 24 | "REDMOND\\naesteve", 25 | "REDMOND\\chawill" 26 | ], 27 | "repositoryName": "microsoft/theme-converter-for-vs" 28 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the color mappings. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Screenshots** 14 | Please include a screenshot of the bug. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | Please describe what you expected this to look like. 25 | 26 | **Environment (please complete the following information):** 27 | - Converter version: [e.g. v0.0.2] 28 | - VS version: [e.g. 16.10.2, 17.0.0 Preview 3] 29 | - Theme used: [e.g. [Panda](https://marketplace.visualstudio.com/items?itemName=tinkertrain.theme-panda)] 30 | 31 | **Impact** 32 | Please describe how this bug impacts your theme. Is this bug blocking you from releasing the theme, or is this a mild annoyance? 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azure-pipelines.1ESPipelineTemplatesSchemaFile": true 3 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Theme Converter for Visual Studio 2 | Theme Converter is a CLI tool which allows you to convert your VS Code theme to work in Visual Studio. It's a simple tool that maps the colors of a theme to Visual Studio. Themes can be packaged into VSIXs and uploaded to the Marketplace so that more people can use your theme! This allows developers who use Visual Studio, or use both VS Code and Visual Studio, to quickly get access to a wide range of fun and interesting themes! 3 | 4 | Here's a quick overview on how the Theme Converter works: 5 | [Tutorial on converting a theme](https://www.youtube.com/watch?v=2Gwqr5uuBt4) 6 | 7 | Our community of authors have already converted a handful of VS Code themes to work in Visual Studio. [Feel free to take a look here](https://aka.ms/vsthemes). 8 | 9 | ## Prerequisites 10 | 1. VS Code 11 | 2. [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) 12 | 3. The following workloads can be installed via the Visual Studio installer: 13 | - .NET Desktop development 14 | - Visual Studio extension development 15 | 16 | ## Instructions 17 | ### Using the tool 18 | 1. Open command line in **Admin** mode. 19 | 2. Clone the repo 20 | 3. Go to `\ThemeConverter\ThemeConverter` and build the converter project with `dotnet build ThemeConverter.csproj`. 21 | 4. Go to `\ThemeConverter\ThemeConverter\bin\Debug\net6.0`. 22 | 5. Get the theme file with steps described in section [Getting a theme's json file](https://github.com/microsoft/theme-converter#getting-a-themes-json-file) 23 | 7. Run `ThemeConverter.exe -h` to see the usage of the tool and use the tool according to your needs. 24 | - If you want to convert a theme and patch it to a target VS: 25 | - Run `ThemeConverter.exe -i -t ` 26 | - Example: `ThemeConverter.exe -i "C:\myTheme\TestTheme.json" -t "C:\Program Files\Microsoft Visual Studio\2022\Community"`. This command will convert the TestTheme, patch the generated json to the target VS and launch the VS with the converted theme. 27 | - Note: the VS installation path can be found in your VS installer (the location field on the modify page). 28 | - Now you can see the your converted theme under `Tools -> Themes`! Enjoy! 29 | 30 | - If you just want to convert a theme and get the generated pkgdef: 31 | - Run `ThemeConverter.exe -i -o ` 32 | - Example: `ThemeConverter.exe -i "C:\myTheme\TestTheme.json" -o "C:\myTheme\results"`. This command will convert the theme and the generated pkgdef will be `C:\myTheme\results\TestTheme.pkgdef`. 33 | 34 | ### Getting a theme's json file 35 | 1. Open VS Code. 36 | 2. Install the desired color theme and switch to this theme in VS Code. Please note that this tool will not convert icon themes. 37 | 3. “Ctrl + Shift + P” and run “Developer: Generate Color Theme from current settings.” 38 | 4. In the generated JSON, uncomment all code. When you uncomment, please be careful about missing commas! Make sure the JSON is valid. 39 | 5. Save this as a “JSON” file for the conversion, using the theme's name as the file name. Please ensure that the file’s extension is .json. (The file shouldn’t be saved as a JSONC file.) 40 | 6. **Note**: Because some part of VS UI does not support customized alpha channel, we recommend reducing the usage of not fully opaque colors for better conversion result. 41 | 42 | ### Creating a VSIX for the new theme 43 | This section describes how you can create a VSIX with the converted theme for publishing and sharing. 44 | 1. In VS 2022, create a new "Empty VSIX Project." 45 | 2. Select the project node and open the "Add existing Item" window: Use "Shift + Alt + A" or right-click on the project node, select Add > Existing Item. 46 | 3. Set filter to All Files (*.*) and select the converted .pkgdef file(s) that you want to include in this VSIX. 47 | 5. Select the newly added pkgdef file in the Solution Explorer and open the Properties window. If the Properties window is not already open, navigate to the View menu at the top > Properties Window. 48 | blueReadme_propertieswindow 49 | 50 | 6. Set `Copy to Output Directory` to `Copy always`. 51 | 7. Set `Include in VSIX` to `true`. 52 | 8. Open the `source.extension.vsixmanifest` file, then select Assets, select New. 53 | 9. Set `Type` to `Microsoft.VisualStudio.VsPackage`, and `Source` to `File on filesystem`. 54 | 10. Select Browse and select the .pkgdef you added. Select OK. 55 | 11. Edit other fields in the vsixmanifest as desired (author, version, company, etc). 56 | 12. Build solution and you now have a vsix in the output folder! Your new theme is most compatible with Visual Studio 2022. 57 | 58 | ### Removing a converted theme from VS 59 | 1. Open target VS and switch to some theme that will not be deleted (like Blue theme). 60 | 2. Go to `\Common7\IDE\CommonExtensions\Platform`. e.g: `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Platform` 61 | and delete the pkgdef of the theme that the you want to remove (The name of the pkgdef will match the name of the theme). 62 | 3. Open Developer Command Prompt of the target VS and run `devenv /updateConfiguration`. 63 | 4. Launch VS again, and the themes should now be removed. 64 | 65 | # Support 66 | 67 | ## How to file issues and get help 68 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. 69 | 70 | ## Microsoft Support Policy 71 | Support for this project is limited to the resources listed above. 72 | 73 | 74 | # Contributing 75 | 76 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 77 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 78 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 79 | 80 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 81 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 82 | provided by the bot. You will only need to do this once across all repos using our CLA. 83 | 84 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 85 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 86 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 87 | 88 | # Trademarks 89 | 90 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 91 | trademarks or logos is subject to and must follow 92 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 93 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 94 | Any use of third-party trademarks or logos are subject to those third-party's policies. 95 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /ThemeConverter/FinalPublicKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/theme-converter-for-vs/b9954d32cb8d45e47f02c4cf33c9bea827d68d14/ThemeConverter/FinalPublicKey.snk -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31316.19 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThemeConverter", "ThemeConverter\ThemeConverter.csproj", "{EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThemeTests", "ThemeTests\ThemeTests.csproj", "{DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThemeConverterTests", "ThemeConverterTests\ThemeConverterTests.csproj", "{7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Debug|x86.ActiveCfg = Debug|Any CPU 23 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Debug|x86.Build.0 = Debug|Any CPU 24 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Release|x86.ActiveCfg = Release|Any CPU 27 | {EC2FEE85-0EB7-4C3E-9294-57BCF2B50369}.Release|x86.Build.0 = Release|Any CPU 28 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Debug|x86.Build.0 = Debug|Any CPU 32 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Release|x86.ActiveCfg = Release|Any CPU 35 | {DBB1FE41-4D7A-46DD-8CC2-03FC634340EB}.Release|x86.Build.0 = Release|Any CPU 36 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Debug|x86.ActiveCfg = Debug|Any CPU 39 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Debug|x86.Build.0 = Debug|Any CPU 40 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Release|x86.ActiveCfg = Release|Any CPU 43 | {7B2AFD3A-CB0E-49C1-9302-3016529DF1E9}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {610C9DFB-78F6-47B2-BFC6-E1B2FA3CA55C} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/CategoryGuid.json: -------------------------------------------------------------------------------- 1 | { 2 | "Text Editor MEF Items": "{75a05685-00a8-4ded-bae5-e7a50bfa929a}", 3 | "Text Editor Text Marker Items": "{ff349800-ea43-46c1-8c98-878e78f46501}", 4 | "Text Editor Language Service Items": "{e0187991-b458-4f7e-8ca9-42c9a573b56c}", 5 | "FSharpInteractive": "{00ccee86-3140-4e06-a65a-a92665a40d6f}", 6 | "WebClient Diagnostic Tools": "{2aa714ae-53be-4393-84e0-dc95b57a1891}", 7 | "Text Editor Text Manager Items": "{58e96763-1d3b-4e05-b6ba-ff7115fd0b7b}", 8 | "Find Results": "{5c48b2cb-0366-4fbf-9786-0bb37e945687}", 9 | "Immediate Window": "{6bb65c5a-2f31-4bde-9f48-8a38dc0c63e7}", 10 | "Output Window": "{9973efdf-317d-431c-8bc1-5e88cbfd4f7f}", 11 | "Folder Difference": "{b36b0228-dbad-4db0-b9c7-2ad3e572010f}", 12 | "Command Window": "{ee1be240-4e81-4beb-8eea-54322b6b1bf5}", 13 | "Package Manager Console": "{f9d6bce6-c669-41db-8ee7-dd953828685b}", 14 | "Autos": "{a7ee6bee-d0aa-4b2f-ad9d-748276a725f6}", 15 | "Locals": "{8259aced-490a-41b3-a0fb-64c842ccdc80}", 16 | "Watch": "{358463d0-d084-400f-997e-a34fc570bc72}", 17 | "Performance Tips": "{7a4c6cc9-8404-4b95-af88-f11b657c7268}", 18 | "Editor Tooltip": "{a9a5637f-b2a8-422e-8fb5-dfb4625f0111}", 19 | "SQL Results - Text": "{587d0421-e473-4032-b214-9359f3b7bc80}", 20 | "SQL Results - Grid": "{6202ff3e-488e-4ead-92cb-be089659f8d7}", 21 | "CodeSense": "{fc88969a-cbed-4940-8f48-142a503e2381}", 22 | "Roslyn Text Editor MEF Items": "{75a05685-00a8-4ded-bae5-e7a50bfa929a}", 23 | "CodeSenseControls": "{de7b1121-99a4-4708-aedf-15f40c9b332f}", 24 | "VisualProfiler": "{CF3994AF-130F-4F0D-A84E-3601E4E357D9}", 25 | "Cpp Text Editor MEF Items": "{75a05685-00a8-4ded-bae5-e7a50bfa929a}", 26 | "JavaScriptDiagnosticsTools": "{6AACDB12-98D1-4448-AFFA-FF460B4339E7}", 27 | "JavascriptRequiredForMonacoEditor": "{ca939bd0-e6fd-47f6-8a98-effc40dfab02}", 28 | "ACDCOverview": "{c8887ac6-3c60-4209-9d69-8f4c12a60044}", 29 | "Cider": "{92d153ee-57d7-431f-a739-0931ca3f7f70}", 30 | "CodeAnalysis": "{bbadd5c0-3c00-4c56-aae8-92cd1d1f516b}", 31 | "CommonControls": "{c01072a1-a915-4abf-89b7-e2f9e8ec4c7f}", 32 | "CommonDocument": "{72eb4027-f4ae-4b6c-9cc2-dfd5b49d9415}", 33 | "Diagnostics": "{6f4c1845-5111-4f31-b204-47cb6a466ee8}", 34 | "Environment": "{624ed9c3-bdfd-41fa-96c3-7c824ea32e3d}", 35 | "Find": "{4370282e-987e-4ac4-ad14-5ffed2ad1e14}", 36 | "GraphicsDebugger": "{40cdc500-10ac-41df-a533-6af2aaaa0c4b}", 37 | "Header": "{4997f547-1379-456e-b985-2f413cdfa536}", 38 | "InfoBar": "{832df9d1-d9a9-4eb7-ad13-ff5b421f7432}", 39 | "InformationBadge": "{63f9718c-889b-4814-a3b4-6e81d14f1156}", 40 | "IntelliTrace": "{ffa74b06-e011-49be-a58c-3efaa4b957d5}", 41 | "ListViewGrid": "{63dec435-18e1-4e62-a9a9-0495fcb524a3}", 42 | "ManifestDesigner": "{b239f458-9f75-4376-959b-4d48b89337f4}", 43 | "NavigateTo": "{d58e4793-9fe6-450b-b200-b93a3c17aa12}", 44 | "NewProjectDialog": "{c36c426e-31c9-4048-84cf-31c111d65ec0}", 45 | "NotificationBubble": "{800c5171-6625-4a74-b3e2-a6cd0b4698d4}", 46 | "ProgressBar": "{94acf70f-a81d-4512-a31d-8196616751ee}", 47 | "ProjectDesigner": "{ef1a2d2c-5d16-4ddb-8d04-79d0f6c1c56e}", 48 | "Promotion": "{8fafb063-4106-4756-81dc-d7769f24d056}", 49 | "SearchControl": "{f1095fad-881f-45f1-8580-589e10325eb8}", 50 | "SharePointTools": "{7e8da76d-24b4-447c-8ece-ff8e0e73f0d4}", 51 | "StartPage": "{65d78f35-869e-4bf3-8e52-991fee554a16}", 52 | "ThemedDialog": "{5e04b2a9-e443-48db-8791-63a2a71cfbd7}", 53 | "ThemedUtilityDialog": "{e2961a72-48a5-4da7-8168-1fee490f78a7}", 54 | "TreeView": "{92ecf08e-8b13-4cf4-99e9-ae2692382185}", 55 | "UnthemedDialog": "{d1fae935-144d-45a6-af9a-d615e3cd7b75}", 56 | "UserInformation": "{b6d9f1fc-e422-4755-a014-3fb666d831dc}", 57 | "UserNotifications": "{5d42b198-efca-431c-92aa-8b595d0d39c2}", 58 | "VisualStudioInstaller": "{53212856-7528-403d-84e6-76820f4cef73}", 59 | "VSSearch": "{04ba5a31-6d4d-4225-9194-2e38a9175e31}", 60 | "ApacheCordovaToolsColors": "{badebe03-e60e-41de-8b51-5a379a844217}", 61 | "ApplicationInsights": "{badebe03-e60e-41de-8b51-5a379a844217}", 62 | "ClientDiagnosticsMemory": "{4ec0c2e0-c165-47be-9f29-2d2b73ad3e93}", 63 | "ClientDiagnosticsTimeline": "{694a8994-9208-45d6-bf31-c58f96236fbe}", 64 | "ClientDiagnosticsTools": "{9e505e47-0d0d-4681-8c9c-baa2a07771b7}", 65 | "CpuUsageTool": "{02071ec8-0cf0-4822-af30-9f001537e7eb}", 66 | "DetailsView": "{a128c60e-947c-4f26-95c9-fb4b3d53547f}", 67 | "DiagnosticsHub": "{f8a8b2a5-dd35-43f6-a382-fd6a61325c22}", 68 | "GraphicsDesigners": "{9cfdb8b3-48aa-4715-ac38-dde2968d204f}", 69 | "TaskRunnerExplorerControls": "{ABB9394C-8C5A-447E-B1B7-965E0B6ABBC3}", 70 | "WelcomeExperience": "{df76799d-28de-4975-81af-3357270f57eb}", 71 | "GraphDocumentColors": "{0cd5aa2b-ef23-4997-80b5-7d0e8fe5b312}", 72 | "TeamExplorer": "{4aff231b-f28a-44f0-a66b-1beeb17cb920}", 73 | "VersionControl": "{6be84f44-74e4-4e5f-aee9-1b930f431375}", 74 | "WorkItemEditor": "{2138d120-456d-425e-80b5-88d2401fca23}", 75 | "PackageManifestEditor": "{3f27d653-86e6-4a04-adb6-a35efa6c8f05}", 76 | "WebEditor": "{75a05685-00a8-4ded-bae5-e7a50bfa929a}" 77 | } -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/CategoryCollectionRecord.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace ThemeConverter.ColorCompiler 10 | { 11 | /// 12 | /// Reads or writes a sequence of category records from a binary stream. 13 | /// A CategoryCollectionRecord contains a sequence of CategoryRecords. 14 | /// Each CategoryRecord contains a sequence of ColorRecords, and each 15 | /// ColorRecord specifies a name and values for a single color. 16 | /// CategoryCollectionRecords are merged together by a ColorTheme to 17 | /// form the full theme. 18 | /// 19 | internal sealed class CategoryCollectionRecord 20 | { 21 | List _categories; 22 | 23 | public IList Categories 24 | { 25 | get 26 | { 27 | return _categories; 28 | } 29 | } 30 | 31 | public CategoryCollectionRecord() 32 | { 33 | _categories = new List(); 34 | } 35 | 36 | public void Write(BinaryWriter writer) 37 | { 38 | writer.Write(_categories.Count); 39 | 40 | foreach (CategoryRecord theme in _categories) 41 | { 42 | theme.Write(writer); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/CategoryRecord.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace ThemeConverter.ColorCompiler 10 | { 11 | /// 12 | /// Reads or writes a category of colors from a binary stream. Each 13 | /// category record contains the GUID identifier for the category 14 | /// and the sequence of color names and values contained in the category. 15 | /// 16 | internal sealed class CategoryRecord 17 | { 18 | Guid _category; 19 | List _colors; 20 | 21 | public IList Colors 22 | { 23 | get 24 | { 25 | return _colors; 26 | } 27 | } 28 | 29 | public CategoryRecord(Guid category) 30 | { 31 | _category = category; 32 | _colors = new List(); 33 | } 34 | 35 | public void Write(BinaryWriter writer) 36 | { 37 | WriteGuid(writer, _category); 38 | writer.Write(_colors.Count); 39 | foreach (ColorRecord entry in _colors) 40 | { 41 | entry.Write(writer); 42 | } 43 | } 44 | 45 | public static void WriteGuid(BinaryWriter writer, Guid guid) 46 | { 47 | writer.Write(guid.ToByteArray()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/CategoryThemeKey.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace ThemeConverter.ColorCompiler 7 | { 8 | internal class CategoryThemeKey 9 | { 10 | public CategoryThemeKey(Guid category, Guid theme) 11 | { 12 | Category = category; 13 | ThemeId = theme; 14 | } 15 | 16 | public Guid Category { get; private set; } 17 | public Guid ThemeId { get; private set; } 18 | 19 | public override bool Equals(object obj) 20 | { 21 | CategoryThemeKey other = obj as CategoryThemeKey; 22 | if (other == null) 23 | { 24 | return false; 25 | } 26 | 27 | return Category == other.Category && ThemeId == other.ThemeId; 28 | } 29 | 30 | public override int GetHashCode() 31 | { 32 | return Category.GetHashCode() ^ ThemeId.GetHashCode(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorCategory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace ThemeConverter.ColorCompiler 7 | { 8 | internal class ColorCategory 9 | { 10 | private const int HashCombiningMultiplier = 1566083941; 11 | 12 | public ColorCategory(Guid categoryId, string name) 13 | { 14 | Id = categoryId; 15 | Name = name; 16 | } 17 | 18 | public Guid Id 19 | { 20 | get; 21 | private set; 22 | } 23 | 24 | public string Name 25 | { 26 | get; 27 | private set; 28 | } 29 | 30 | public override bool Equals(object obj) 31 | { 32 | ColorCategory other = obj as ColorCategory; 33 | if (other == null) 34 | { 35 | return false; 36 | } 37 | 38 | return Id == other.Id && Name == other.Name; 39 | } 40 | 41 | public override int GetHashCode() 42 | { 43 | return CombineHashes(Name.GetHashCode(), Id.GetHashCode()); 44 | } 45 | 46 | /// 47 | /// Combines 2 hashes together. 48 | /// 49 | private static int CombineHashes(int hash1, int hash2) 50 | { 51 | unchecked 52 | { 53 | return (RotateLeft(hash1, 5) ^ (hash2 * HashCombiningMultiplier)); 54 | } 55 | } 56 | 57 | /// 58 | /// Rotates the bits of an int value to the left 59 | /// 60 | /// The value to rotate 61 | /// The number of positions to rotate 62 | /// The rotated value 63 | private static int RotateLeft(int value, int count) 64 | { 65 | const int totalBits = 32; 66 | return unchecked((value << count) | (value >> (totalBits - count))); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorEntry.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Drawing; 5 | 6 | namespace ThemeConverter.ColorCompiler 7 | { 8 | internal class ColorEntry 9 | { 10 | public ColorEntry(ColorName name) 11 | { 12 | Name = name; 13 | } 14 | 15 | public ColorName Name { get; set; } 16 | 17 | public ColorTheme Theme { get; set; } 18 | 19 | public bool IsEmpty { get; set; } 20 | 21 | public Color Background { get; set; } 22 | 23 | public uint BackgroundSource { get; set; } 24 | 25 | public __VSCOLORTYPE BackgroundType { get; set; } 26 | 27 | public Color Foreground { get; set; } 28 | 29 | public uint ForegroundSource { get; set; } 30 | 31 | public __VSCOLORTYPE ForegroundType { get; set; } 32 | 33 | public static Color FromRgba(uint rgba) 34 | { 35 | byte alpha = (byte)(rgba >> 24); 36 | byte blue = (byte)(rgba >> 16); 37 | byte green = (byte)(rgba >> 8); 38 | byte red = (byte)rgba; 39 | return Color.FromArgb(alpha, red, green, blue); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | 8 | namespace ThemeConverter.ColorCompiler 9 | { 10 | internal class ColorManager 11 | { 12 | private static readonly Guid HighContrastThemeId = new Guid("a5c004b4-2d4b-494e-bf01-45fc492522c7"); 13 | private static readonly Guid LightThemeId = new Guid("de3dbbcd-f642-433c-8353-8f1df4370aba"); 14 | private static readonly Guid DarkThemeId = new Guid("1ded0138-47ce-435e-84ef-9ec1f439b749"); 15 | private static readonly Guid BlueThemeId = new Guid("a4d6a176-b948-4b29-8c66-53c97a1ed7d0"); 16 | private static readonly Guid AdditionalContrastThemeId = new Guid("ce94d289-8481-498b-8ca9-9b6191a315b9"); 17 | 18 | private ColorThemeCollection _themes; 19 | private ObservableCollection _categories; 20 | private Dictionary _themeIndex; 21 | private Dictionary _categoryIndex; 22 | private Dictionary _colorIndex; 23 | private ColorRowCollection _colors; 24 | 25 | public ColorManager() 26 | { 27 | AddRequiredThemes(); 28 | _categories = new ObservableCollection(); 29 | } 30 | 31 | private void AddRequiredThemes() 32 | { 33 | foreach (Theme requiredTheme in RequiredThemes) 34 | { 35 | ColorTheme theme = GetOrCreateTheme(requiredTheme.Guid); 36 | theme.IsBuiltInTheme = true; 37 | 38 | if (string.IsNullOrEmpty(theme.Name)) 39 | { 40 | theme.Name = requiredTheme.Name; 41 | } 42 | } 43 | } 44 | 45 | private IEnumerable RequiredThemes 46 | { 47 | get 48 | { 49 | yield return new Theme(LightThemeId, "Light"); 50 | yield return new Theme(DarkThemeId, "Dark"); 51 | yield return new Theme(BlueThemeId, "Blue"); 52 | yield return new Theme(AdditionalContrastThemeId, "AdditionalContrast"); 53 | yield return new Theme(HighContrastThemeId, "HighContrast"); 54 | } 55 | } 56 | 57 | public ColorTheme GetOrCreateTheme(Guid themeId) 58 | { 59 | ColorTheme theme; 60 | if (!ThemeIndex.TryGetValue(themeId, out theme)) 61 | { 62 | theme = new ColorTheme(themeId); 63 | Themes.Add(theme); 64 | } 65 | return theme; 66 | } 67 | 68 | public IDictionary ThemeIndex 69 | { 70 | get 71 | { 72 | return _themeIndex = _themeIndex ?? new Dictionary(); 73 | } 74 | } 75 | 76 | public IList Categories 77 | { 78 | get 79 | { 80 | return _categories = _categories ?? new ObservableCollection(); 81 | } 82 | } 83 | 84 | public IList Themes 85 | { 86 | get 87 | { 88 | return _themes = _themes ?? new ColorThemeCollection(this); 89 | } 90 | } 91 | 92 | public IDictionary CategoryIndex 93 | { 94 | get 95 | { 96 | return _categoryIndex = _categoryIndex ?? new Dictionary(); 97 | } 98 | } 99 | 100 | public IDictionary ColorIndex 101 | { 102 | get 103 | { 104 | return _colorIndex = _colorIndex ?? new Dictionary(); 105 | } 106 | } 107 | 108 | public IList Colors 109 | { 110 | get 111 | { 112 | return _colors = _colors ?? new ColorRowCollection(this); 113 | } 114 | } 115 | 116 | public ColorEntry GetOrCreateEntry(Guid themeId, ColorName name) 117 | { 118 | ColorTheme theme = GetOrCreateTheme(themeId); 119 | ColorEntry entry; 120 | if (!theme.Index.TryGetValue(name, out entry)) 121 | { 122 | entry = new ColorEntry(name); 123 | theme.Colors.Add(entry); 124 | 125 | ColorRow row; 126 | if (!ColorIndex.TryGetValue(name, out row)) 127 | { 128 | row = new ColorRow(this, name); 129 | Colors.Add(row); 130 | } 131 | } 132 | 133 | return entry; 134 | } 135 | 136 | public ColorCategory RegisterCategory(Guid categoryId, string name) 137 | { 138 | ColorCategory category; 139 | if (!CategoryIndex.TryGetValue(categoryId, out category)) 140 | { 141 | category = new ColorCategory(categoryId, name); 142 | CategoryIndex[categoryId] = category; 143 | Categories.Add(category); 144 | } 145 | 146 | return category; 147 | } 148 | 149 | private class Theme 150 | { 151 | public Theme(Guid guid, string name) 152 | { 153 | Guid = guid; 154 | Name = name; 155 | } 156 | 157 | public Guid Guid { get; private set; } 158 | 159 | public string Name { get; private set; } 160 | } 161 | 162 | private class ColorThemeCollection : OwnershipCollection 163 | { 164 | private readonly ColorManager _manager; 165 | 166 | public ColorThemeCollection(ColorManager manager) 167 | { 168 | _manager = manager; 169 | } 170 | 171 | protected override void TakeOwnership(ColorTheme item) 172 | { 173 | _manager.ThemeIndex[item.ThemeId] = item; 174 | item.Manager = _manager; 175 | } 176 | 177 | protected override void LoseOwnership(ColorTheme item) 178 | { 179 | _manager.ThemeIndex.Remove(item.ThemeId); 180 | item.Manager = null; 181 | } 182 | } 183 | 184 | private class ColorRowCollection : OwnershipCollection 185 | { 186 | private readonly ColorManager _manager; 187 | 188 | public ColorRowCollection(ColorManager manager) 189 | { 190 | _manager = manager; 191 | } 192 | 193 | protected override void TakeOwnership(ColorRow item) 194 | { 195 | _manager.ColorIndex[item.Name] = item; 196 | } 197 | 198 | protected override void LoseOwnership(ColorRow item) 199 | { 200 | _manager.ColorIndex.Remove(item.Name); 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorName.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace ThemeConverter.ColorCompiler 7 | { 8 | internal class ColorName 9 | { 10 | readonly ColorCategory _category; 11 | readonly string _name; 12 | 13 | public ColorName(ColorCategory category, string name) 14 | { 15 | _category = category; 16 | _name = name; 17 | } 18 | 19 | public ColorCategory Category 20 | { 21 | get 22 | { 23 | return _category; 24 | } 25 | } 26 | 27 | public string Name 28 | { 29 | get 30 | { 31 | return _name; 32 | } 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | ColorName other = obj as ColorName; 38 | if (other == null) 39 | { 40 | return false; 41 | } 42 | 43 | return Object.Equals(Category, other.Category) && Object.Equals(Name, other.Name); 44 | } 45 | 46 | public override int GetHashCode() 47 | { 48 | return Name == null ? 0 : Name.GetHashCode(); 49 | } 50 | } 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorRecord.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace ThemeConverter.ColorCompiler 8 | { 9 | /// 10 | /// Reads or writes the value of a color from a binary stream. The color record 11 | /// captures the string name of the color and its default background and foreground 12 | /// values. The ColorRecord must be scoped within a CategoryRecord to fully-identify 13 | /// the color's name. 14 | /// 15 | internal sealed class ColorRecord 16 | { 17 | string _name; 18 | 19 | public __VSCOLORTYPE BackgroundType 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | public __VSCOLORTYPE ForegroundType 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | public uint Background 32 | { 33 | get; 34 | set; 35 | } 36 | 37 | public uint Foreground 38 | { 39 | get; 40 | set; 41 | } 42 | 43 | public ColorRecord(string name) 44 | { 45 | _name = name; 46 | } 47 | 48 | public ColorRecord(BinaryReader reader) 49 | { 50 | int nameLength = reader.ReadInt32(); 51 | _name = Encoding.UTF8.GetString(reader.ReadBytes(nameLength)); 52 | 53 | BackgroundType = (__VSCOLORTYPE)reader.ReadByte(); 54 | if (IsValidColorType(BackgroundType)) 55 | { 56 | Background = reader.ReadUInt32(); 57 | } 58 | else 59 | { 60 | BackgroundType = (byte)__VSCOLORTYPE.CT_INVALID; 61 | Background = 0; 62 | } 63 | ForegroundType = (__VSCOLORTYPE)reader.ReadByte(); 64 | if (IsValidColorType(ForegroundType)) 65 | { 66 | Foreground = reader.ReadUInt32(); 67 | } 68 | else 69 | { 70 | ForegroundType = (byte)__VSCOLORTYPE.CT_INVALID; 71 | Foreground = 0; 72 | } 73 | } 74 | 75 | public void Write(BinaryWriter writer) 76 | { 77 | byte[] nameBytes = Encoding.UTF8.GetBytes(_name); 78 | writer.Write(nameBytes.Length); 79 | writer.Write(nameBytes); 80 | 81 | writer.Write((byte)BackgroundType); 82 | if (IsValidColorType(BackgroundType)) 83 | { 84 | writer.Write(Background); 85 | } 86 | writer.Write((byte)ForegroundType); 87 | if (IsValidColorType(ForegroundType)) 88 | { 89 | writer.Write(Foreground); 90 | } 91 | } 92 | 93 | static bool IsValidColorType(__VSCOLORTYPE colorType) 94 | { 95 | return colorType == __VSCOLORTYPE.CT_RAW || colorType == __VSCOLORTYPE.CT_SYSCOLOR || colorType == __VSCOLORTYPE.CT_AUTOMATIC || colorType == __VSCOLORTYPE.CT_COLORINDEX; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorRow.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeConverter.ColorCompiler 5 | { 6 | internal class ColorRow 7 | { 8 | public ColorRow(ColorManager manager, ColorName name) 9 | { 10 | Manager = manager; 11 | Name = name; 12 | } 13 | 14 | public ColorName Name 15 | { 16 | get; 17 | private set; 18 | } 19 | 20 | private ColorManager Manager 21 | { 22 | get; 23 | set; 24 | } 25 | 26 | public ColorEntry GetOrCreateEntry(ColorTheme theme) 27 | { 28 | return theme.Manager.GetOrCreateEntry(theme.ThemeId, Name); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/ColorTheme.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace ThemeConverter.ColorCompiler 8 | { 9 | internal class ColorTheme 10 | { 11 | private ColorEntryCollection _colors; 12 | private Dictionary _index; 13 | 14 | public ColorTheme(Guid themeId) 15 | { 16 | ThemeId = themeId; 17 | } 18 | 19 | public Guid ThemeId 20 | { 21 | get; 22 | private set; 23 | } 24 | 25 | public bool IsBuiltInTheme 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | public string Name { get; set; } 32 | 33 | public Guid FallbackId { get; set; } 34 | 35 | public ColorManager Manager { get; set; } 36 | 37 | public IList Colors 38 | { 39 | get 40 | { 41 | return _colors = _colors ?? new ColorEntryCollection(this); 42 | } 43 | } 44 | 45 | public IDictionary Index 46 | { 47 | get 48 | { 49 | return _index = _index ?? new Dictionary(); 50 | } 51 | } 52 | 53 | class ColorEntryCollection : OwnershipCollection 54 | { 55 | private ColorTheme _theme; 56 | 57 | public ColorEntryCollection(ColorTheme theme) 58 | { 59 | this._theme = theme; 60 | } 61 | 62 | protected override void TakeOwnership(ColorEntry item) 63 | { 64 | if (item.Theme != null) 65 | { 66 | throw new InvalidOperationException("Color entry can only belong to one theme"); 67 | } 68 | 69 | item.Theme = _theme; 70 | _theme.Index[item.Name] = item; 71 | } 72 | 73 | protected override void LoseOwnership(ColorEntry item) 74 | { 75 | _theme.Index.Remove(item.Name); 76 | item.Theme = null; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/FileWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | 8 | namespace ThemeConverter.ColorCompiler 9 | { 10 | internal abstract class FileWriter 11 | { 12 | private readonly ColorManager _colorManager; 13 | protected FileWriter(ColorManager manager) 14 | { 15 | _colorManager = manager; 16 | } 17 | 18 | protected ColorManager ColorManager { get { return _colorManager; } } 19 | 20 | public abstract void SaveToFile(string fileName); 21 | 22 | public static void SaveColorManagerToFile(ColorManager manager, string fileName, bool registerTheme = false) 23 | { 24 | string extension = Path.GetExtension(fileName); 25 | if (string.Equals(extension, ".xml", StringComparison.OrdinalIgnoreCase)) 26 | { 27 | XmlFileWriter writer = new XmlFileWriter(manager); 28 | writer.SaveToFile(fileName); 29 | } 30 | else if (string.Equals(extension, ".pkgdef", StringComparison.OrdinalIgnoreCase)) 31 | { 32 | PkgDefWriter writer = new PkgDefWriter(manager); 33 | writer.SaveToFile(fileName); 34 | } 35 | else 36 | { 37 | throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, "Invalid file extension '{0}'. Only XML files and PKGDEF files are allowed.", extension)); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/OwnershipCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | 8 | namespace ThemeConverter.ColorCompiler 9 | { 10 | internal abstract class OwnershipCollection : ObservableCollection 11 | { 12 | protected abstract void TakeOwnership(T item); 13 | protected abstract void LoseOwnership(T item); 14 | 15 | protected override void ClearItems() 16 | { 17 | List removedElements = new List(this); 18 | 19 | base.ClearItems(); 20 | 21 | // it's important to only make this call 22 | // after the collection has been updated 23 | // so listeners see the current collection 24 | foreach (T element in removedElements) 25 | { 26 | LoseOwnership(element); 27 | } 28 | } 29 | 30 | protected override void InsertItem(int index, T item) 31 | { 32 | if (item == null) 33 | { 34 | // not an ArgumentNullException since this is not the public method originally called 35 | throw new InvalidOperationException("Collection item cannot be null"); 36 | } 37 | 38 | base.InsertItem(index, item); 39 | 40 | // it's important to only make this call 41 | // after the collection has been updated 42 | // so listeners see the current collection 43 | TakeOwnership(item); 44 | } 45 | 46 | protected override void SetItem(int index, T item) 47 | { 48 | if (item == null) 49 | { 50 | // not an ArgumentNullException since this is not the public method originally called 51 | throw new InvalidOperationException("Collection item cannot be null"); 52 | } 53 | 54 | T changedItem = this[index]; 55 | 56 | base.SetItem(index, item); 57 | 58 | // it's important to only make this call 59 | // after the collection has been updated 60 | // so listeners see the current collection 61 | LoseOwnership(changedItem); 62 | TakeOwnership(item); 63 | } 64 | 65 | protected override void RemoveItem(int index) 66 | { 67 | T removedItem = this[index]; 68 | 69 | base.RemoveItem(index); 70 | 71 | // it's important to only make this call 72 | // after the collection has been updated 73 | // so listeners see the current collection 74 | LoseOwnership(removedItem); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/PkgDefConstants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Text.RegularExpressions; 5 | 6 | namespace ThemeConverter.ColorCompiler 7 | { 8 | internal static class PkgDefConstants 9 | { 10 | public const int MaxBinaryBlobSize = 1000000; 11 | public const string DataValueName = "Data"; 12 | public static readonly Regex FindThemeExpression = new Regex(@"\$RootKey\$\\Themes\\(?'name'[^\\]*)", RegexOptions.Singleline); 13 | public static readonly Regex FindCategoryNameExpression = new Regex(@"\$RootKey\$\\Themes\\(?'name'[^\\]*)\\(?'categoryName'[^\\]*)", RegexOptions.Singleline); 14 | public const int ExpectedVersion = 11; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/PkgDefFileWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace ThemeConverter.ColorCompiler 9 | { 10 | internal class PkgDefFileWriter : IDisposable 11 | { 12 | private StreamWriter file; 13 | private bool isOpen; 14 | private string lastSectionWritten; 15 | private bool disposedValue; 16 | 17 | private class Constants 18 | { 19 | public const string SectionStartChar = @"["; 20 | public const string SectionEndChar = @"]"; 21 | public const string BinaryPrefix = "hex:"; 22 | } 23 | 24 | public PkgDefFileWriter(string filePath, bool overwriteExisting) 25 | { 26 | this.file = new StreamWriter(filePath, !overwriteExisting, System.Text.Encoding.UTF8); 27 | this.isOpen = true; 28 | this.lastSectionWritten = ""; 29 | } 30 | 31 | public bool Write(PkgDefItem item) 32 | { 33 | if (!this.isOpen) 34 | return false; 35 | 36 | if ((item.SectionName == String.Empty) || 37 | (item.SectionName == null)) 38 | return false; 39 | 40 | if (item.SectionName != this.lastSectionWritten) 41 | { 42 | if (this.lastSectionWritten != String.Empty) 43 | { 44 | file.WriteLine(); 45 | } 46 | string line = String.Format("{0}{1}{2}", 47 | Constants.SectionStartChar, 48 | item.SectionName, 49 | Constants.SectionEndChar); 50 | file.WriteLine(line); 51 | this.lastSectionWritten = item.SectionName; 52 | } 53 | 54 | if ((item.ValueName != null) && 55 | (item.ValueName != String.Empty)) 56 | { 57 | if (item.ValueName == "@") 58 | { 59 | file.Write(item.ValueName); 60 | } 61 | else 62 | { 63 | string line = String.Format("\"{0}\"", item.ValueName); 64 | file.Write(line); 65 | } 66 | 67 | file.Write("="); 68 | 69 | switch (item.ValueDataType) 70 | { 71 | //Todo: catch invalid cast exceptions, report, and continue; 72 | case PkgDefValueType.PKGDEF_VALUE_STRING: 73 | { 74 | string line = String.Format("\"{0}\"", (string)item.ValueDataString); 75 | file.Write(line); 76 | break; 77 | } 78 | case PkgDefValueType.PKGDEF_VALUE_BINARY: 79 | { 80 | string line = String.Format("{0}{1}", 81 | Constants.BinaryPrefix, 82 | this.DataToHexString((byte[])item.ValueDataBinary, item.ValueDataBinaryLength)); 83 | file.Write(line); 84 | break; 85 | } 86 | } 87 | 88 | file.WriteLine(); 89 | } 90 | 91 | return true; 92 | } 93 | 94 | private string DataToHexString(byte[] binaryData, int length) 95 | { 96 | if (!this.isOpen) 97 | return null; 98 | 99 | var dataString = new StringBuilder(); 100 | for (int i = 0; i < length; i++) 101 | { 102 | dataString.Append(binaryData[i].ToString("x2")); 103 | dataString.Append(","); 104 | } 105 | return dataString.ToString().TrimEnd(','); 106 | } 107 | 108 | protected virtual void Dispose(bool disposing) 109 | { 110 | if (!disposedValue) 111 | { 112 | if (disposing) 113 | { 114 | // Dispose managed resources 115 | this.file?.Dispose(); 116 | } 117 | 118 | disposedValue = true; 119 | } 120 | } 121 | 122 | public void Dispose() 123 | { 124 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 125 | Dispose(disposing: true); 126 | GC.SuppressFinalize(this); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/PkgDefItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Text; 5 | 6 | namespace ThemeConverter.ColorCompiler 7 | { 8 | /// 9 | /// Represents an item in a pkgdef file. 10 | /// 11 | internal struct PkgDefItem 12 | { 13 | public string SectionName { get; set; } 14 | 15 | public string ValueName { get; set; } 16 | 17 | public PkgDefValueType ValueDataType { get; set; } 18 | public string ValueDataString { get; set; } 19 | 20 | public string[] ValueDataStringArray 21 | { 22 | get; set; 23 | } 24 | 25 | 26 | public byte[] ValueDataBinary 27 | { 28 | get; set; 29 | } 30 | 31 | public int ValueDataBinaryLength 32 | { 33 | get; set; 34 | } 35 | 36 | public uint ValueDataDWORD 37 | { 38 | get; set; 39 | } 40 | 41 | public ulong ValueDataQWORD 42 | { 43 | get; set; 44 | } 45 | 46 | public override string ToString() 47 | { 48 | StringBuilder stringBuilder = new StringBuilder(); 49 | stringBuilder.Append("["); 50 | stringBuilder.Append(SectionName); 51 | stringBuilder.Append("]"); 52 | if (!string.IsNullOrEmpty(ValueName)) 53 | { 54 | stringBuilder.Append(" "); 55 | stringBuilder.Append(ValueName); 56 | stringBuilder.Append("="); 57 | stringBuilder.Append("???"); 58 | } 59 | 60 | return stringBuilder.ToString(); 61 | } 62 | 63 | //public sealed override bool Equals(object obj) 64 | //{ 65 | // if (obj == null) 66 | // { 67 | // return false; 68 | // } 69 | 70 | 71 | 72 | // if (obj.GetType() != GetType()) 73 | // { 74 | // return false; 75 | // } 76 | 77 | 78 | 79 | // ValueType valueType = (PkgDefItem)obj; 80 | // if (SectionName != ((PkgDefItem)valueType).SectionName) 81 | // { 82 | // return false; 83 | // } 84 | 85 | 86 | 87 | // if (ValueName != ((PkgDefItem)valueType).ValueName) 88 | // { 89 | // return false; 90 | // } 91 | 92 | 93 | 94 | // PkgDefValueType valueDataType = ValueDataType; 95 | // if (valueDataType != ((PkgDefItem)valueType).ValueDataType) 96 | // { 97 | // return false; 98 | // } 99 | 100 | 101 | 102 | // switch (valueDataType) 103 | // { 104 | // case PkgDefValueType.PKGDEF_VALUE_STRING: 105 | // case PkgDefValueType.PKGDEF_VALUE_EXPAND_SZ: 106 | // if (ValueDataString != ((PkgDefItem)valueType).ValueDataString) 107 | // { 108 | // return false; 109 | // } 110 | 111 | 112 | 113 | // break; 114 | // case PkgDefValueType.PKGDEF_VALUE_MULTI_SZ: 115 | // { 116 | // string[] valueDataStringArray = ValueDataStringArray; 117 | // int num2 = (valueDataStringArray != null) ? 1 : 0; 118 | // if ((((((PkgDefItem)valueType).ValueDataStringArray != null) ? 1 : 0) ^ num2) != 0) 119 | // { 120 | // return false; 121 | // } 122 | 123 | 124 | 125 | // if (valueDataStringArray == null) 126 | // { 127 | // break; 128 | // } 129 | 130 | 131 | 132 | // if (ValueDataStringArray.GetLength(0) != ((PkgDefItem)valueType).ValueDataStringArray.GetLength(0)) 133 | // { 134 | // return false; 135 | // } 136 | 137 | 138 | 139 | // int length = ValueDataStringArray.GetLength(0); 140 | // int num3 = 0; 141 | // if (0 >= length) 142 | // { 143 | // break; 144 | // } 145 | 146 | 147 | 148 | // do 149 | // { 150 | // if (!(ValueDataStringArray[num3] != ((PkgDefItem)valueType).ValueDataStringArray[num3])) 151 | // { 152 | // num3++; 153 | // continue; 154 | // } 155 | 156 | 157 | 158 | // return false; 159 | // } 160 | // while (num3 < length); 161 | // break; 162 | // } 163 | // case PkgDefValueType.PKGDEF_VALUE_DWORD: 164 | // if (ValueDataDWORD != ((PkgDefItem)valueType).ValueDataDWORD) 165 | // { 166 | // return false; 167 | // } 168 | 169 | 170 | 171 | // break; 172 | // case PkgDefValueType.PKGDEF_VALUE_QWORD: 173 | // if (ValueDataQWORD != ((PkgDefItem)valueType).ValueDataQWORD) 174 | // { 175 | // return false; 176 | // } 177 | 178 | 179 | 180 | // break; 181 | // case PkgDefValueType.PKGDEF_VALUE_BINARY: 182 | // { 183 | // int valueDataBinaryLength = ValueDataBinaryLength; 184 | // if (valueDataBinaryLength != ((PkgDefItem)valueType).ValueDataBinaryLength) 185 | // { 186 | // return false; 187 | // } 188 | 189 | 190 | 191 | // if (valueDataBinaryLength <= 0) 192 | // { 193 | // break; 194 | // } 195 | 196 | 197 | 198 | // byte[] valueDataBinary = ValueDataBinary; 199 | // if (valueDataBinary != null && ((PkgDefItem)valueType).ValueDataBinary != null) 200 | // { 201 | // if (ValueDataBinaryLength <= valueDataBinary.GetLength(0) && ((PkgDefItem)valueType).ValueDataBinaryLength <= ((PkgDefItem)valueType).ValueDataBinary.GetLength(0)) 202 | // { 203 | // int num = 0; 204 | // int valueDataBinaryLength2 = ValueDataBinaryLength; 205 | // if (0 >= valueDataBinaryLength2) 206 | // { 207 | // break; 208 | // } 209 | 210 | 211 | 212 | // byte[] valueDataBinary2 = ValueDataBinary; 213 | // do 214 | // { 215 | // if (valueDataBinary2[num] == ((PkgDefItem)valueType).ValueDataBinary[num]) 216 | // { 217 | // num++; 218 | // continue; 219 | // } 220 | 221 | 222 | 223 | // return false; 224 | // } 225 | // while (num < valueDataBinaryLength2); 226 | // break; 227 | // } 228 | 229 | 230 | 231 | // throw new IndexOutOfRangeException(); 232 | // } 233 | 234 | 235 | 236 | // return false; 237 | // } 238 | // } 239 | 240 | 241 | 242 | // return true; 243 | //} 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/PkgDefValueType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeConverter.ColorCompiler 5 | { 6 | /// 7 | /// Pkgdef value type. 8 | /// 9 | internal enum PkgDefValueType 10 | { 11 | PKGDEF_VALUE_STRING, 12 | PKGDEF_VALUE_EXPAND_SZ, 13 | PKGDEF_VALUE_MULTI_SZ, 14 | PKGDEF_VALUE_BINARY, 15 | PKGDEF_VALUE_DWORD, 16 | PKGDEF_VALUE_QWORD 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/PkgDefWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Linq; 9 | 10 | namespace ThemeConverter.ColorCompiler 11 | { 12 | /// 13 | /// Writes a ColorManager out to a pkgdef file. 14 | /// 15 | internal class PkgDefWriter 16 | { 17 | private readonly ColorManager _colorManager; 18 | 19 | public PkgDefWriter(ColorManager manager) 20 | { 21 | _colorManager = manager; 22 | } 23 | 24 | public void SaveToFile(string fileName) 25 | { 26 | EnsureDirectoryExists(Path.GetDirectoryName(fileName)); 27 | 28 | using (PkgDefFileWriter writer = new PkgDefFileWriter(fileName, true)) 29 | { 30 | WriterRegistration(writer); 31 | WriterThemes(writer); 32 | } 33 | } 34 | 35 | private void WriterRegistration(PkgDefFileWriter writer) 36 | { 37 | PkgDefItem item = new PkgDefItem(); 38 | foreach (ColorTheme theme in _colorManager.Themes) 39 | { 40 | if (theme.IsBuiltInTheme) 41 | continue; 42 | 43 | item.SectionName = string.Format(CultureInfo.InvariantCulture, @"$RootKey$\Themes\{0:B}", theme.ThemeId); 44 | item.ValueDataType = PkgDefValueType.PKGDEF_VALUE_STRING; 45 | 46 | item.ValueName = "@"; 47 | item.ValueDataString = theme.Name; 48 | writer.Write(item); 49 | 50 | item.ValueName = "Name"; 51 | item.ValueDataString = theme.Name; 52 | writer.Write(item); 53 | 54 | if (theme.FallbackId != Guid.Empty) 55 | { 56 | item.ValueName = nameof(theme.FallbackId); 57 | item.ValueDataString = theme.FallbackId.ToString("B"); 58 | writer.Write(item); 59 | } 60 | } 61 | } 62 | 63 | private void WriterThemes(PkgDefFileWriter writer) 64 | { 65 | Dictionary> entries = new Dictionary>(); 66 | 67 | foreach (ColorEntry entry in _colorManager.Themes.SelectMany(t => t.Colors)) 68 | { 69 | CategoryThemeKey key = new CategoryThemeKey(entry.Name.Category.Id, entry.Theme.ThemeId); 70 | List keyEntries; 71 | if (!entries.TryGetValue(key, out keyEntries)) 72 | { 73 | keyEntries = new List(); 74 | entries[key] = keyEntries; 75 | } 76 | 77 | keyEntries.Add(entry); 78 | } 79 | 80 | PkgDefItem item = new PkgDefItem(); 81 | item.ValueDataBinary = new byte[PkgDefConstants.MaxBinaryBlobSize]; 82 | 83 | foreach (KeyValuePair> unsavedSet in entries) 84 | { 85 | if (unsavedSet.Value.Any(e => !e.IsEmpty)) 86 | { 87 | ColorCategory category = _colorManager.CategoryIndex[unsavedSet.Key.Category]; 88 | item.SectionName = string.Format(CultureInfo.InvariantCulture, @"$RootKey$\Themes\{0:B}\{1}", unsavedSet.Key.ThemeId, category.Name); 89 | item.ValueName = null; 90 | item.ValueDataType = PkgDefValueType.PKGDEF_VALUE_STRING; 91 | writer.Write(item); 92 | 93 | item.ValueName = "Data"; 94 | item.ValueDataType = PkgDefValueType.PKGDEF_VALUE_BINARY; 95 | PopulateThemeCategoryInfo(ref item, category.Id, unsavedSet.Value); 96 | writer.Write(item); 97 | } 98 | } 99 | } 100 | 101 | private void EnsureDirectoryExists(string directory) 102 | { 103 | if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) 104 | { 105 | Directory.CreateDirectory(directory); 106 | } 107 | } 108 | 109 | private void PopulateThemeCategoryInfo(ref PkgDefItem item, Guid category, List colorEntries) 110 | { 111 | using (MemoryStream stream = new MemoryStream()) 112 | { 113 | using (VersionedBinaryWriter versionedWriter = new VersionedBinaryWriter(stream)) 114 | { 115 | CategoryCollectionRecord record = new CategoryCollectionRecord(); 116 | 117 | CategoryRecord categoryRecord = new CategoryRecord(category); 118 | record.Categories.Add(categoryRecord); 119 | 120 | 121 | foreach (ColorEntry entry in colorEntries) 122 | { 123 | if (!entry.IsEmpty) 124 | { 125 | ColorRecord colorRecord = CreateColorRecord(entry); 126 | categoryRecord.Colors.Add(colorRecord); 127 | } 128 | } 129 | 130 | versionedWriter.WriteVersioned(PkgDefConstants.ExpectedVersion, (checkedWriter, version) => 131 | { 132 | record.Write(checkedWriter); 133 | }); 134 | } 135 | 136 | byte[] newBytes = stream.ToArray(); 137 | item.ValueDataBinaryLength = newBytes.Length; 138 | newBytes.CopyTo(item.ValueDataBinary, 0); 139 | } 140 | } 141 | 142 | private ColorRecord CreateColorRecord(ColorEntry entry) 143 | { 144 | ColorRecord colorRecord = new ColorRecord(entry.Name.Name); 145 | colorRecord.BackgroundType = entry.BackgroundType; 146 | colorRecord.Background = entry.BackgroundSource; 147 | colorRecord.ForegroundType = entry.ForegroundType; 148 | colorRecord.Foreground = entry.ForegroundSource; 149 | return colorRecord; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/VersionedBinaryWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.IO; 6 | 7 | namespace ThemeConverter.ColorCompiler 8 | { 9 | /// 10 | /// Specialization of BinaryWriter that writes a versioned byte stream. 11 | /// 12 | internal class VersionedBinaryWriter : BinaryWriter 13 | { 14 | public VersionedBinaryWriter(Stream stream) 15 | : base(stream) 16 | { } 17 | 18 | /// 19 | /// Delegate that will write the body of the stream. 20 | /// 21 | /// The VersionedBinaryWriter 22 | /// The version of the stream. It is for 23 | /// reference only; the delegate does not have to write it to 24 | /// the stream. 25 | public delegate void WriteCallback(VersionedBinaryWriter writer, int version); 26 | 27 | /// 28 | /// Writes versioning header to a stream, the calls a delegate to write 29 | /// the meat of the data. 30 | /// 31 | /// Version number to write 32 | /// The delegate that will write the body of the stream 33 | public void WriteVersioned(int version, WriteCallback callback) 34 | { 35 | // remember the starting stream seek position 36 | Stream stream = BaseStream; 37 | long startingPosition = stream.Position; 38 | 39 | // write a placeholder for the integer number of bytes we write; when we're 40 | // done we'll seek back and overwrite this with the correct value 41 | Write((int)-1); 42 | 43 | // now write the version 44 | Write(version); 45 | 46 | try 47 | { 48 | // let the delegate write the real data 49 | callback(this, version); 50 | } 51 | catch (Exception) 52 | { 53 | // rewind the stream to the starting position 54 | stream.Position = startingPosition; 55 | 56 | throw; 57 | } 58 | 59 | // go back and update the number of bytes we wrote 60 | long endingPosition = stream.Position; 61 | stream.Position = startingPosition; 62 | Write((int)(endingPosition - startingPosition)); 63 | 64 | // return to the end of the stream 65 | stream.Position = endingPosition; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/XmlFileReader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Xml; 8 | 9 | namespace ThemeConverter.ColorCompiler 10 | { 11 | /// 12 | /// This class reads Visual Studio XML theme files. It does no verification, so the 13 | /// file should be verified before being passed to this class. 14 | /// 15 | internal class XmlFileReader 16 | { 17 | private ColorTheme _currentTheme = null; 18 | private ColorCategory _currentCategory = null; 19 | private ColorName _currentColor = null; 20 | private ColorEntry _currentEntry = null; 21 | private XmlReader _reader; 22 | private ColorManager _colorManager; 23 | protected string _fileName; 24 | 25 | public XmlFileReader(string fileName) 26 | { 27 | _fileName = fileName; 28 | } 29 | 30 | public ColorManager ColorManager 31 | { 32 | get 33 | { 34 | if (_colorManager == null) 35 | { 36 | _colorManager = new ColorManager(); 37 | if (!FileIsEmptyOrNonExistent()) 38 | { 39 | LoadColorManagerFromFile(); 40 | } 41 | } 42 | return _colorManager; 43 | } 44 | } 45 | 46 | protected void LoadColorManagerFromFile() 47 | { 48 | XmlReaderSettings settings = new XmlReaderSettings() 49 | { 50 | DtdProcessing = DtdProcessing.Prohibit, 51 | XmlResolver = null, 52 | }; 53 | 54 | _reader = XmlReader.Create(_fileName, settings); 55 | 56 | while (_reader.Read()) 57 | { 58 | if (_reader.NodeType == XmlNodeType.Element) 59 | { 60 | switch (_reader.Name) 61 | { 62 | case "Theme": 63 | ReadThemeElement(); 64 | break; 65 | case "Category": 66 | ReadCategoryElement(); 67 | break; 68 | case "Color": 69 | ReadColorElement(); 70 | break; 71 | case "Background": 72 | ReadBackgroundElement(); 73 | break; 74 | case "Foreground": 75 | ReadForegroundElement(); 76 | break; 77 | default: break; 78 | } 79 | } 80 | } 81 | 82 | _reader.Close(); 83 | } 84 | 85 | private bool FileIsEmptyOrNonExistent() 86 | { 87 | FileInfo info = new FileInfo(_fileName); 88 | return !info.Exists || info.Length == 0; 89 | } 90 | 91 | private void ReadThemeElement() 92 | { 93 | Guid guid; 94 | if (Guid.TryParse(_reader.GetAttribute("GUID"), out guid)) 95 | { 96 | _currentTheme = ColorManager.GetOrCreateTheme(guid); 97 | _currentTheme.Name = _reader.GetAttribute("Name"); 98 | 99 | if (Guid.TryParse(_reader.GetAttribute("FallbackId"), out Guid fallBackguid)) 100 | { 101 | _currentTheme.FallbackId = fallBackguid; 102 | } 103 | } 104 | } 105 | 106 | private void ReadCategoryElement() 107 | { 108 | Guid guid; 109 | if (Guid.TryParse(_reader.GetAttribute("GUID"), out guid)) 110 | { 111 | _currentCategory = ColorManager.RegisterCategory(guid, _reader.GetAttribute("Name")); 112 | } 113 | } 114 | 115 | private void ReadColorElement() 116 | { 117 | _currentColor = new ColorName(_currentCategory, _reader.GetAttribute("Name")); 118 | _currentEntry = ColorManager.GetOrCreateEntry(_currentTheme.ThemeId, _currentColor); 119 | } 120 | 121 | private void ReadBackgroundElement() 122 | { 123 | __VSCOLORTYPE colorType; 124 | uint source; 125 | 126 | if (Enum.TryParse<__VSCOLORTYPE>(_reader.GetAttribute("Type"), out colorType)) 127 | { 128 | _currentEntry.BackgroundType = colorType; 129 | } 130 | 131 | if (UInt32.TryParse(_reader.GetAttribute("Source"), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out source)) 132 | { 133 | _currentEntry.BackgroundSource = SwapARGBandABGR(source, colorType); 134 | } 135 | } 136 | 137 | private void ReadForegroundElement() 138 | { 139 | __VSCOLORTYPE colorType; 140 | uint source; 141 | 142 | if (Enum.TryParse<__VSCOLORTYPE>(_reader.GetAttribute("Type"), out colorType)) 143 | { 144 | _currentEntry.ForegroundType = colorType; 145 | } 146 | 147 | if (UInt32.TryParse(_reader.GetAttribute("Source"), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out source)) 148 | { 149 | _currentEntry.ForegroundSource = SwapARGBandABGR(source, colorType); 150 | } 151 | } 152 | 153 | private uint SwapARGBandABGR(uint argb, __VSCOLORTYPE type) 154 | { 155 | if (type == __VSCOLORTYPE.CT_RAW) 156 | { 157 | byte alpha = (byte)(argb >> 24); 158 | byte blue = (byte)(argb >> 16); 159 | byte green = (byte)(argb >> 8); 160 | byte red = (byte)argb; 161 | 162 | return (uint)(alpha << 24 | red << 16 | green << 8 | blue); 163 | } 164 | else return argb; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/XmlFileWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Xml; 10 | 11 | namespace ThemeConverter.ColorCompiler 12 | { 13 | /// 14 | /// Writes a color manager out to an xml file. 15 | /// 16 | internal class XmlFileWriter : FileWriter 17 | { 18 | private const string EmptyThemesXml = ""; 19 | 20 | private XmlDocument _document = new XmlDocument() { XmlResolver = null }; 21 | 22 | public XmlFileWriter(ColorManager colorManager) 23 | : base(colorManager) 24 | { 25 | } 26 | 27 | /// 28 | /// Writes the data for the XmlFileWriter to an xml file. 29 | /// 30 | public override void SaveToFile(string fileName) 31 | { 32 | _document = new XmlDocument() { XmlResolver = null }; 33 | WriteThemes(_document); 34 | _document.Save(fileName); 35 | } 36 | 37 | private void WriteThemes(XmlDocument document) 38 | { 39 | XmlReaderSettings settings = new XmlReaderSettings() 40 | { 41 | DtdProcessing = DtdProcessing.Prohibit, 42 | XmlResolver = null, 43 | }; 44 | 45 | using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(EmptyThemesXml))) 46 | using (XmlReader reader = XmlReader.Create(stream, settings)) 47 | { 48 | document.Load(reader); 49 | foreach (ColorTheme theme in ColorManager.Themes) 50 | { 51 | AddTheme(theme, document.DocumentElement); 52 | } 53 | } 54 | } 55 | 56 | private void AddTheme(ColorTheme theme, XmlElement parent) 57 | { 58 | XmlElement themeElement = AddNewElement("Theme", parent); 59 | AddNameAttribute(theme.Name, themeElement); 60 | AddGuidAttribute(theme.ThemeId, themeElement); 61 | 62 | if (theme.FallbackId != Guid.Empty) 63 | { 64 | AddFallbackIdAttribute(theme.FallbackId, themeElement); 65 | } 66 | 67 | foreach (ColorCategory category in ColorManager.Categories) 68 | { 69 | AddCategory(category, themeElement, theme); 70 | } 71 | 72 | if (themeElement.ChildNodes.Count == 0) 73 | { 74 | parent.RemoveChild(themeElement); 75 | } 76 | } 77 | 78 | private void AddCategory(ColorCategory category, XmlElement themeElement, ColorTheme theme) 79 | { 80 | XmlElement categoryElement = AddNewElement("Category", themeElement); 81 | string categoryName = category.Name; 82 | AddNameAttribute(categoryName, categoryElement); 83 | AddGuidAttribute(category.Id, categoryElement); 84 | 85 | foreach (ColorRow colorRow in ColorManager.Colors.Where(color => color.Name.Category.Name == categoryName)) 86 | { 87 | AddColor(colorRow, categoryElement, theme); 88 | } 89 | 90 | if (categoryElement.ChildNodes.Count == 0) 91 | { 92 | themeElement.RemoveChild(categoryElement); 93 | } 94 | } 95 | 96 | private void AddColor(ColorRow colorRow, XmlElement category, ColorTheme theme) 97 | { 98 | ColorEntry entry = colorRow.GetOrCreateEntry(theme); 99 | if (entry.IsEmpty) 100 | return; 101 | 102 | XmlElement colorElement = AddNewElement("Color", category); 103 | AddNameAttribute(colorRow.Name.Name, colorElement); 104 | AddColorEntry(entry, colorElement); 105 | } 106 | 107 | private void AddColorEntry(ColorEntry entry, XmlElement colorRow) 108 | { 109 | if (entry.BackgroundType != __VSCOLORTYPE.CT_INVALID) 110 | { 111 | XmlElement colorEntryElement = AddNewElement("Background", colorRow); 112 | AddTypeAttribute(entry.BackgroundType, colorEntryElement); 113 | AddSourceAttribute(entry.BackgroundSource, entry.BackgroundType, colorEntryElement); 114 | } 115 | 116 | if (entry.ForegroundType != __VSCOLORTYPE.CT_INVALID) 117 | { 118 | XmlElement colorEntryElement = AddNewElement("Foreground", colorRow); 119 | AddTypeAttribute(entry.ForegroundType, colorEntryElement); 120 | AddSourceAttribute(entry.ForegroundSource, entry.ForegroundType, colorEntryElement); 121 | } 122 | } 123 | 124 | private void AddSourceAttribute(uint source, __VSCOLORTYPE type, XmlElement element) 125 | { 126 | AddAttribute("Source", ConvertColorSourceHexToString(source, type), element); 127 | } 128 | 129 | private string ConvertColorSourceHexToString(uint source, __VSCOLORTYPE type) 130 | { 131 | return SwapARGBandABGR(source, type).ToString("X8", CultureInfo.InvariantCulture); 132 | } 133 | 134 | private void AddTypeAttribute(__VSCOLORTYPE type, XmlElement element) 135 | { 136 | AddAttribute("Type", type.ToString(), element); 137 | } 138 | 139 | private XmlElement AddNewElement(string newElementName, XmlElement parent) 140 | { 141 | XmlElement newElement = _document.CreateElement(newElementName); 142 | parent.AppendChild(newElement); 143 | return newElement; 144 | } 145 | 146 | private void AddNameAttribute(string name, XmlElement element) 147 | { 148 | AddAttribute("Name", name, element); 149 | } 150 | 151 | private void AddGuidAttribute(Guid guid, XmlElement element) 152 | { 153 | // format for guid is '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}' 154 | AddAttribute("GUID", guid.ToString("B"), element); 155 | } 156 | 157 | private void AddFallbackIdAttribute(Guid fallbackId, XmlElement element) 158 | { 159 | AddAttribute("FallbackId", fallbackId.ToString("B"), element); 160 | } 161 | 162 | private void AddAttribute(string name, string value, XmlElement element) 163 | { 164 | XmlAttribute attribute = _document.CreateAttribute(name); 165 | attribute.Value = value; 166 | element.Attributes.Append(attribute); 167 | } 168 | 169 | private uint SwapARGBandABGR(uint argb, __VSCOLORTYPE type) 170 | { 171 | if (type == __VSCOLORTYPE.CT_RAW) 172 | { 173 | byte alpha = (byte)(argb >> 24); 174 | byte blue = (byte)(argb >> 16); 175 | byte green = (byte)(argb >> 8); 176 | byte red = (byte)argb; 177 | 178 | return (uint)(alpha << 24 | red << 16 | green << 8 | blue); 179 | } 180 | else return argb; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ColorCompiler/__VSCOLORTYPE.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeConverter.ColorCompiler 5 | { 6 | internal enum __VSCOLORTYPE 7 | { 8 | CT_INVALID, 9 | CT_RAW, 10 | CT_COLORINDEX, 11 | CT_SYSCOLOR, 12 | CT_VSCOLOR, 13 | CT_AUTOMATIC, 14 | CT_TRACK_FOREGROUND, 15 | CT_TRACK_BACKGROUND, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/Converter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | #nullable enable 5 | 6 | namespace ThemeConverter 7 | { 8 | using System; 9 | using System.CodeDom.Compiler; 10 | using System.Collections.Generic; 11 | using System.Globalization; 12 | using System.IO; 13 | using Newtonsoft.Json.Linq; 14 | using ThemeConverter.ColorCompiler; 15 | 16 | public sealed class Converter 17 | { 18 | private static Guid DarkThemeId = new Guid("{1ded0138-47ce-435e-84ef-9ec1f439b749}"); 19 | private static Guid LightThemeId = new Guid("{de3dbbcd-f642-433c-8353-8f1df4370aba}"); 20 | 21 | private static Lazy> ScopeMappings = new Lazy>(ParseMapping.CreateScopeMapping()); 22 | private static Lazy> CategoryGuids = new Lazy>(ParseMapping.CreateCategoryGuids()); 23 | private static Lazy> VSCTokenFallback = new Lazy>(ParseMapping.CreateVSCTokenFallback()); 24 | private static Lazy> OverlayMappings = new Lazy>(ParseMapping.CreateOverlayMapping()); 25 | 26 | /// 27 | /// Convert the theme file and patch the pkgdef to the target VS if specified. 28 | /// 29 | /// The VS Code theme json file path. 30 | /// Output folder path to write the .pkgdef file to. 31 | /// 32 | /// Full path to the theme .pkgdef file created in the folder. 33 | /// 34 | public static string ConvertFile(string themeJsonFilePath, string pkgdefOutputPath) 35 | { 36 | string themeName = Path.GetFileNameWithoutExtension(themeJsonFilePath); 37 | 38 | // Parse VS Code theme file and uncomment the code. 39 | 40 | var lines = File.ReadAllLines(themeJsonFilePath); 41 | 42 | for (int i = 0; i < lines.Length; i++) 43 | { 44 | if (lines[i].Trim().StartsWith("//")) 45 | { 46 | lines[i] = lines[i].Remove(lines[i].IndexOf("//"), 2); 47 | 48 | if (!lines[i - 1].EndsWith(',') && !lines[i - 1].EndsWith('{')) 49 | { 50 | lines[i - 1] = lines[i - 1] + ","; 51 | } 52 | } 53 | } 54 | 55 | string text = string.Empty; 56 | foreach (string str in lines) 57 | { 58 | text += str; 59 | } 60 | 61 | var jobject = JObject.Parse(text); 62 | var theme = jobject.ToObject(); 63 | 64 | if (theme == null) 65 | throw new Exception("Failed to get theme object."); 66 | 67 | // Group colors by category. 68 | var colorCategories = GroupColorsByCategory(theme); 69 | 70 | // Compile VS theme. 71 | string tempPkgdefFilePath = CompileVsTheme(themeName, theme, colorCategories); 72 | try 73 | { 74 | // Copy pkgdef to specified folder 75 | Directory.CreateDirectory(pkgdefOutputPath); 76 | 77 | string destPkgdefFilePath = Path.Combine(pkgdefOutputPath, $"{themeName}.pkgdef"); 78 | File.Copy(tempPkgdefFilePath, destPkgdefFilePath, overwrite: true); 79 | 80 | return destPkgdefFilePath; 81 | } 82 | finally 83 | { 84 | // Delete temporary file. 85 | File.Delete(tempPkgdefFilePath); 86 | } 87 | } 88 | 89 | public static void ValidateDataFiles(Action reportFunc) 90 | { 91 | ParseMapping.CheckDuplicateMapping(reportFunc); 92 | } 93 | 94 | #region Compile VS Theme 95 | 96 | /// 97 | /// Generate the pkgdef from the theme. 98 | /// 99 | /// The name of theme. 100 | /// The theme object from the json file. 101 | /// Colors grouped by category. 102 | /// Path to the generated pkgdef 103 | private static string CompileVsTheme( 104 | string themeName, 105 | ThemeFileContract theme, 106 | Dictionary> colorCategories) 107 | { 108 | using (TempFileCollection tempFileCollection = new TempFileCollection()) 109 | { 110 | string tempThemeFile = tempFileCollection.AddExtension("vstheme"); 111 | 112 | using (var writer = new StreamWriter(tempThemeFile)) 113 | { 114 | writer.WriteLine($""); 115 | 116 | Guid themeGuid = Guid.NewGuid(); 117 | 118 | if (theme.Type == "dark") 119 | { 120 | writer.WriteLine($" "); 121 | } 122 | else 123 | { 124 | writer.WriteLine($" "); 125 | } 126 | 127 | foreach (var category in colorCategories) 128 | { 129 | writer.WriteLine($" "); 130 | 131 | foreach (var color in category.Value) 132 | { 133 | if (color.Value.Foreground is not null || color.Value.Background is not null) 134 | { 135 | WriteColor(writer, color.Key, color.Value.Foreground, color.Value.Background); 136 | } 137 | } 138 | 139 | writer.WriteLine($" "); 140 | } 141 | 142 | writer.WriteLine($" "); 143 | writer.WriteLine($""); 144 | 145 | } 146 | 147 | // Compile the pkgdef 148 | XmlFileReader reader = new XmlFileReader(tempThemeFile); 149 | ColorManager manager = reader.ColorManager; 150 | 151 | string tempPkgdef = tempFileCollection.AddExtension("pkgdef", keepFile: true); 152 | FileWriter.SaveColorManagerToFile(manager, tempPkgdef, true); 153 | 154 | return tempPkgdef; 155 | } 156 | } 157 | #endregion Compile VS Theme 158 | 159 | #region Translate VS Theme 160 | 161 | /// 162 | /// Group converted colors by category. 163 | /// 164 | /// the theme contract. 165 | /// Mapping from Category to Color Tokens 166 | private static Dictionary> GroupColorsByCategory(ThemeFileContract theme) 167 | { 168 | // category -> colorKeyName => color value 169 | var colorCategories = new Dictionary>(); 170 | // category -> colorKeyName -> assigned by VSC token 171 | var assignBy = new Dictionary>(); 172 | 173 | Dictionary keyUsed = new Dictionary(); 174 | foreach (string key in ScopeMappings.Value.Keys) 175 | { 176 | keyUsed.Add(key, false); 177 | } 178 | 179 | // Add the editor colors 180 | if (theme.TokenColors != null) 181 | { 182 | foreach (var ruleContract in theme.TokenColors) 183 | { 184 | foreach (var scopeName in ruleContract.ScopeNames) 185 | { 186 | string[] scopes = scopeName.Split(','); 187 | foreach (var scopeRaw in scopes) 188 | { 189 | var scope = scopeRaw.Trim(); 190 | foreach (string key in ScopeMappings.Value.Keys) 191 | { 192 | if (key.StartsWith(scope) && scope != "") 193 | { 194 | if (ScopeMappings.Value.TryGetValue(key, out var colorKeys)) 195 | { 196 | keyUsed[key] = true; 197 | AssignEditorColors(colorKeys, scope, ruleContract, ref colorCategories, ref assignBy); 198 | } 199 | } 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | // for keys that were not used during hierarchical assigning, check if there's any fallback that we can use... 207 | foreach (string key in keyUsed.Keys) 208 | { 209 | if (!keyUsed[key]) 210 | { 211 | if (VSCTokenFallback.Value.TryGetValue(key, out var fallbackToken)) 212 | { 213 | // if the fallback is foreground, assign it like a shell color 214 | if (fallbackToken == "foreground" && theme.Colors.ContainsKey("foreground")) 215 | { 216 | if (ScopeMappings.Value.TryGetValue(key, out var colorKeys)) 217 | { 218 | AssignShellColors(theme, theme.Colors["foreground"], colorKeys, ref colorCategories); 219 | } 220 | } 221 | 222 | if (theme.TokenColors != null) 223 | { 224 | foreach (var ruleContract in theme.TokenColors) 225 | { 226 | foreach (var scopeName in ruleContract.ScopeNames) 227 | { 228 | string[] scopes = scopeName.Split(','); 229 | foreach (var scopeRaw in scopes) 230 | { 231 | var scope = scopeRaw.Trim(); 232 | 233 | if ((fallbackToken.StartsWith(scope) && scope != "")) 234 | { 235 | if (ScopeMappings.Value.TryGetValue(key, out var colorKeys)) 236 | { 237 | AssignEditorColors(colorKeys, scope, ruleContract, ref colorCategories, ref assignBy); 238 | } 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | } 246 | } 247 | 248 | // Add the shell colors 249 | foreach (var color in theme.Colors) 250 | { 251 | if (ScopeMappings.Value.TryGetValue(color.Key.Trim(), out var colorKeyList)) 252 | { 253 | if (!TryGetColorValue(theme, color.Key, out string? colorValue)) 254 | { 255 | continue; 256 | } 257 | 258 | // calculate the actual border color for editor overlay colors 259 | if (OverlayMappings.Value.ContainsKey(color.Key) && TryGetColorValue(theme, OverlayMappings.Value[color.Key].Item2, out string? backgroundColor)) 260 | { 261 | colorValue = GetCompoundColor(colorValue!, backgroundColor!, VSOpacity: OverlayMappings.Value[color.Key].Item1); 262 | } 263 | 264 | AssignShellColors(theme, colorValue!, colorKeyList, ref colorCategories); 265 | } 266 | } 267 | 268 | return colorCategories; 269 | } 270 | 271 | private static bool TryGetColorValue(ThemeFileContract theme, string token, out string? colorValue) 272 | { 273 | theme.Colors.TryGetValue(token, out colorValue); 274 | 275 | string key = token; 276 | 277 | while (colorValue == null) 278 | { 279 | if (VSCTokenFallback.Value.TryGetValue(key, out var fallbackToken)) 280 | { 281 | key = fallbackToken; 282 | theme.Colors.TryGetValue(key, out colorValue); 283 | } 284 | else 285 | { 286 | break; 287 | } 288 | } 289 | 290 | return colorValue != null; 291 | } 292 | 293 | /// 294 | /// Compute what is the compound color of 2 overlayed colors with transparency 295 | /// 296 | /// What is the opacity that VS will use when displaying this color 297 | /// The opacity that VSC will apply to this token under special circumstances. 298 | /// Color value for VS 299 | private static string GetCompoundColor(string overlayColor, string baseColor, float VSOpacity = 1, float VSCOpacity = 1) 300 | { 301 | overlayColor = ReviseColor(overlayColor); 302 | baseColor = ReviseColor(baseColor); 303 | float overlayA = (float)System.Convert.ToInt32(overlayColor.Substring(0, 2), 16) * VSCOpacity / 255; 304 | float overlayR = System.Convert.ToInt32(overlayColor.Substring(2, 2), 16); 305 | float overlayG = System.Convert.ToInt32(overlayColor.Substring(4, 2), 16); 306 | float overlayB = System.Convert.ToInt32(overlayColor.Substring(6, 2), 16); 307 | 308 | float baseA = (float)System.Convert.ToInt32(baseColor.Substring(0, 2), 16) / 255; 309 | float baseR = System.Convert.ToInt32(baseColor.Substring(2, 2), 16); 310 | float baseG = System.Convert.ToInt32(baseColor.Substring(4, 2), 16); 311 | float baseB = System.Convert.ToInt32(baseColor.Substring(6, 2), 16); 312 | 313 | float R = (overlayA / VSOpacity) * overlayR + (1 - overlayA / VSOpacity) * baseA * baseR; 314 | float G = (overlayA / VSOpacity) * overlayG + (1 - overlayA / VSOpacity) * baseA * baseG; 315 | float B = (overlayA / VSOpacity) * overlayB + (1 - overlayA / VSOpacity) * baseA * baseB; 316 | 317 | R = Math.Clamp(R, 0, 255); 318 | G = Math.Clamp(G, 0, 255); 319 | B = Math.Clamp(B, 0, 255); 320 | 321 | return $"{(int)R:X2}{(int)G:X2}{(int)B:X2}FF"; 322 | } 323 | 324 | private static void AssignEditorColors(ColorKey[] colorKeys, 325 | string scope, 326 | RuleContract ruleContract, 327 | ref Dictionary> colorCategories, 328 | ref Dictionary> assignBy) 329 | { 330 | foreach (var colorKey in colorKeys) 331 | { 332 | if (!colorCategories.TryGetValue(colorKey.CategoryName, out var rulesList)) 333 | { 334 | rulesList = new Dictionary(); 335 | colorCategories[colorKey.CategoryName] = rulesList; 336 | } 337 | 338 | if (!assignBy.TryGetValue(colorKey.CategoryName, out var assignList)) 339 | { 340 | assignList = new Dictionary(); 341 | assignBy[colorKey.CategoryName] = assignList; 342 | } 343 | 344 | if (rulesList.ContainsKey(colorKey.KeyName)) 345 | { 346 | if (scope.StartsWith(assignList[colorKey.KeyName]) && ruleContract.Settings.Foreground != null) 347 | { 348 | rulesList[colorKey.KeyName] = ruleContract.Settings; 349 | assignList[colorKey.KeyName] = scope; 350 | } 351 | } 352 | else 353 | { 354 | rulesList.Add(colorKey.KeyName, ruleContract.Settings); 355 | assignList.Add(colorKey.KeyName, scope); 356 | } 357 | } 358 | } 359 | 360 | private static void AssignShellColors(ThemeFileContract theme, string colorValue, ColorKey[] colorKeys, ref Dictionary> colorCategories) 361 | { 362 | foreach (var colorKey in colorKeys) 363 | { 364 | if (colorKey.ForegroundOpacity is not null && colorKey.VSCBackground is not null) 365 | { 366 | if (TryGetColorValue(theme, colorKey.VSCBackground, out string? backgroundColor)) 367 | { 368 | colorValue = GetCompoundColor(colorValue, backgroundColor!, 1, colorKey.ForegroundOpacity.Value); 369 | } 370 | } 371 | 372 | if (!colorCategories.TryGetValue(colorKey.CategoryName, out var rulesList)) 373 | { 374 | // token name to colors 375 | rulesList = new Dictionary(); 376 | colorCategories[colorKey.CategoryName] = rulesList; 377 | } 378 | 379 | if (!rulesList.TryGetValue(colorKey.KeyName, out var colorSetting)) 380 | { 381 | colorSetting = new SettingsContract(); 382 | rulesList.Add(colorKey.KeyName, colorSetting); 383 | } 384 | 385 | if (colorKey.isBackground) 386 | { 387 | colorSetting.Background = colorValue; 388 | } 389 | else 390 | { 391 | colorSetting.Foreground = colorValue; 392 | } 393 | } 394 | } 395 | 396 | #endregion Translate VS Theme 397 | 398 | #region Write VS Theme 399 | 400 | private static void WriteColor(StreamWriter writer, string colorKeyName, string? foregroundColor, string? backgroundColor) 401 | { 402 | writer.WriteLine($" "); 403 | 404 | if (backgroundColor is not null) 405 | { 406 | writer.WriteLine($" "); 407 | } 408 | 409 | if (foregroundColor is not null) 410 | { 411 | writer.WriteLine($" "); 412 | } 413 | 414 | writer.WriteLine($" "); 415 | } 416 | 417 | private static string ReviseColor(string color) 418 | { 419 | var revisedColor = color.Trim('#'); 420 | switch (revisedColor.Length) 421 | { 422 | case 3: 423 | { 424 | string r = revisedColor.Substring(0, 1); 425 | string g = revisedColor.Substring(1, 1); 426 | string b = revisedColor.Substring(2, 1); 427 | revisedColor = string.Format("FF{0}{0}{1}{1}{2}{2}", r, g, b); 428 | break; 429 | } 430 | case 4: 431 | { 432 | string r = revisedColor.Substring(0, 1); 433 | string g = revisedColor.Substring(1, 1); 434 | string b = revisedColor.Substring(2, 1); 435 | string a = revisedColor.Substring(3, 1); 436 | revisedColor = string.Format("{0}{0}{1}{1}{2}{2}{3}{3}", a, r, g, b); 437 | break; 438 | } 439 | case 6: 440 | { 441 | revisedColor = $"FF{revisedColor}"; 442 | break; 443 | } 444 | case 8: 445 | { 446 | // go from RRGGBBAA to AARRGGBB 447 | revisedColor = string.Format("{0}{1}", revisedColor.Substring(6), revisedColor.Substring(0, 6)); 448 | break; 449 | } 450 | default: 451 | break; 452 | } 453 | return revisedColor; 454 | } 455 | 456 | #endregion Write VS Theme 457 | } 458 | 459 | internal sealed class ColorKey 460 | { 461 | public ColorKey(string categoryName, string keyName, string backgroundOrForeground, string? foregroundOpacity = null, string? vscBackground = null) 462 | { 463 | this.CategoryName = categoryName; 464 | this.KeyName = keyName; 465 | this.Aspect = backgroundOrForeground; 466 | 467 | if (backgroundOrForeground.Equals("Background", StringComparison.OrdinalIgnoreCase)) 468 | { 469 | isBackground = true; 470 | } 471 | else 472 | { 473 | isBackground = false; 474 | } 475 | 476 | this.ForegroundOpacity = foregroundOpacity == null ? null : float.Parse(foregroundOpacity, CultureInfo.InvariantCulture.NumberFormat); 477 | this.VSCBackground = vscBackground; 478 | } 479 | 480 | public string CategoryName { get; } 481 | 482 | public string KeyName { get; } 483 | 484 | public string Aspect { get; } 485 | 486 | public bool isBackground { get; } 487 | 488 | public float? ForegroundOpacity { get; } 489 | 490 | public string? VSCBackground { get; } 491 | 492 | public override string ToString() 493 | { 494 | return this.CategoryName + "&" + this.KeyName + "&" + this.Aspect; 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/JSON/RuleContract.cs: -------------------------------------------------------------------------------- 1 | namespace ThemeConverter 2 | { 3 | using System; 4 | using System.Runtime.Serialization; 5 | using Newtonsoft.Json.Linq; 6 | 7 | [DataContract] 8 | internal class RuleContract 9 | { 10 | [DataMember(Name = "name", IsRequired = false)] 11 | public string Name { get; set; } 12 | 13 | [DataMember(Name = "scope", IsRequired = false)] 14 | public JToken Scope { get; set; } 15 | 16 | [DataMember(Name = "settings")] 17 | public SettingsContract Settings { get; set; } 18 | 19 | public string[] ScopeNames 20 | { 21 | get 22 | { 23 | try 24 | { 25 | if (this.Scope is null) 26 | { 27 | return Array.Empty(); 28 | } 29 | 30 | return new[] { this.Scope.ToObject() }; 31 | } 32 | catch (Exception) 33 | { 34 | return this.Scope.ToObject(); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/JSON/SettingsContract.cs: -------------------------------------------------------------------------------- 1 | namespace ThemeConverter 2 | { 3 | using System.Runtime.Serialization; 4 | 5 | [DataContract] 6 | internal class SettingsContract 7 | { 8 | [DataMember(Name = "foreground", IsRequired = false)] 9 | public string Foreground { get; set; } 10 | 11 | [DataMember(Name = "background", IsRequired = false)] 12 | public string Background { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/JSON/ThemeContract.cs: -------------------------------------------------------------------------------- 1 | namespace ThemeConverter 2 | { 3 | using System.Collections.Generic; 4 | using System.Runtime.Serialization; 5 | 6 | [DataContract] 7 | internal class ThemeFileContract 8 | { 9 | [DataMember(Name = "type")] 10 | public string Type { get; set; } 11 | 12 | [DataMember(Name = "colors")] 13 | public Dictionary Colors { get; set; } 14 | 15 | [DataMember(Name = "tokenColors")] 16 | public RuleContract[] TokenColors { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/OverlayMapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.lineHighlightBorder": { 3 | "Item1": 1.0, 4 | "Item2": "editor.background" 5 | }, 6 | "editor.lineHighlightBackground": { 7 | "Item1": 0.25, 8 | "Item2": "editor.background" 9 | }, 10 | "editor.selectionBackground": { 11 | "Item1": 0.4, 12 | "Item2": "editor.background" 13 | }, 14 | "editor.foldBackground": { 15 | "Item1": 1.0, 16 | "Item2": "editor.background" 17 | }, 18 | "editor.inactiveSelectionBackground": { 19 | "Item1": 0.4, 20 | "Item2": "editor.background" 21 | }, 22 | "editorWhitespace.foreground": { 23 | "Item1": 1.0, 24 | "Item2": "editor.background" 25 | }, 26 | "editorBracketMatch.border": { 27 | "Item1": 1.0, 28 | "Item2": "editor.background" 29 | }, 30 | "editorBracketMatch.background": { 31 | "Item1": 1.0, 32 | "Item2": "editor.background" 33 | }, 34 | "editor.selectionHighlightBackground": { 35 | "Item1": 1.0, 36 | "Item2": "editor.background" 37 | }, 38 | "editor.wordHighlightStrongBackground": { 39 | "Item1": 1.0, 40 | "Item2": "editor.background" 41 | }, 42 | "editor.wordHighlightBackground": { 43 | "Item1": 1.0, 44 | "Item2": "editor.background" 45 | }, 46 | "minimapSlider.background": { 47 | "Item1": 1.0, 48 | "Item2": "minimap.background" 49 | }, 50 | "menu.selectionBackground": { 51 | "Item1": 1.0, 52 | "Item2": "menu.background" 53 | }, 54 | "menubar.selectionBackground": { 55 | "Item1": 1.0, 56 | "Item2": "titleBar.activeBackground" 57 | }, 58 | "list.inactiveSelectionBackground": { 59 | "Item1": 1.0, 60 | "Item2": "sideBar.background" 61 | }, 62 | "list.activeSelectionBackground": { 63 | "Item1": 1.0, 64 | "Item2": "sideBar.background" 65 | }, 66 | "list.hoverBackground": { 67 | "Item1": 1.0, 68 | "Item2": "sideBar.background" 69 | }, 70 | "sideBarSectionHeader.background": { 71 | "Item1": 1.0, 72 | "Item2": "sideBar.background" 73 | }, 74 | "scrollbarSlider.background": { 75 | "Item1": 1.0, 76 | "Item2": "editor.background" 77 | }, 78 | "scrollbarSlider.hoverBackground": { 79 | "Item1": 1.0, 80 | "Item2": "editor.background" 81 | } 82 | } -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ParseMapping.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | 10 | namespace ThemeConverter 11 | { 12 | internal class ParseMapping 13 | { 14 | private static Dictionary ScopeMappings = new Dictionary(); 15 | private static Dictionary CategoryGuids = new Dictionary(); 16 | private static Dictionary VSCTokenFallback = new Dictionary(); 17 | private static Dictionary OverlayMapping = new Dictionary(); 18 | private static List MappedVSTokens = new List(); 19 | private const string TokenColorsName = "tokenColors"; 20 | private const string VSCTokenName = "VSC Token"; 21 | private const string VSTokenName = "VS Token"; 22 | private const string TokenMappingFileName = "TokenMappings.json"; 23 | 24 | public static void CheckDuplicateMapping(Action reportFunc) 25 | { 26 | var contents = System.IO.File.ReadAllText(TokenMappingFileName); 27 | var file = JObject.Parse(contents); 28 | var colors = file[TokenColorsName]; 29 | 30 | var addedMappings = new List(); 31 | 32 | foreach (var color in colors) 33 | { 34 | var VSCToken = color[VSCTokenName]; 35 | string key = VSCToken.ToString(); 36 | 37 | var VSTokens = color[VSTokenName]; 38 | 39 | foreach (var VSToken in VSTokens) 40 | { 41 | if (addedMappings.Contains(VSToken.ToString())) 42 | { 43 | reportFunc(key + ": " + VSToken.ToString()); 44 | } 45 | else 46 | { 47 | addedMappings.Add(VSToken.ToString()); 48 | } 49 | } 50 | } 51 | } 52 | 53 | public static Dictionary CreateScopeMapping() 54 | { 55 | var contents = System.IO.File.ReadAllText(TokenMappingFileName); 56 | 57 | // JObject.Parse will skip JSON comments by default 58 | var file = JObject.Parse(contents); 59 | 60 | var colors = file[TokenColorsName]; 61 | foreach (var color in colors) 62 | { 63 | var VSCToken = color[VSCTokenName]; 64 | string key = VSCToken.ToString(); 65 | 66 | var VSTokens = color[VSTokenName]; 67 | List values = new List(); 68 | foreach (var VSToken in VSTokens) 69 | { 70 | string[] colorKey = VSToken.ToString()?.Split("&"); 71 | ColorKey newColorKey; 72 | switch(colorKey.Length) 73 | { 74 | case 2: // category & token name (by default foreground) 75 | newColorKey = new ColorKey(colorKey[0], colorKey[1], "Foreground"); 76 | break; 77 | case 3: // category & token name & aspect 78 | newColorKey = new ColorKey(colorKey[0], colorKey[1], colorKey[2]); 79 | break; 80 | case 4: // category & token name & vsc opacity & vscode background 81 | newColorKey = new ColorKey(colorKey[0], colorKey[1], "Foreground", colorKey[2], colorKey[3]); 82 | break; 83 | case 5: // category & token name & aspect & vsc opacity & vscode background 84 | newColorKey = new ColorKey(colorKey[0], colorKey[1], colorKey[2], colorKey[3], colorKey[4]); 85 | break; 86 | default: 87 | throw new Exception("Invalid mapping format"); 88 | } 89 | 90 | values.Add(newColorKey); 91 | MappedVSTokens.Add(string.Format("{0}&{1}&{2}", newColorKey.CategoryName, newColorKey.KeyName, newColorKey.Aspect)); 92 | } 93 | 94 | ScopeMappings.Add(key, values.ToArray()); 95 | } 96 | 97 | CheckForMissingVSTokens(); 98 | 99 | return ScopeMappings; 100 | } 101 | 102 | private static void CheckForMissingVSTokens() 103 | { 104 | if (MappedVSTokens.Count > 0) 105 | { 106 | var text = File.ReadAllText("VSTokens.json"); 107 | var jobject = JArray.Parse(text); 108 | var availableTokens = jobject.ToObject>(); 109 | 110 | var missingVSTokens = new List(); 111 | 112 | foreach (var token in availableTokens) 113 | { 114 | if (!MappedVSTokens.Contains(token)) 115 | { 116 | missingVSTokens.Add(token); 117 | } 118 | } 119 | 120 | string json = JsonConvert.SerializeObject(missingVSTokens, Formatting.Indented); 121 | File.WriteAllText("MissingVSTokens.json", json); 122 | } 123 | } 124 | 125 | public static Dictionary CreateCategoryGuids() 126 | { 127 | var contents = System.IO.File.ReadAllText("CategoryGuid.json"); 128 | var file = JsonConvert.DeserializeObject(contents); 129 | 130 | foreach (var item in file) 131 | { 132 | CategoryGuids.Add(item.Key, item.Value.ToString()); 133 | } 134 | 135 | return CategoryGuids; 136 | } 137 | 138 | public static Dictionary CreateVSCTokenFallback() 139 | { 140 | var contents = System.IO.File.ReadAllText("VSCTokenFallback.json"); 141 | var file = JsonConvert.DeserializeObject(contents); 142 | 143 | foreach (var item in file) 144 | { 145 | VSCTokenFallback.Add(item.Key, item.Value.ToString()); 146 | } 147 | 148 | return VSCTokenFallback; 149 | } 150 | 151 | public static Dictionary CreateOverlayMapping() 152 | { 153 | var contents = System.IO.File.ReadAllText("OverlayMapping.json"); 154 | OverlayMapping = JsonConvert.DeserializeObject>(contents); 155 | return OverlayMapping; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeConverter 5 | { 6 | using System; 7 | using System.Diagnostics; 8 | using System.Globalization; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using Mono.Options; 13 | 14 | internal sealed class Program 15 | { 16 | private const string PathToVSThemeFolder = @"Common7\IDE\CommonExtensions\Platform"; 17 | private const string PathToVSExe = @"Common7\IDE\devenv.exe"; 18 | 19 | private static readonly string ProductVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; 20 | 21 | static int Main(string[] args) 22 | { 23 | try 24 | { 25 | string inputPath = null, outputPath = null, targetVS = null; 26 | bool showHelp = false; 27 | 28 | var options = new OptionSet() 29 | { 30 | {"i|input=", Resources.Input, i => inputPath = i }, 31 | {"o|output=", Resources.Output, o => outputPath = o }, 32 | {"t|targetVS=", Resources.TargetVS, t => targetVS = t }, 33 | {"h|help", Resources.Help, h => showHelp = h != null}, 34 | }; 35 | 36 | options.Parse(args); 37 | 38 | if (showHelp) 39 | { 40 | Console.WriteLine(string.Format(CultureInfo.CurrentUICulture, Resources.HelpHeader, ProductVersion)); 41 | options.WriteOptionDescriptions(Console.Out); 42 | return 0; 43 | } 44 | 45 | Converter.ValidateDataFiles((error) => Console.WriteLine(error)); 46 | 47 | if (inputPath == null || !IsValidPath(inputPath)) 48 | { 49 | throw new ApplicationException(String.Format(CultureInfo.CurrentUICulture, Resources.InputNotExistException, inputPath)); 50 | } 51 | 52 | if (outputPath == null) 53 | { 54 | outputPath = GetDirName(inputPath); 55 | } 56 | 57 | if (targetVS != null && !Directory.Exists(targetVS)) 58 | { 59 | throw new ApplicationException(String.Format(CultureInfo.CurrentUICulture, Resources.TargetVSNotExistException, targetVS)); 60 | } 61 | 62 | Convert(inputPath, outputPath, targetVS); 63 | 64 | return 0; 65 | } 66 | catch (Exception ex) 67 | { 68 | Console.WriteLine(); 69 | Console.WriteLine(ex); 70 | return -1; 71 | } 72 | } 73 | 74 | private static string GetDirName(string path) 75 | { 76 | return Directory.Exists(path) ? path : Path.GetDirectoryName(path); 77 | } 78 | 79 | private static bool IsValidPath(string path) 80 | { 81 | return Directory.Exists(path) || File.Exists(path); 82 | } 83 | 84 | private static void Convert(string sourcePath, string pkgdefOutputPath, string deployInstall) 85 | { 86 | var sourceFiles = Directory.Exists(sourcePath) 87 | ? Directory.EnumerateFiles(sourcePath, "*.json") 88 | : Enumerable.Repeat(sourcePath, 1); 89 | 90 | if (!sourceFiles.Any()) 91 | { 92 | throw new ApplicationException(Resources.NoJSONFoundException); 93 | } 94 | 95 | foreach (var sourceFile in sourceFiles) 96 | { 97 | Console.WriteLine($"Converting {sourceFile}"); 98 | Console.WriteLine(); 99 | 100 | string pkgdefFilePath = Converter.ConvertFile(sourceFile, pkgdefOutputPath); 101 | if (!string.IsNullOrEmpty(deployInstall)) 102 | { 103 | string deployFilePath = Path.Combine(deployInstall, PathToVSThemeFolder, Path.GetFileName(pkgdefFilePath)); 104 | File.Copy(pkgdefFilePath, deployFilePath, overwrite: true); 105 | } 106 | } 107 | 108 | if (!string.IsNullOrEmpty(deployInstall)) 109 | { 110 | LaunchVS(deployInstall); 111 | } 112 | } 113 | 114 | private static void LaunchVS(string deployInstall) 115 | { 116 | string vsPath = Path.Combine(deployInstall, PathToVSExe); 117 | if (!File.Exists(vsPath)) 118 | throw new ApplicationException(String.Format(CultureInfo.CurrentUICulture, Resources.TargetDevEnvNotExistException, vsPath)); 119 | 120 | Console.WriteLine(Resources.RunningUpdateConfiguration); 121 | Console.WriteLine(); 122 | 123 | var updateConfigProcess = Process.Start(vsPath, "/updateconfiguration"); 124 | updateConfigProcess.WaitForExit(); 125 | if (updateConfigProcess.ExitCode != 0) 126 | throw new ApplicationException(Resources.UpdateConfigurationException); 127 | 128 | // Launch Visual Studio. 129 | Console.WriteLine(Resources.LaunchingVS); 130 | Process.Start(vsPath); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ThemeConverter": { 4 | "commandName": "Project", 5 | "commandLineArgs": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ThemeConverter { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ThemeConverter.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Show help text.. 65 | /// 66 | internal static string Help { 67 | get { 68 | return ResourceManager.GetString("Help", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Theme Converter for Visual Studio v{0} 74 | ///A utility that converts VSCode theme json file(s) to VS pkgdef file(s). 75 | /// 76 | ///Arguments:. 77 | /// 78 | internal static string HelpHeader { 79 | get { 80 | return ResourceManager.GetString("HelpHeader", resourceCulture); 81 | } 82 | } 83 | 84 | /// 85 | /// Looks up a localized string similar to The source path of the theme json file or the folder that contains the theme files.. 86 | /// 87 | internal static string Input { 88 | get { 89 | return ResourceManager.GetString("Input", resourceCulture); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized string similar to Could not find input file or folder: "{0}".. 95 | /// 96 | internal static string InputNotExistException { 97 | get { 98 | return ResourceManager.GetString("InputNotExistException", resourceCulture); 99 | } 100 | } 101 | 102 | /// 103 | /// Looks up a localized string similar to Launching Visual Studio.. 104 | /// 105 | internal static string LaunchingVS { 106 | get { 107 | return ResourceManager.GetString("LaunchingVS", resourceCulture); 108 | } 109 | } 110 | 111 | /// 112 | /// Looks up a localized string similar to No JSON file was found under the given directory.. 113 | /// 114 | internal static string NoJSONFoundException { 115 | get { 116 | return ResourceManager.GetString("NoJSONFoundException", resourceCulture); 117 | } 118 | } 119 | 120 | /// 121 | /// Looks up a localized string similar to The output folder path of the converted pkgdef(s). When specified, the converter will save the converted pkgdef(s) to this path. If not specified, the output will be saved under the same directory of the input file/folder. OPTIONAL. 122 | /// 123 | internal static string Output { 124 | get { 125 | return ResourceManager.GetString("Output", resourceCulture); 126 | } 127 | } 128 | 129 | /// 130 | /// Looks up a localized string similar to Running UpdateConfiguration (this might take a while).. 131 | /// 132 | internal static string RunningUpdateConfiguration { 133 | get { 134 | return ResourceManager.GetString("RunningUpdateConfiguration", resourceCulture); 135 | } 136 | } 137 | 138 | /// 139 | /// Looks up a localized string similar to The devenv.exe file was not found at "{0}".. 140 | /// 141 | internal static string TargetDevEnvNotExistException { 142 | get { 143 | return ResourceManager.GetString("TargetDevEnvNotExistException", resourceCulture); 144 | } 145 | } 146 | 147 | /// 148 | /// Looks up a localized string similar to The installation path of the target VS. When specified, the converter will patch the converted pkgdef(s) to the target VS, run "updateConfiguration" and launch this VS. (e.g.: "C:\Program Files\Microsoft Visual Studio\2022\Preview") OPTIONAL. 149 | /// 150 | internal static string TargetVS { 151 | get { 152 | return ResourceManager.GetString("TargetVS", resourceCulture); 153 | } 154 | } 155 | 156 | /// 157 | /// Looks up a localized string similar to The target VS installation directory "{0}" does not exist.. 158 | /// 159 | internal static string TargetVSNotExistException { 160 | get { 161 | return ResourceManager.GetString("TargetVSNotExistException", resourceCulture); 162 | } 163 | } 164 | 165 | /// 166 | /// Looks up a localized string similar to Fatal error running "devenv.exe /updateconfiguration".. 167 | /// 168 | internal static string UpdateConfigurationException { 169 | get { 170 | return ResourceManager.GetString("UpdateConfigurationException", resourceCulture); 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Show help text. 122 | 123 | 124 | Theme Converter for Visual Studio v{0} 125 | A utility that converts VSCode theme json file(s) to VS pkgdef file(s). 126 | 127 | Arguments: 128 | 129 | 130 | The source path of the theme json file or the folder that contains the theme files. 131 | 132 | 133 | Could not find input file or folder: "{0}". 134 | 135 | 136 | Launching Visual Studio. 137 | 138 | 139 | No JSON file was found under the given directory. 140 | 141 | 142 | The output folder path of the converted pkgdef(s). When specified, the converter will save the converted pkgdef(s) to this path. If not specified, the output will be saved under the same directory of the input file/folder. OPTIONAL 143 | 144 | 145 | Running UpdateConfiguration (this might take a while). 146 | 147 | 148 | The devenv.exe file was not found at "{0}". 149 | 150 | 151 | The installation path of the target VS. When specified, the converter will patch the converted pkgdef(s) to the target VS, run "updateConfiguration" and launch this VS. (e.g.: "C:\Program Files\Microsoft Visual Studio\2022\Preview") OPTIONAL 152 | 153 | 154 | The target VS installation directory "{0}" does not exist. 155 | 156 | 157 | Fatal error running "devenv.exe /updateconfiguration". 158 | 159 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/ThemeConverter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | latest 7 | 0.1.0 8 | 8002 9 | 10 | 11 | 12 | True 13 | ..\FinalPublicKey.snk 14 | True 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | True 30 | True 31 | Resources.resx 32 | 33 | 34 | 35 | 36 | 37 | ResXFileCodeGenerator 38 | Resources.Designer.cs 39 | Always 40 | 41 | 42 | 43 | 44 | 45 | Always 46 | 47 | 48 | Always 49 | 50 | 51 | Always 52 | 53 | 54 | Always 55 | 56 | 57 | Always 58 | 59 | 60 | 61 | 62 | 63 | Microsoft400 64 | StrongName 65 | 66 | 67 | Microsoft400 68 | 69 | 70 | 3PartySHA2 71 | 72 | 73 | 3PartySHA2 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverter/VSCTokenFallback.json: -------------------------------------------------------------------------------- 1 | { 2 | "dropdown.foreground": "foreground", 3 | "editor.lineHighlightBackground": "editor.background", 4 | "editorBracketMatch.background": "editor.background", 5 | "entity.name.type.class.cs": "storage.type.cs", 6 | "entity.name.variable.enum-member.cs": "variable.other.object.property.cs", 7 | "entity.name.variable.field.cs": "editor.foreground", 8 | "inputOption.activeBackground": "input.background", 9 | "inputOption.activeBorder": "input.border", 10 | "inputOption.activeForeground": "input.foreground", 11 | "keyword.type": "storage.modifier.cs", 12 | "list.inactiveSelectionForeground": "foreground", 13 | "list.hoverForeground": "foreground", 14 | "menu.selectionBorder": "menu.selectionBackground", 15 | "menubar.selectionBorder": "menubar.selectionBackground", 16 | "minimap.background": "editor.background", 17 | "sideBar.foreground": "foreground", 18 | "sideBarSectionHeader.foreground": "foreground", 19 | "sideBarTitle.foreground": "sideBar.foreground", 20 | "tab.activeBorder": "tab.activeBackground", 21 | "tab.activeBorderTop": "tab.activeBorder", 22 | "tab.border": "tab.inactiveBackground", 23 | "tab.hoverBackground": "tab.inactiveBackground", 24 | "editor.selectionForeground": "editor.foreground", 25 | "editor.selectionHighlightBorder": "editor.background", 26 | "editor.wordHighlightBorder": "editor.background", 27 | "editor.wordHighlightStrongBorder": "editor.background", 28 | "tab.hoverBorder": "tab.Border", 29 | "tab.hoverForeground": "tab.inactiveForeground", 30 | "variable.other.object.property.cs": "entity.name.variable.property.cs", 31 | "terminal.background": "panel.background", 32 | "terminal.foreground": "foreground" 33 | } 34 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverterTests/ConversionTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeConverterTests 5 | { 6 | using System; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text.RegularExpressions; 11 | using FluentAssertions; 12 | using ThemeConverter; 13 | using Xunit; 14 | 15 | public class ConversionTest 16 | { 17 | private const string DarkThemeFallback = "{1ded0138-47ce-435e-84ef-9ec1f439b749}"; 18 | private const string LightThemeFallback = "{de3dbbcd-f642-433c-8353-8f1df4370aba}"; 19 | 20 | /// 21 | /// The minimum set of categories that should be present in the case of 22 | /// a complete theme successful conversion. 23 | /// 24 | private static readonly string[] ExpectedCategoryNames = 25 | { 26 | "Text Editor Language Service Items", 27 | "Roslyn Text Editor MEF Items", 28 | "Text Editor Text Marker Items", 29 | "Cider", 30 | "CommonControls", 31 | "CommonDocument", 32 | "Diagnostics", 33 | "Environment", 34 | "Header", 35 | "IntelliTrace", 36 | "ManifestDesigner", 37 | "NewProjectDialog", 38 | "NotificationBubble", 39 | "PackageManifestEditor", 40 | "ProjectDesigner", 41 | "SharePointTools", 42 | "ThemedDialog", 43 | "TreeView", 44 | "UserNotifications", 45 | "VisualStudioInstaller", 46 | "VSSearch", 47 | "Find", 48 | "Output Window", 49 | "StartPage", 50 | "ThemedUtilityDialog", 51 | "Text Editor Text Manager Items", 52 | "WebClient Diagnostic Tools", 53 | "UserInformation", 54 | "InfoBar", 55 | "ClientDiagnosticsMemory", 56 | "CodeSenseControls", 57 | "GraphDocumentColors", 58 | "GraphicsDesigners", 59 | "InformationBadge", 60 | "Promotion", 61 | "TaskRunnerExplorerControls", 62 | "TeamExplorer", 63 | "WelcomeExperience", 64 | "ClientDiagnosticsTimeline", 65 | "SearchControl", 66 | "ACDCOverview", 67 | "Editor Tooltip", 68 | "CodeSense", 69 | "Command Window", 70 | "Find Results", 71 | "Immediate Window", 72 | "Locals", 73 | "Package Manager Console", 74 | "Performance Tips", 75 | "Watch", 76 | "ApplicationInsights", 77 | "ClientDiagnosticsTools", 78 | "DetailsView", 79 | "DiagnosticsHub", 80 | "NavigateTo", 81 | "VersionControl", 82 | "WorkItemEditor", 83 | "ProgressBar", 84 | "UnthemedDialog", 85 | }; 86 | 87 | private static readonly Regex GuidRegex = new Regex("[({][a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}[})]", RegexOptions.Compiled); 88 | private static readonly Regex DataRegex = new Regex("^\"Data\"=hex:[a-fA-F0-9]+(,[a-fA-F0-9]+)*", RegexOptions.Compiled); 89 | 90 | private static readonly string ResultsFolderPath = Path.Combine(Directory.GetCurrentDirectory(), "TestResults", DateTime.Now.ToString("yyyy-MM-dd-HHmmss")); 91 | private static readonly string ThemesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestFiles"); 92 | 93 | [Fact] 94 | public void Incomplete_NoColors() 95 | { 96 | string pkgdefPath = ConvertTheme("Incomplete_NoColors.json"); 97 | File.Exists(pkgdefPath).Should().BeTrue(); 98 | 99 | string[] lines = File.ReadAllLines(pkgdefPath); 100 | ValidateGeneralThemeInformation(lines, "Incomplete_NoColors", LightThemeFallback); 101 | 102 | lines.Where(l => l.Contains("\"Data\"=hex:")).Should().BeEmpty(); 103 | } 104 | 105 | [Fact] 106 | public void Incomplete_MissingCriticalColors() 107 | { 108 | string pkgdefPath = ConvertTheme("Incomplete_MissingCriticalColors.json"); 109 | File.Exists(pkgdefPath).Should().BeTrue(); 110 | 111 | string[] lines = File.ReadAllLines(pkgdefPath); 112 | ValidateGeneralThemeInformation(lines, "Incomplete_MissingCriticalColors", LightThemeFallback); 113 | 114 | lines.Where(l => l.Contains("\"Data\"=hex:")).Count().Should().Be(1); 115 | } 116 | 117 | [Fact] 118 | public void Complete_Dark() 119 | { 120 | ConvertAndValidateCompleteTheme("Complete_Dark.json", DarkThemeFallback); 121 | } 122 | 123 | [Fact] 124 | public void Complete_Light() 125 | { 126 | ConvertAndValidateCompleteTheme("Complete_Light.json", LightThemeFallback); 127 | } 128 | 129 | private static void ConvertAndValidateCompleteTheme(string testFileName, string themeFallbackGuid) 130 | { 131 | string pkgdefPath = ConvertTheme(testFileName); 132 | File.Exists(pkgdefPath).Should().BeTrue(); 133 | 134 | string themeName = Path.GetFileNameWithoutExtension(testFileName); 135 | string[] lines = File.ReadAllLines(pkgdefPath); 136 | 137 | // We check for a limited set of things to be correct in order to keep the test low maintenance. 138 | // This unfortunately doesn't include verifying the actual colors in the pkgdef. 139 | string themeGuid = ValidateGeneralThemeInformation(lines, themeName, themeFallbackGuid); 140 | ValidateThemeCategories(lines, themeGuid, ExpectedCategoryNames); 141 | } 142 | 143 | private static string ValidateGeneralThemeInformation(string[] lines, string themeName, string themeFallbackGuid) 144 | { 145 | // Extract the theme guid from the first line so we can later search for lines that contain it 146 | Match m = GuidRegex.Match(lines[0]); 147 | m.Success.Should().BeTrue(); 148 | string themeGuid = m.Groups[0].Value; 149 | 150 | // Check the general theme information 151 | lines[0].Should().Be($"[$RootKey$\\Themes\\{themeGuid}]"); 152 | lines[1].Should().Be($"@=\"{themeName}\""); 153 | lines[2].Should().Be($"\"Name\"=\"{themeName}\""); 154 | lines[3].Should().Be($"\"FallbackId\"=\"{themeFallbackGuid}\""); 155 | 156 | return themeGuid; 157 | } 158 | 159 | private static void ValidateThemeCategories(string[] lines, string themeGuid, string[] categoryNames) 160 | { 161 | // Check that all expected categories are found 162 | foreach (string categoryName in categoryNames) 163 | { 164 | // Ensure category line is present 165 | string categoryLine = lines.SingleOrDefault(l => l == $"[$RootKey$\\Themes\\{themeGuid}\\{categoryName}]"); 166 | categoryLine.Should().NotBeNull($"{categoryName} not found"); 167 | int categoryLineIndex = Array.IndexOf(lines, categoryLine); 168 | 169 | // Next line is data line 170 | string dataLine = lines[categoryLineIndex + 1]; 171 | Match m = DataRegex.Match(dataLine); 172 | m.Success.Should().BeTrue(); 173 | } 174 | } 175 | 176 | private static string ConvertTheme(string testFileName) 177 | { 178 | return Converter.ConvertFile(Path.Combine(ThemesFolderPath, testFileName), ResultsFolderPath); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverterTests/DataValidationTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeConverterTests 5 | { 6 | using System.Collections.Generic; 7 | using FluentAssertions; 8 | using ThemeConverter; 9 | using Xunit; 10 | 11 | /// 12 | /// Tests to ensure that our internal data files don't have inconsistencies (duplicates, etc). 13 | /// 14 | public class DataValidationTest 15 | { 16 | [Fact] 17 | public void NoValidationError() 18 | { 19 | var errors = new List(); 20 | 21 | Converter.ValidateDataFiles((error) => errors.Add(error)); 22 | 23 | errors.Should().BeEmpty(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverterTests/TestFiles/Incomplete_MissingCriticalColors.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "vscode://schemas/color-theme", 3 | "type": "light", 4 | "colors": { 5 | "activityBar.border": "#219fd544", 6 | "activityBar.foreground": "#99d0f7" 7 | }, 8 | "tokenColors": [ 9 | { 10 | "scope": [ 11 | "meta.paragraph.markdown", 12 | "string.other.link.description.title.markdown" 13 | ], 14 | "settings": { 15 | "foreground": "#110000" 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverterTests/TestFiles/Incomplete_NoColors.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "vscode://schemas/color-theme", 3 | "type": "light", 4 | "colors": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeConverterTests/ThemeConverterTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | PreserveNewest 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | ############################### 3 | # Core EditorConfig Options # 4 | ############################### 5 | root = true 6 | # All files 7 | [*] 8 | indent_style = space 9 | 10 | # XML project files 11 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 12 | indent_size = 2 13 | 14 | # XML config files 15 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 16 | indent_size = 2 17 | 18 | # Code files 19 | [*.{cs,csx,vb,vbx}] 20 | indent_size = 4 21 | insert_final_newline = true 22 | charset = utf-8-bom 23 | ############################### 24 | # .NET Coding Conventions # 25 | ############################### 26 | [*.{cs,vb}] 27 | # Organize usings 28 | dotnet_sort_system_directives_first = true 29 | # this. preferences 30 | dotnet_style_qualification_for_field = false:silent 31 | dotnet_style_qualification_for_property = false:silent 32 | dotnet_style_qualification_for_method = false:silent 33 | dotnet_style_qualification_for_event = false:silent 34 | # Language keywords vs BCL types preferences 35 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 36 | dotnet_style_predefined_type_for_member_access = true:silent 37 | # Parentheses preferences 38 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 40 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 41 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 42 | # Modifier preferences 43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 44 | dotnet_style_readonly_field = true:suggestion 45 | # Expression-level preferences 46 | dotnet_style_object_initializer = true:suggestion 47 | dotnet_style_collection_initializer = true:suggestion 48 | dotnet_style_explicit_tuple_names = true:suggestion 49 | dotnet_style_null_propagation = true:suggestion 50 | dotnet_style_coalesce_expression = true:suggestion 51 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 52 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 54 | dotnet_style_prefer_auto_properties = true:silent 55 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 56 | dotnet_style_prefer_conditional_expression_over_return = true:silent 57 | ############################### 58 | # Naming Conventions # 59 | ############################### 60 | # Style Definitions 61 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 62 | # Use PascalCase for constant fields 63 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 64 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 65 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 66 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 67 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 68 | dotnet_naming_symbols.constant_fields.required_modifiers = const 69 | ############################### 70 | # C# Coding Conventions # 71 | ############################### 72 | [*.cs] 73 | # var preferences 74 | csharp_style_var_for_built_in_types = true:silent 75 | csharp_style_var_when_type_is_apparent = true:silent 76 | csharp_style_var_elsewhere = true:silent 77 | # Expression-bodied members 78 | csharp_style_expression_bodied_methods = false:silent 79 | csharp_style_expression_bodied_constructors = false:silent 80 | csharp_style_expression_bodied_operators = false:silent 81 | csharp_style_expression_bodied_properties = true:silent 82 | csharp_style_expression_bodied_indexers = true:silent 83 | csharp_style_expression_bodied_accessors = true:silent 84 | # Expression value 85 | csharp_style_unused_value_expression_statement_preference = false:silent 86 | # Pattern matching preferences 87 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 88 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 89 | # Null-checking preferences 90 | csharp_style_throw_expression = true:suggestion 91 | csharp_style_conditional_delegate_call = true:suggestion 92 | # Modifier preferences 93 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 94 | # Expression-level preferences 95 | csharp_prefer_braces = true:silent 96 | csharp_style_deconstructed_variable_declaration = true:suggestion 97 | csharp_prefer_simple_default_expression = true:suggestion 98 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 99 | csharp_style_inlined_variable_declaration = true:suggestion 100 | ############################### 101 | # C# Formatting Rules # 102 | ############################### 103 | # New line preferences 104 | csharp_new_line_before_open_brace = all 105 | csharp_new_line_before_else = true 106 | csharp_new_line_before_catch = true 107 | csharp_new_line_before_finally = true 108 | csharp_new_line_before_members_in_object_initializers = true 109 | csharp_new_line_before_members_in_anonymous_types = true 110 | csharp_new_line_between_query_expression_clauses = true 111 | # Indentation preferences 112 | csharp_indent_case_contents = true 113 | csharp_indent_switch_labels = true 114 | csharp_indent_labels = flush_left 115 | # Space preferences 116 | csharp_space_after_cast = false 117 | csharp_space_after_keywords_in_control_flow_statements = true 118 | csharp_space_between_method_call_parameter_list_parentheses = false 119 | csharp_space_between_method_declaration_parameter_list_parentheses = false 120 | csharp_space_between_parentheses = false 121 | csharp_space_before_colon_in_inheritance_clause = true 122 | csharp_space_after_colon_in_inheritance_clause = true 123 | csharp_space_around_binary_operators = before_and_after 124 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 125 | csharp_space_between_method_call_name_and_opening_parenthesis = false 126 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 127 | # Wrapping preferences 128 | csharp_preserve_single_line_statements = true 129 | csharp_preserve_single_line_blocks = true 130 | ############################### 131 | # VB Coding Conventions # 132 | ############################### 133 | 134 | # CA1816: Dispose methods should call SuppressFinalize 135 | dotnet_diagnostic.CA1816.severity = none 136 | 137 | [*.vb] 138 | # Modifier preferences 139 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 140 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/AllExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeTests 5 | { 6 | using System; 7 | using System.Diagnostics; 8 | using System.Drawing; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Threading; 13 | 14 | using FlaUI.Core; 15 | using FlaUI.Core.AutomationElements; 16 | using FlaUI.Core.Conditions; 17 | using FlaUI.Core.Input; 18 | using FlaUI.Core.Tools; 19 | using FlaUI.Core.WindowsAPI; 20 | using FlaUI.UIA3; 21 | 22 | static class AllExtensions 23 | { 24 | /// 25 | /// Closes the given window and invokes the "Don't save" button 26 | /// 27 | public static void CloseWindowWithDontSave(this Window window) 28 | { 29 | window.Close(); 30 | var modalWindows = Retry.WhileEmpty(() => window.ModalWindows).Result; 31 | var dontSaveButton = Retry.WhileNull(() => modalWindows[0].FindFirstDescendant(cf => cf.ByAutomationId("7")).AsButton()).Result; 32 | dontSaveButton.Invoke(); 33 | } 34 | 35 | public static void HoverButton(this Window getToCodeWindow, string elementName) 36 | { 37 | var newProjButton = Retry.WhileNull(() => getToCodeWindow.FindFirstDescendant((cf) => cf.ByName(elementName))).Result; 38 | newProjButton.MoveToElement(); 39 | } 40 | 41 | public static Button FocusButton(this Window window, string elementName) 42 | { 43 | var element = Retry.WhileNull(() => window.FindFirstDescendant(cf => cf.ByName(elementName))).Result; 44 | element.FocusNative(); 45 | return element.AsButton(); 46 | } 47 | 48 | public static AutomationElement ByName(this AutomationElement element, string name) 49 | { 50 | return Retry.WhileNull(() => element.FindFirstDescendant(cf => cf.ByName(name))).Result; 51 | } 52 | 53 | public static void HoldDown(this Window window, string elementName) 54 | { 55 | var element = window.ByName(elementName); 56 | Mouse.MoveTo(element.GetCenter()); 57 | Mouse.Down(); 58 | } 59 | 60 | public static Point GetCenter(this AutomationElement element) 61 | { 62 | var bounds = element.BoundingRectangle; 63 | var center = new Point(bounds.X + (bounds.Width / 2), bounds.Y + (bounds.Height / 2)); 64 | return center; 65 | } 66 | 67 | public static void MoveToElement(this AutomationElement element) 68 | { 69 | Mouse.MoveTo(element.GetCenter()); 70 | } 71 | 72 | public static void PressNewProjectDialogButton(this Window getToCodeWindow) 73 | { 74 | var newProjButton = Retry.WhileNull(() => getToCodeWindow.FindFirstDescendant((cf) => cf.ByName("Create a _new project"))).Result.AsButton(); 75 | newProjButton.Invoke(); 76 | } 77 | 78 | public static void PressOpenExistingProjectButton(this Window getToCodeWindow) 79 | { 80 | var openProjectButton = Retry.WhileNull(() => getToCodeWindow.FindFirstDescendant(cf => cf.ByName("Open a _project or solution"))).Result.AsButton(); 81 | openProjectButton.Invoke(); 82 | } 83 | 84 | public static void PressContinueWithoutCodeButton(this Window getToCodeWindow) 85 | { 86 | var button = Retry.WhileNull(() => getToCodeWindow.FindFirstByXPath("//Button[@Name='Continue without code']")).Result.AsButton(); 87 | button.Click(); 88 | } 89 | 90 | public static void CreateNewProject(this Window getToCodeWindow, string templateName = "Console Application", params string[] tags) 91 | { 92 | var templateList = Retry.WhileNull(() => getToCodeWindow.FindFirstDescendant("ListViewTemplates").AsListBox()).Result; 93 | // Wait for at least 1 template to appear... 94 | _ = Retry.WhileNull(() => templateList.FindFirstChild()).Result; 95 | // Check if template we are looking for appeared... 96 | var consoleItem = GetTemplate(templateList, templateName, tags); 97 | if (consoleItem == null) 98 | { 99 | Keyboard.TypeSimultaneously(VirtualKeyShort.ALT, VirtualKeyShort.KEY_S); 100 | Keyboard.Type(templateName); 101 | consoleItem = Retry.WhileNull(() => GetTemplate(templateList, templateName, tags)).Result; 102 | } 103 | consoleItem.DoubleClick(); 104 | var nextButton = Retry.WhileNull(() => getToCodeWindow.FindFirstDescendant("button_Next")).Result.AsButton(); 105 | nextButton.Invoke(); 106 | nextButton = Retry.WhileNull(() => getToCodeWindow.FindFirstDescendant("button_Next")).Result.AsButton(); 107 | _ = Retry.WhileFalse(() => nextButton.IsEnabled); 108 | nextButton.Invoke(); 109 | } 110 | 111 | public static void OpenProject(this Window openProjectDialog, string projectPath) 112 | { 113 | var filePathInput = Retry.WhileNull(() => 114 | openProjectDialog.FindFirstDescendant( 115 | new AndCondition( 116 | ConditionFactory.ByControlType(FlaUI.Core.Definitions.ControlType.Edit), 117 | ConditionFactory.ByText("File name:"))) 118 | .AsTextBox()) 119 | .Result; 120 | filePathInput.Text = projectPath; 121 | var okButton = Retry.WhileNull( 122 | () => openProjectDialog.FindFirstChild(ConditionFactory.ByName("Open")).AsButton()).Result; 123 | okButton.Invoke(); 124 | } 125 | 126 | private static AutomationElement GetTemplate(ListBox templateList, string templateName, string[] tags) 127 | { 128 | foreach (var child in templateList.FindAllChildren((cf) => cf.ByName(templateName))) 129 | { 130 | bool missingTag = false; 131 | foreach (var tag in tags) 132 | { 133 | if (child.FindFirstDescendant((c) => c.ByAutomationId("TextBlock_1018").And(c.ByName(tag))) == null) 134 | { 135 | missingTag = true; 136 | break; 137 | } 138 | } 139 | if (missingTag == false) 140 | { 141 | return child; 142 | } 143 | } 144 | return null; 145 | } 146 | 147 | public static void ClickOnText(this TextBox editor, string text) 148 | { 149 | var doc = editor.Patterns.Text.Pattern.DocumentRange; 150 | var textRange = doc.FindText(text, false, false); 151 | textRange.ScrollIntoView(false); 152 | Mouse.Click(textRange.GetBoundingRectangles()[0].Center()); 153 | } 154 | 155 | public static TreeItem OpenFile(this Tree solutionExplorer, params string[] children) 156 | { 157 | var file = SelectFile(solutionExplorer, children); 158 | Keyboard.Type(VirtualKeyShort.RETURN); 159 | return file; 160 | } 161 | 162 | public static TreeItem SelectFile(this Tree solutionExplorer, params string[] children) 163 | { 164 | var file = solutionExplorer.FindFirstChild().AsTreeItem().GetChildTreeItem(children); 165 | file.Patterns.ScrollItem.Pattern.ScrollIntoView(); 166 | file.Select(); 167 | return file; 168 | } 169 | 170 | public static void OpenSolution(this Application app, string solutionRelativeFilePath) 171 | { 172 | var mainWindow = app.GetMainVSWindow(); 173 | var testFilesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestFiles"); 174 | var solutionFilePath = Path.Combine(testFilesFolderPath, solutionRelativeFilePath); 175 | var fileMenuItem = mainWindow.FindFirstByXPath("//MenuItem[@Name='File']").AsMenuItem(); 176 | var openMenuItem = fileMenuItem.Items.Single(x => x.Name == "Open").AsMenuItem(); 177 | var projectSolutionMenuItem = openMenuItem.Items.Single(x => x.Name == "Project/Solution...").AsMenuItem(); 178 | _ = projectSolutionMenuItem.Invoke(); 179 | 180 | var openDialog = app.GetFileOpenDialog(); 181 | openDialog.OpenProject(solutionFilePath); 182 | 183 | // Maybe not the most correct, but works well enough for now 184 | app.WaitWhileBusy(TimeSpan.FromSeconds(5)); 185 | app.WaitUntilCanStartDebugging(); 186 | } 187 | 188 | public static void CloseSolution(this Application app) 189 | { 190 | var mainWindow = app.GetMainVSWindow(); 191 | var fileMenuItem = mainWindow.FindFirstByXPath("//MenuItem[@Name='File']").AsMenuItem(); 192 | var closeSolutionMenuItem = fileMenuItem.Items.Single(x => x.Name == "Close Solution").AsMenuItem(); 193 | _ = closeSolutionMenuItem.Invoke(); 194 | app.WaitWhileBusy(TimeSpan.FromSeconds(3)); 195 | 196 | var window = app.GetGetToCodeWindow(); 197 | Keyboard.Type(VirtualKeyShort.ESC); 198 | } 199 | 200 | public static bool CanStartDebugging(this Application app) 201 | { 202 | return app.GetMainVSWindow()?.FindFirstDescendant((c) => c.ByAutomationId("PART_FocusTarget").And(c.ByName("Debug Target")))?.IsEnabled ?? false; 203 | } 204 | 205 | public static void WaitUntilCanStartDebugging(this Application app) 206 | { 207 | Retry.WhileFalse(() => CanStartDebugging(app)); 208 | } 209 | 210 | public static void WaitUntilSolutionFullyLoaded(this TextBox editor) 211 | { 212 | // editor.Parent => WpfTextViewHost 213 | Retry.WhileTrue(() => (editor.Parent.FindFirstDescendant("ProjectsList")?.AsComboBox().SelectedItem?.Text ?? "Miscellaneous Files") == "Miscellaneous Files"); 214 | } 215 | 216 | public static bool IsDebugging(this Application app) 217 | { 218 | return app.GetMainVSWindow()?.FindFirstDescendant("PART_FocusTarget")?.IsEnabled ?? false; 219 | } 220 | 221 | public static void WaitUntilBreakpointHit(this TextBox editor) 222 | { 223 | // editor.Parent => WpfTextViewHost 224 | Retry.WhileNull(() => editor.Parent.FindFirstDescendant("WpfEditorUIGlyphMarginGrid")?.FindFirstDescendant(c => c.ByName("Current Statement"))); 225 | } 226 | 227 | public static void WaitUntilExited(this Application app) 228 | { 229 | // Make sure that by time we exit, devenv.exe also exited... 230 | // So PerfView can capture it all... 231 | while (!app.HasExited) 232 | { 233 | Thread.Sleep(100); 234 | } 235 | // And lets add another second just to be sure 236 | Thread.Sleep(1000); 237 | } 238 | 239 | public static Window GetGetToCodeWindow(this Application app) 240 | { 241 | return GetWindow(app, "WorkflowHostView"); 242 | } 243 | 244 | public static void OpenSolutionExplorer(this Application app) 245 | { 246 | var mainWindow = app.GetMainVSWindow(); 247 | var viewMenuItem = mainWindow.FindFirstByXPath("//MenuItem[@Name='View']").AsMenuItem(); 248 | var solutionExplorerMenuItem = viewMenuItem.Items.Single(x => x.Name == "Solution Explorer").AsMenuItem(); 249 | _ = solutionExplorerMenuItem.Invoke(); 250 | } 251 | 252 | public static Tree GetSolutionExplorer(this Application app) 253 | { 254 | return Retry.WhileNull(() => app.GetMainVSWindow().FindFirstDescendant("SolutionExplorer")).Result.AsTree(); 255 | } 256 | 257 | public static AutomationElement GetSolutionExplorerPane(this Application app) 258 | { 259 | return Retry.WhileNull(() => 260 | app.GetMainVSWindow().FindFirstDescendant( 261 | new AndCondition( 262 | ConditionFactory.ByControlType(FlaUI.Core.Definitions.ControlType.Pane), 263 | ConditionFactory.ByName("Solution Explorer")))).Result; 264 | } 265 | 266 | public static TextBox GetTextEditor(this Application app) 267 | { 268 | return Retry.WhileNull(() => app.GetMainVSWindow().FindFirstDescendant("WpfTextView")).Result.AsTextBox(); 269 | } 270 | 271 | public static TextBox GetCtrlQSearchBox(this Application app) 272 | { 273 | return Retry.WhileNull(() => app.GetMainVSWindow().FindFirstDescendant("SearchBox")).Result.AsTextBox(); 274 | } 275 | 276 | public static void CloseAllTabs(this Application app) 277 | { 278 | var mainWindow = app.GetMainVSWindow(); 279 | var windowMenu = mainWindow.FindFirstDescendant(cf => cf.ByName("Window").And(cf.ByControlType(FlaUI.Core.Definitions.ControlType.MenuItem))).AsMenuItem(); 280 | var closeAllTabsMenuItem = windowMenu.Items.Single(x => x.Name == "Close All Tabs").AsMenuItem(); 281 | _ = closeAllTabsMenuItem.Invoke(); 282 | } 283 | 284 | public static ITextRange FindText(this TextBox editor, string text) 285 | { 286 | return editor.Patterns.Text.Pattern.DocumentRange.FindText(text, false, false); 287 | } 288 | 289 | public static TreeItem GetChildTreeItem(this TreeItem rootItem, params string[] children) 290 | { 291 | if (children.Length == 0) 292 | return rootItem; 293 | rootItem.Expand(); 294 | return GetChildTreeItem(Retry.WhileNull(() => rootItem.Items.FirstOrDefault(i => i.Name == children[0])).Result, children.Skip(1).ToArray()); 295 | } 296 | 297 | private static Window GetWindow(Application app, string automationId, TimeSpan? timeout = null) 298 | { 299 | Window mainWindow; 300 | var sw = Stopwatch.StartNew(); 301 | while (true) 302 | { 303 | //TODO: Timeout, consider using Retry 304 | try 305 | { 306 | mainWindow = app.GetMainWindow(BaseTest.Automation); 307 | if (mainWindow.ControlType == FlaUI.Core.Definitions.ControlType.Window && mainWindow.AutomationId == automationId) 308 | { 309 | break; 310 | } 311 | 312 | if (mainWindow.FindFirstChild(automationId).AsWindow() is { } childWindow) 313 | { 314 | mainWindow = childWindow; 315 | break; 316 | } 317 | } 318 | catch 319 | { 320 | //Sometimes things go wrong here, probably when splash window closes 321 | } 322 | Thread.Sleep(10); 323 | if (timeout is TimeSpan { } timeOut) 324 | { 325 | if (sw.Elapsed > timeout) 326 | return null; 327 | } 328 | } 329 | 330 | return mainWindow; 331 | } 332 | 333 | public static Window GetMainVSWindow(this Application app, TimeSpan? timeout = null) 334 | { 335 | return GetWindow(app, "VisualStudioMainWindow", timeout); 336 | } 337 | 338 | public static Window GetOptionsDialogWindow(this Application app) 339 | => Retry.WhileNull(() => app.GetMainVSWindow().ModalWindows.Where(tlw => tlw.Name == "Options").Single()).Result.AsWindow(); 340 | 341 | public static Window GetPopupWindow(this Application app, TimeSpan? timeout = null) 342 | { 343 | return Retry.WhileNull(() => app.GetAllTopLevelWindows(BaseTest.Automation).FirstOrDefault((w) => w.ClassName == "Popup"), timeout).Result; 344 | } 345 | 346 | 347 | public static Window GetFileOpenDialog(this Application app) 348 | { 349 | return Retry.WhileNull(() => app.GetMainWindow(BaseTest.Automation) 350 | .AsWindow() 351 | .FindFirstChild(ConditionFactory.ByName("Open Project/Solution"))).Result.AsWindow(); 352 | } 353 | 354 | 355 | 356 | public static readonly ConditionFactory ConditionFactory = new(new UIA3PropertyLibrary()); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/BaseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | using FlaUI.Core.Tools; 7 | using FlaUI.UIA3; 8 | 9 | using Xunit; 10 | 11 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 12 | 13 | namespace ThemeTests 14 | { 15 | public class BaseTest 16 | { 17 | public static readonly UIA3Automation Automation = new() 18 | { 19 | TransactionTimeout = TimeSpan.FromMinutes(5), 20 | ConnectionTimeout = TimeSpan.FromMinutes(5) 21 | }; 22 | 23 | static BaseTest() 24 | { 25 | Retry.DefaultInterval = TimeSpan.FromMilliseconds(100); 26 | Retry.DefaultTimeout = TimeSpan.FromMinutes(5); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/BaseThemeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeTests 5 | { 6 | using FlaUI.Core; 7 | using FlaUI.Core.AutomationElements; 8 | 9 | using Xunit.Abstractions; 10 | 11 | public class BaseThemeTest : BaseTest 12 | { 13 | private readonly Logger logger; 14 | private readonly ThemeTestFixture fixture; 15 | 16 | protected Logger Logger => this.logger; 17 | protected Application App => this.fixture.App; 18 | 19 | public BaseThemeTest(ThemeTestFixture fixture, ITestOutputHelper outputHelper) 20 | { 21 | this.fixture = fixture; 22 | this.logger = new Logger(this.App.GetMainWindow(BaseTest.Automation), this.GetType().Name, outputHelper); 23 | } 24 | 25 | protected Logger.Scenario Scenario(string description, AutomationElement element = null) => this.logger.RunScenario(description, element); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/GetToCodeThemeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeTests 5 | { 6 | using FlaUI.Core.AutomationElements; 7 | using FlaUI.Core.Input; 8 | using FlaUI.Core.Tools; 9 | 10 | using System.Linq; 11 | using System.Threading; 12 | 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | 16 | public class GetToCodeThemeTest : BaseThemeTest, IClassFixture 17 | { 18 | public GetToCodeThemeTest(Fixture fixture, ITestOutputHelper outputHelper) : base(fixture, outputHelper) 19 | { 20 | } 21 | 22 | [Fact] 23 | public void StartWindow() 24 | { 25 | using (this.Logger.RunScenario()) 26 | { 27 | // capture the start window 28 | var window = this.App.GetGetToCodeWindow(); 29 | var mru = Retry.WhileNull(() => window.FindFirstDescendant("MRUItemsListBox")).Result.AsListBox(); 30 | 31 | using (var s = this.Scenario(nameof(StartWindow), window)) 32 | { 33 | _ = Retry.WhileFalse(() => mru.Items.Any()); 34 | window.HoverButton("_Clone a repository"); 35 | s.Snapshot("Clone.Hover"); 36 | 37 | var x = window.FindAllDescendants().Where(d => d.Properties.IsKeyboardFocusable).ToArray(); 38 | 39 | for (int i = 0; i < 6; i++) 40 | { 41 | Keyboard.Press(FlaUI.Core.WindowsAPI.VirtualKeyShort.TAB); 42 | Thread.Sleep(50); 43 | s.Snapshot("Focus"); 44 | } 45 | } 46 | } 47 | } 48 | 49 | public class Fixture : ThemeTestFixture 50 | { 51 | public Fixture(IMessageSink messageSink) : base(messageSink) 52 | { 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/Logger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeTests 5 | { 6 | using FlaUI.Core.AutomationElements; 7 | using FlaUI.Core.Capturing; 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Runtime.CompilerServices; 13 | using System.Runtime.InteropServices; 14 | 15 | using Xunit.Abstractions; 16 | 17 | public class Logger 18 | { 19 | private readonly Stack scenarios; 20 | private int count; 21 | private AutomationElement rootElement; 22 | private string outputPath; 23 | private readonly ITestOutputHelper outputHelper; 24 | private string namePrefix; 25 | private bool diagnostic = false; // Toggle this to true to get more verbose logging 26 | private static readonly string RootDirectoryForRun = Path.Combine(Directory.GetCurrentDirectory(), "TestResults", DateTime.Now.ToString("yyyy-dd-MM-HHmmss")); 27 | 28 | public AutomationElement RootElement { get => this.rootElement; set => this.rootElement = value; } 29 | 30 | public Logger(AutomationElement rootScope, string scopeName, ITestOutputHelper outputHelper) 31 | { 32 | this.scenarios = new Stack(); 33 | this.rootElement = rootScope; 34 | this.outputHelper = outputHelper; 35 | this.outputPath = RootDirectoryForRun; 36 | this.namePrefix = scopeName + "."; 37 | this.outputHelper.WriteLine($"INFO: Logging to {new Uri(outputPath, UriKind.Absolute).ToString()}"); 38 | if (!Directory.Exists(this.outputPath)) 39 | { 40 | Directory.CreateDirectory(this.outputPath); 41 | } 42 | } 43 | 44 | public Scenario RunScenario([CallerMemberName] string name = null, AutomationElement element = null, bool captureOnDispose = false) 45 | { 46 | this.namePrefix += $"{name}."; 47 | var scenario = new Scenario(this, name, element, captureOnDispose); 48 | this.scenarios.Push(scenario); 49 | return scenario; 50 | } 51 | 52 | public void WriteInfo(string error) => this.outputHelper.WriteLine($"INFO: {error}"); 53 | public void WriteError(string error) => this.outputHelper.WriteLine($"ERROR: {error}"); 54 | public void WriteDiagnostic(string error) 55 | { 56 | if (this.diagnostic) 57 | this.outputHelper.WriteLine($"DIAG: {error}"); 58 | } 59 | 60 | public class Scenario : IDisposable 61 | { 62 | private readonly bool captureOnDispose; 63 | 64 | public string Name { get; } 65 | public AutomationElement Element { get; set; } 66 | public Logger Scope { get; } 67 | 68 | public Scenario(Logger scope, string name, AutomationElement element = null, bool captureOnDispose = false) 69 | { 70 | this.Scope = scope; 71 | this.Name = name; 72 | this.Element = element; 73 | this.captureOnDispose = captureOnDispose; 74 | } 75 | 76 | public void Snapshot(string description, AutomationElement scopedElement = null) 77 | { 78 | this.DoCapture(description, scopedElement); 79 | } 80 | 81 | public void Dispose() 82 | { 83 | if (this.Scope.scenarios.Peek() != this) 84 | throw new Exception(); 85 | 86 | if (this.captureOnDispose) 87 | this.DoCapture("ScenarioEnd"); 88 | 89 | var disposedScenario = this.Scope.scenarios.Pop(); 90 | this.Scope.namePrefix = this.Scope.namePrefix.Substring(0, this.Scope.namePrefix.Length - (disposedScenario.Name.Length + 1)); 91 | } 92 | 93 | private void DoCapture(string description, AutomationElement scopedElement = null) 94 | { 95 | try 96 | { 97 | var filename = $"{this.Scope.namePrefix}{this.Scope.count++}.{description}.png"; 98 | var filepath = Path.Combine(this.Scope.outputPath, filename); 99 | var image = Capture.Element(scopedElement ?? this.Element ?? this.Scope.rootElement); 100 | this.Scope.WriteDiagnostic($"URI test: {new Uri(filepath, UriKind.Absolute).ToString()}"); 101 | image.ToFile(filepath); 102 | } 103 | catch (ExternalException e) 104 | { 105 | this.Scope.WriteError(e.ToString()); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/MainShellThemeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeTests 5 | { 6 | using FlaUI.Core.AutomationElements; 7 | using FlaUI.Core.Tools; 8 | using System.Linq; 9 | using System.Threading; 10 | 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | 14 | public class MainShellThemeTest : BaseThemeTest, IClassFixture 15 | { 16 | public MainShellThemeTest(Fixture fixture, ITestOutputHelper outputHelper) : base(fixture, outputHelper) 17 | { 18 | } 19 | 20 | [Fact] 21 | public void MainWindow() 22 | { 23 | // setup - dismiss start window 24 | _ = Retry.While(() => this.App.GetMainWindow(BaseTest.Automation).ModalWindows.Length, x => x > 0); 25 | Thread.Sleep(100); 26 | using (var s = this.Scenario(nameof(MainWindow))) 27 | { 28 | this.App.GetMainWindow(BaseTest.Automation).Click(); 29 | s.Snapshot("EmptyEnvironment"); 30 | } 31 | } 32 | 33 | [Fact] 34 | public void TopLevelMenus() 35 | { 36 | var mainWindow = this.App.GetMainWindow(BaseTest.Automation); 37 | var menuBar = mainWindow.FindFirstByXPath("//MenuBar[@AutomationId='MenuBar']").AsMenu(); 38 | using (var s = this.Scenario(nameof(TopLevelMenus), menuBar)) 39 | { 40 | s.Snapshot("MainMenu"); 41 | var items = menuBar.Items.Select(s => s.AsMenuItem()).ToArray(); 42 | 43 | // single item focused 44 | items[0].Focus(); 45 | s.Snapshot($"MenuFocused", items[0]); 46 | 47 | // each menu expanded 48 | foreach (var item in items) 49 | { 50 | _ = item.Expand(); 51 | // ensure the children items have rendered 52 | _ = Retry.WhileFalse(() => item.Items.All(y => !y.IsOffscreen)); 53 | s.Snapshot($"{item.Name}.Opened", mainWindow); 54 | _ = item.Collapse(); 55 | } 56 | } 57 | } 58 | 59 | [Fact] 60 | public void ErrorList() 61 | { 62 | var mainWindow = this.App.GetMainWindow(BaseTest.Automation); 63 | var viewMenuItem = mainWindow.FindFirstByXPath("//MenuItem[@Name='View']").AsMenuItem(); 64 | var errorListMenuItem = viewMenuItem.Items.Single(x => x.Name == "Error List").AsMenuItem(); 65 | _ = errorListMenuItem.Invoke(); 66 | 67 | var errorList = Retry.WhileNull(() => mainWindow.FindFirstByXPath("//Pane[contains(@Name, 'Error List')]")).Result; 68 | using (var scenario = this.Scenario(nameof(ErrorList), errorList)) 69 | { 70 | using (var s = this.Scenario("ScopeCombo", errorList)) 71 | { 72 | var scopeCombo = errorList.FindFirstByXPath("//ComboBox[@Name='Show items contained by']").AsComboBox(); 73 | s.Snapshot($"{nameof(scopeCombo)}.Default"); 74 | scopeCombo.MoveToElement(); 75 | s.Snapshot($"{nameof(scopeCombo)}.Hovered"); 76 | scopeCombo.Expand(); 77 | _ = Retry.WhileFalse(() => scopeCombo.Items.All(i => !i.IsOffscreen)); 78 | s.Snapshot($"{nameof(scopeCombo)}.Expanded"); 79 | scopeCombo.Items[1].Focus(); 80 | var r = Retry.WhileFalse(() => scopeCombo.Items[1].Properties.HasKeyboardFocus); 81 | this.Logger.WriteInfo($"Retries: {r.Iterations}"); 82 | s.Snapshot($"{nameof(scopeCombo)}.ItemFocus"); 83 | // reset 84 | _ = scopeCombo.Items[0].Select(); 85 | scopeCombo.Collapse(); 86 | } 87 | 88 | using (var s = this.Scenario("ToolbarButton", errorList)) 89 | { 90 | var errorButton = errorList.FindFirstByXPath("//Button[contains(@Name, 'Errors')]").AsToggleButton(); 91 | s.Snapshot($"{nameof(errorButton)}.ToggleOn"); 92 | errorButton.MoveToElement(); 93 | s.Snapshot($"{nameof(errorButton)}.Hovered"); 94 | // Seems there is a bug in UIA or maybe VS here - Toggle or Click doesn't update the UI, only 95 | // a full mouse move and click emulation does the trick 96 | errorButton.Click(moveMouse: true); 97 | s.Snapshot($"{nameof(errorButton)}.Clicked"); 98 | errorList.MoveToElement(); 99 | this.Logger.WriteInfo($"Toggle state: {errorButton.ToggleState}"); 100 | s.Snapshot($"{nameof(errorButton)}.ToggleOff"); 101 | errorButton.Toggle(); 102 | this.Logger.WriteInfo($"Toggle state: {errorButton.ToggleState}"); 103 | } 104 | } 105 | } 106 | 107 | [Fact] 108 | public void OutputWindow() 109 | { 110 | var mainWindow = this.App.GetMainWindow(BaseTest.Automation); 111 | try 112 | { 113 | // Open a solution so text appears in output window 114 | this.App.OpenSolution(@"CSharpApp\CSharpApp.sln"); 115 | 116 | var viewMenuItem = mainWindow.FindFirstByXPath("//MenuItem[@Name='View']").AsMenuItem(); 117 | var outputMenuItem = viewMenuItem.Items.Single(x => x.Name == "Output").AsMenuItem(); 118 | _ = outputMenuItem.Invoke(); 119 | 120 | var outputWindow = Retry.WhileNull(() => mainWindow.FindFirstByXPath("//Pane[contains(@Name, 'Output')]")).Result; 121 | using var scenario = this.Scenario(nameof(OutputWindow), outputWindow); 122 | scenario.Snapshot("OutputWindow"); 123 | } 124 | finally 125 | { 126 | this.App.CloseSolution(); 127 | } 128 | } 129 | 130 | [Fact] 131 | public void EditorLanguages() 132 | { 133 | var mainWindow = this.App.GetMainWindow(BaseTest.Automation); 134 | try 135 | { 136 | using var scenario = this.Scenario(nameof(EditorLanguages), mainWindow); 137 | 138 | this.App.OpenSolution(@"CSharpApp\CSharpApp.sln"); 139 | 140 | // Open every file located in the Languages folder 141 | this.App.OpenSolutionExplorer(); 142 | var solutionExplorer = this.App.GetSolutionExplorer(); 143 | var languagesTreeItem = solutionExplorer.SelectFile("CSharpApp", "Languages"); 144 | var fileNames = languagesTreeItem.FindAllChildren().Select(element => element.Name); 145 | 146 | foreach (var fileName in fileNames) 147 | { 148 | solutionExplorer.OpenFile("CSharpApp", "Languages", fileName); 149 | var editor = this.App.GetTextEditor(); 150 | scenario.Snapshot(fileName, editor); 151 | this.App.CloseAllTabs(); 152 | } 153 | } 154 | finally 155 | { 156 | this.App.CloseSolution(); 157 | } 158 | } 159 | 160 | [Fact] 161 | public void ToolsOptionsDialog() 162 | { 163 | { 164 | var mainWindow = this.App.GetMainVSWindow(); 165 | var toolsMenu = mainWindow.FindFirstDescendant(cf => cf.ByName("Tools").And(cf.ByControlType(FlaUI.Core.Definitions.ControlType.MenuItem))).AsMenuItem(); 166 | toolsMenu.Click(); 167 | var optionsMenu = toolsMenu.Items.Single(i => i.Name == "Options..."); 168 | _ = optionsMenu.Invoke(); 169 | var toolsOptions = this.App.GetOptionsDialogWindow(); 170 | using (var scenario = this.Scenario(nameof(ToolsOptionsDialog), toolsOptions)) 171 | { 172 | scenario.Snapshot("Tools Options"); 173 | toolsOptions.Close(); 174 | } 175 | } 176 | } 177 | 178 | public class Fixture : ThemeTestFixture 179 | { 180 | public Fixture(IMessageSink messageSink) : base(messageSink) 181 | { 182 | var window = this.App.GetGetToCodeWindow(); 183 | window.PressContinueWithoutCodeButton(); 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/ManualTestFiles/BasicTests.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This test plan is for verifying whether a theme converted by [Theme-Converter](https://github.com/microsoft/theme-converter/tree/main/ThemeConverter/ThemeConverter) will severely break the readability on common user workflows. 4 | For each step, observe if the UI is readable under default/selected/hovered situation, when the window has focus and doesn't have focus. 5 | 6 | # Prerequisites 7 | 8 | VS 2022 Preview 3 or later, with the theme to be tested installed. 9 | VS Code *(optional)* 10 | 11 | ### Note 12 | 13 | Scenarios marked with **VS Code compare** in the title should be compared with VS Code to see if there's any obvious color mismatch. 14 | 15 | # Scenarios 16 | 17 | ## Scenario 1: Create a new project from the Start Window 18 | 19 | 1. Open the Start Window: navigate to File -> Start Window; select the Start Window icon from the toolbar; 20 | 2. Select "Create a new project" 21 | 3. Create a new project using an available template 22 | 23 | ## Scenario 2: Editing a file (VS Code Compare) 24 | 25 | 1. Open different types of files ([sample files](https://github.com/kai-oswald/NightOwl-VS-Theme/tree/master/demo)) 26 | 27 | ## Scenario 3: Debugging (VS Code Compare) 28 | 29 | 1. Open the project created earlier and place a breakpoint 30 | 2. Start debugging 31 | 3. Open different debug windows 32 | 33 | ### VS Code instruction: 34 | 35 | 1. Open the project's folder (File > Open Folder...) 36 | 2. Place a breakpoint at the same location 37 | 3. Select Run > Start Debugging 38 | 39 | ## Scenario 4: Install extension from extension manager 40 | 41 | 1. Open Extensions > Manage Extensions 42 | 2. Open different pages and try to scroll/select items 43 | 44 | ## Scenario 5: Run/debug unit tests 45 | 46 | 1. Create a unit test project with some basic test methods 47 | 2. Open Test > Test Explorer 48 | 3. Run/Debug/Select tests. 49 | 50 | ## Scenario 6: Solution Explorer (VS Code Compare) 51 | 52 | 1. Open a project and open the solution explorer 53 | 2. Try selecting/right clicking 54 | 3. Do a search 55 | 56 | ### VS Code instruction: 57 | 58 | 1. The corresponding page is the File Explorer 59 | 60 | ![image](https://user-images.githubusercontent.com/14095891/129639337-f78e5b53-bcce-4ee5-a885-fa15cb390f19.png) 61 | 62 | 2. The search window will be invoked by 'Ctrl + T` 63 | 64 | ## Scenario 7: Version Control (VS Code Compare) 65 | 66 | 1. Clone a git repo 67 | 2. Open View > Git Changes 68 | 3. Edit some files and check the window 69 | 70 | ### VS Code instruction: 71 | 72 | 1. Corresponding page is the Source Control window: 73 | 74 | ![image](https://user-images.githubusercontent.com/14095891/129639468-eafe2687-1284-42cf-b3c7-0e4e0da52ec2.png) 75 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/README.md: -------------------------------------------------------------------------------- 1 | # ThemeTests README 2 | 3 | This project provides some basic infrastructure to make testing theme changes across UI surface area less tedious and more consistent. 4 | 5 | To run tests - build the project, then choose one or more tests or feature areas to capture UI for from Test Explorer. 6 | 7 | Each test will drive the UI and take screenshots of UI in various states. This uses UIA and keyboard/mouse emulation - so try not to multitask on the machine while the tests are running. 8 | 9 | When the tests are done, there will be a link to the output folder in the Test Explorer summary pane. You'll find a variety of screenshots for the scenarios you choose to run, and can look through these to make sure your changes look good or haven't regressed. -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/CSharpApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/CSharpApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31513.29 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpApp", "CSharpApp.csproj", "{6B319BA9-1350-4862-8E1B-95104F3FF54B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6B319BA9-1350-4862-8E1B-95104F3FF54B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6B319BA9-1350-4862-8E1B-95104F3FF54B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6B319BA9-1350-4862-8E1B-95104F3FF54B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6B319BA9-1350-4862-8E1B-95104F3FF54B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {ADB6A56B-9B26-4F6B-82FB-5E77D01EC728} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/cplusplus.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Calculator.h" 3 | 4 | 5 | /// 6 | /// Method description. 7 | /// 8 | /// First parameter. 9 | /// Second parameter. 10 | /// The sum. 11 | int Calculator::Add(int a, int b) 12 | { 13 | // TODO: Add your implementation code here. 14 | "text"; 15 | return a + b; 16 | } 17 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/cplusplus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class Calculator 3 | { 4 | public: 5 | int Add(int a, int b); 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/csharp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | class MyClass 4 | { 5 | private readonly int hundred = 100; 6 | private bool enabled = true; 7 | public static string Text => "string"; 8 | 9 | /// 10 | /// Documentation comment for member method. 11 | /// 12 | /// Some output value. 13 | public void MyMethod(out int result) 14 | { 15 | try 16 | { 17 | foreach (var name in new string[] { "name1", "name2" }) 18 | { 19 | Console.WriteLine(name); 20 | } 21 | } 22 | catch (Exception ex) 23 | { 24 | Console.WriteLine(ex.Message); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/css.css: -------------------------------------------------------------------------------- 1 | li a, h1 { 2 | font-family: 'Courier New', Courier, Serif; 3 | } 4 | 5 | a:hover { 6 | background-color: green; 7 | } 8 | 9 | nav { 10 | background-color: aliceblue; 11 | } 12 | 13 | .jumbotron { 14 | padding: 2rem 1rem; 15 | margin-bottom: 2rem; 16 | background-color: #e9ecef; 17 | border-radius: .3rem; 18 | } 19 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The Title 6 | 7 | 8 |

The Header

9 |

The paragraph text goes here.

10 | 14 | 15 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/javascript.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | /* 5 | Setup: Enter your storage account name and shared key in main() 6 | */ 7 | 8 | const { BlobServiceClient } = require("@azure/storage-blob"); 9 | 10 | // Load the .env file if it exists 11 | require("dotenv").config(); 12 | 13 | async function main() { 14 | // Create Blob Service Client from Account connection string or SAS connection string 15 | // Account connection string example - `DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=accountKey;EndpointSuffix=core.windows.net` 16 | // SAS connection string example - `BlobEndpoint=https://myaccount.blob.core.windows.net/;QueueEndpoint=https://myaccount.queue.core.windows.net/;FileEndpoint=https://myaccount.file.core.windows.net/;TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sasString` 17 | const STORAGE_CONNECTION_STRING = process.env.STORAGE_CONNECTION_STRING || ""; 18 | // Note - Account connection string can only be used in node. 19 | const blobServiceClient = BlobServiceClient.fromConnectionString(STORAGE_CONNECTION_STRING); 20 | 21 | let index = 1; 22 | for await (const container of blobServiceClient.listContainers()) { 23 | console.log(`Container ${index++}: ${container.name}`); 24 | } 25 | 26 | // Create a container 27 | const containerName = `newcontainer${new Date().getTime()}`; 28 | const containerClient = blobServiceClient.getContainerClient(containerName); 29 | 30 | const createContainerResponse = await containerClient.create(); 31 | console.log(`Create container ${containerName} successfully`, createContainerResponse.requestId); 32 | 33 | // Delete container 34 | await containerClient.delete(); 35 | 36 | console.log("deleted container"); 37 | } 38 | 39 | main().catch((err) => { 40 | console.error("Error running sample:", err.message); 41 | }); 42 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "vscode://schemas/testing", 3 | "object": { 4 | "color": "#ffffff1a", 5 | "text": "hello world", 6 | "boolean": true, 7 | "array": [ 100, 200, 300 ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/markdown.md: -------------------------------------------------------------------------------- 1 | # Header 1 2 | ## Header 2 3 | ### Header 3 4 | 5 | Another Header 6 | -------------- 7 | 8 | Yet Another Header 9 | ================== 10 | 11 | - List 1 12 | - List 2 13 | 14 | 1. Numbered list 1 15 | 2. Numbered list 2 16 | 17 | ``` 18 | code block no language 19 | ``` 20 | 21 | javascript code block: 22 | 23 | ```javascript 24 | const constant = 100; 25 | var message = "hello world " + constant.toString(); 26 | ``` 27 | 28 | Inline `code block` here 29 | 30 | Some **bold** and *italic* text 31 | 32 | [Microsoft](https:///www.microsoft.com) 33 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/visualbasic.vb: -------------------------------------------------------------------------------- 1 | Public Class XmlSamples 2 | 3 | Public Sub Main() 4 | ' Initialize the objects. 5 | 6 | Dim phoneNumbers2 As Phone() = { 7 | New Phone("home", "206-555-0144"), 8 | New Phone("work", "425-555-0145")} 9 | 10 | ' Convert the data contained in phoneNumbers2 to XML. 11 | 12 | Dim contact2 = 13 | 14 | Patrick Hines 15 | <%= From p In phoneNumbers2 16 | Select ><%= p.Number %> 17 | %> 18 | 19 | 20 | Console.WriteLine(contact2) 21 | End Sub 22 | 23 | End Class 24 | 25 | Class Phone 26 | Public Type As String 27 | Public Number As String 28 | Public Sub New(ByVal t As String, ByVal n As String) 29 | Type = t 30 | Number = n 31 | End Sub 32 | End Class 33 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Languages/xml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/TestFiles/CSharpApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CSharpApp 4 | { 5 | class Program 6 | { 7 | /// 8 | /// Main entry point. 9 | /// 10 | /// 11 | static void Main(string[] args) 12 | { 13 | Console.WriteLine("Hello World!"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/ThemeTestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace ThemeTests 5 | { 6 | using FlaUI.Core; 7 | using FlaUI.Core.Input; 8 | 9 | using Microsoft.VisualStudio.Setup.Configuration; 10 | 11 | using System; 12 | using System.IO; 13 | 14 | using Xunit.Abstractions; 15 | using Xunit.Sdk; 16 | 17 | public abstract class ThemeTestFixture : IDisposable 18 | { 19 | private readonly double storedMovePixelsPerMillisecond; 20 | private readonly Application app; 21 | 22 | public Application App => this.app; 23 | 24 | public ThemeTestFixture(IMessageSink messageSink) 25 | { 26 | string devenvPath = GetPathToTargetVisualStudioInstall(messageSink); 27 | this.app = Application.Launch(devenvPath); 28 | this.storedMovePixelsPerMillisecond = Mouse.MovePixelsPerMillisecond; 29 | Mouse.MovePixelsPerMillisecond = 10; 30 | } 31 | 32 | public static string GetPathToTargetVisualStudioInstall(IMessageSink messageSink) 33 | { 34 | string vsInstallDir = Environment.GetEnvironmentVariable("VSINSTALLDIR"); 35 | string reason = string.Empty; 36 | 37 | if (!string.IsNullOrEmpty(vsInstallDir) && Directory.Exists(vsInstallDir)) 38 | { 39 | reason = "VSINSTALLDIR environment variable."; 40 | } 41 | else 42 | { 43 | var setupConfig = new SetupConfiguration(); 44 | var setupInstances = setupConfig.EnumAllInstances(); 45 | var instances = new ISetupInstance[1]; 46 | 47 | setupInstances.Next(instances.Length, instances, out int fetched); 48 | 49 | if (fetched != 1) 50 | { 51 | throw new Exception("Could not find a VS install to target"); 52 | } 53 | 54 | vsInstallDir = instances[0].GetInstallationPath(); 55 | reason = $"instance ID {instances[0].GetInstanceId()} being first in SetupConfiguration."; 56 | if (!Directory.Exists(vsInstallDir)) 57 | { 58 | throw new Exception($"Could not find devenv.exe at {vsInstallDir}"); 59 | } 60 | } 61 | 62 | var devenvPath = Path.Combine(vsInstallDir, @"Common7\IDE\devenv.exe"); 63 | messageSink.OnMessage(new DiagnosticMessage($"Targeting {devenvPath} by way of {reason}")); 64 | return devenvPath; 65 | } 66 | 67 | public void Dispose() 68 | { 69 | _ = this.app?.Close(); 70 | this.app?.Dispose(); 71 | Mouse.MovePixelsPerMillisecond = this.storedMovePixelsPerMillisecond; 72 | } 73 | 74 | protected virtual void DisposeInternal() 75 | { 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/ThemeTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0-windows 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | $([MSBuild]::EnsureTrailingSlash(%(LinkBase))) 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ThemeConverter/ThemeTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "diagnosticMessages": true // set to true to see test diagnostics in the Test pane in output window 4 | } -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(date:yy)$(DayOfYear)$(rev:.r) 2 | trigger: 3 | branches: 4 | include: 5 | - main 6 | pr: 7 | branches: 8 | include: 9 | - main 10 | drafts: false 11 | variables: 12 | TeamName: VS Core - IDE Experience 13 | SolutionFile: ThemeConverter/ThemeConverter.sln 14 | Platform: Any CPU 15 | BuildConfiguration: Release 16 | ProductBinariesFolder: '$(System.DefaultWorkingDirectory)/ThemeConverter/ThemeConverter/bin/$(BuildConfiguration)/net6.0' 17 | VersionMajor: 0 18 | VersionMinor: 1 19 | AssemblyVersion: $(VersionMajor).$(VersionMinor).0.0 20 | ProductVersion: $(VersionMajor).$(VersionMinor).$(Build.BuildNumber) 21 | SignType: Test 22 | CodeQL.Enabled: true 23 | Codeql.TSAEnabled: false 24 | Codeql.TSAOptionsPath: $(Build.SourcesDirectory)/.config/tsaoptions.json 25 | resources: 26 | repositories: 27 | - repository: MicroBuildTemplate 28 | type: git 29 | name: 1ESPipelineTemplates/MicroBuildTemplate 30 | ref: refs/tags/release 31 | extends: 32 | template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate 33 | parameters: 34 | sdl: 35 | policheck: 36 | enabled: true 37 | tsa: 38 | enabled: false 39 | pool: 40 | name: VSEngSS-MicroBuild2022-1ES 41 | demands: 42 | - msbuild 43 | - VisualStudio_17.0 44 | customBuildTags: 45 | - ES365AIMigrationTooling 46 | stages: 47 | - stage: Build 48 | jobs: 49 | - job: Build_And_Compliance 50 | displayName: Build and Compliance 51 | templateContext: 52 | mb: 53 | signing: 54 | enabled: true 55 | signType: $(SignType) 56 | feedSource: 'https://pkgs.dev.azure.com/devdiv/_packaging/MicroBuildToolset/nuget/v3/index.json' 57 | sbom: 58 | enabled: true 59 | feedSource: 'https://pkgs.dev.azure.com/devdiv/_packaging/MicroBuildToolset/nuget/v3/index.json' 60 | outputs: 61 | - output: pipelineArtifact 62 | displayName: 'Publish Staging Directory' 63 | targetPath: $(Build.StagingDirectory) 64 | steps: 65 | - checkout: self 66 | clean: true 67 | - task: NuGetCommand@2 68 | displayName: Restore NuGet Packages 69 | inputs: 70 | command: 'restore' 71 | restoreSolution: $(SolutionFile) 72 | - task: MSBuild@1 73 | displayName: Build Product 74 | inputs: 75 | solution: $(SolutionFile) 76 | platform: $(Platform) 77 | configuration: $(BuildConfiguration) 78 | msbuildArguments: /Property:Version=$(ProductVersion) /Property:FileVersion=$(ProductVersion) /Property:AssemblyVersion=$(AssemblyVersion) /Property:SignType=$(SignType) 79 | continueOnError: false 80 | - task: DotNetCoreCLI@2 81 | displayName: Run Tests 82 | inputs: 83 | command: 'test' 84 | projects: '**/ThemeConverterTests.csproj' 85 | arguments: '--configuration Release' 86 | - task: ArchiveFiles@2 87 | displayName: Archive Binaries 88 | inputs: 89 | rootFolderOrFile: '$(ProductBinariesFolder)' 90 | includeRootFolder: true 91 | archiveType: 'zip' 92 | archiveFile: '$(Build.StagingDirectory)/ThemeConverter-$(VersionMajor).$(VersionMinor).$(Build.BuildNumber).zip' 93 | replaceExistingArchive: true 94 | - task: ManifestGeneratorTask@0 95 | displayName: 'Generation Task' 96 | inputs: 97 | BuildDropPath: '$(Build.StagingDirectory)' 98 | - task: TSAUpload@2 99 | displayName: 'TSA upload to Codebase (Theme Converter for VS)' 100 | inputs: 101 | GdnPublishTsaOnboard: True 102 | GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)/.config/tsaoptions.json' 103 | condition: false 104 | --------------------------------------------------------------------------------