├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── build.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DemoSnippets.ItemTemplates ├── CSharp.vstemplate ├── DemoSnippets.ItemTemplates.csproj ├── FSharp.vstemplate ├── General.vstemplate ├── Javascript.vstemplate ├── Properties │ └── AssemblyInfo.cs ├── Test.vstemplate ├── VC.vstemplate ├── VisualBasic.vstemplate ├── Web.vstemplate ├── icon.png └── instructions.demosnippets ├── DemoSnippets.Tests ├── AssertExtensions.cs ├── DemoSnippets.Tests.csproj ├── Examples │ ├── example.demosnippets.cpp │ ├── example.demosnippets.cs │ ├── example.demosnippets.css │ ├── example.demosnippets.html │ ├── example.demosnippets.js │ └── example.demosnippets.vb ├── Properties │ └── AssemblyInfo.cs ├── StandardParserTests.cs ├── SubExtParserTestsCPP.cs ├── SubExtParserTestsCSS.cs ├── SubExtParserTestsCSharp.cs ├── SubExtParserTestsHtml.cs ├── SubExtParserTestsJS.cs ├── SubExtParserTestsVB.cs └── TestsBase.cs ├── DemoSnippets.sln ├── DemoSnippets ├── AnalyticsConfig.cs ├── Classifier │ ├── DemoSnippets.cs │ ├── DemoSnippetsClassificationTypes.cs │ ├── DemoSnippetsClassifier.cs │ ├── DemoSnippetsClassifierProvider.cs │ ├── DemoSnippetsLabelFormatDefinition.cs │ └── DemoSnippetsTabFormatDefinition.cs ├── Commands │ ├── AddAllDemoSnippets.cs │ ├── AddToToolbox.cs │ ├── BaseCommand.cs │ ├── RefreshThisFileInToolbox.cs │ ├── RemoveAllDemoSnippets.cs │ └── RemoveEmptyTabs.cs ├── DemoSnippetRunningDocTableEvents.cs ├── DemoSnippets.csproj ├── DemoSnippets.ruleset ├── DemoSnippetsLineType.cs ├── DemoSnippetsLineTypeIdentifier.cs ├── DemoSnippetsPackage.cs ├── DemoSnippetsPackage.vsct ├── DemoSnippetsParser.cs ├── Icons │ ├── Monikers.imagemanifest │ └── demos.png ├── LICENSE.txt ├── OptionPageGrid.cs ├── Outlining │ ├── OutliningTagger.cs │ └── OutliningTaggerProvider.cs ├── OutputPane.cs ├── Properties │ └── AssemblyInfo.cs ├── Resources │ ├── AddToToolboxPackage.ico │ └── Icon.png ├── SponsorDetector.cs ├── SponsorRequestHelper.cs ├── StyleCop.json ├── TaskExtensions.cs ├── ToolboxEntry.cs ├── ToolboxInteractionLogic.cs ├── VSPackage.resx ├── filestosign.txt ├── icons.pkgdef ├── signvsix.targets ├── source.extension.cs └── source.extension.vsixmanifest ├── Directory.Build.props ├── Formatting.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── art ├── sample-html.png ├── sample-tab.png └── screenshot.png └── nuget.config /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # SA1600: Elements should be documented 4 | dotnet_diagnostic.SA1600.severity = none 5 | 6 | # SA1618: Generic type parameters should be documented 7 | dotnet_diagnostic.SA1618.severity = none 8 | 9 | # SA1604: Element documentation should have summary 10 | dotnet_diagnostic.SA1604.severity = none 11 | 12 | # SA1611: Element parameters should be documented 13 | dotnet_diagnostic.SA1611.severity = none 14 | 15 | # SA1612: Element parameter documentation should match element parameters 16 | dotnet_diagnostic.SA1612.severity = none 17 | 18 | # SA0001: XML comment analysis is disabled due to project configuration 19 | dotnet_diagnostic.SA0001.severity = none 20 | 21 | # SA1602: Enumeration items should be documented 22 | dotnet_diagnostic.SA1602.severity = none 23 | 24 | # Default severity for analyzer diagnostics with category 'StyleCop.CSharp.DocumentationRules' 25 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none 26 | csharp_indent_labels = one_less_than_current 27 | csharp_using_directive_placement = outside_namespace:silent 28 | csharp_prefer_simple_using_statement = true:suggestion 29 | csharp_prefer_braces = true:silent 30 | csharp_style_namespace_declarations = block_scoped:silent 31 | csharp_style_prefer_method_group_conversion = true:silent 32 | csharp_style_prefer_top_level_statements = true:silent 33 | csharp_style_expression_bodied_methods = false:silent 34 | csharp_style_expression_bodied_constructors = false:silent 35 | csharp_style_expression_bodied_operators = false:silent 36 | csharp_style_expression_bodied_properties = true:silent 37 | csharp_style_expression_bodied_indexers = true:silent 38 | csharp_style_expression_bodied_accessors = true:silent 39 | csharp_style_expression_bodied_lambdas = true:silent 40 | csharp_style_expression_bodied_local_functions = false:silent 41 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 42 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 43 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 44 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent 45 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent 46 | csharp_style_prefer_extended_property_pattern = true:suggestion 47 | csharp_style_prefer_primary_constructors = true:suggestion 48 | csharp_prefer_system_threading_lock = true:suggestion 49 | 50 | [*.{cs,vb}] 51 | #### Naming styles #### 52 | 53 | # Naming rules 54 | 55 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 56 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 57 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 58 | 59 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 60 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 61 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 62 | 63 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 64 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 65 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 66 | 67 | # Symbol specifications 68 | 69 | dotnet_naming_symbols.interface.applicable_kinds = interface 70 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 71 | dotnet_naming_symbols.interface.required_modifiers = 72 | 73 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 74 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 75 | dotnet_naming_symbols.types.required_modifiers = 76 | 77 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 78 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 79 | dotnet_naming_symbols.non_field_members.required_modifiers = 80 | 81 | # Naming styles 82 | 83 | dotnet_naming_style.begins_with_i.required_prefix = I 84 | dotnet_naming_style.begins_with_i.required_suffix = 85 | dotnet_naming_style.begins_with_i.word_separator = 86 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 87 | 88 | dotnet_naming_style.pascal_case.required_prefix = 89 | dotnet_naming_style.pascal_case.required_suffix = 90 | dotnet_naming_style.pascal_case.word_separator = 91 | dotnet_naming_style.pascal_case.capitalization = pascal_case 92 | 93 | dotnet_naming_style.pascal_case.required_prefix = 94 | dotnet_naming_style.pascal_case.required_suffix = 95 | dotnet_naming_style.pascal_case.word_separator = 96 | dotnet_naming_style.pascal_case.capitalization = pascal_case 97 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 98 | tab_width = 4 99 | indent_size = 4 100 | end_of_line = crlf 101 | dotnet_style_coalesce_expression = true:suggestion 102 | dotnet_style_null_propagation = true:suggestion 103 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 104 | dotnet_style_prefer_auto_properties = true:silent 105 | dotnet_style_object_initializer = true:suggestion 106 | dotnet_style_collection_initializer = true:suggestion 107 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 108 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 109 | dotnet_style_prefer_conditional_expression_over_return = true:silent 110 | dotnet_style_explicit_tuple_names = true:suggestion 111 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 112 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 113 | dotnet_style_prefer_compound_assignment = true:suggestion 114 | dotnet_style_prefer_simplified_interpolation = true:suggestion 115 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 116 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 117 | insert_final_newline = true 118 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mrlacey 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | name: "Build" 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths-ignore: 8 | - '*.md' 9 | pull_request: 10 | branches: [main] 11 | paths-ignore: 12 | - '*.md' 13 | 14 | jobs: 15 | build: 16 | outputs: 17 | version: ${{ steps.vsix_version.outputs.version-number }} 18 | name: Build 19 | runs-on: windows-2022 20 | env: 21 | Configuration: Debug 22 | DeployExtension: False 23 | VsixManifestPath: DemoSnippets\source.extension.vsixmanifest 24 | VsixManifestSourcePath: DemoSnippets\source.extension.cs 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Setup .NET build dependencies 30 | uses: timheuer/bootstrap-dotnet@v2 31 | with: 32 | nuget: 'false' 33 | sdk: 'false' 34 | msbuild: 'true' 35 | 36 | - name: Increment VSIX version 37 | id: vsix_version 38 | uses: timheuer/vsix-version-stamp@v2 39 | with: 40 | manifest-file: ${{ env.VsixManifestPath }} 41 | vsix-token-source-file: ${{ env.VsixManifestSourcePath }} 42 | 43 | - name: Build 44 | run: msbuild /v:m -restore /p:OutDir=\_built ./DemoSnippets.sln 45 | 46 | - name: Upload artifact 47 | uses: actions/upload-artifact@v4 48 | with: 49 | name: ${{ github.event.repository.name }}.vsix 50 | path: /_built/**/*.vsix 51 | 52 | - name: Run Tests 53 | # See https://github.com/microsoft/vstest-action/issues/31 54 | # uses: microsoft/vstest-action@v1.0.0 55 | uses: josepho0918/vstest-action@main 56 | with: 57 | searchFolder: /_built/ 58 | testAssembly: '**/**/*Tests.dll' 59 | 60 | - name: Publish Test Results 61 | uses: EnricoMi/publish-unit-test-result-action/windows@v2 62 | id: test-results 63 | with: 64 | files: testresults\**\*.trx 65 | 66 | - name: Set badge color 67 | shell: bash 68 | run: | 69 | case ${{ fromJSON( steps.test-results.outputs.json ).conclusion }} in 70 | success) 71 | echo "BADGE_COLOR=31c653" >> $GITHUB_ENV 72 | ;; 73 | failure) 74 | echo "BADGE_COLOR=800000" >> $GITHUB_ENV 75 | ;; 76 | neutral) 77 | echo "BADGE_COLOR=696969" >> $GITHUB_ENV 78 | ;; 79 | esac 80 | 81 | - name: Create badge 82 | uses: emibcn/badge-action@808173dd03e2f30c980d03ee49e181626088eee8 83 | with: 84 | label: Tests 85 | status: '${{ fromJSON( steps.test-results.outputs.json ).formatted.stats.tests }} tests: ${{ fromJSON( steps.test-results.outputs.json ).conclusion }}' 86 | color: ${{ env.BADGE_COLOR }} 87 | path: DemoSnippets.badge.svg 88 | 89 | - name: Upload badge to Gist 90 | # Upload only for main branch 91 | if: > 92 | github.event_name == 'workflow_run' && github.event.workflow_run.head_branch == 'main' || 93 | github.event_name != 'workflow_run' && github.ref == 'refs/heads/main' 94 | uses: jpontdia/append-gist-action@master 95 | with: 96 | token: ${{ secrets.GIST_TOKEN }} 97 | gistURL: https://gist.githubusercontent.com/mrlacey/c586ff0f495b4a8dd76ab0dbdf9c89e0 98 | file: DemoSnippets.badge.svg 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | 3 | # User files 4 | *.suo 5 | *.user 6 | *.sln.docstates 7 | .vs/ 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Rr]elease/ 12 | x64/ 13 | [Bb]in/ 14 | [Oo]bj/ 15 | 16 | # MSTest test Results 17 | [Tt]est[Rr]esult*/ 18 | [Bb]uild[Ll]og.* 19 | 20 | # NCrunch 21 | *.ncrunchsolution 22 | *.ncrunchproject 23 | _NCrunch_WebCompiler -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Road map 2 | 3 | - [ ] [Open to suggestions...](https://github.com/mrlacey/DemoSnippets/issues/new) 4 | 5 | Features that have a checkmark are complete and available for 6 | download in the 7 | [CI build](http://vsixgallery.com/extension/DemoSnippets.e2d68c23-8599-40e8-b402-a57060bf3d29/). 8 | 9 | # Change log 10 | 11 | These are the changes to each version that has been released 12 | on the official Visual Studio extension gallery. 13 | 14 | ## 1.9 15 | 16 | - [x] Support Visual Studio 2022 17 | 18 | ## 1.8 19 | 20 | - [x] Add collapsible sections in .demosnippets files 21 | - [x] Add custom icon for .demosnippets files 22 | 23 | ## 1.7 24 | 25 | - [x] support labels that start with '@' (or any character) 26 | 27 | ## 1.6 28 | 29 | - [x] Support sub-extensions of "*.demosnippets.*" 30 | 31 | ## 1.5 32 | 33 | - [X] Add colorization of .demosnippets files 34 | - [x] Add Item Template for .demosnippets files 35 | 36 | ## 1.4 37 | 38 | - [x] Fix file save operating on all files 39 | 40 | ## 1.3 41 | 42 | - [x] Allow removing of empty tabs from the toolbox (context menu option on the Toolbox) 43 | - [x] Do not duplicate existing toolbox entries with values from the same .demosnipets files 44 | - [x] Reload Toolbox entries when .demosnippets files are saved 45 | 46 | ## 1.2 47 | 48 | - [x] Allow processing multiple .demosnippets files at once (content menu option on the solution node) 49 | - [x] Allow removing all snippets from the toolbox (context menu option on the Toolbox) 50 | - [X] Automatically load all *.demosnippets files on solution load (and remove on solution close) 51 | 52 | ## 1.1 53 | 54 | - [x] Support installation on VS2019 55 | - [x] Allow setting which tab in the toolbox snippets are added to 56 | 57 | ## 1.0 58 | 59 | - [x] Initial release 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Looking to contribute something? **Here's how you can help.** 4 | 5 | Please take a moment to review this document in order to make the contribution 6 | process easy and effective for everyone involved. 7 | 8 | Following these guidelines helps to communicate that you respect the time of 9 | the developers managing and developing this open source project. In return, 10 | they should reciprocate that respect in addressing your issue or assessing 11 | patches and features. 12 | 13 | 14 | ## Using the issue tracker 15 | 16 | The issue tracker is the preferred channel for [bug reports](#bug-reports), 17 | [features requests](#feature-requests) and 18 | [submitting pull requests](#pull-requests), but please respect the 19 | following restrictions: 20 | 21 | * Please **do not** use the issue tracker for personal support requests. Stack 22 | Overflow is a better place to get help. 23 | 24 | * Please **do not** derail or troll issues. Keep the discussion on topic and 25 | respect the opinions of others. 26 | 27 | * Please **do not** open issues or pull requests which *belongs to* third party 28 | components. 29 | 30 | 31 | ## Bug reports 32 | 33 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 34 | Good bug reports are extremely helpful, so thanks! 35 | 36 | Guidelines for bug reports: 37 | 38 | 1. **Use the GitHub issue search** — check if the issue has already been 39 | reported. 40 | 41 | 2. **Check if the issue has been fixed** — try to reproduce it using the 42 | latest `master` or development branch in the repository. 43 | 44 | 3. **Isolate the problem** — ideally create an 45 | [SSCCE](http://www.sscce.org/) and a live example. 46 | Uploading the project on cloud storage (OneDrive, DropBox, et el.) 47 | or creating a sample GitHub repository is also helpful. 48 | 49 | 50 | A good bug report shouldn't leave others needing to chase you up for more 51 | information. Please try to be as detailed as possible in your report. What is 52 | your environment? What steps will reproduce the issue? What browser(s) and OS 53 | experience the problem? Do other browsers show the bug differently? What 54 | would you expect to be the outcome? All these details will help people to fix 55 | any potential bugs. 56 | 57 | Example: 58 | 59 | > Short and descriptive example bug report title 60 | > 61 | > A summary of the issue and the Visual Studio, browser, OS environments 62 | > in which it occurs. If suitable, include the steps required to reproduce the bug. 63 | > 64 | > 1. This is the first step 65 | > 2. This is the second step 66 | > 3. Further steps, etc. 67 | > 68 | > `` - a link to the project/file uploaded on cloud storage or other publicly accessible medium. 69 | > 70 | > Any other information you want to share that is relevant to the issue being 71 | > reported. This might include the lines of code that you have identified as 72 | > causing the bug, and potential solutions (and your opinions on their 73 | > merits). 74 | 75 | 76 | ## Feature requests 77 | 78 | Feature requests are welcome. But take a moment to find out whether your idea 79 | fits with the scope and aims of the project. It's up to *you* to make a strong 80 | case to convince the project's developers of the merits of this feature. Please 81 | provide as much detail and context as possible. 82 | 83 | 84 | ## Pull requests 85 | 86 | Good pull requests, patches, improvements and new features are a fantastic 87 | help. They should remain focused in scope and avoid containing unrelated 88 | commits. 89 | 90 | **Please ask first** before embarking on any significant pull request (e.g. 91 | implementing features, refactoring code, porting to a different language), 92 | otherwise you risk spending a lot of time working on something that the 93 | project's developers might not want to merge into the project. 94 | 95 | Please adhere to the [coding guidelines](#code-guidelines) used throughout the 96 | project (indentation, accurate comments, etc.) and any other requirements 97 | (such as test coverage). 98 | 99 | Adhering to the following process is the best way to get your work 100 | included in the project: 101 | 102 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 103 | and configure the remotes: 104 | 105 | ```bash 106 | # Clone your fork of the repo into the current directory 107 | git clone https://github.com//.git 108 | # Navigate to the newly cloned directory 109 | cd 110 | # Assign the original repo to a remote called "upstream" 111 | git remote add upstream https://github.com/madskristensen/.git 112 | ``` 113 | 114 | 2. If you cloned a while ago, get the latest changes from upstream: 115 | 116 | ```bash 117 | git checkout master 118 | git pull upstream master 119 | ``` 120 | 121 | 3. Create a new topic branch (off the main project development branch) to 122 | contain your feature, change, or fix: 123 | 124 | ```bash 125 | git checkout -b 126 | ``` 127 | 128 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 129 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 130 | or your code is unlikely be merged into the main project. Use Git's 131 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 132 | feature to tidy up your commits before making them public. Also, prepend name of the feature 133 | to the commit message. For instance: "SCSS: Fixes compiler results for IFileListener.\nFixes `#123`" 134 | 135 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 136 | 137 | ```bash 138 | git pull [--rebase] upstream master 139 | ``` 140 | 141 | 6. Push your topic branch up to your fork: 142 | 143 | ```bash 144 | git push origin 145 | ``` 146 | 147 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 148 | with a clear title and description against the `master` branch. 149 | 150 | 151 | ## Code guidelines 152 | 153 | - Always use proper indentation. 154 | - In Visual Studio under `Tools > Options > Text Editor > C# > Advanced`, make sure 155 | `Place 'System' directives first when sorting usings` option is enabled (checked). 156 | - Before committing, organize usings for each updated C# source file. Either you can 157 | right-click editor and select `Organize Usings > Remove and sort` OR use extension 158 | like [BatchFormat](http://visualstudiogallery.msdn.microsoft.com/a7f75c34-82b4-4357-9c66-c18e32b9393e). 159 | - Before committing, run Code Analysis in `Debug` configuration and follow the guidelines 160 | to fix CA issues. Code Analysis commits can be made separately. 161 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/CSharp.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.CSharp 9 | CSharp 10 | CSharp 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/DemoSnippets.ItemTemplates.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | Debug 11 | AnyCPU 12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | {09BB7475-2826-42A2-8B9E-23333129A010} 14 | Library 15 | Properties 16 | DemoSnippets.ItemTemplates 17 | DemoSnippets.ItemTemplates 18 | v4.7.2 19 | 512 20 | false 21 | false 22 | false 23 | false 24 | false 25 | false 26 | false 27 | false 28 | false 29 | false 30 | 31 | 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | 40 | 41 | pdbonly 42 | true 43 | bin\Release\ 44 | TRACE 45 | prompt 46 | 4 47 | 48 | 49 | 50 | False 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | General 66 | 67 | 68 | 69 | 70 | Designer 71 | 72 | 73 | 74 | 75 | General 76 | 77 | 78 | General 79 | 80 | 81 | General 82 | 83 | 84 | General 85 | 86 | 87 | General 88 | 89 | 90 | General 91 | 92 | 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/FSharp.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.FSharp 9 | FSharp 10 | FSharp 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/General.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.General 9 | General 10 | true 11 | 1 12 | steps.demosnippets 13 | 14 | 15 | instructions.demosnippets 16 | 17 | 18 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/Javascript.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.JavaScript 9 | JavaScript 10 | JavaScript 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DemoSnippets.ItemTemplates")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Matt Lacey")] 12 | [assembly: AssemblyProduct("DemoSnippets.ItemTemplates")] 13 | [assembly: AssemblyCopyright("Copyright © Matt Lacey 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("09bb7475-2826-42a2-8b9e-23333129a010")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/Test.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.Test 9 | Test 10 | Test 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/VC.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.VC 9 | VC 10 | VC 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/VisualBasic.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.VisualBasic 9 | VisualBasic 10 | VisualBasic 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/Web.vstemplate: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | DemoSnippets 5 | A repository for pieces of code to drag from the Toolbox when giving a demo or presentation 6 | icon.png 7 | 100 8 | DemoSnippets.Web 9 | Web 10 | Web 11 | true 12 | 1 13 | steps.demosnippets 14 | 15 | 16 | instructions.demosnippets 17 | 18 | 19 | -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/DemoSnippets.ItemTemplates/icon.png -------------------------------------------------------------------------------- /DemoSnippets.ItemTemplates/instructions.demosnippets: -------------------------------------------------------------------------------- 1 |  2 | # Comments 3 | 4 | TAB: Tab name 5 | 6 | - Step 1 7 | 8 | // snippet 1 line 1 9 | // snippet 1 line 2 10 | 11 | 12 | - Step 2 13 | 14 | // snippet 2 line 1 15 | // snippet 2 line 2 16 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/AssertExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace DemoSnippets.Tests 5 | { 6 | public static class AssertExtensions 7 | { 8 | public static void ToolboxEntriesAreEqual(this Assert assert, List expected, List actual) 9 | { 10 | Assert.AreEqual(expected.Count, actual.Count, $"List sizes differ."); 11 | 12 | for (var index = 0; index < expected.Count; index++) 13 | { 14 | var exp = expected[index]; 15 | var act = actual[index]; 16 | Assert.AreEqual(exp.Tab, act.Tab, $"Item {index + 1} has differing 'Tab' value."); 17 | Assert.AreEqual(exp.Label, act.Label, $"Item {index + 1} has differing 'Label' value."); 18 | Assert.AreEqual(exp.Snippet, act.Snippet, $"Item {index + 1} has differing 'Snippet' value."); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/DemoSnippets.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E3BD83D6-A271-42C6-BD82-E528179B6D2B} 8 | Library 9 | Properties 10 | DemoSnippets.Tests 11 | DemoSnippets.Tests 12 | v4.8.1 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | PreserveNewest 57 | 58 | 59 | PreserveNewest 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | PreserveNewest 66 | 67 | 68 | 69 | 70 | 2.5.192 71 | 72 | 73 | 6.1.0 74 | 75 | 76 | 3.6.4 77 | 78 | 79 | 3.6.4 80 | 81 | 82 | 3.6.4 83 | runtime; build; native; contentfiles; analyzers; buildtransitive 84 | all 85 | 86 | 87 | 8.0.0 88 | 89 | 90 | 9.0.0 91 | 92 | 93 | 1.7.0 94 | runtime; build; native; contentfiles; analyzers; buildtransitive 95 | all 96 | 97 | 98 | 99 | 100 | {12e84876-766b-4e73-a499-018dec0e9e8f} 101 | DemoSnippets 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/Examples/example.demosnippets.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE in the project root for license information. 3 | 4 | // DEMOSNIPPETS-TAB C++ 5 | // DEMOSNIPPETS-LABEL includes 6 | #include "pch.h" 7 | #include "common.h" 8 | #include "ColorChangedEventArgs.h" 9 | 10 | // DEMOSNIPPETS-LABEL OldColor 11 | winrt::Color ColorChangedEventArgs::OldColor() 12 | { 13 | return m_oldColor; 14 | } 15 | //DEMOSNIPPETS-ENDSNIPPET 16 | winrt::Color ColorChangedEventArgs::NewColor() 17 | { 18 | return m_newColor; 19 | } 20 | 21 | void ColorChangedEventArgs::OldColor(winrt::Color const& value) 22 | { 23 | m_oldColor = value; 24 | } 25 | /* DEMOSNIPPETS-LABEL NewColor */ 26 | void ColorChangedEventArgs::NewColor(winrt::Color const& value) 27 | { 28 | m_newColor = value; 29 | } -------------------------------------------------------------------------------- /DemoSnippets.Tests/Examples/example.demosnippets.cs: -------------------------------------------------------------------------------- 1 | // DEMOSNIPPETS-TAB My C# Demo 2 | 3 | // DEMOSNIPPETS-LABEL Step 1 4 | 5 | // Add necessary namespace 6 | usings MyCoolNamespace; 7 | 8 | // DEMOSNIPPETS-LABEL Step 2 9 | 10 | public struct SuperOptions 11 | { 12 | public int Id { get; set; } 13 | public string Label { get; set; } 14 | public bool IsEnabled { get; set; } 15 | } 16 | 17 | 18 | // DEMOSNIPPETS-LABEL Step 3 19 | 20 | if (UserOptions.TryGetOptions(out SuperOptions[] options)) 21 | { 22 | Debug.WriteLine(options.Length); 23 | } 24 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/Examples/example.demosnippets.css: -------------------------------------------------------------------------------- 1 | /* This original sample is from https://www.w3.org/Style/Examples/011/firstcss.en.html */ 2 | 3 | /* DEMOSNIPPETS-LABEL Step 1*/ 4 | 5 | body { 6 | padding-left: 11em; 7 | font-family: Georgia, "Times New Roman", Times, serif; 8 | color: purple; 9 | background-color: #d8da3d 10 | } 11 | /* DEMOSNIPPETS-TAB CSS Stuff */ 12 | /* DEMOSNIPPETS-LABEL Step 2*/ 13 | 14 | ul.navbar { 15 | list-style-type: none; 16 | padding: 0; 17 | margin: 0; 18 | position: absolute; 19 | top: 2em; 20 | left: 1em; 21 | width: 9em } 22 | /* DEMOSNIPPETS-TAB Demo */ 23 | /* DEMOSNIPPETS-LABEL Step 3 */ 24 | h1 { 25 | font-family: Helvetica, Geneva, Arial, 26 | SunSans-Regular, sans-serif } 27 | /* DEMOSNIPPETS-TAB CSS Stuff */ 28 | /* DEMOSNIPPETS-LABEL Step 4*/ 29 | ul.navbar li { 30 | background: white; 31 | margin: 0.5em 0; 32 | padding: 0.3em; 33 | border-right: 1em solid black } 34 | ul.navbar a { 35 | text-decoration: none } 36 | 37 | /* DEMOSNIPPETS-LABEL Step 5*/ 38 | a:link { 39 | color: blue } 40 | a:visited { 41 | color: purple } 42 | /* DEMOSNIPPETS-ENDSNIPPET */ 43 | address { 44 | margin-top: 1em; 45 | padding-top: 1em; 46 | border-top: thin dotted } -------------------------------------------------------------------------------- /DemoSnippets.Tests/Examples/example.demosnippets.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Demo stuff 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Conference Demos

16 | 17 | 18 |
    19 |
  • Home
  • 20 |
  • Store
  • 21 |
  • About
  • 22 |
  • Admin
  • 23 |
24 | 25 |

Some important text to have on the page.

26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/Examples/example.demosnippets.js: -------------------------------------------------------------------------------- 1 |  2 | 3 | // DEMOSNIPPETS-TAB DEMO-DOM 4 | 5 | // DEMOSNIPPETS-LABEL Step 1 6 | 7 | document.getElementById("demo").innerHTML = "Hello World!"; 8 | 9 | 10 | // DEMOSNIPPETS-TAB DEMO-Functions 11 | 12 | 13 | // DEMOSNIPPETS-LABEL Step 1 - Empty function 14 | 15 | function myFunction(p1, p2) { 16 | } 17 | 18 | // DEMOSNIPPETS-ENDSNIPPET 19 | 20 | function myFunction(p1, p2) { 21 | 22 | // DEMOSNIPPETS-LABEL Step 2 - Function body 23 | return p1 * p2; // The function returns the product of p1 and p2 24 | // DEMOSNIPPETS-ENDSNIPPET 25 | } 26 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/Examples/example.demosnippets.vb: -------------------------------------------------------------------------------- 1 | ' DEMOSNIPPETS-TAB VB Demos 2 | 'DEMOSNIPPETS-LABEL a) imports 3 | Imports System.Runtime.CompilerServices 4 | 5 | 'DEMOSNIPPETS-ENDSNIPPET 6 | Namespace Helpers 7 | Public Module TaskExtensions 8 | 'DEMOSNIPPETS-LABEL b) method 9 | 10 | Public Sub FireAndForget(task As Task) 11 | ' This method allows you to call an async method without awaiting it. 12 | ' Use it when you don't want or need to wait for the task to complete. 13 | End Sub 14 | ''' DEMOSNIPPETS-ENDSNIPPET 15 | End Module 16 | End Namespace 17 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DemoSnippets.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DemoSnippets.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("de701081-1b99-4ba6-b902-00b8a934cab4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/StandardParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace DemoSnippets.Tests 5 | { 6 | [TestClass] 7 | public class StandardParserTests : TestsBase 8 | { 9 | [TestMethod] 10 | public void BlankLines_ReturnsEmptyList() 11 | { 12 | var lines = "" 13 | + Environment.NewLine + "" 14 | + Environment.NewLine + "" 15 | + Environment.NewLine + ""; 16 | 17 | var actual = ParseAsLines(lines); 18 | 19 | Assert.AreEqual(0, actual.Count); 20 | } 21 | 22 | [TestMethod] 23 | public void JustComments_ReturnsEmptyList() 24 | { 25 | var lines = "# comment line 1" 26 | + Environment.NewLine + "# comment line 2" 27 | + Environment.NewLine + ""; 28 | 29 | var actual = ParseAsLines(lines); 30 | 31 | Assert.AreEqual(0, actual.Count); 32 | } 33 | 34 | [TestMethod] 35 | public void SingleItem_AfterComment() 36 | { 37 | var lines = "# comment line 1" 38 | + Environment.NewLine + "" 39 | + Environment.NewLine + "- Step 1" 40 | + Environment.NewLine + "" 41 | + Environment.NewLine + "snippet line 1" 42 | + Environment.NewLine + "snippet line 2" 43 | + Environment.NewLine + ""; 44 | 45 | var actual = ParseAsLines(lines); 46 | 47 | Assert.AreEqual(1, actual.Count); 48 | Assert.AreEqual("Step 1", actual[0].Label); 49 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 50 | Assert.AreEqual(string.Empty, actual[0].Tab); 51 | } 52 | 53 | [TestMethod] 54 | public void TwoItems_AfterComment() 55 | { 56 | var lines = "# comment line 1" 57 | + Environment.NewLine + "" 58 | + Environment.NewLine + "- Step 1" 59 | + Environment.NewLine + "" 60 | + Environment.NewLine + "snippet line 1" 61 | + Environment.NewLine + "snippet line 2" 62 | + Environment.NewLine + "" 63 | + Environment.NewLine + "- Step 2" 64 | + Environment.NewLine + "" 65 | + Environment.NewLine + "snippet2 line 1" 66 | + Environment.NewLine + "snippet2 line 2" 67 | + Environment.NewLine + ""; 68 | 69 | var actual = ParseAsLines(lines); 70 | 71 | Assert.AreEqual(2, actual.Count); 72 | Assert.AreEqual("Step 1", actual[0].Label); 73 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 74 | Assert.AreEqual(string.Empty, actual[0].Tab); 75 | Assert.AreEqual("Step 2", actual[1].Label); 76 | Assert.AreEqual($"snippet2 line 1{Environment.NewLine}snippet2 line 2", actual[1].Snippet); 77 | Assert.AreEqual(string.Empty, actual[1].Tab); 78 | } 79 | 80 | [TestMethod] 81 | public void FirstLineOfSnippet_UsedAsLabelIfNotDefined() 82 | { 83 | var lines = "# comment line 1" 84 | + Environment.NewLine + "" 85 | + Environment.NewLine + "-" 86 | + Environment.NewLine + "" 87 | + Environment.NewLine + "snippet line 1" 88 | + Environment.NewLine + "snippet line 2" 89 | + Environment.NewLine + ""; 90 | 91 | var actual = ParseAsLines(lines); 92 | 93 | Assert.AreEqual(1, actual.Count); 94 | Assert.AreEqual("snippet line 1", actual[0].Label); 95 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 96 | Assert.AreEqual(string.Empty, actual[0].Tab); 97 | } 98 | 99 | [TestMethod] 100 | public void CommentsInSnippetsIgnored() 101 | { 102 | var lines = "# comment line 1" 103 | + Environment.NewLine + "" 104 | + Environment.NewLine + "- Step 1" 105 | + Environment.NewLine + "" 106 | + Environment.NewLine + "#Comment" 107 | + Environment.NewLine + "snippet line 1" 108 | + Environment.NewLine + "#Comment" 109 | + Environment.NewLine + "snippet line 2" 110 | + Environment.NewLine + "#Comment" 111 | + Environment.NewLine + ""; 112 | 113 | var actual = ParseAsLines(lines); 114 | 115 | Assert.AreEqual(1, actual.Count); 116 | Assert.AreEqual("Step 1", actual[0].Label); 117 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 118 | Assert.AreEqual(string.Empty, actual[0].Tab); 119 | } 120 | 121 | [TestMethod] 122 | public void DuplicateLabels_NotAnIssue() 123 | { 124 | var lines = "# comment line 1" 125 | + Environment.NewLine + "" 126 | + Environment.NewLine + "- Add this" 127 | + Environment.NewLine + "" 128 | + Environment.NewLine + "snippet line 1" 129 | + Environment.NewLine + "snippet line 2" 130 | + Environment.NewLine + "" 131 | + Environment.NewLine + "- Add this " 132 | + Environment.NewLine + "" 133 | + Environment.NewLine + "snippet2 line 1" 134 | + Environment.NewLine + "snippet2 line 2" 135 | + Environment.NewLine + ""; 136 | 137 | var actual = ParseAsLines(lines); 138 | 139 | Assert.AreEqual(2, actual.Count); 140 | Assert.AreEqual("Add this", actual[0].Label); 141 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 142 | Assert.AreEqual(string.Empty, actual[0].Tab); 143 | Assert.AreEqual("Add this", actual[1].Label); 144 | Assert.AreEqual($"snippet2 line 1{Environment.NewLine}snippet2 line 2", actual[1].Snippet); 145 | Assert.AreEqual(string.Empty, actual[1].Tab); 146 | } 147 | 148 | [TestMethod] 149 | public void SingleLineSnippet() 150 | { 151 | var lines = "- Step 1" 152 | + Environment.NewLine + "" 153 | + Environment.NewLine + "snippet line 1" 154 | + Environment.NewLine + ""; 155 | 156 | var actual = ParseAsLines(lines); 157 | 158 | Assert.AreEqual(1, actual.Count); 159 | Assert.AreEqual("Step 1", actual[0].Label); 160 | Assert.AreEqual($"snippet line 1", actual[0].Snippet); 161 | Assert.AreEqual(string.Empty, actual[0].Tab); 162 | } 163 | 164 | [TestMethod] 165 | public void ItemMustHaveSnippet() 166 | { 167 | var lines = "# comment line 1" 168 | + Environment.NewLine + "" 169 | + Environment.NewLine + "- Step 0" 170 | + Environment.NewLine + "- Step 1" 171 | + Environment.NewLine + "" 172 | + Environment.NewLine + "snippet line 1" 173 | + Environment.NewLine + "snippet line 2" 174 | + Environment.NewLine + "" 175 | + Environment.NewLine + "- Step 2" 176 | + Environment.NewLine + "" 177 | + Environment.NewLine + ""; 178 | 179 | var actual = ParseAsLines(lines); 180 | 181 | Assert.AreEqual(1, actual.Count); 182 | Assert.AreEqual("Step 1", actual[0].Label); 183 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 184 | Assert.AreEqual(string.Empty, actual[0].Tab); 185 | } 186 | 187 | [TestMethod] 188 | public void EmptyItemsWithoutLabelsIgnored() 189 | { 190 | var lines = "# comment line 1" 191 | + Environment.NewLine + "" 192 | + Environment.NewLine + "- " 193 | + Environment.NewLine + "- Step 1" 194 | + Environment.NewLine + "" 195 | + Environment.NewLine + "snippet line 1" 196 | + Environment.NewLine + "snippet line 2" 197 | + Environment.NewLine + "" 198 | + Environment.NewLine + "- " 199 | + Environment.NewLine + "" 200 | + Environment.NewLine + ""; 201 | 202 | var actual = ParseAsLines(lines); 203 | 204 | Assert.AreEqual(1, actual.Count); 205 | Assert.AreEqual("Step 1", actual[0].Label); 206 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 207 | Assert.AreEqual(string.Empty, actual[0].Tab); 208 | } 209 | 210 | [TestMethod] 211 | public void CanSet_Tab() 212 | { 213 | var lines = "# comment line 1" 214 | + Environment.NewLine + "" 215 | + Environment.NewLine + "Tab: My Demo" 216 | + Environment.NewLine + "- Step 1" 217 | + Environment.NewLine + "" 218 | + Environment.NewLine + "snippet line 1" 219 | + Environment.NewLine + "snippet line 2" 220 | + Environment.NewLine + "" 221 | + Environment.NewLine + "- " 222 | + Environment.NewLine + "" 223 | + Environment.NewLine + ""; 224 | 225 | var actual = ParseAsLines(lines); 226 | 227 | Assert.AreEqual(1, actual.Count); 228 | Assert.AreEqual("Step 1", actual[0].Label); 229 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 230 | Assert.AreEqual("My Demo", actual[0].Tab); 231 | } 232 | 233 | [TestMethod] 234 | public void WhiteSpaceTab_IsEmpty() 235 | { 236 | var lines = "# comment line 1" 237 | + Environment.NewLine + "" 238 | + Environment.NewLine + "Tab: " 239 | + Environment.NewLine + "- Step 1" 240 | + Environment.NewLine + "" 241 | + Environment.NewLine + "snippet line 1" 242 | + Environment.NewLine + "snippet line 2" 243 | + Environment.NewLine + "" 244 | + Environment.NewLine + "- " 245 | + Environment.NewLine + "" 246 | + Environment.NewLine + ""; 247 | 248 | var actual = ParseAsLines(lines); 249 | 250 | Assert.AreEqual(1, actual.Count); 251 | Assert.AreEqual("Step 1", actual[0].Label); 252 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 253 | Assert.AreEqual(string.Empty, actual[0].Tab); 254 | } 255 | 256 | [TestMethod] 257 | public void Tab_IsBlankIfNotSet() 258 | { 259 | var lines = "# comment line 1" 260 | + Environment.NewLine + "" 261 | + Environment.NewLine + "- step 1" 262 | + Environment.NewLine + "" 263 | + Environment.NewLine + "snippet line 1" 264 | + Environment.NewLine + "snippet line 2" 265 | + Environment.NewLine + "" 266 | + Environment.NewLine + "tab: Demo 2" 267 | + Environment.NewLine + "- Step 2" 268 | + Environment.NewLine + "" 269 | + Environment.NewLine + "snippet2 line 1" 270 | + Environment.NewLine + "snippet2 line 2" 271 | + Environment.NewLine + ""; 272 | 273 | var actual = ParseAsLines(lines); 274 | 275 | Assert.AreEqual(2, actual.Count); 276 | Assert.AreEqual(string.Empty, actual[0].Tab); 277 | Assert.AreEqual("Demo 2", actual[1].Tab); 278 | } 279 | 280 | [TestMethod] 281 | public void IfMultipleTabsSetInARow_UseLast() 282 | { 283 | var lines = "# comment line 1" 284 | + Environment.NewLine + "" 285 | + Environment.NewLine + "Tab: My Demo" 286 | + Environment.NewLine + "Tab: My second Demo" 287 | + Environment.NewLine + "Tab: My third Demo" 288 | + Environment.NewLine + "- Step 1" 289 | + Environment.NewLine + "" 290 | + Environment.NewLine + "snippet line 1" 291 | + Environment.NewLine + "snippet line 2" 292 | + Environment.NewLine + "" 293 | + Environment.NewLine + "- " 294 | + Environment.NewLine + "" 295 | + Environment.NewLine + ""; 296 | 297 | var actual = ParseAsLines(lines); 298 | 299 | Assert.AreEqual(1, actual.Count); 300 | Assert.AreEqual("Step 1", actual[0].Label); 301 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 302 | Assert.AreEqual("My third Demo", actual[0].Tab); 303 | } 304 | 305 | [TestMethod] 306 | public void TabCanApply_ToMultipleSnippets() 307 | { 308 | var lines = "# comment line 1" 309 | + Environment.NewLine + "" 310 | + Environment.NewLine + "TAB: DemoX" 311 | + Environment.NewLine + "- Step 1" 312 | + Environment.NewLine + "" 313 | + Environment.NewLine + "snippet line 1" 314 | + Environment.NewLine + "snippet line 2" 315 | + Environment.NewLine + "" 316 | + Environment.NewLine + "- Step 2" 317 | + Environment.NewLine + "" 318 | + Environment.NewLine + "snippet2 line 1" 319 | + Environment.NewLine + "snippet2 line 2" 320 | + Environment.NewLine + ""; 321 | 322 | var actual = ParseAsLines(lines); 323 | 324 | Assert.AreEqual(2, actual.Count); 325 | Assert.AreEqual("Step 1", actual[0].Label); 326 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 327 | Assert.AreEqual("DemoX", actual[0].Tab); 328 | Assert.AreEqual("Step 2", actual[1].Label); 329 | Assert.AreEqual($"snippet2 line 1{Environment.NewLine}snippet2 line 2", actual[1].Snippet); 330 | Assert.AreEqual("DemoX", actual[1].Tab); 331 | } 332 | 333 | [TestMethod] 334 | public void DifferentTabs_InOneFile() 335 | { 336 | var lines = "# comment line 1" 337 | + Environment.NewLine + "" 338 | + Environment.NewLine + "TAB: Demo 1" 339 | + Environment.NewLine + "- Step 1" 340 | + Environment.NewLine + "" 341 | + Environment.NewLine + "snippet line 1" 342 | + Environment.NewLine + "snippet line 2" 343 | + Environment.NewLine + "" 344 | + Environment.NewLine + "tab: Demo 2" 345 | + Environment.NewLine + "- Step 2" 346 | + Environment.NewLine + "" 347 | + Environment.NewLine + "snippet2 line 1" 348 | + Environment.NewLine + "snippet2 line 2" 349 | + Environment.NewLine + ""; 350 | 351 | var actual = ParseAsLines(lines); 352 | 353 | Assert.AreEqual(2, actual.Count); 354 | Assert.AreEqual("Step 1", actual[0].Label); 355 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 356 | Assert.AreEqual("Demo 1", actual[0].Tab); 357 | Assert.AreEqual("Step 2", actual[1].Label); 358 | Assert.AreEqual($"snippet2 line 1{Environment.NewLine}snippet2 line 2", actual[1].Snippet); 359 | Assert.AreEqual("Demo 2", actual[1].Tab); 360 | } 361 | 362 | [TestMethod] 363 | public void DifferentTabs_InOneFile_PlusLabelInference() 364 | { 365 | var lines = "# comment line 1" 366 | + Environment.NewLine + "" 367 | + Environment.NewLine + "TAB: Demo 1" 368 | + Environment.NewLine + "-" 369 | + Environment.NewLine + "" 370 | + Environment.NewLine + "snippet line 1" 371 | + Environment.NewLine + "snippet line 2" 372 | + Environment.NewLine + "" 373 | + Environment.NewLine + "tab: Demo 2" 374 | + Environment.NewLine + "-" 375 | + Environment.NewLine + "" 376 | + Environment.NewLine + "snippet2 line 1" 377 | + Environment.NewLine + "snippet2 line 2" 378 | + Environment.NewLine + ""; 379 | 380 | var actual = ParseAsLines(lines); 381 | 382 | Assert.AreEqual(2, actual.Count); 383 | Assert.AreEqual("snippet line 1", actual[0].Label); 384 | Assert.AreEqual($"snippet line 1{Environment.NewLine}snippet line 2", actual[0].Snippet); 385 | Assert.AreEqual("Demo 1", actual[0].Tab); 386 | Assert.AreEqual("snippet2 line 1", actual[1].Label); 387 | Assert.AreEqual($"snippet2 line 1{Environment.NewLine}snippet2 line 2", actual[1].Snippet); 388 | Assert.AreEqual("Demo 2", actual[1].Tab); 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/SubExtParserTestsCPP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DemoSnippets.Tests 6 | { 7 | [TestClass] 8 | public class SubExtParserTestsCPP : TestsBase 9 | { 10 | [TestMethod] 11 | public void ExampleFile_CPP() 12 | { 13 | var actual = ParseExampleFile("./Examples/example.demosnippets.cpp"); 14 | 15 | var expected = new List 16 | { 17 | new ToolboxEntry{ 18 | Tab = "C++", 19 | Label = "includes", 20 | Snippet = "#include \"pch.h\"" 21 | + Environment.NewLine + "#include \"common.h\"" 22 | + Environment.NewLine + "#include \"ColorChangedEventArgs.h\"" 23 | }, 24 | new ToolboxEntry{ 25 | Tab = "C++", 26 | Label = "OldColor", 27 | Snippet = "winrt::Color ColorChangedEventArgs::OldColor()" 28 | + Environment.NewLine + "{" 29 | + Environment.NewLine + " return m_oldColor;" 30 | + Environment.NewLine + "}" 31 | }, 32 | new ToolboxEntry{ 33 | Tab = "C++", 34 | Label = "NewColor", 35 | Snippet = "void ColorChangedEventArgs::NewColor(winrt::Color const& value)" 36 | + Environment.NewLine + "{" 37 | + Environment.NewLine + " m_newColor = value;" 38 | + Environment.NewLine + "}" 39 | }, 40 | }; 41 | 42 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/SubExtParserTestsCSS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DemoSnippets.Tests 6 | { 7 | [TestClass] 8 | public class SubExtParserTestsCSS : TestsBase 9 | { 10 | [TestMethod] 11 | public void ExampleFile_CSS() 12 | { 13 | var actual = ParseExampleFile("./Examples/example.demosnippets.css"); 14 | 15 | var expected = new List 16 | { 17 | new ToolboxEntry{ 18 | Tab = string.Empty, 19 | Label = "Step 1", 20 | Snippet = "body {" 21 | + Environment.NewLine + " padding-left: 11em;" 22 | + Environment.NewLine + " font-family: Georgia, \"Times New Roman\", Times, serif;" 23 | + Environment.NewLine + " color: purple;" 24 | + Environment.NewLine + " background-color: #d8da3d" 25 | + Environment.NewLine + "}" 26 | }, 27 | new ToolboxEntry{ 28 | Tab = "CSS Stuff", 29 | Label = "Step 2", 30 | Snippet = "ul.navbar {" 31 | + Environment.NewLine + " list-style-type: none;" 32 | + Environment.NewLine + " padding: 0;" 33 | + Environment.NewLine + " margin: 0;" 34 | + Environment.NewLine + " position: absolute;" 35 | + Environment.NewLine + " top: 2em;" 36 | + Environment.NewLine + " left: 1em;" 37 | + Environment.NewLine + " width: 9em }" 38 | }, 39 | new ToolboxEntry{ 40 | Tab = "Demo", 41 | Label = "Step 3", 42 | Snippet = "h1 {" 43 | + Environment.NewLine + " font-family: Helvetica, Geneva, Arial," 44 | + Environment.NewLine + " SunSans-Regular, sans-serif }" 45 | }, 46 | new ToolboxEntry{ 47 | Tab = "CSS Stuff", 48 | Label = "Step 4", 49 | Snippet = "ul.navbar li {" 50 | + Environment.NewLine + " background: white;" 51 | + Environment.NewLine + " margin: 0.5em 0;" 52 | + Environment.NewLine + " padding: 0.3em;" 53 | + Environment.NewLine + " border-right: 1em solid black }" 54 | + Environment.NewLine + "ul.navbar a {" 55 | + Environment.NewLine + " text-decoration: none }" 56 | }, 57 | new ToolboxEntry{ 58 | Tab = "CSS Stuff", 59 | Label = "Step 5", 60 | Snippet = "a:link {" 61 | + Environment.NewLine + " color: blue }" 62 | + Environment.NewLine + "a:visited {" 63 | + Environment.NewLine + " color: purple }" 64 | }, 65 | }; 66 | 67 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/SubExtParserTestsCSharp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace DemoSnippets.Tests 7 | { 8 | [TestClass] 9 | public class SubExtParserTestsCSharp : TestsBase 10 | { 11 | [TestMethod] 12 | public void ExampleFile_CS() 13 | { 14 | var actual = ParseExampleFile("./Examples/example.demosnippets.cs"); 15 | 16 | var expected = new List 17 | { 18 | new ToolboxEntry{ 19 | Tab = "My C# Demo", 20 | Label = "Step 1", 21 | Snippet = "// Add necessary namespace" 22 | + Environment.NewLine + "usings MyCoolNamespace;" 23 | }, 24 | new ToolboxEntry{ 25 | Tab = "My C# Demo", 26 | Label = "Step 2", 27 | Snippet = "public struct SuperOptions" 28 | + Environment.NewLine + "{" 29 | + Environment.NewLine + " public int Id { get; set; }" 30 | + Environment.NewLine + " public string Label { get; set; }" 31 | + Environment.NewLine + " public bool IsEnabled { get; set; }" 32 | + Environment.NewLine + "}" 33 | }, 34 | new ToolboxEntry{ 35 | Tab = "My C# Demo", 36 | Label = "Step 3", 37 | Snippet = "if (UserOptions.TryGetOptions(out SuperOptions[] options))" 38 | + Environment.NewLine + "{" 39 | + Environment.NewLine + " Debug.WriteLine(options.Length);" 40 | + Environment.NewLine + "}" 41 | } 42 | 43 | }; 44 | 45 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/SubExtParserTestsHtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DemoSnippets.Tests 6 | { 7 | [TestClass] 8 | public class SubExtParserTestsHtml : TestsBase 9 | { 10 | [TestMethod] 11 | public void ExampleFile_HTML() 12 | { 13 | var actual = ParseExampleFile("./Examples/example.demosnippets.html"); 14 | 15 | var expected = new List 16 | { 17 | new ToolboxEntry 18 | { 19 | Tab = "Head", 20 | Label = "Step 1 - Add Javascript", 21 | Snippet = @" " 22 | + Environment.NewLine + @" " 23 | }, 24 | new ToolboxEntry 25 | { 26 | Tab = "Head", 27 | Label = "Step 2 - Add CSS", 28 | Snippet = @" " 29 | }, 30 | new ToolboxEntry 31 | { 32 | Tab = "Body", 33 | Label = "Step 3 - Add links", 34 | Snippet = @"
    " 35 | + Environment.NewLine + @"
  • Home
  • " 36 | + Environment.NewLine + @"
  • Store
  • " 37 | + Environment.NewLine + @"
  • About
  • " 38 | + Environment.NewLine + @"
  • Admin
  • " 39 | + Environment.NewLine + @"
" 40 | }, 41 | new ToolboxEntry 42 | { 43 | Tab = "Body", 44 | Label = "Step 4 - Add text", 45 | Snippet = @"

Some important text to have on the page.

" 46 | }, 47 | }; 48 | 49 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 50 | } 51 | 52 | [TestMethod] 53 | public void NoTab() 54 | { 55 | var html = "" 56 | + Environment.NewLine + "" 57 | + Environment.NewLine + ""; 58 | 59 | var actual = ParseAsLines(html); 60 | 61 | var expected = new List 62 | { 63 | new ToolboxEntry 64 | { 65 | Tab = string.Empty, 66 | Label = "Step 1 - Add Javascript", 67 | Snippet = @"" 68 | + Environment.NewLine + @"" 69 | }, 70 | }; 71 | 72 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 73 | } 74 | 75 | [TestMethod] 76 | public void Tab_NoLabel() 77 | { 78 | var html = "" 79 | + Environment.NewLine + "" 80 | + Environment.NewLine + ""; 81 | 82 | var actual = ParseAsLines(html); 83 | 84 | var expected = new List 85 | { 86 | new ToolboxEntry 87 | { 88 | Tab = "Head", 89 | Label = "", 90 | Snippet = @"" 91 | + Environment.NewLine + @"" 92 | }, 93 | }; 94 | 95 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 96 | } 97 | 98 | [TestMethod] 99 | public void WhitespaceAround_TabAndLabel_IsIgnored() 100 | { 101 | var html = "" 102 | + Environment.NewLine + "" 103 | + Environment.NewLine + ""; 104 | 105 | var actual = ParseAsLines(html); 106 | 107 | var expected = new List 108 | { 109 | new ToolboxEntry 110 | { 111 | Tab = "Head", 112 | Label = "Label1", 113 | Snippet = @"" 114 | }, 115 | }; 116 | 117 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 118 | } 119 | 120 | [TestMethod] 121 | public void NoWhitespaceAround_TabAndLabel_IsFine() 122 | { 123 | var html = "" 124 | + Environment.NewLine + "" 125 | + Environment.NewLine + ""; 126 | 127 | var actual = ParseAsLines(html); 128 | 129 | var expected = new List 130 | { 131 | new ToolboxEntry 132 | { 133 | Tab = "Head", 134 | Label = "Label1", 135 | Snippet = @"" 136 | }, 137 | }; 138 | 139 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 140 | } 141 | 142 | [TestMethod] 143 | public void JustEndSnippet() 144 | { 145 | var html = ""; 146 | 147 | var actual = ParseAsLines(html); 148 | 149 | var expected = new List(); 150 | 151 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 152 | } 153 | 154 | [TestMethod] 155 | public void JustTabAndEndSnippet() 156 | { 157 | var html = "" 158 | + Environment.NewLine + ""; 159 | 160 | var actual = ParseAsLines(html); 161 | 162 | var expected = new List(); 163 | 164 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 165 | } 166 | 167 | [TestMethod] 168 | public void JustTab() 169 | { 170 | var html = ""; 171 | 172 | var actual = ParseAsLines(html); 173 | 174 | var expected = new List(); 175 | 176 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 177 | } 178 | 179 | [TestMethod] 180 | public void JustLabel() 181 | { 182 | var html = ""; 183 | 184 | var actual = ParseAsLines(html); 185 | 186 | var expected = new List(); 187 | 188 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 189 | } 190 | 191 | [TestMethod] 192 | public void JustTabAndLabel() 193 | { 194 | var html = "" 195 | + Environment.NewLine + ""; 196 | 197 | var actual = ParseAsLines(html); 198 | 199 | var expected = new List(); 200 | 201 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 202 | } 203 | 204 | [TestMethod] 205 | public void SnippetClosedWithEndSnippet_ButJustBlankLines() 206 | { 207 | var html = "" 208 | + Environment.NewLine + "" 209 | + Environment.NewLine + "" 210 | + Environment.NewLine + "" 211 | + Environment.NewLine + "" 212 | + Environment.NewLine + "" 213 | + Environment.NewLine + "" 214 | + Environment.NewLine + ""; 215 | 216 | var actual = ParseAsLines(html); 217 | 218 | var expected = new List(); 219 | 220 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/SubExtParserTestsJS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DemoSnippets.Tests 6 | { 7 | [TestClass] 8 | public class SubExtParserTestsJS : TestsBase 9 | { 10 | [TestMethod] 11 | public void ExampleFile_JS() 12 | { 13 | var actual = ParseExampleFile("./Examples/example.demosnippets.js"); 14 | 15 | var expected = new List 16 | { 17 | new ToolboxEntry{ 18 | Tab = "DEMO-DOM", 19 | Label = "Step 1", 20 | Snippet = "document.getElementById(\"demo\").innerHTML = \"Hello World!\";" 21 | }, 22 | new ToolboxEntry{ 23 | Tab = "DEMO-Functions", 24 | Label = "Step 1 - Empty function", 25 | Snippet = "function myFunction(p1, p2) {" 26 | + Environment.NewLine + "}" 27 | }, 28 | new ToolboxEntry{ 29 | Tab = "DEMO-Functions", 30 | Label = "Step 2 - Function body", 31 | Snippet = " return p1 * p2; // The function returns the product of p1 and p2" 32 | }, 33 | }; 34 | 35 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/SubExtParserTestsVB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DemoSnippets.Tests 6 | { 7 | [TestClass] 8 | public class SubExtParserTestsVB : TestsBase 9 | { 10 | [TestMethod] 11 | public void ExampleFile_VB() 12 | { 13 | var actual = ParseExampleFile("./Examples/example.demosnippets.vb"); 14 | 15 | var expected = new List 16 | { 17 | new ToolboxEntry{ 18 | Tab = "VB Demos", 19 | Label = "a) imports", 20 | Snippet = "Imports System.Runtime.CompilerServices" 21 | }, 22 | new ToolboxEntry{ 23 | Tab = "VB Demos", 24 | Label = "b) method", 25 | Snippet = " " 26 | + Environment.NewLine + " Public Sub FireAndForget(task As Task)" 27 | + Environment.NewLine + " ' This method allows you to call an async method without awaiting it." 28 | + Environment.NewLine + " ' Use it when you don't want or need to wait for the task to complete." 29 | + Environment.NewLine + " End Sub" 30 | }, 31 | }; 32 | 33 | Assert.That.ToolboxEntriesAreEqual(expected, actual); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DemoSnippets.Tests/TestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | 6 | namespace DemoSnippets.Tests 7 | { 8 | public class TestsBase 9 | { 10 | internal List ParseAsLines(string lines) 11 | { 12 | const string crlf = "\r\n"; 13 | const string cr = "\r"; 14 | const string lf = "\n"; 15 | 16 | var envnl = Environment.NewLine; 17 | 18 | var splitString = envnl; 19 | 20 | if (lines.Contains(crlf)) 21 | { 22 | splitString = crlf; 23 | } 24 | else if (lines.Contains(cr)) 25 | { 26 | splitString = cr; 27 | } 28 | else if (lines.Contains(lf)) 29 | { 30 | splitString = lf; 31 | } 32 | 33 | var sut = new DemoSnippetsParser(); 34 | 35 | return sut.GetItemsToAdd(lines.Split(new[] { splitString }, StringSplitOptions.None)); 36 | } 37 | 38 | internal List ParseExampleFile(string fileName) 39 | { 40 | return ParseAsLines(File.ReadAllText(fileName)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DemoSnippets.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35507.96 d17.13 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoSnippets", "DemoSnippets\DemoSnippets.csproj", "{12E84876-766B-4E73-A499-018DEC0E9E8F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6784C7E4-6011-46AF-98EE-3B49701AD391}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | CHANGELOG.md = CHANGELOG.md 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoSnippets.Tests", "DemoSnippets.Tests\DemoSnippets.Tests.csproj", "{E3BD83D6-A271-42C6-BD82-E528179B6D2B}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoSnippets.ItemTemplates", "DemoSnippets.ItemTemplates\DemoSnippets.ItemTemplates.csproj", "{09BB7475-2826-42A2-8B9E-23333129A010}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {12E84876-766B-4E73-A499-018DEC0E9E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {12E84876-766B-4E73-A499-018DEC0E9E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {12E84876-766B-4E73-A499-018DEC0E9E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {12E84876-766B-4E73-A499-018DEC0E9E8F}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {E3BD83D6-A271-42C6-BD82-E528179B6D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {E3BD83D6-A271-42C6-BD82-E528179B6D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {E3BD83D6-A271-42C6-BD82-E528179B6D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {E3BD83D6-A271-42C6-BD82-E528179B6D2B}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {09BB7475-2826-42A2-8B9E-23333129A010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {09BB7475-2826-42A2-8B9E-23333129A010}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {09BB7475-2826-42A2-8B9E-23333129A010}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {09BB7475-2826-42A2-8B9E-23333129A010}.Release|Any CPU.Build.0 = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | GlobalSection(ExtensibilityGlobals) = postSolution 42 | SolutionGuid = {196E55ED-E349-490D-A8DF-4A22982707D0} 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /DemoSnippets/AnalyticsConfig.cs: -------------------------------------------------------------------------------- 1 | namespace DemoSnippets 2 | { 3 | public static class AnalyticsConfig 4 | { 5 | public static string TelemetryConnectionString { get; set; } = ""; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /DemoSnippets/Classifier/DemoSnippets.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel.Composition; 6 | using Microsoft.VisualStudio.Utilities; 7 | 8 | namespace DemoSnippets.Classifier 9 | { 10 | internal static class DemoSnippets 11 | { 12 | internal const string ContentType = nameof(DemoSnippets); 13 | 14 | internal const string FileExtension = ".demosnippets"; 15 | 16 | [Export] 17 | [Name(ContentType)] 18 | [BaseDefinition("code")] 19 | #pragma warning disable SA1401 // Fields must be private 20 | internal static ContentTypeDefinition ContentTypeDefinition = null; 21 | 22 | [Export] 23 | [Name(ContentType + nameof(FileExtensionToContentTypeDefinition))] 24 | [ContentType(ContentType)] 25 | [FileExtension(FileExtension)] 26 | internal static FileExtensionToContentTypeDefinition FileExtensionToContentTypeDefinition = null; 27 | #pragma warning restore SA1401 // Fields must be private 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DemoSnippets/Classifier/DemoSnippetsClassificationTypes.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel.Composition; 6 | using Microsoft.VisualStudio.Language.StandardClassification; 7 | using Microsoft.VisualStudio.Text.Classification; 8 | using Microsoft.VisualStudio.Utilities; 9 | 10 | namespace DemoSnippets.Classifier 11 | { 12 | internal static class DemoSnippetsClassificationTypes 13 | { 14 | public const string DemoSnippetsTab = "ds_tab"; 15 | public const string DemoSnippetsLabel = "ds_label"; 16 | public const string DemoSnippetsComment = PredefinedClassificationTypeNames.Comment; 17 | public const string DemoSnippetsOther = PredefinedClassificationTypeNames.Other; 18 | 19 | [Export(typeof(ClassificationTypeDefinition))] 20 | [Name(DemoSnippetsTab)] 21 | public static ClassificationTypeDefinition DemoSnippetsClassificationTab { get; set; } 22 | 23 | [Export(typeof(ClassificationTypeDefinition))] 24 | [Name(DemoSnippetsLabel)] 25 | public static ClassificationTypeDefinition DemoSnippetsClassificationLabel { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DemoSnippets/Classifier/DemoSnippetsClassifier.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using Microsoft.VisualStudio.Text; 8 | using Microsoft.VisualStudio.Text.Classification; 9 | 10 | namespace DemoSnippets.Classifier 11 | { 12 | internal class DemoSnippetsClassifier : IClassifier 13 | { 14 | private readonly IClassificationType classificationTab; 15 | private readonly IClassificationType classificationLabel; 16 | private readonly IClassificationType classificationComment; 17 | private readonly IClassificationType classificationOther; 18 | 19 | internal DemoSnippetsClassifier(IClassificationTypeRegistryService registry) 20 | { 21 | this.classificationTab = registry.GetClassificationType(DemoSnippetsClassificationTypes.DemoSnippetsTab); 22 | this.classificationLabel = registry.GetClassificationType(DemoSnippetsClassificationTypes.DemoSnippetsLabel); 23 | this.classificationComment = registry.GetClassificationType(DemoSnippetsClassificationTypes.DemoSnippetsComment); 24 | this.classificationOther = registry.GetClassificationType(DemoSnippetsClassificationTypes.DemoSnippetsOther); 25 | } 26 | 27 | #pragma warning disable 67 //CS0067 28 | public event EventHandler ClassificationChanged; 29 | #pragma warning restore 67 30 | 31 | public IList GetClassificationSpans(SnapshotSpan span) 32 | { 33 | var list = new List(); 34 | 35 | var text = span.GetText(); 36 | 37 | list.Add(new ClassificationSpan(span, this.GetClassificationType(text))); 38 | 39 | return list; 40 | } 41 | 42 | private IClassificationType GetClassificationType(string line) 43 | { 44 | switch (new DemoSnippetsLineTypeIdentifier().GetLineType(line)) 45 | { 46 | case DemoSnippetsLineType.Comment: 47 | return this.classificationComment; 48 | 49 | case DemoSnippetsLineType.Tab: 50 | return this.classificationTab; 51 | 52 | case DemoSnippetsLineType.Label: 53 | return this.classificationLabel; 54 | 55 | case DemoSnippetsLineType.Other: 56 | default: 57 | return this.classificationOther; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DemoSnippets/Classifier/DemoSnippetsClassifierProvider.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel.Composition; 6 | using Microsoft.VisualStudio.Text; 7 | using Microsoft.VisualStudio.Text.Classification; 8 | using Microsoft.VisualStudio.Utilities; 9 | 10 | namespace DemoSnippets.Classifier 11 | { 12 | [Export(typeof(IClassifierProvider))] 13 | [ContentType(DemoSnippets.ContentType)] 14 | internal class DemoSnippetsClassifierProvider : IClassifierProvider 15 | { 16 | [Import] 17 | private IClassificationTypeRegistryService ClassificationRegistry { get; set; } 18 | 19 | public IClassifier GetClassifier(ITextBuffer buffer) => 20 | buffer.Properties.GetOrCreateSingletonProperty(() => 21 | new DemoSnippetsClassifier(this.ClassificationRegistry)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DemoSnippets/Classifier/DemoSnippetsLabelFormatDefinition.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel.Composition; 6 | using Microsoft.VisualStudio.Text.Classification; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | namespace DemoSnippets.Classifier 10 | { 11 | [Export(typeof(EditorFormatDefinition))] 12 | [ClassificationType(ClassificationTypeNames = DemoSnippetsClassificationTypes.DemoSnippetsLabel)] 13 | [Name(DemoSnippetsClassificationTypes.DemoSnippetsLabel)] 14 | [UserVisible(true)] 15 | internal sealed class DemoSnippetsLabelFormatDefinition : ClassificationFormatDefinition 16 | { 17 | public DemoSnippetsLabelFormatDefinition() 18 | { 19 | this.IsBold = true; 20 | this.DisplayName = "DemoSnippets Label"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DemoSnippets/Classifier/DemoSnippetsTabFormatDefinition.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel.Composition; 6 | using System.Windows.Media; 7 | using Microsoft.VisualStudio.Text.Classification; 8 | using Microsoft.VisualStudio.Utilities; 9 | 10 | namespace DemoSnippets.Classifier 11 | { 12 | [Export(typeof(EditorFormatDefinition))] 13 | [ClassificationType(ClassificationTypeNames = DemoSnippetsClassificationTypes.DemoSnippetsTab)] 14 | [Name(DemoSnippetsClassificationTypes.DemoSnippetsTab)] 15 | [UserVisible(true)] 16 | internal sealed class DemoSnippetsTabFormatDefinition : ClassificationFormatDefinition 17 | { 18 | public DemoSnippetsTabFormatDefinition() 19 | { 20 | this.BackgroundColor = Colors.LightGray; 21 | this.BackgroundOpacity = .4; 22 | this.DisplayName = "DemoSnippets Tab"; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DemoSnippets/Commands/AddAllDemoSnippets.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using System.IO; 8 | using EnvDTE; 9 | using Microsoft.VisualStudio.Shell; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace DemoSnippets.Commands 13 | { 14 | internal sealed class AddAllDemoSnippets : BaseCommand 15 | { 16 | public const int CommandId = 0x0300; 17 | 18 | private AddAllDemoSnippets(AsyncPackage package, OleMenuCommandService commandService) 19 | : base(package, commandService) 20 | { 21 | var menuCommandId = new CommandID(CommandSet, CommandId); 22 | var menuItem = new MenuCommand(this.Execute, menuCommandId); 23 | commandService.AddCommand(menuItem); 24 | } 25 | 26 | public static async Task InitializeAsync(AsyncPackage package) 27 | { 28 | // Switch to the main thread - the call to AddCommand in the constructor requires the UI thread. 29 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 30 | 31 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 32 | Instance = new AddAllDemoSnippets(package, commandService); 33 | } 34 | 35 | #pragma warning disable VSTHRD100 // Avoid async void methods 36 | private async void Execute(object sender, EventArgs e) 37 | #pragma warning restore VSTHRD100 // Avoid async void methods 38 | { 39 | try 40 | { 41 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(this.Package.DisposalToken); 42 | 43 | if (await this.Package.GetServiceAsync(typeof(DTE)) is DTE dte) 44 | { 45 | var fileName = dte.Solution.FileName; 46 | 47 | if (!string.IsNullOrWhiteSpace(fileName) && File.Exists(fileName)) 48 | { 49 | var slnDir = Path.GetDirectoryName(fileName); 50 | 51 | var (fileCount, snippetCount) = await ToolboxInteractionLogic.ProcessAllSnippetFilesAsync(slnDir); 52 | 53 | var filePlural = fileCount == 1 ? string.Empty : "s"; 54 | var snippetPlural = snippetCount == 1 ? string.Empty : "s"; 55 | 56 | await OutputPane.Instance.WriteAsync($"Added {snippetCount} snippet{snippetPlural}, from {fileCount} file{filePlural}."); 57 | } 58 | else 59 | { 60 | await OutputPane.Instance.WriteAsync("Could not access solution file to use to find .demosnippets files."); 61 | } 62 | } 63 | } 64 | catch (Exception exc) 65 | { 66 | await OutputPane.Instance.WriteAsync("Error adding All Demo Snippets: " + exc.Message); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DemoSnippets/Commands/AddToToolbox.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace DemoSnippets.Commands 11 | { 12 | internal sealed class AddToToolbox : BaseCommand 13 | { 14 | public const int CommandId = 0x0100; 15 | 16 | private AddToToolbox(AsyncPackage package, OleMenuCommandService commandService) 17 | : base(package, commandService) 18 | { 19 | var menuCommandId = new CommandID(CommandSet, CommandId); 20 | var menuItem = new OleMenuCommand(this.Execute, menuCommandId); 21 | menuItem.BeforeQueryStatus += this.MenuItem_BeforeQueryStatus; 22 | commandService.AddCommand(menuItem); 23 | } 24 | 25 | public static async Task InitializeAsync(AsyncPackage package) 26 | { 27 | // Switch to the main thread - the call to AddCommand in AddToToolbox's constructor requires the UI thread. 28 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 29 | 30 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 31 | Instance = new AddToToolbox(package, commandService); 32 | } 33 | 34 | #pragma warning disable VSTHRD100 // Avoid async void methods 35 | private async void Execute(object sender, EventArgs e) 36 | #pragma warning restore VSTHRD100 // Avoid async void methods 37 | { 38 | try 39 | { 40 | await OutputPane.Instance.WriteAsync($"Adding snippets from '{this.SelectedFileName}' to the Toolbox."); 41 | var itemCount = await ToolboxInteractionLogic.LoadToolboxItemsAsync(this.SelectedFileName); 42 | 43 | var plural = itemCount == 1 ? string.Empty : "s"; 44 | 45 | await OutputPane.Instance.WriteAsync($"Added {itemCount} snippet{plural} to the Toolbox."); 46 | } 47 | catch (Exception exc) 48 | { 49 | await OutputPane.Instance.WriteAsync("Error adding to toolbox: " + exc.Message); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DemoSnippets/Commands/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | using Microsoft.VisualStudio; 9 | using Microsoft.VisualStudio.Shell; 10 | using Microsoft.VisualStudio.Shell.Interop; 11 | 12 | namespace DemoSnippets.Commands 13 | { 14 | internal class BaseCommand 15 | { 16 | public static readonly Guid CommandSet = new Guid("edc0c9c2-6d4c-4c5c-855f-6d4e670f519d"); 17 | 18 | public BaseCommand(AsyncPackage package, OleMenuCommandService commandService) 19 | { 20 | this.Package = package ?? throw new ArgumentNullException(nameof(package)); 21 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 22 | } 23 | 24 | public static BaseCommand Instance { get; protected set; } 25 | 26 | protected string SelectedFileName { get; set; } 27 | 28 | protected AsyncPackage Package { get; private set; } 29 | 30 | protected Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => this.Package; 31 | 32 | protected void MenuItem_BeforeQueryStatus(object sender, EventArgs e) 33 | { 34 | ThreadHelper.ThrowIfNotOnUIThread(); 35 | 36 | if (sender is OleMenuCommand menuCmd) 37 | { 38 | menuCmd.Visible = menuCmd.Enabled = false; 39 | 40 | if (!this.IsSingleProjectItemSelection(out var hierarchy, out var itemId)) 41 | { 42 | this.SelectedFileName = null; 43 | return; 44 | } 45 | 46 | ((IVsProject)hierarchy).GetMkDocument(itemId, out var itemFullPath); 47 | var transformFileInfo = new FileInfo(itemFullPath); 48 | 49 | // Save the name of the selected file so we have it when the command is executed 50 | this.SelectedFileName = transformFileInfo.FullName; 51 | 52 | var lowercaseName = transformFileInfo.Name.ToLowerInvariant(); 53 | 54 | if (lowercaseName.EndsWith(".demosnippets") || lowercaseName.Contains(".demosnippets.")) 55 | { 56 | menuCmd.Visible = menuCmd.Enabled = true; 57 | } 58 | } 59 | } 60 | 61 | private bool IsSingleProjectItemSelection(out IVsHierarchy hierarchy, out uint itemId) 62 | { 63 | ThreadHelper.ThrowIfNotOnUIThread(); 64 | 65 | hierarchy = null; 66 | itemId = VSConstants.VSITEMID_NIL; 67 | 68 | var solution = Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; 69 | #pragma warning disable SA1119 // Statement must not use unnecessary parenthesis 70 | if (!(Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(SVsShellMonitorSelection)) is IVsMonitorSelection monitorSelection) || solution == null) 71 | #pragma warning restore SA1119 // Statement must not use unnecessary parenthesis 72 | { 73 | return false; 74 | } 75 | 76 | var hierarchyPtr = IntPtr.Zero; 77 | var selectionContainerPtr = IntPtr.Zero; 78 | 79 | try 80 | { 81 | var hr = monitorSelection.GetCurrentSelection(out hierarchyPtr, out itemId, out var multiItemSelect, out selectionContainerPtr); 82 | 83 | if (ErrorHandler.Failed(hr) || hierarchyPtr == IntPtr.Zero || itemId == VSConstants.VSITEMID_NIL) 84 | { 85 | // there is no selection 86 | return false; 87 | } 88 | 89 | if (multiItemSelect != null) 90 | { 91 | // multiple items are selected 92 | return false; 93 | } 94 | 95 | if (itemId == VSConstants.VSITEMID_ROOT) 96 | { 97 | // there is a hierarchy root node selected, thus it is not a single item inside a project 98 | return false; 99 | } 100 | 101 | hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy; 102 | if (hierarchy == null) 103 | { 104 | return false; 105 | } 106 | 107 | if (ErrorHandler.Failed(solution.GetGuidOfProject(hierarchy, out var _))) 108 | { 109 | // hierarchy is not a project inside the Solution if it does not have a ProjectID Guid 110 | return false; 111 | } 112 | 113 | // if we got this far then there is a single project item selected 114 | return true; 115 | } 116 | finally 117 | { 118 | if (selectionContainerPtr != IntPtr.Zero) 119 | { 120 | Marshal.Release(selectionContainerPtr); 121 | } 122 | 123 | if (hierarchyPtr != IntPtr.Zero) 124 | { 125 | Marshal.Release(hierarchyPtr); 126 | } 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /DemoSnippets/Commands/RefreshThisFileInToolbox.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace DemoSnippets.Commands 11 | { 12 | internal sealed class RefreshThisFileInToolbox : BaseCommand 13 | { 14 | public const int CommandId = 0x0500; 15 | 16 | private RefreshThisFileInToolbox(AsyncPackage package, OleMenuCommandService commandService) 17 | : base(package, commandService) 18 | { 19 | var menuCommandId = new CommandID(CommandSet, CommandId); 20 | var menuItem = new OleMenuCommand(this.Execute, menuCommandId); 21 | menuItem.BeforeQueryStatus += this.MenuItem_BeforeQueryStatus; 22 | commandService.AddCommand(menuItem); 23 | } 24 | 25 | public static async Task InitializeAsync(AsyncPackage package) 26 | { 27 | // Switch to the main thread - the call to AddCommand in command's constructor requires the UI thread. 28 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 29 | 30 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 31 | Instance = new RefreshThisFileInToolbox(package, commandService); 32 | } 33 | 34 | #pragma warning disable VSTHRD100 // Avoid async void methods 35 | private async void Execute(object sender, EventArgs e) 36 | #pragma warning restore VSTHRD100 // Avoid async void methods 37 | { 38 | try 39 | { 40 | await ToolboxInteractionLogic.RefreshEntriesFromFileAsync(this.SelectedFileName); 41 | } 42 | catch (Exception exc) 43 | { 44 | await OutputPane.Instance.WriteAsync("Error refreshing this file in toolbox: " + exc.Message); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DemoSnippets/Commands/RemoveAllDemoSnippets.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace DemoSnippets.Commands 11 | { 12 | internal sealed class RemoveAllDemoSnippets : BaseCommand 13 | { 14 | public const int CommandId = 0x0200; 15 | 16 | private RemoveAllDemoSnippets(AsyncPackage package, OleMenuCommandService commandService) 17 | : base(package, commandService) 18 | { 19 | var menuCommandId = new CommandID(CommandSet, CommandId); 20 | var menuItem = new MenuCommand(this.Execute, menuCommandId); 21 | commandService.AddCommand(menuItem); 22 | } 23 | 24 | public static async Task InitializeAsync(AsyncPackage package) 25 | { 26 | // Switch to the main thread - the call to AddCommand in command's constructor requires the UI thread. 27 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 28 | 29 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 30 | Instance = new RemoveAllDemoSnippets(package, commandService); 31 | } 32 | 33 | #pragma warning disable VSTHRD100 // Avoid async void methods 34 | private async void Execute(object sender, EventArgs e) 35 | #pragma warning restore VSTHRD100 // Avoid async void methods 36 | { 37 | try 38 | { 39 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(this.Package.DisposalToken); 40 | 41 | await OutputPane.Instance.WriteAsync($"Attempting to remove all demosnippets from the toolbox."); 42 | 43 | await ToolboxInteractionLogic.RemoveAllDemoSnippetsAsync(this.Package.DisposalToken); 44 | } 45 | catch (Exception exc) 46 | { 47 | await OutputPane.Instance.WriteAsync("Error removing all demo snippets: " + exc.Message); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DemoSnippets/Commands/RemoveEmptyTabs.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace DemoSnippets.Commands 11 | { 12 | internal sealed class RemoveEmptyTabs : BaseCommand 13 | { 14 | public const int CommandId = 0x0400; 15 | 16 | private RemoveEmptyTabs(AsyncPackage package, OleMenuCommandService commandService) 17 | : base(package, commandService) 18 | { 19 | var menuCommandId = new CommandID(CommandSet, CommandId); 20 | var menuItem = new MenuCommand(this.Execute, menuCommandId); 21 | commandService.AddCommand(menuItem); 22 | } 23 | 24 | public static async Task InitializeAsync(AsyncPackage package) 25 | { 26 | // Switch to the main thread - the call to AddCommand in command's constructor requires the UI thread. 27 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 28 | 29 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 30 | Instance = new RemoveEmptyTabs(package, commandService); 31 | } 32 | 33 | #pragma warning disable VSTHRD100 // Avoid async void methods 34 | private async void Execute(object sender, EventArgs e) 35 | #pragma warning restore VSTHRD100 // Avoid async void methods 36 | { 37 | try 38 | { 39 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(this.Package.DisposalToken); 40 | 41 | await OutputPane.Instance.WriteAsync("Attempting to remove any empty tabs in Toolbox."); 42 | 43 | var tabs = await ToolboxInteractionLogic.GetAllTabNamesAsync(this.Package.DisposalToken); 44 | 45 | foreach (var tab in tabs) 46 | { 47 | await ToolboxInteractionLogic.RemoveTabIfEmptyAsync(tab, this.Package.DisposalToken); 48 | } 49 | } 50 | catch (Exception exc) 51 | { 52 | await OutputPane.Instance.WriteAsync("Error removing empty tabs: " + exc.Message); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippetRunningDocTableEvents.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.IO; 6 | using Microsoft.VisualStudio; 7 | using Microsoft.VisualStudio.Shell; 8 | using Microsoft.VisualStudio.Shell.Interop; 9 | 10 | namespace DemoSnippets 11 | { 12 | internal class DemoSnippetRunningDocTableEvents : IVsRunningDocTableEvents 13 | { 14 | private readonly DemoSnippetsPackage package; 15 | private readonly RunningDocumentTable runningDocumentTable; 16 | 17 | public DemoSnippetRunningDocTableEvents(DemoSnippetsPackage package, RunningDocumentTable runningDocumentTable) 18 | { 19 | this.package = package; 20 | this.runningDocumentTable = runningDocumentTable; 21 | } 22 | 23 | public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) 24 | { 25 | return VSConstants.S_OK; 26 | } 27 | 28 | public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) 29 | { 30 | return VSConstants.S_OK; 31 | } 32 | 33 | public int OnAfterSave(uint docCookie) 34 | { 35 | if (this.package.IsFileRefreshEnabled) 36 | { 37 | var documentInfo = this.runningDocumentTable.GetDocumentInfo(docCookie); 38 | 39 | var documentPath = documentInfo.Moniker; 40 | 41 | var extension = Path.GetExtension(documentPath); 42 | var fileName = Path.GetFileName(documentPath); 43 | 44 | if (extension?.ToLowerInvariant() == ".demosnippets" || fileName.ToLowerInvariant().Contains(".demosnippets.")) 45 | { 46 | _ = this.package.JoinableTaskFactory.RunAsync(async () => 47 | await ToolboxInteractionLogic.RefreshEntriesFromFileAsync(documentPath)); 48 | } 49 | } 50 | 51 | return VSConstants.S_OK; 52 | } 53 | 54 | public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) 55 | { 56 | return VSConstants.S_OK; 57 | } 58 | 59 | public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) 60 | { 61 | return VSConstants.S_OK; 62 | } 63 | 64 | public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) 65 | { 66 | return VSConstants.S_OK; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippets.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Debug 21 | AnyCPU 22 | 2.0 23 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 24 | {12E84876-766B-4E73-A499-018DEC0E9E8F} 25 | Library 26 | Properties 27 | DemoSnippets 28 | DemoSnippets 29 | v4.8.1 30 | true 31 | true 32 | true 33 | true 34 | true 35 | false 36 | Program 37 | $(DevEnvDir)devenv.exe 38 | /rootsuffix Exp 39 | 40 | 41 | true 42 | full 43 | false 44 | bin\Debug\ 45 | DEBUG;TRACE 46 | prompt 47 | 4 48 | DemoSnippets.ruleset 49 | 50 | 51 | pdbonly 52 | true 53 | bin\Release\ 54 | TRACE 55 | prompt 56 | 4 57 | DemoSnippets.ruleset 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Component 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | True 86 | True 87 | source.extension.vsixmanifest 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Resources\LICENSE 99 | true 100 | 101 | 102 | 103 | 104 | PreserveNewest 105 | true 106 | 107 | 108 | true 109 | 110 | 111 | 112 | true 113 | 114 | 115 | true 116 | 117 | 118 | 119 | Designer 120 | VsixManifestGenerator 121 | source.extension.cs 122 | 123 | 124 | 125 | 126 | 127 | Menus.ctmenu 128 | Designer 129 | 130 | 131 | 132 | 133 | 134 | 2.5.192 135 | 136 | 137 | 2.22.0 138 | 139 | 140 | 6.1.0 141 | 142 | 143 | 17.6.36389 144 | 145 | 146 | 17.7.47 147 | runtime; build; native; contentfiles; analyzers; buildtransitive 148 | all 149 | 150 | 151 | 17.12.19 152 | runtime; build; native; contentfiles; analyzers; buildtransitive 153 | all 154 | 155 | 156 | 8.0.0 157 | 158 | 159 | 9.0.0 160 | 161 | 162 | 1.7.0 163 | runtime; build; native; contentfiles; analyzers; buildtransitive 164 | all 165 | 166 | 167 | 168 | 169 | true 170 | VSPackage 171 | Designer 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | {09BB7475-2826-42A2-8B9E-23333129A010} 185 | DemoSnippets.ItemTemplates 186 | ItemTemplates 187 | false 188 | TemplateProjectOutputGroup%3b 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippets.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippetsLineType.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | namespace DemoSnippets 6 | { 7 | public enum DemoSnippetsLineType 8 | { 9 | Other, 10 | Comment, 11 | Tab, 12 | Label, 13 | EndSnippet, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippetsLineTypeIdentifier.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | namespace DemoSnippets 8 | { 9 | public class DemoSnippetsLineTypeIdentifier 10 | { 11 | internal const string LineStartComment = "#"; 12 | internal const string LineStartTab = "tab:"; 13 | internal const string LineStartLabel = "-"; 14 | private const string CommentTab = "DEMOSNIPPETS-TAB"; 15 | private const string CommentLabel = "DEMOSNIPPETS-LABEL"; 16 | private const string CommentEndSnippet = "DEMOSNIPPETS-ENDSNIPPET"; 17 | 18 | // For some languages (e.g C++) '#' has a separate meaning to a DemoSnippets comment indicator 19 | // Track if in a file containing subExt formatting so know not to look for standard formatting 20 | private bool subExtFormattingOnly = false; 21 | 22 | public DemoSnippetsLineType GetLineType(string line) 23 | { 24 | if (!this.subExtFormattingOnly && line.StartsWith(LineStartComment)) 25 | { 26 | return DemoSnippetsLineType.Comment; 27 | } 28 | else if (!this.subExtFormattingOnly && line.StartsWith(LineStartLabel)) 29 | { 30 | return DemoSnippetsLineType.Label; 31 | } 32 | else if (line.ToUpperInvariant().Contains(CommentLabel)) 33 | { 34 | this.subExtFormattingOnly = true; 35 | return DemoSnippetsLineType.Label; 36 | } 37 | else if (!this.subExtFormattingOnly && line.ToLowerInvariant().StartsWith(LineStartTab)) 38 | { 39 | return DemoSnippetsLineType.Tab; 40 | } 41 | else if (line.ToUpperInvariant().Contains(CommentTab)) 42 | { 43 | this.subExtFormattingOnly = true; 44 | return DemoSnippetsLineType.Tab; 45 | } 46 | else if (line.ToUpperInvariant().Contains(CommentEndSnippet)) 47 | { 48 | this.subExtFormattingOnly = true; 49 | return DemoSnippetsLineType.EndSnippet; 50 | } 51 | else 52 | { 53 | return DemoSnippetsLineType.Other; 54 | } 55 | } 56 | 57 | public string GetTabName(string line) 58 | { 59 | if (line.ToLowerInvariant().StartsWith(LineStartTab)) 60 | { 61 | return line.Substring(4).Trim(); 62 | } 63 | else 64 | { 65 | var startIndex = line.IndexOf(CommentTab, StringComparison.InvariantCultureIgnoreCase); 66 | 67 | return RemoveClosingCommentTabs(line.Substring(startIndex + CommentTab.Length)).Trim(); 68 | } 69 | } 70 | 71 | public string GetLabelName(string line) 72 | { 73 | if (line.ToLowerInvariant().StartsWith(LineStartLabel)) 74 | { 75 | return line.Substring(1).Trim(); 76 | } 77 | else 78 | { 79 | var startIndex = line.IndexOf(CommentLabel, StringComparison.InvariantCultureIgnoreCase); 80 | 81 | return RemoveClosingCommentTabs(line.Substring(startIndex + CommentLabel.Length)).Trim(); 82 | } 83 | } 84 | 85 | private static string RemoveClosingCommentTabs(string input) 86 | { 87 | return input.Replace("*/", string.Empty) 88 | .Replace("-->", string.Empty) 89 | .TrimEnd(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippetsPackage.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Runtime.InteropServices; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using DemoSnippets.Commands; 12 | using EnvDTE; 13 | using Microsoft.ApplicationInsights; 14 | using Microsoft.ApplicationInsights.Extensibility; 15 | using Microsoft.VisualStudio; 16 | using Microsoft.VisualStudio.Shell; 17 | using Microsoft.VisualStudio.Shell.Interop; 18 | using Task = System.Threading.Tasks.Task; 19 | 20 | namespace DemoSnippets 21 | { 22 | [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.SolutionHasMultipleProjects, PackageAutoLoadFlags.BackgroundLoad)] 23 | [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.SolutionHasSingleProject, PackageAutoLoadFlags.BackgroundLoad)] 24 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 25 | [InstalledProductRegistration("#110", "#112", Vsix.Version, IconResourceID = 400)] // Info on this package for Help/About 26 | [ProvideMenuResource("Menus.ctmenu", 1)] 27 | [Guid(DemoSnippetsPackage.PackageGuidString)] 28 | [ProvideOptionPage(typeof(OptionPageGrid), Vsix.Name, "General", 0, 0, true)] 29 | [ProvideProfileAttribute(typeof(OptionPageGrid), Vsix.Name, "General", 106, 107, isToolsOptionPage: true, DescriptionResourceID = 108)] 30 | public sealed class DemoSnippetsPackage : AsyncPackage 31 | { 32 | public const string PackageGuidString = "9538932d-8cd5-4512-adb9-4c6b73adf57c"; 33 | 34 | public DemoSnippetsPackage() 35 | { 36 | } 37 | 38 | public bool IsAutoLoadEnabled 39 | { 40 | get 41 | { 42 | var options = (OptionPageGrid)this.GetDialogPage(typeof(OptionPageGrid)); 43 | return options.AutoLoadUnload; 44 | } 45 | } 46 | 47 | public bool IsFileRefreshEnabled 48 | { 49 | get 50 | { 51 | var options = (OptionPageGrid)this.GetDialogPage(typeof(OptionPageGrid)); 52 | return options.RefreshOnFileSave; 53 | } 54 | } 55 | 56 | /// 57 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 58 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 59 | /// 60 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 61 | /// A provider for progress updates. 62 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 63 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 64 | { 65 | // When initialized asynchronously, the current thread may be a background thread at this point. 66 | // Do any initialization that requires the UI thread after switching to the UI thread. 67 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 68 | 69 | await OutputPane.Instance.WriteAsync($"{Vsix.Name} v{Vsix.Version}"); 70 | await OutputPane.Instance.WriteAsync("The DemoSnippets extension works with .demosnippets files. Learn more at https://github.com/mrlacey/DemoSnippets/formatting.md "); 71 | await OutputPane.Instance.WriteAsync("If you have problems with this extension, or suggestions for improvement, report them at https://github.com/mrlacey/DemoSnippets/issues/new "); 72 | await OutputPane.Instance.WriteAsync(string.Empty); 73 | 74 | await ToolboxInteractionLogic.InitializeAsync(this); 75 | await AddToToolbox.InitializeAsync(this); 76 | await RemoveAllDemoSnippets.InitializeAsync(this); 77 | await AddAllDemoSnippets.InitializeAsync(this); 78 | await RemoveEmptyTabs.InitializeAsync(this); 79 | await RefreshThisFileInToolbox.InitializeAsync(this); 80 | 81 | await SponsorRequestHelper.CheckIfNeedToShowAsync(); 82 | 83 | await this.SetUpRunningDocumentTableEventsAsync(cancellationToken); 84 | 85 | // Since this package might not be initialized until after a solution has finished loading, 86 | // we need to check if a solution has already been loaded and then handle it. 87 | bool isSolutionLoaded = await this.IsSolutionLoadedAsync(cancellationToken); 88 | 89 | if (isSolutionLoaded) 90 | { 91 | await this.HandleOpenSolutionAsync(cancellationToken); 92 | } 93 | 94 | // Listen for subsequent solution events 95 | Microsoft.VisualStudio.Shell.Events.SolutionEvents.OnAfterOpenSolution += this.HandleOpenSolution; 96 | Microsoft.VisualStudio.Shell.Events.SolutionEvents.OnAfterCloseSolution += this.HandleCloseSolution; 97 | 98 | await TrackBasicUsageAnalyticsAsync(); 99 | } 100 | 101 | private static async Task TrackBasicUsageAnalyticsAsync() 102 | { 103 | try 104 | { 105 | #if !DEBUG 106 | if (string.IsNullOrWhiteSpace(AnalyticsConfig.TelemetryConnectionString)) 107 | { 108 | return; 109 | } 110 | 111 | var config = new TelemetryConfiguration 112 | { 113 | ConnectionString = AnalyticsConfig.TelemetryConnectionString, 114 | }; 115 | 116 | var client = new TelemetryClient(config); 117 | 118 | var properties = new Dictionary 119 | { 120 | { "VsixVersion", Vsix.Version }, 121 | { "VsVersion", Microsoft.VisualStudio.Telemetry.TelemetryService.DefaultSession?.GetSharedProperty("VS.Core.ExeVersion") }, 122 | { "Architecture", RuntimeInformation.ProcessArchitecture.ToString() }, 123 | { "MsInternal", Microsoft.VisualStudio.Telemetry.TelemetryService.DefaultSession?.IsUserMicrosoftInternal.ToString() }, 124 | }; 125 | 126 | client.TrackEvent(Vsix.Name, properties); 127 | #endif 128 | } 129 | catch (Exception exc) 130 | { 131 | System.Diagnostics.Debug.WriteLine(exc); 132 | await OutputPane.Instance.WriteAsync("Error tracking usage analytics: " + exc.Message); 133 | } 134 | } 135 | 136 | private async Task SetUpRunningDocumentTableEventsAsync(CancellationToken cancellationToken) 137 | { 138 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 139 | 140 | var runningDocumentTable = new RunningDocumentTable(this); 141 | 142 | var plugin = new DemoSnippetRunningDocTableEvents(this, runningDocumentTable); 143 | 144 | runningDocumentTable.Advise(plugin); 145 | } 146 | 147 | private void HandleCloseSolution(object sender, EventArgs e) 148 | { 149 | this.JoinableTaskFactory.RunAsync(() => this.HandleCloseSolutionAsync(this.DisposalToken)).Task.LogAndForget("DemoSnippets"); 150 | } 151 | 152 | private async Task HandleCloseSolutionAsync(CancellationToken cancellationToken) 153 | { 154 | if (this.IsAutoLoadEnabled) 155 | { 156 | await OutputPane.Instance.WriteAsync("Removing DemoSnippets from the Toolbox as solution has been closed."); 157 | 158 | await ToolboxInteractionLogic.RemoveAllDemoSnippetsAsync(cancellationToken); 159 | } 160 | } 161 | 162 | private async Task IsSolutionLoadedAsync(CancellationToken cancellationToken) 163 | { 164 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 165 | 166 | if (!(await this.GetServiceAsync(typeof(SVsSolution)) is IVsSolution solService)) 167 | { 168 | throw new ArgumentNullException(nameof(solService)); 169 | } 170 | 171 | ErrorHandler.ThrowOnFailure(solService.GetProperty((int)__VSPROPID.VSPROPID_IsSolutionOpen, out object value)); 172 | 173 | return value is bool isSolOpen && isSolOpen; 174 | } 175 | 176 | private void HandleOpenSolution(object sender, EventArgs e) 177 | { 178 | this.JoinableTaskFactory.RunAsync(() => this.HandleOpenSolutionAsync(this.DisposalToken)).Task.LogAndForget("DemoSnippets"); 179 | } 180 | 181 | private async Task HandleOpenSolutionAsync(CancellationToken cancellationToken) 182 | { 183 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 184 | 185 | if (this.IsAutoLoadEnabled) 186 | { 187 | // Get all *.demosnippets files from the solution 188 | // Do this now for performance and to avoid thread issues 189 | if (await this.GetServiceAsync(typeof(DTE)) is DTE dte) 190 | { 191 | var fileName = dte.Solution.FileName; 192 | 193 | if (!string.IsNullOrWhiteSpace(fileName) && File.Exists(fileName)) 194 | { 195 | var slnDir = Path.GetDirectoryName(fileName); 196 | var (fileCount, snippetCount) = await ToolboxInteractionLogic.ProcessAllSnippetFilesAsync(slnDir); 197 | 198 | var filePlural = fileCount == 1 ? string.Empty : "s"; 199 | var snippetPlural = snippetCount == 1 ? string.Empty : "s"; 200 | 201 | await OutputPane.Instance.WriteAsync($"Added {snippetCount} snippet{snippetPlural}, from {fileCount} file{filePlural}."); 202 | } 203 | else 204 | { 205 | await OutputPane.Instance.WriteAsync("Could not access solution file to use to find .demosnippets files."); 206 | } 207 | } 208 | } 209 | else 210 | { 211 | await OutputPane.Instance.WriteAsync("Automatic loading of .demosnippets files has been disabled."); 212 | } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippetsPackage.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 39 | 45 | 51 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /DemoSnippets/DemoSnippetsParser.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace DemoSnippets 9 | { 10 | public class DemoSnippetsParser 11 | { 12 | public List GetItemsToAdd(string[] lines) 13 | { 14 | var result = new List(); 15 | 16 | ToolboxEntry toAdd = null; 17 | 18 | // need to keep track of last set tab name separately so can remember it after ENDSNIPPET sections 19 | var tab = string.Empty; 20 | 21 | var lineIdentifier = new DemoSnippetsLineTypeIdentifier(); 22 | 23 | for (var i = 0; i < lines.Length; i++) 24 | { 25 | var line = lines[i]; 26 | var lineType = lineIdentifier.GetLineType(line); 27 | 28 | if (lineType == DemoSnippetsLineType.Comment) 29 | { 30 | // Ignore comments 31 | continue; 32 | } 33 | 34 | if (lineType == DemoSnippetsLineType.Tab) 35 | { 36 | if (toAdd == null) 37 | { 38 | toAdd = new ToolboxEntry(); 39 | } 40 | 41 | if (!string.IsNullOrWhiteSpace(toAdd.Snippet)) 42 | { 43 | toAdd.Snippet = this.RemoveBlankLinesFromEnds(toAdd.Snippet); 44 | 45 | result.Add(toAdd); 46 | 47 | toAdd = new ToolboxEntry(); 48 | } 49 | 50 | tab = lineIdentifier.GetTabName(line); 51 | toAdd.Tab = tab; 52 | 53 | continue; 54 | } 55 | 56 | if (lineType == DemoSnippetsLineType.Label) 57 | { 58 | if (toAdd == null) 59 | { 60 | toAdd = new ToolboxEntry 61 | { 62 | Label = lineIdentifier.GetLabelName(line), 63 | Tab = tab, 64 | }; 65 | } 66 | else 67 | { 68 | if (string.IsNullOrWhiteSpace(toAdd.Snippet)) 69 | { 70 | toAdd.Label = lineIdentifier.GetLabelName(line); 71 | } 72 | else 73 | { 74 | toAdd.Snippet = this.RemoveBlankLinesFromEnds(toAdd.Snippet); 75 | 76 | result.Add(toAdd); 77 | 78 | toAdd = new ToolboxEntry 79 | { 80 | Label = lineIdentifier.GetLabelName(line), 81 | Tab = tab, 82 | }; 83 | } 84 | } 85 | } 86 | else if (lineType == DemoSnippetsLineType.EndSnippet) 87 | { 88 | if (!string.IsNullOrWhiteSpace(toAdd?.Snippet)) 89 | { 90 | toAdd.Snippet = this.RemoveBlankLinesFromEnds(toAdd.Snippet); 91 | 92 | result.Add(toAdd); 93 | 94 | toAdd = null; 95 | } 96 | } 97 | else 98 | { 99 | if (toAdd != null) 100 | { 101 | if (string.IsNullOrWhiteSpace(toAdd.Label)) 102 | { 103 | toAdd.Label = line.Trim(); 104 | } 105 | 106 | if (string.IsNullOrWhiteSpace(toAdd.Snippet)) 107 | { 108 | toAdd.Snippet = line; 109 | } 110 | else 111 | { 112 | toAdd.Snippet += $"{Environment.NewLine}{line}"; 113 | } 114 | } 115 | } 116 | } 117 | 118 | if (toAdd != null && !string.IsNullOrWhiteSpace(toAdd.Snippet)) 119 | { 120 | toAdd.Snippet = this.RemoveBlankLinesFromEnds(toAdd.Snippet); 121 | 122 | result.Add(toAdd); 123 | } 124 | 125 | return result; 126 | } 127 | 128 | private string RemoveBlankLinesFromEnds(string snippet) 129 | { 130 | return snippet.Trim('\r', '\n'); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /DemoSnippets/Icons/Monikers.imagemanifest: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /DemoSnippets/Icons/demos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/DemoSnippets/Icons/demos.png -------------------------------------------------------------------------------- /DemoSnippets/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matt Lacey 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 | -------------------------------------------------------------------------------- /DemoSnippets/OptionPageGrid.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel; 6 | using Microsoft.VisualStudio.Shell; 7 | 8 | namespace DemoSnippets 9 | { 10 | public class OptionPageGrid : DialogPage 11 | { 12 | [Category("DemoSnippets")] 13 | [DisplayName("Auto-load when solution opens")] 14 | [Description("Automatically load all .demosnippets files in the solution when opened. Also removes them from the Toolbox when the solution is closed.")] 15 | public bool AutoLoadUnload { get; set; } = true; 16 | 17 | [Category("DemoSnippets")] 18 | [DisplayName("Refresh Toolbox on file save")] 19 | [Description("Automatically refresh Toolbox entries from open .demosnippets files when saved.")] 20 | public bool RefreshOnFileSave { get; set; } = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DemoSnippets/Outlining/OutliningTagger.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Microsoft.VisualStudio.Text; 9 | using Microsoft.VisualStudio.Text.Tagging; 10 | 11 | namespace DemoSnippets.Outlining 12 | { 13 | internal sealed class OutliningTagger : ITagger 14 | { 15 | private const string Ellipsis = "..."; 16 | private readonly ITextBuffer buffer; 17 | private ITextSnapshot snapshot; 18 | private List regions; 19 | 20 | public OutliningTagger(ITextBuffer buffer) 21 | { 22 | this.buffer = buffer; 23 | this.snapshot = buffer.CurrentSnapshot; 24 | this.regions = new List(); 25 | this.ReParse(); 26 | this.buffer.Changed += this.BufferChanged; 27 | } 28 | 29 | public event EventHandler TagsChanged; 30 | 31 | public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) 32 | { 33 | if (spans.Count == 0) 34 | { 35 | yield break; 36 | } 37 | 38 | List currentRegions = this.regions; 39 | ITextSnapshot currentSnapshot = this.snapshot; 40 | SnapshotSpan entire = new SnapshotSpan(spans[0].Start, spans[spans.Count - 1].End).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeExclusive); 41 | int startLineNumber = entire.Start.GetContainingLine().LineNumber; 42 | int endLineNumber = entire.End.GetContainingLine().LineNumber; 43 | 44 | foreach (var region in currentRegions) 45 | { 46 | if (region.StartLine <= endLineNumber && 47 | region.EndLine >= startLineNumber) 48 | { 49 | var startLine = currentSnapshot.GetLineFromLineNumber(region.StartLine); 50 | var endLine = currentSnapshot.GetLineFromLineNumber(region.EndLine); 51 | 52 | yield return new TagSpan( 53 | new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End), 54 | new OutliningRegionTag(false, false, Ellipsis, string.Empty)); 55 | } 56 | } 57 | } 58 | 59 | private static SnapshotSpan AsSnapshotSpan(Region region, ITextSnapshot snapshot) 60 | { 61 | var startLine = snapshot.GetLineFromLineNumber(region.StartLine); 62 | var endLine = (region.StartLine == region.EndLine) 63 | ? startLine 64 | : snapshot.GetLineFromLineNumber(region.EndLine); 65 | 66 | return new SnapshotSpan(startLine.Start + region.StartOffset, endLine.End); 67 | } 68 | 69 | private void BufferChanged(object sender, TextContentChangedEventArgs e) 70 | { 71 | // If this isn't the most up-to-date version of the buffer, then ignore it for now (we'll eventually get another change event). 72 | if (e.After != this.buffer.CurrentSnapshot) 73 | { 74 | return; 75 | } 76 | 77 | this.ReParse(); 78 | } 79 | 80 | private void ReParse() 81 | { 82 | ITextSnapshot newSnapshot = this.buffer.CurrentSnapshot; 83 | List newRegions = new List(); 84 | 85 | PartialRegion currentTab = null; 86 | PartialRegion currentLabel = null; 87 | 88 | foreach (var line in newSnapshot.Lines) 89 | { 90 | string text = line.GetText(); 91 | 92 | if (text.StartsWith(DemoSnippetsLineTypeIdentifier.LineStartTab, StringComparison.InvariantCultureIgnoreCase)) 93 | { 94 | if (currentLabel != null) 95 | { 96 | newRegions.Add(new Region() 97 | { 98 | Level = 2, 99 | StartLine = currentLabel.StartLine, 100 | StartOffset = currentLabel.StartOffset, 101 | EndLine = line.LineNumber - 1, 102 | }); 103 | 104 | currentLabel = null; 105 | } 106 | 107 | if (currentTab != null) 108 | { 109 | newRegions.Add(new Region() 110 | { 111 | Level = 1, 112 | StartLine = currentTab.StartLine, 113 | StartOffset = currentTab.StartOffset, 114 | EndLine = line.LineNumber - 1, 115 | }); 116 | 117 | currentTab = null; 118 | } 119 | 120 | currentTab = new PartialRegion() 121 | { 122 | StartLine = line.LineNumber, 123 | StartOffset = text.TrimEnd().Length, 124 | }; 125 | } 126 | else if (text.StartsWith(DemoSnippetsLineTypeIdentifier.LineStartLabel, StringComparison.InvariantCultureIgnoreCase)) 127 | { 128 | if (currentLabel != null) 129 | { 130 | newRegions.Add(new Region() 131 | { 132 | Level = 2, 133 | StartLine = currentLabel.StartLine, 134 | StartOffset = currentLabel.StartOffset, 135 | EndLine = line.LineNumber - 1, 136 | }); 137 | 138 | currentLabel = null; 139 | } 140 | 141 | currentLabel = new PartialRegion() 142 | { 143 | StartLine = line.LineNumber, 144 | StartOffset = text.TrimEnd().Length, 145 | PartialParent = currentTab, 146 | }; 147 | } 148 | } 149 | 150 | // Close any regions still open 151 | if (currentLabel != null) 152 | { 153 | newRegions.Add(new Region() 154 | { 155 | Level = 2, 156 | StartLine = currentLabel.StartLine, 157 | StartOffset = currentLabel.StartOffset, 158 | EndLine = newSnapshot.Lines.Count() - 1, 159 | }); 160 | } 161 | 162 | if (currentTab != null) 163 | { 164 | newRegions.Add(new Region() 165 | { 166 | Level = 1, 167 | StartLine = currentTab.StartLine, 168 | StartOffset = currentTab.StartOffset, 169 | EndLine = newSnapshot.Lines.Count() - 1, 170 | }); 171 | 172 | currentTab = null; 173 | } 174 | 175 | // determine the changed span, and send a changed event with the new spans 176 | List oldSpans = 177 | new List(this.regions.Select(r => AsSnapshotSpan(r, this.snapshot) 178 | .TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive) 179 | .Span)); 180 | List newSpans = 181 | new List(newRegions.Select(r => AsSnapshotSpan(r, newSnapshot).Span)); 182 | 183 | NormalizedSpanCollection oldSpanCollection = new NormalizedSpanCollection(oldSpans); 184 | NormalizedSpanCollection newSpanCollection = new NormalizedSpanCollection(newSpans); 185 | 186 | var removed = NormalizedSpanCollection.Difference(oldSpanCollection, newSpanCollection); 187 | 188 | int changeStart = int.MaxValue; 189 | int changeEnd = -1; 190 | 191 | if (removed.Count > 0) 192 | { 193 | changeStart = removed[0].Start; 194 | changeEnd = removed[removed.Count - 1].End; 195 | } 196 | 197 | if (newSpans.Count > 0) 198 | { 199 | changeStart = Math.Min(changeStart, newSpans[0].Start); 200 | changeEnd = Math.Max(changeEnd, newSpans[newSpans.Count - 1].End); 201 | } 202 | 203 | this.snapshot = newSnapshot; 204 | this.regions = newRegions; 205 | 206 | if (changeStart <= changeEnd) 207 | { 208 | ITextSnapshot snap = this.snapshot; 209 | this.TagsChanged?.Invoke( 210 | this, 211 | new SnapshotSpanEventArgs(new SnapshotSpan(this.snapshot, Span.FromBounds(changeStart, changeEnd)))); 212 | } 213 | } 214 | 215 | private class PartialRegion 216 | { 217 | public int StartLine { get; set; } 218 | 219 | public int StartOffset { get; set; } 220 | 221 | public int Level { get; set; } 222 | 223 | public PartialRegion PartialParent { get; set; } 224 | } 225 | 226 | private class Region : PartialRegion 227 | { 228 | public int EndLine { get; set; } 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /DemoSnippets/Outlining/OutliningTaggerProvider.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel.Composition; 6 | using Microsoft.VisualStudio.Text; 7 | using Microsoft.VisualStudio.Text.Tagging; 8 | using Microsoft.VisualStudio.Utilities; 9 | 10 | namespace DemoSnippets.Outlining 11 | { 12 | [Export(typeof(ITaggerProvider))] 13 | [TagType(typeof(IOutliningRegionTag))] 14 | [ContentType("text")] 15 | internal sealed class OutliningTaggerProvider : ITaggerProvider 16 | { 17 | public ITagger CreateTagger(ITextBuffer buffer) 18 | where T : ITag 19 | { 20 | // Create a single tagger for each buffer 21 | return buffer.Properties.GetOrCreateSingletonProperty( 22 | () => { return new OutliningTagger(buffer) as ITagger; }); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DemoSnippets/OutputPane.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using Microsoft.VisualStudio; 8 | using Microsoft.VisualStudio.Shell; 9 | using Microsoft.VisualStudio.Shell.Interop; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace DemoSnippets 13 | { 14 | public class OutputPane 15 | { 16 | private static Guid dsPaneGuid = new Guid("DF45715C-F87B-4B53-B926-BB8C30C7F804"); 17 | 18 | private static OutputPane instance; 19 | 20 | private readonly IVsOutputWindowPane pane; 21 | 22 | private OutputPane() 23 | { 24 | ThreadHelper.ThrowIfNotOnUIThread(); 25 | 26 | if (ServiceProvider.GlobalProvider.GetService(typeof(SVsOutputWindow)) is IVsOutputWindow outWindow 27 | && (ErrorHandler.Failed(outWindow.GetPane(ref dsPaneGuid, out this.pane)) || this.pane == null)) 28 | { 29 | if (ErrorHandler.Failed(outWindow.CreatePane(ref dsPaneGuid, "Demo Snippets", 1, 0))) 30 | { 31 | System.Diagnostics.Debug.WriteLine("Failed to create output pane."); 32 | return; 33 | } 34 | 35 | if (ErrorHandler.Failed(outWindow.GetPane(ref dsPaneGuid, out this.pane)) || (this.pane == null)) 36 | { 37 | System.Diagnostics.Debug.WriteLine("Failed to get output pane."); 38 | } 39 | } 40 | } 41 | 42 | public static OutputPane Instance => instance ?? (instance = new OutputPane()); 43 | 44 | public async Task ActivateAsync() 45 | { 46 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); 47 | 48 | this.pane?.Activate(); 49 | } 50 | 51 | public async Task WriteAsync(string message) 52 | { 53 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); 54 | 55 | this.pane?.OutputStringThreadSafe($"{message}{Environment.NewLine}"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DemoSnippets/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | 8 | // General Information about an assembly is controlled through the following 9 | // set of attributes. Change these attribute values to modify the information 10 | // associated with an assembly. 11 | [assembly: AssemblyTitle(DemoSnippets.Vsix.Name)] 12 | [assembly: AssemblyDescription(DemoSnippets.Vsix.Description)] 13 | [assembly: AssemblyConfiguration("")] 14 | [assembly: AssemblyCompany("")] 15 | [assembly: AssemblyProduct(DemoSnippets.Vsix.Name)] 16 | [assembly: AssemblyCopyright("")] 17 | [assembly: AssemblyTrademark("")] 18 | [assembly: AssemblyCulture("")] 19 | 20 | // Setting ComVisible to false makes the types in this assembly not visible 21 | // to COM components. If you need to access a type in this assembly from 22 | // COM, set the ComVisible attribute to true on that type. 23 | [assembly: ComVisible(false)] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion(DemoSnippets.Vsix.Version)] 36 | [assembly: AssemblyFileVersion(DemoSnippets.Vsix.Version)] 37 | -------------------------------------------------------------------------------- /DemoSnippets/Resources/AddToToolboxPackage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/DemoSnippets/Resources/AddToToolboxPackage.ico -------------------------------------------------------------------------------- /DemoSnippets/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/DemoSnippets/Resources/Icon.png -------------------------------------------------------------------------------- /DemoSnippets/SponsorDetector.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.Threading.Tasks; 6 | 7 | namespace DemoSnippets 8 | { 9 | public class SponsorDetector 10 | { 11 | // This might be the code you see, but it's not what I compile into the extensions when built ;) 12 | public static async Task IsSponsorAsync() 13 | { 14 | return await Task.FromResult(false); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DemoSnippets/SponsorRequestHelper.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace DemoSnippets 9 | { 10 | public class SponsorRequestHelper 11 | { 12 | public static async Task CheckIfNeedToShowAsync() 13 | { 14 | if (await SponsorDetector.IsSponsorAsync()) 15 | { 16 | if (new Random().Next(1, 10) == 2) 17 | { 18 | await ShowThanksForSponsorshipMessageAsync(); 19 | } 20 | } 21 | else 22 | { 23 | await ShowPromptForSponsorshipAsync(); 24 | } 25 | } 26 | 27 | private static async Task ShowThanksForSponsorshipMessageAsync() 28 | { 29 | await OutputPane.Instance.WriteAsync("Thank you for your sponsorship. It really helps."); 30 | await OutputPane.Instance.WriteAsync("If you have ideas for new features or suggestions for new features"); 31 | await OutputPane.Instance.WriteAsync("please raise an issue at https://github.com/mrlacey/DemoSnippets/issues"); 32 | await OutputPane.Instance.WriteAsync(string.Empty); 33 | } 34 | 35 | private static async Task ShowPromptForSponsorshipAsync() 36 | { 37 | await OutputPane.Instance.WriteAsync("********************************************************************************************************"); 38 | await OutputPane.Instance.WriteAsync("This is a free extension that is made possible thanks to the kind and generous donations of:"); 39 | await OutputPane.Instance.WriteAsync(""); 40 | await OutputPane.Instance.WriteAsync("Daniel, James, Mike, Bill, unicorns39283, Martin, Richard, Alan, Howard, Mike, Dave, Joe, "); 41 | await OutputPane.Instance.WriteAsync("Alvin, Anders, Melvyn, Nik, Kevin, Richard, Orien, Shmueli, Gabriel, Martin, Neil, Daniel, "); 42 | await OutputPane.Instance.WriteAsync("Victor, Uno, Paula, Tom, Nick, Niki, chasingcode, luatnt, holeow, logarrhythmic, kokolorix, "); 43 | await OutputPane.Instance.WriteAsync("Guiorgy, Jessé, pharmacyhalo, MXM-7, atexinspect, João, hals1010, WTD-leachA, andermikael, "); 44 | await OutputPane.Instance.WriteAsync("spudwa, Cleroth, relentless-dev-purchases & 20+ more"); 45 | await OutputPane.Instance.WriteAsync(""); 46 | await OutputPane.Instance.WriteAsync("Join them to show you appreciation and ensure future maintenance and development by becoming a sponsor."); 47 | await OutputPane.Instance.WriteAsync(""); 48 | await OutputPane.Instance.WriteAsync("Go to https://github.com/sponsors/mrlacey"); 49 | await OutputPane.Instance.WriteAsync(""); 50 | await OutputPane.Instance.WriteAsync("Any amount, as either a one-off or on a monthly basis, is appreciated more than you can imagine."); 51 | await OutputPane.Instance.WriteAsync(""); 52 | await OutputPane.Instance.WriteAsync("I'll also tell you how to hide this message too. ;)"); 53 | await OutputPane.Instance.WriteAsync(""); 54 | await OutputPane.Instance.WriteAsync(""); 55 | await OutputPane.Instance.WriteAsync("If you can't afford to support financially, you can always"); 56 | await OutputPane.Instance.WriteAsync("leave a positive review at https://marketplace.visualstudio.com/items?itemName=MattLaceyLtd.DemoSnippets&ssr=false#review-details"); 57 | await OutputPane.Instance.WriteAsync(""); 58 | await OutputPane.Instance.WriteAsync("********************************************************************************************************"); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DemoSnippets/StyleCop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "documentationRules": { 5 | "companyName": "Matt Lacey Ltd.", 6 | "copyrightText": "Copyright (c) {companyName} All rights reserved.", 7 | "xmlHeader": true, 8 | "headerDecoration": "", 9 | "fileNamingConvention": "metadata", 10 | "documentInterfaces": false, 11 | "documentExposedElements": false, 12 | "documentInternalElements": false 13 | }, 14 | "layoutRules": { 15 | "newlineAtEndOfFile": "require" 16 | }, 17 | "orderingRules": { 18 | "usingDirectivesPlacement": "outsideNamespace" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /DemoSnippets/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace DemoSnippets 11 | { 12 | internal static class TaskExtensions 13 | { 14 | internal static void LogAndForget(this Task task, string source) => 15 | task.ContinueWith( 16 | (t, s) => VsShellUtilities.LogError(s as string, t.Exception?.ToString()), 17 | source, 18 | CancellationToken.None, 19 | TaskContinuationOptions.OnlyOnFaulted, 20 | VsTaskLibraryHelper.GetTaskScheduler(VsTaskRunContext.UIThreadNormalPriority)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DemoSnippets/ToolboxEntry.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | namespace DemoSnippets 6 | { 7 | public class ToolboxEntry 8 | { 9 | public ToolboxEntry() 10 | { 11 | this.Label = string.Empty; 12 | this.Snippet = string.Empty; 13 | this.Tab = string.Empty; 14 | } 15 | 16 | public string Label { get; set; } 17 | 18 | public string Snippet { get; set; } 19 | 20 | public string Tab { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DemoSnippets/ToolboxInteractionLogic.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Ltd. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | using Microsoft.VisualStudio; 13 | using Microsoft.VisualStudio.Shell; 14 | using Microsoft.VisualStudio.Shell.Interop; 15 | using IDataObject = Microsoft.VisualStudio.OLE.Interop.IDataObject; 16 | using Task = System.Threading.Tasks.Task; 17 | 18 | namespace DemoSnippets 19 | { 20 | public class ToolboxInteractionLogic 21 | { 22 | private const string DefaultTabName = "Demo"; 23 | private const string ToolboxDataSourceId = "DemoSnippets"; 24 | private const string ToolboxDataLabel = "DSLabel"; 25 | private const string ToolboxDataFileName = "DSFileName"; 26 | 27 | private readonly AsyncPackage package; 28 | 29 | public ToolboxInteractionLogic(AsyncPackage package) 30 | { 31 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 32 | } 33 | 34 | public static ToolboxInteractionLogic Instance { get; private set; } 35 | 36 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => this.package; 37 | 38 | public static async Task InitializeAsync(AsyncPackage package) 39 | { 40 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 41 | 42 | Instance = new ToolboxInteractionLogic(package); 43 | } 44 | 45 | public static async Task LoadToolboxItemsAsync(string filePath) 46 | { 47 | var lines = File.ReadAllLines(filePath); 48 | 49 | var dsp = new DemoSnippetsParser(); 50 | var toAdd = dsp.GetItemsToAdd(lines); 51 | 52 | var addedCount = 0; 53 | 54 | foreach (var item in toAdd) 55 | { 56 | if (string.IsNullOrWhiteSpace(item.Tab)) 57 | { 58 | item.Tab = DefaultTabName; 59 | } 60 | 61 | await AddToToolboxAsync(item.Tab, item.Label, item.Snippet, Path.GetFileName(filePath)); 62 | addedCount += 1; 63 | } 64 | 65 | return addedCount; 66 | } 67 | 68 | public static async Task RemoveFromToolboxAsync(ToolboxEntry item, CancellationToken cancellationToken) 69 | { 70 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 71 | 72 | try 73 | { 74 | await OutputPane.Instance.WriteAsync($"Removing '{item.Label}' from tab '{item.Tab}'"); 75 | 76 | var toolbox = await Instance.ServiceProvider.GetServiceAsync(); 77 | 78 | IEnumToolboxItems tbItems = null; 79 | 80 | toolbox?.EnumItems(item.Tab, out tbItems); 81 | 82 | var dataObjects = new IDataObject[1]; 83 | uint fetched = 0; 84 | 85 | bool found = false; 86 | 87 | while (!found && tbItems?.Next(1, dataObjects, out fetched) == VSConstants.S_OK) 88 | { 89 | if (dataObjects[0] != null && fetched == 1) 90 | { 91 | var itemDataObject = new OleDataObject(dataObjects[0]); 92 | 93 | if (itemDataObject.ContainsText(TextDataFormat.Text)) 94 | { 95 | var name = itemDataObject.GetText(TextDataFormat.Text); 96 | 97 | if (name?.ToString() == item.Snippet) 98 | { 99 | toolbox?.RemoveItem(dataObjects[0]); 100 | found = true; 101 | } 102 | } 103 | } 104 | } 105 | } 106 | catch (Exception e) 107 | { 108 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 109 | } 110 | } 111 | 112 | public static async Task RemoveTabIfEmptyAsync(string tab, CancellationToken cancellationToken) 113 | { 114 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 115 | 116 | var comparableTabName = tab.Trim().ToLowerInvariant(); 117 | 118 | switch (comparableTabName) 119 | { 120 | case "general": 121 | await OutputPane.Instance.WriteAsync("General tab cannot be removed, even if empty."); 122 | return; 123 | case "clipboard ring": 124 | await OutputPane.Instance.WriteAsync("Clipboard Ring tab cannot be removed, even if empty."); 125 | return; 126 | default: 127 | try 128 | { 129 | if (await Instance.ServiceProvider.GetServiceAsync() is IVsToolbox2 toolbox) 130 | { 131 | IEnumToolboxItems tbItems = null; 132 | 133 | toolbox?.EnumItems(tab, out tbItems); 134 | 135 | var dataObjects = new IDataObject[1]; 136 | uint fetched = 0; 137 | 138 | var item = tbItems?.Next(1, dataObjects, out fetched); // == VSConstants.S_OK) 139 | 140 | if (fetched == 0) 141 | { 142 | await OutputPane.Instance.WriteAsync($"Removing tab '{tab}'"); 143 | 144 | toolbox.RemoveTab(tab); 145 | } 146 | } 147 | else 148 | { 149 | await OutputPane.Instance.WriteAsync("Failed to access Toolbox."); 150 | } 151 | } 152 | catch (Exception e) 153 | { 154 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 155 | } 156 | 157 | break; 158 | } 159 | } 160 | 161 | public static async Task RemoveAllDemoSnippetsAsync(CancellationToken cancellationToken) 162 | { 163 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 164 | 165 | try 166 | { 167 | if (await Instance.ServiceProvider.GetServiceAsync() is IVsToolbox2 toolbox) 168 | { 169 | var tabsToRemove = new List(); 170 | 171 | IEnumToolboxTabs tbTabs = null; 172 | toolbox?.EnumTabs(out tbTabs); 173 | var returnedTabNames = new string[1]; 174 | 175 | // Check each tab 176 | while (tbTabs?.Next(1, returnedTabNames, out uint tabsReturned) == VSConstants.S_OK) 177 | { 178 | var tabName = returnedTabNames[0]; 179 | 180 | // Check entries in the tab 181 | IEnumToolboxItems tbItems = null; 182 | 183 | toolbox?.EnumItems(tabName, out tbItems); 184 | 185 | var dataObjects = new IDataObject[1]; 186 | uint fetched = 0; 187 | 188 | while (tbItems?.Next(1, dataObjects, out fetched) == VSConstants.S_OK) 189 | { 190 | if (dataObjects[0] != null && fetched == 1) 191 | { 192 | var itemDataObject = new OleDataObject(dataObjects[0]); 193 | 194 | if (itemDataObject.ContainsText(TextDataFormat.Text)) 195 | { 196 | var isDemoSnippet = itemDataObject.GetData(ToolboxDataSourceId); 197 | 198 | if (isDemoSnippet != null) 199 | { 200 | var label = itemDataObject.GetData(ToolboxDataLabel).ToString(); 201 | 202 | await OutputPane.Instance.WriteAsync($"Removing '{label}' from tab '{tabName}'"); 203 | 204 | toolbox?.RemoveItem(dataObjects[0]); 205 | tabsToRemove.Add(tabName); 206 | } 207 | } 208 | } 209 | } 210 | } 211 | 212 | foreach (var tabToRemove in tabsToRemove.Distinct().ToList()) 213 | { 214 | await RemoveTabIfEmptyAsync(tabToRemove, CancellationToken.None); 215 | } 216 | } 217 | else 218 | { 219 | await OutputPane.Instance.WriteAsync("Failed to access Toolbox."); 220 | } 221 | } 222 | catch (Exception e) 223 | { 224 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 225 | } 226 | } 227 | 228 | public static async Task<(int files, int snippets)> ProcessAllSnippetFilesAsync(string slnDirectory) 229 | { 230 | await OutputPane.Instance.WriteAsync($"Loading *.demosnippets files under: {slnDirectory}"); 231 | 232 | var allSnippetFiles = Directory.EnumerateFiles(slnDirectory, "*.demosnippets*", SearchOption.AllDirectories); 233 | 234 | var fileCount = 0; 235 | var itemsCount = 0; 236 | 237 | foreach (var snippetFile in allSnippetFiles) 238 | { 239 | await OutputPane.Instance.WriteAsync($"Loading snippets from: {snippetFile}"); 240 | 241 | var added = await LoadToolboxItemsAsync(snippetFile); 242 | 243 | if (added == 0) 244 | { 245 | await OutputPane.Instance.WriteAsync($"Found nothing to add in {snippetFile}"); 246 | } 247 | else 248 | { 249 | fileCount += 1; 250 | itemsCount += added; 251 | } 252 | } 253 | 254 | return (fileCount, itemsCount); 255 | } 256 | 257 | public static async Task> GetAllTabNamesAsync(CancellationToken cancellationToken) 258 | { 259 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 260 | 261 | var result = new List(); 262 | 263 | try 264 | { 265 | if (await Instance.ServiceProvider.GetServiceAsync() is IVsToolbox2 toolbox) 266 | { 267 | IEnumToolboxTabs tbTabs = null; 268 | toolbox?.EnumTabs(out tbTabs); 269 | var returnedTabNames = new string[1]; 270 | 271 | while (tbTabs?.Next(1, returnedTabNames, out uint tabsReturned) == VSConstants.S_OK) 272 | { 273 | var tabName = returnedTabNames[0]; 274 | 275 | result.Add(tabName); 276 | } 277 | } 278 | } 279 | catch (Exception e) 280 | { 281 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 282 | } 283 | 284 | return result; 285 | } 286 | 287 | public static async Task RefreshEntriesFromFileAsync(string documentPath) 288 | { 289 | await RemoveAllItemsFromFileAsync(documentPath); 290 | await LoadToolboxItemsAsync(documentPath); 291 | } 292 | 293 | private static async Task AddToToolboxAsync(string tab, string label, string actualText, string sourceFileName) 294 | { 295 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); 296 | 297 | try 298 | { 299 | var toolbox = await Instance.ServiceProvider.GetServiceAsync(); 300 | 301 | var itemInfo = new TBXITEMINFO[1]; 302 | var tbItem = new OleDataObject(); 303 | 304 | // Prefix with a zero-width whitespace so can use any character as first char (@ prefix at start of string indicates a resource reference) 305 | itemInfo[0].bstrText = "\u200B" + label; 306 | itemInfo[0].dwFlags = (uint)__TBXITEMINFOFLAGS.TBXIF_DONTPERSIST; 307 | 308 | tbItem.SetText(actualText, TextDataFormat.Text); 309 | 310 | // Add identifier so can know which entries were added by this tool. 311 | tbItem.SetData(ToolboxDataSourceId, true); 312 | 313 | // Set label here so easy to read back and use in logging when removing. 314 | tbItem.SetData(ToolboxDataLabel, label); 315 | 316 | // Store this so can avoid duplicating toolbox entries when repeatedly loading the same file 317 | tbItem.SetData(ToolboxDataFileName, sourceFileName); 318 | 319 | if (await RemoveExistingItemAsync(tab, label, sourceFileName)) 320 | { 321 | await OutputPane.Instance.WriteAsync($"Replacing '{label}' in tab '{tab}'"); 322 | } 323 | else 324 | { 325 | await OutputPane.Instance.WriteAsync($"Adding '{label}' to tab '{tab}'"); 326 | } 327 | 328 | toolbox?.AddItem(tbItem, itemInfo, tab); 329 | 330 | return tbItem; 331 | } 332 | catch (Exception e) 333 | { 334 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 335 | return null; 336 | } 337 | } 338 | 339 | private static async Task RemoveExistingItemAsync(string tabName, string label, string originalSourceFile) 340 | { 341 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); 342 | 343 | try 344 | { 345 | var toolbox = await Instance.ServiceProvider.GetServiceAsync(); 346 | 347 | IEnumToolboxTabs tbTabs = null; 348 | toolbox?.EnumTabs(out tbTabs); 349 | var returnedTabNames = new string[1]; 350 | 351 | while (tbTabs?.Next(1, returnedTabNames, out uint tabsReturned) == VSConstants.S_OK) 352 | { 353 | if (tabName == returnedTabNames[0]) 354 | { 355 | IEnumToolboxItems tbItems = null; 356 | 357 | toolbox?.EnumItems(tabName, out tbItems); 358 | 359 | var dataObjects = new IDataObject[1]; 360 | uint fetched = 0; 361 | 362 | while (tbItems?.Next(1, dataObjects, out fetched) == VSConstants.S_OK) 363 | { 364 | if (dataObjects[0] != null && fetched == 1) 365 | { 366 | var itemDataObject = new OleDataObject(dataObjects[0]); 367 | 368 | if (itemDataObject.ContainsText(TextDataFormat.Text)) 369 | { 370 | var isDemoSnippet = itemDataObject.GetData(ToolboxDataSourceId); 371 | 372 | if (isDemoSnippet != null) 373 | { 374 | var itemFile = itemDataObject.GetData(ToolboxDataFileName).ToString(); 375 | 376 | if (itemFile == originalSourceFile) 377 | { 378 | var itemLabel = itemDataObject.GetData(ToolboxDataLabel).ToString(); 379 | 380 | if (itemLabel == label) 381 | { 382 | toolbox?.RemoveItem(dataObjects[0]); 383 | return true; 384 | } 385 | } 386 | } 387 | } 388 | } 389 | } 390 | } 391 | } 392 | } 393 | catch (Exception e) 394 | { 395 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 396 | } 397 | 398 | return false; 399 | } 400 | 401 | private static async Task RemoveAllItemsFromFileAsync(string filePath) 402 | { 403 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); 404 | 405 | try 406 | { 407 | var fileName = Path.GetFileName(filePath); 408 | 409 | if (await Instance.ServiceProvider.GetServiceAsync() is IVsToolbox2 toolbox) 410 | { 411 | IEnumToolboxTabs tbTabs = null; 412 | toolbox?.EnumTabs(out tbTabs); 413 | var returnedTabNames = new string[1]; 414 | 415 | // Check each tab 416 | while (tbTabs?.Next(1, returnedTabNames, out uint tabsReturned) == VSConstants.S_OK) 417 | { 418 | var tabName = returnedTabNames[0]; 419 | 420 | // Check each item in the tab 421 | IEnumToolboxItems tbItems = null; 422 | 423 | toolbox?.EnumItems(tabName, out tbItems); 424 | 425 | var dataObjects = new IDataObject[1]; 426 | uint fetched = 0; 427 | 428 | while (tbItems?.Next(1, dataObjects, out fetched) == VSConstants.S_OK) 429 | { 430 | if (dataObjects[0] != null && fetched == 1) 431 | { 432 | var itemDataObject = new OleDataObject(dataObjects[0]); 433 | 434 | if (itemDataObject.ContainsText(TextDataFormat.Text)) 435 | { 436 | var isDemoSnippet = itemDataObject.GetData(ToolboxDataSourceId); 437 | 438 | if (isDemoSnippet != null) 439 | { 440 | var dataFileName = itemDataObject.GetData(ToolboxDataFileName).ToString(); 441 | 442 | if (fileName == dataFileName) 443 | { 444 | toolbox?.RemoveItem(dataObjects[0]); 445 | } 446 | } 447 | } 448 | } 449 | } 450 | } 451 | } 452 | else 453 | { 454 | await OutputPane.Instance.WriteAsync("Failed to access Toolbox."); 455 | } 456 | } 457 | catch (Exception e) 458 | { 459 | await OutputPane.Instance.WriteAsync($"Error: {e.Message}{Environment.NewLine}{e.Source}{Environment.NewLine}{e.StackTrace}"); 460 | } 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /DemoSnippets/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | DemoSnippets 122 | 123 | 124 | General 125 | 126 | 127 | AutoLoadUnload 128 | 129 | 130 | Demo Snippets 131 | 132 | 133 | Add *.demosnippets file contents to the Toolbox. 134 | 135 | 136 | 137 | Resources\AddToToolboxPackage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 138 | 139 | -------------------------------------------------------------------------------- /DemoSnippets/filestosign.txt: -------------------------------------------------------------------------------- 1 | DemoSnippets.dll -------------------------------------------------------------------------------- /DemoSnippets/icons.pkgdef: -------------------------------------------------------------------------------- 1 |  2 | [$RootKey$\ShellFileAssociations\.demosnippets] 3 | "DefaultIconMoniker"="dc1a92a6-2584-4967-9273-bc507eff6a23:0" 4 | -------------------------------------------------------------------------------- /DemoSnippets/signvsix.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /DemoSnippets/source.extension.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by VSIX Synchronizer 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace DemoSnippets 7 | { 8 | internal sealed partial class Vsix 9 | { 10 | public const string Id = "DemoSnippets.e2d68c23-8599-40e8-b402-a57060bf3d29"; 11 | public const string Name = "DemoSnippets"; 12 | public const string Description = @"A way to easily add the contents of *.demosnippets files to the Toolbox."; 13 | public const string Language = "en-US"; 14 | public const string Version = "1.9.6"; 15 | public const string Author = "Matt Lacey"; 16 | public const string Tags = "demo toolbox snippet"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DemoSnippets/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | DemoSnippets 6 | A way to easily add the contents of *.demosnippets files to the Toolbox. 7 | https://github.com/mrlacey/DemoSnippets 8 | LICENSE.txt 9 | https://github.com/mrlacey/DemoSnippets/blob/master/CHANGELOG.md 10 | Resources\Icon.png 11 | demo toolbox snippet 12 | 13 | 14 | 15 | amd64 16 | 17 | 18 | arm64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 12.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /Formatting.md: -------------------------------------------------------------------------------- 1 | # Demo Snippets - Formatting 2 | 3 | The DemoSnippets VS Extension works with files that use the ".demosnippets" extension or "sub-extension". 4 | 5 | ## *.demosnippets files 6 | 7 | These files are the simplest and default way of automatically adding snippets to the Toolbox. 8 | 9 | ### Formatting 10 | 11 | Files use a simple line based format. 12 | 13 | - Comment lines start with a hash (#). 14 | - Labels (displayed in the toolbox) start with a hyphen (-). 15 | - Text between labels is included as the contents of the snippet. 16 | - Snippets are added to the "Demo" tab. This can be overridden by specifying a tab name in a line starting "Tab:". 17 | 18 | ### Example 19 | 20 | A .demosnippets file might look like this 21 | 22 | ``` 23 | # Comment 24 | 25 | TAB: Tab name 26 | 27 | - Step 1 28 | 29 | // snippet 1 line 1 30 | // snippet 1 line 2 31 | 32 | 33 | - Step 2 34 | 35 | snippet 2 line 1 36 | snippet 2 line 2 37 | 38 | ``` 39 | 40 | The above produces this in the Toolbox 41 | 42 | ![Example file output in Toolbox](./art/sample-tab.png) 43 | 44 | ## *.demosnippets.{ext} files (sub-extensions) 45 | 46 | Any file can contain DemoSnippets by defining tab and label names in embedded comments. This allows Visual Studio to provide syntax highlighting for the code you want to include in snippets. 47 | 48 | The VSIX extension will look for demo snippets to add to the Toolbox in files that include the ".demosnippets" sub-extension. A "sub-extension" is an additional extension before the file extension. For example, `Program.demosnippets.cs` and `index.demosnippets.html` are C# and HTML file names with the appropriate sub-extension. Visual studio will continue to treat them like any other .cs or .html files but the DemoSnippets extension will know to look at them also. 49 | 50 | ### Formatting 51 | 52 | With files using a ".demosnippets" sub-extension, formatting is indicated in comments. The works with any style of comment that the language/file type may use. 53 | 54 | There are three indicators that can be used in code comments. 55 | 56 | - `DEMOSNIPPETS-TAB` - This is used to specify the name of the tab in which to put subsequently indicated snippets. The name of the tab is specified after the indicator. If left blank the last defined tab will continue to be used. If no tab is specified, snippets will be placed in a tab named "Demo". 57 | 58 | - `DEMOSNIPPETS-LABEL` - This is unsed to indicate the name of the tab and that subsequent lines should be included in a snippet. The desired name should appear immediately after the indicator. If no name is specified, the first line of the snippet will be used as the name. All lines after the label specifier will be included until either a new tab definition, a new label definition, or an "ENDSNIPPET" indicator is reached. 59 | 60 | - `DEMOSNIPPETS-ENDSNIPPET` - This is used to indicate the the end of the snippet has been reached and any following lines should not be included. This is useful when you have lines in the file that you do not want included in any snippet. 61 | 62 | Any tab or label name will not include a closing comment indicator. 63 | 64 | All other comments or code in the file are ignored. 65 | 66 | ### Example 67 | 68 | Here is an example of a HTML file with embedded DemoSnippets comments. 69 | 70 | ```html 71 | 72 | 73 | 74 | 75 | Demo stuff 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |

Conference Demos

86 | 87 | 88 |
    89 |
  • Home
  • 90 |
  • Store
  • 91 |
  • About
  • 92 |
  • Admin
  • 93 |
94 | 95 |

Some important text to have on the page.

96 | 97 | 98 | 99 | ``` 100 | 101 | The above produces this in the Toolbox 102 | 103 | ![Example HTML file output in Toolbox](./art/sample-html.png) 104 | 105 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Installed product versions 2 | - Visual Studio: [example 2015 Professional] 3 | - This extension: [example 1.1.21] 4 | 5 | ### Description 6 | Replace this text with a short description 7 | 8 | ### Steps to recreate 9 | 1. Replace this 10 | 2. text with 11 | 3. the steps 12 | 4. to recreate 13 | 14 | ### Current behavior 15 | Explain what it's doing and why it's wrong 16 | 17 | ### Expected behavior 18 | Explain what it should be doing after it's fixed. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matt Lacey 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 | # Demo Snippets 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) 4 | ![Works with Visual Studio 2019](https://img.shields.io/static/v1.svg?label=VS&message=2019&color=A853C7) 5 | ![Works with Visual Studio 2022](https://img.shields.io/static/v1.svg?label=VS&message=2022&color=A853C7) 6 | ![Visual Studio Marketplace 5 Stars](https://img.shields.io/badge/VS%20Marketplace-★★★★★-green) 7 | 8 | [![Build](https://github.com/mrlacey/DemoSnippets/actions/workflows/build.yaml/badge.svg)](https://github.com/mrlacey/DemoSnippets/actions/workflows/build.yaml) 9 | ![Tests](https://gist.githubusercontent.com/mrlacey/c586ff0f495b4a8dd76ab0dbdf9c89e0/raw/DemoSnippets.badge.svg) 10 | 11 | Download this extension from the [VS Marketplace](https://marketplace.visualstudio.com/items?itemName=MattLaceyLtd.DemoSnippets). 12 | 13 | --------------------------------------- 14 | 15 | Visual Studio extension that provides functionality relating to *.demosnippets files and interacting with the Toolbox. 16 | Intended to make it easy to use code snippets in demos. 17 | 18 | ![screenshot](./art/screenshot.png) 19 | 20 | ## Features 21 | 22 | - Store your code snippets in a file with a **.demosnippets** extension. 23 | - Automatically add the contents of all .demosnippets files to the Toolbox when a solution loads. (Can be disabled in Options.) 24 | - Automatically remove all demo snippets from the Toolbox when a solution is closed. (Can be disabled in Options.) 25 | - Add individual files to the Toolbox by right-clicking on the file in Solution Explorer and selecting **'Add to Toolbox'**. 26 | - Add all files to the Toolbox by right-clicking on the solution in Solution Explorer and selecting **'Add All DemoSnippets to Toolbox'**. 27 | - Remove all DemoSnippets from the Toolbox by right-clicking on the Toolbox and selecting **'Remove All DemoSnippets'**. 28 | - Remove any empty tabs from the Toolbox by right-clicking on the Toolbox and selecting **'Remove Empty Tabs'**. 29 | - Automatically reload Toolbox entries when .demosnippets files are saved. (Can be disabled in Options.) 30 | - Colorization of .demosnippets files. 31 | - Create .demosnippets files by selecting File > New Item. 32 | 33 | [Formatting DemoSnippets files](./Formatting.md) 34 | 35 | 36 | See the [change log](CHANGELOG.md) for changes and road map. 37 | 38 | 39 | ## Contribute 40 | Check out the [contribution guidelines](CONTRIBUTING.md) 41 | if you want to contribute to this project. 42 | 43 | For cloning and building this project yourself, make sure 44 | to install the 45 | [Extensibility Tools](https://visualstudiogallery.msdn.microsoft.com/ab39a092-1343-46e2-b0f1-6a3f91155aa6) 46 | extension for Visual Studio which enables some features 47 | used by this project. 48 | -------------------------------------------------------------------------------- /art/sample-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/art/sample-html.png -------------------------------------------------------------------------------- /art/sample-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/art/sample-tab.png -------------------------------------------------------------------------------- /art/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/DemoSnippets/1984e94c0d5e863e286c94f6968d822e148694ca/art/screenshot.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------