├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build.yaml │ ├── codeql.yml │ └── dependency-review.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── README.md ├── assets ├── error-helper-128.png ├── error-helper-256.png ├── knownmonikers │ ├── Copy.png │ ├── Item.png │ ├── OverlayError.png │ ├── SearchContract.png │ └── Searchoverlay-extracted.png └── screenshot.png ├── nuget.config └── src ├── .editorconfig ├── AnalyticsConfig.cs ├── CommandBase.cs ├── CopyDescriptionCommand.cs ├── ErrorHelper.Tests ├── ErrorHelper.Tests.csproj ├── ParseUrls.cs ├── Properties │ └── AssemblyInfo.cs └── StripPaths.cs ├── ErrorHelper.csproj ├── ErrorHelper.ico ├── ErrorHelper.png ├── ErrorHelper.ruleset ├── ErrorHelper.sln ├── ErrorHelperPackage.cs ├── ErrorHelperPackage.vsct ├── LICENSE.txt ├── OpenUrlCommand.cs ├── OptionPageGrid.cs ├── OutputPane.cs ├── Properties └── AssemblyInfo.cs ├── SearchDescriptionCommand.cs ├── SearchDescriptionWithoutPathsCommand.cs ├── SearchEngine.cs ├── SponsorDetector.cs ├── SponsorRequestHelper.cs ├── VSPackage.resx ├── filestosign.txt ├── signvsix.targets ├── source.extension.cs └── source.extension.vsixmanifest /.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/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/src/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | ignore: 13 | # Don't want to automatically update the SDK as it needs to match the min target version of the extension 14 | - dependency-name: "Microsoft.VisualStudio.SDK" 15 | -------------------------------------------------------------------------------- /.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 | permissions: 21 | checks: write 22 | pull-requests: write 23 | env: 24 | Configuration: Debug 25 | DeployExtension: False 26 | VsixManifestPath: src\source.extension.vsixmanifest 27 | VsixManifestSourcePath: src\source.extension.cs 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - name: Setup .NET build dependencies 33 | uses: timheuer/bootstrap-dotnet@v2 34 | with: 35 | nuget: 'false' 36 | sdk: 'false' 37 | msbuild: 'true' 38 | 39 | - name: Increment VSIX version 40 | id: vsix_version 41 | uses: timheuer/vsix-version-stamp@v2 42 | with: 43 | manifest-file: ${{ env.VsixManifestPath }} 44 | vsix-token-source-file: ${{ env.VsixManifestSourcePath }} 45 | 46 | - name: Build 47 | run: msbuild /v:m -restore /p:OutDir=\_built ./src/ErrorHelper.sln 48 | 49 | - name: Upload artifact 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: ${{ github.event.repository.name }}.vsix 53 | path: /_built/**/*.vsix 54 | 55 | - name: Run Tests 56 | # See https://github.com/microsoft/vstest-action/issues/31 57 | # uses: microsoft/vstest-action@v1.0.0 58 | uses: josepho0918/vstest-action@main 59 | with: 60 | searchFolder: /_built/ 61 | testAssembly: '**/**/*Tests.dll' 62 | 63 | - name: Publish Test Results 64 | uses: EnricoMi/publish-unit-test-result-action/windows@v2 65 | id: test-results 66 | with: 67 | files: testresults\**\*.trx 68 | 69 | - name: Set badge color 70 | shell: bash 71 | run: | 72 | case ${{ fromJSON( steps.test-results.outputs.json ).conclusion }} in 73 | success) 74 | echo "BADGE_COLOR=31c653" >> $GITHUB_ENV 75 | ;; 76 | failure) 77 | echo "BADGE_COLOR=800000" >> $GITHUB_ENV 78 | ;; 79 | neutral) 80 | echo "BADGE_COLOR=696969" >> $GITHUB_ENV 81 | ;; 82 | esac 83 | 84 | - name: Create badge 85 | uses: emibcn/badge-action@808173dd03e2f30c980d03ee49e181626088eee8 86 | with: 87 | label: Tests 88 | status: '${{ fromJSON( steps.test-results.outputs.json ).formatted.stats.tests }} tests: ${{ fromJSON( steps.test-results.outputs.json ).conclusion }}' 89 | color: ${{ env.BADGE_COLOR }} 90 | path: ErrorHelper.badge.svg 91 | 92 | - name: Upload badge to Gist 93 | # Upload only for main branch 94 | if: > 95 | github.event_name == 'workflow_run' && github.event.workflow_run.head_branch == 'main' || 96 | github.event_name != 'workflow_run' && github.ref == 'refs/heads/main' 97 | uses: jpontdia/append-gist-action@master 98 | with: 99 | token: ${{ secrets.GIST_TOKEN }} 100 | gistURL: https://gist.githubusercontent.com/mrlacey/c586ff0f495b4a8dd76ab0dbdf9c89e0 101 | file: ErrorHelper.badge.svg 102 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | 20 | jobs: 21 | analyze: 22 | name: Analyze (${{ matrix.language }}) 23 | # Runner size impacts CodeQL analysis time. To learn more, please see: 24 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 25 | # - https://gh.io/supported-runners-and-hardware-resources 26 | # - https://gh.io/using-larger-runners (GitHub.com only) 27 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 28 | runs-on: 'windows-latest' 29 | timeout-minutes: 360 30 | permissions: 31 | # required for all workflows 32 | security-events: write 33 | 34 | # required to fetch internal or private CodeQL packs 35 | packages: read 36 | 37 | # only required for workflows in private repositories 38 | actions: read 39 | contents: read 40 | 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | include: 45 | - language: csharp 46 | build-mode: autobuild 47 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 48 | # Use `c-cpp` to analyze code written in C, C++ or both 49 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 50 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 51 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 52 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 53 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 54 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 55 | steps: 56 | - name: Checkout repository 57 | uses: actions/checkout@v4 58 | 59 | # Initializes the CodeQL tools for scanning. 60 | - name: Initialize CodeQL 61 | uses: github/codeql-action/init@v3 62 | with: 63 | languages: ${{ matrix.language }} 64 | build-mode: ${{ matrix.build-mode }} 65 | # If you wish to specify custom queries, you can do so here or in a config file. 66 | # By default, queries listed here will override any specified in a config file. 67 | # Prefix the list here with "+" to use these queries and those in the config file. 68 | 69 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 70 | # queries: security-extended,security-and-quality 71 | 72 | # If the analyze step fails for one of the languages you are analyzing with 73 | # "We were unable to automatically build your code", modify the matrix above 74 | # to set the build mode to "manual" for that language. Then modify this step 75 | # to build your code. 76 | # ℹ️ Command-line programs to run using the OS shell. 77 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 78 | - if: matrix.build-mode == 'manual' 79 | shell: bash 80 | run: | 81 | echo 'If you are using a "manual" build mode for one or more of the' \ 82 | 'languages you are analyzing, replace this with the commands to build' \ 83 | 'your code, for example:' 84 | echo ' make bootstrap' 85 | echo ' make release' 86 | exit 1 87 | 88 | - name: Perform CodeQL Analysis 89 | uses: github/codeql-action/analyze@v3 90 | with: 91 | category: "/language:${{matrix.language}}" 92 | 93 | - name: Generate SBOM 94 | run: | 95 | Invoke-WebRequest -Uri "https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-win-x64.exe" -OutFile "sbom-tool.exe" 96 | 97 | Start-Process -FilePath "sbom-tool.exe" -ArgumentList "generate -b ./src/bin/Debug/ -bc ./src/bin/Debug -pn ErrorHelper -pv 1.0.0 -ps MRLacey -nsb https://sbom.mrlacey.com -V Verbose" -NoNewWindow 98 | 99 | - name: Upload a Build Artifact 100 | uses: actions/upload-artifact@v4 101 | with: 102 | path: src/bin/Debug/ 103 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable 6 | # packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 10 | name: 'Dependency review' 11 | on: 12 | pull_request: 13 | branches: [ "main" ] 14 | 15 | # If using a dependency submission action in this workflow this permission will need to be set to: 16 | # 17 | # permissions: 18 | # contents: write 19 | # 20 | # https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api 21 | permissions: 22 | contents: read 23 | # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option 24 | pull-requests: write 25 | 26 | jobs: 27 | dependency-review: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: 'Checkout repository' 31 | uses: actions/checkout@v4 32 | - name: 'Dependency Review' 33 | uses: actions/dependency-review-action@v4 34 | # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. 35 | with: 36 | comment-summary-in-pr: always 37 | # fail-on-severity: moderate 38 | # deny-licenses: GPL-1.0-or-later, LGPL-2.0-or-later 39 | # retry-on-snapshot-warnings: true 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 12.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2024 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 | # ErrorHelper 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/ErrorHelper/actions/workflows/build.yaml/badge.svg)](https://github.com/mrlacey/ErrorHelper/actions/workflows/build.yaml) 9 | ![Tests](https://gist.githubusercontent.com/mrlacey/c586ff0f495b4a8dd76ab0dbdf9c89e0/raw/ErrorHelper.badge.svg) 10 | 11 | Make it easier to work with items in the Visual Studio Error List by copying or searching (with Ecosia, Bing, DuckDuckGo, Google, or StackOverflow) for just the description of an error. 12 | 13 | ![screenshot showing context menu options](./assets/screenshot.png) 14 | 15 | No more wasting time copying the whole row and then extracting the description. This extension adds helper commands to make it easier to deal with errors. 16 | 17 | When there are bugs to fix, you don't want to mess about with re-typing error messages or a clipboard full of unhelpful junk. 18 | 19 | Search for the error message (with or without any identifying paths or file names) directly from Visual Studio. 20 | Or, directly open the URL in an error message. 21 | -------------------------------------------------------------------------------- /assets/error-helper-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/error-helper-128.png -------------------------------------------------------------------------------- /assets/error-helper-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/error-helper-256.png -------------------------------------------------------------------------------- /assets/knownmonikers/Copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/knownmonikers/Copy.png -------------------------------------------------------------------------------- /assets/knownmonikers/Item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/knownmonikers/Item.png -------------------------------------------------------------------------------- /assets/knownmonikers/OverlayError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/knownmonikers/OverlayError.png -------------------------------------------------------------------------------- /assets/knownmonikers/SearchContract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/knownmonikers/SearchContract.png -------------------------------------------------------------------------------- /assets/knownmonikers/Searchoverlay-extracted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/knownmonikers/Searchoverlay-extracted.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/assets/screenshot.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # SA0001: XML comment analysis is disabled due to project configuration 4 | dotnet_diagnostic.SA0001.severity = none 5 | -------------------------------------------------------------------------------- /src/AnalyticsConfig.cs: -------------------------------------------------------------------------------- 1 | namespace ErrorHelper 2 | { 3 | public static class AnalyticsConfig 4 | { 5 | public static string TelemetryConnectionString { get; set; } = ""; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CommandBase.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | using EnvDTE; 8 | using Microsoft.VisualStudio; 9 | using Microsoft.VisualStudio.Shell; 10 | using Microsoft.VisualStudio.Shell.Interop; 11 | using Task = System.Threading.Tasks.Task; 12 | 13 | namespace ErrorHelper 14 | { 15 | public class CommandBase 16 | { 17 | public static readonly Guid CommandSet = new Guid("5fea58eb-df34-46e8-ab5d-4708b07cf330"); 18 | 19 | private readonly AsyncPackage package; 20 | 21 | public CommandBase(AsyncPackage package) 22 | { 23 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 24 | } 25 | 26 | public async Task GetDescriptionAsync() 27 | { 28 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 29 | 30 | if (await this.package.GetServiceAsync(typeof(SVsErrorList)) is IVsTaskList2 tasks) 31 | { 32 | tasks.EnumSelectedItems(out IVsEnumTaskItems itemsEnum); 33 | 34 | var vsTaskItem = new IVsTaskItem[1]; 35 | 36 | if (itemsEnum.Next(1, vsTaskItem, null) == 0) 37 | { 38 | var getTextResult = vsTaskItem[0].get_Text(out string description); 39 | 40 | if (getTextResult == VSConstants.S_OK) 41 | { 42 | if (!string.IsNullOrWhiteSpace(description)) 43 | { 44 | return description; 45 | } 46 | } 47 | else 48 | { 49 | System.Diagnostics.Debug.WriteLine($"Error getting Text from IVsTaskItem: {getTextResult}"); 50 | } 51 | } 52 | } 53 | 54 | return string.Empty; 55 | } 56 | 57 | protected async Task ShowStatusBarMessageAsync(string message) 58 | { 59 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 60 | 61 | try 62 | { 63 | if (await this.package.GetServiceAsync(typeof(EnvDTE.DTE)) is DTE dte) 64 | { 65 | dte.StatusBar.Text = message; 66 | } 67 | } 68 | catch (Exception exc) 69 | { 70 | System.Diagnostics.Debug.WriteLine(exc); 71 | } 72 | } 73 | 74 | protected string GetSearchUrlBase() 75 | { 76 | try 77 | { 78 | var searchEngine = ErrorHelperPackage.Instance?.Options?.SearchEngine; 79 | 80 | switch (searchEngine) 81 | { 82 | case SearchEngine.Google: 83 | return "https://www.google.com/search?q="; 84 | 85 | case SearchEngine.StackOverflow: 86 | return "https://stackoverflow.com/search?q="; 87 | 88 | case SearchEngine.Bing: 89 | return "https://www.bing.com/search?q="; 90 | 91 | case SearchEngine.DuckDuckGo: 92 | return "https://duckduckgo.com/?q="; 93 | 94 | case SearchEngine.Custom: 95 | return ErrorHelperPackage.Instance?.Options?.SearchUrl; 96 | 97 | case SearchEngine.Ecosia: 98 | default: 99 | break; 100 | } 101 | } 102 | catch (Exception exc) 103 | { 104 | System.Diagnostics.Debug.WriteLine(exc); 105 | } 106 | 107 | return "https://www.ecosia.org/search?q="; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/CopyDescriptionCommand.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. 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 ErrorHelper 11 | { 12 | internal sealed class CopyDescriptionCommand : CommandBase 13 | { 14 | public const int CommandId = 0x0100; 15 | 16 | private CopyDescriptionCommand(AsyncPackage package, OleMenuCommandService commandService) 17 | : base(package) 18 | { 19 | commandService = commandService ?? throw new ArgumentNullException(nameof(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 CopyDescriptionCommand Instance { get; private set; } 27 | 28 | public static async Task InitializeAsync(AsyncPackage package) 29 | { 30 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 31 | 32 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 33 | Instance = new CopyDescriptionCommand(package, commandService); 34 | } 35 | 36 | #pragma warning disable VSTHRD100 // Avoid async void methods 37 | private async void Execute(object sender, EventArgs e) 38 | #pragma warning restore VSTHRD100 // Avoid async void methods 39 | { 40 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 41 | 42 | try 43 | { 44 | var desc = await this.GetDescriptionAsync(); 45 | 46 | if (!string.IsNullOrEmpty(desc)) 47 | { 48 | System.Windows.Forms.Clipboard.SetText(desc); 49 | } 50 | else 51 | { 52 | OutputPane.Instance.WriteLine($"Description could not be found."); 53 | await this.ShowStatusBarMessageAsync("Unable to copy the description."); 54 | } 55 | } 56 | catch (Exception exc) 57 | { 58 | await this.ShowStatusBarMessageAsync("Unable to copy error description."); 59 | OutputPane.Instance.WriteLine($"ErrorHelper: {exc}"); 60 | OutputPane.Instance.Activate(); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ErrorHelper.Tests/ErrorHelper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {213B6CCA-C5AA-4BB4-A935-BA3F75491E57} 8 | Library 9 | Properties 10 | ErrorHelper.Tests 11 | ErrorHelper.Tests 12 | v4.8.1 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 3.1.2 53 | 54 | 55 | 6.1.0 56 | 57 | 58 | 4.7.36 59 | 60 | 61 | 3.7.3 62 | 63 | 64 | 3.7.2 65 | 66 | 67 | 3.7.3 68 | runtime; build; native; contentfiles; analyzers; buildtransitive 69 | all 70 | 71 | 72 | 2.20.20 73 | 74 | 75 | 9.0.1 76 | 77 | 78 | 1.7.0 79 | runtime; build; native; contentfiles; analyzers; buildtransitive 80 | all 81 | 82 | 83 | 84 | 85 | {509e30aa-a6e4-44cd-a7e9-52e672d8a4d7} 86 | ErrorHelper 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/ErrorHelper.Tests/ParseUrls.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace ErrorHelper.Tests 5 | { 6 | [TestClass] 7 | public class ParseUrls 8 | { 9 | [TestMethod] 10 | public void EmptyString() 11 | { 12 | InputProducesExpected(string.Empty, new List()); 13 | } 14 | 15 | [TestMethod] 16 | public void NoUrls() 17 | { 18 | InputProducesExpected("just some regular text without any urls", new List()); 19 | } 20 | 21 | [TestMethod] 22 | public void JustOneUrl() 23 | { 24 | InputProducesExpected( 25 | "https://rapidxaml.dev", 26 | new List { "https://rapidxaml.dev" }); 27 | } 28 | 29 | [TestMethod] 30 | public void OneUrl_AtStart() 31 | { 32 | InputProducesExpected( 33 | "https://rapidxaml.dev followed by some text", 34 | new List { "https://rapidxaml.dev" }); 35 | } 36 | 37 | [TestMethod] 38 | public void OneUrl_InMiddle() 39 | { 40 | InputProducesExpected( 41 | "Some text with a single url https://rapidxaml.dev followed by some more text", 42 | new List { "https://rapidxaml.dev" }); 43 | } 44 | 45 | [TestMethod] 46 | public void OneUrl_AtEnd() 47 | { 48 | InputProducesExpected( 49 | "Some text with a single url at the end https://rapidxaml.dev", 50 | new List { "https://rapidxaml.dev" }); 51 | } 52 | 53 | [TestMethod] 54 | public void ManyUrls_OneLine() 55 | { 56 | InputProducesExpected( 57 | "Some text with two urls. The first is https://rapidxaml.dev and the second is https://mrlacey.com", 58 | new List { "https://rapidxaml.dev", "https://mrlacey.com" }); 59 | } 60 | 61 | [TestMethod] 62 | public void ManyUrls_MultipleLines() 63 | { 64 | InputProducesExpected( 65 | @"Some text with two urls. 66 | The first is https://rapidxaml.dev and 67 | the second is https://mrlacey.com/book", 68 | new List { "https://rapidxaml.dev", "https://mrlacey.com/book" }); 69 | } 70 | 71 | [TestMethod] 72 | public void ManyUrls_WithEndPunctuation() 73 | { 74 | InputProducesExpected( 75 | @"Some text with many urls. 76 | The first is https://rapidxaml.dev. and 77 | the second is https://mrlacey.com/: and 78 | the third is https://mrlacey.com/; and 79 | the fourth is https://mrlacey.com/, and 80 | the fifth is https://mrlacey.com/' and 81 | the sixth is https://mrlacey.com/"" and ", 82 | new List { "https://rapidxaml.dev", "https://mrlacey.com/", "https://mrlacey.com/", "https://mrlacey.com/", "https://mrlacey.com/", "https://mrlacey.com/" }); 83 | } 84 | 85 | [TestMethod] 86 | public void RealWorldExample() 87 | { 88 | InputProducesExpected( 89 | "The certificate specified has expired. For more information about renewing certificates, see http://go.microsoft.com/fwlink/?LinkID=241478.", 90 | new List { "http://go.microsoft.com/fwlink/?LinkID=241478" }); 91 | } 92 | 93 | private void InputProducesExpected(string input, List expected) 94 | { 95 | var actual = OpenUrlCommand.ParseUrls(input); 96 | 97 | Assert.AreEqual(expected.Count, actual.Count); 98 | 99 | for (int i = 0; i < expected.Count; i++) 100 | { 101 | Assert.AreEqual(expected[i], actual[i]); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/ErrorHelper.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("ErrorHelper.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("ErrorHelper.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2019")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("213b6cca-c5aa-4bb4-a935-ba3f75491e57")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /src/ErrorHelper.Tests/StripPaths.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace ErrorHelper.Tests 5 | { 6 | [TestClass] 7 | public class StripPaths 8 | { 9 | [TestMethod] 10 | public void NoPaths() 11 | { 12 | InputProducesExpected("abcdefghijkl", "abcdefghijkl"); 13 | } 14 | 15 | [TestMethod] 16 | public void Null() 17 | { 18 | InputProducesExpected(null, string.Empty); 19 | } 20 | 21 | [TestMethod] 22 | public void EmptyString() 23 | { 24 | InputProducesExpected(string.Empty, string.Empty); 25 | } 26 | 27 | [TestMethod] 28 | public void WhiteSpace() 29 | { 30 | InputProducesExpected(" ", " "); 31 | } 32 | 33 | [TestMethod] 34 | public void SpacesAround_AbsolutePath_ForwardSlash() 35 | { 36 | InputProducesExpected( 37 | "something is wrong in C:/path/file.ext that needs fixing.", 38 | "something is wrong in that needs fixing."); 39 | } 40 | 41 | [TestMethod] 42 | public void SingleQuoteAround_AbsolutePath_ForwardSlash() 43 | { 44 | InputProducesExpected( 45 | "something is wrong in 'C:/path/file.ext' that needs fixing.", 46 | "something is wrong in that needs fixing."); 47 | } 48 | 49 | [TestMethod] 50 | public void SpacesAround_RelativePath_ForwardSlash() 51 | { 52 | InputProducesExpected( 53 | "something is wrong in ../otherpath/file.ext that needs fixing.", 54 | "something is wrong in that needs fixing."); 55 | } 56 | 57 | [TestMethod] 58 | public void SingleQuoteAround_RelativePath_ForwardSlash() 59 | { 60 | InputProducesExpected( 61 | "something is wrong in '../otherpath/file.ext' that needs fixing.", 62 | "something is wrong in that needs fixing."); 63 | } 64 | 65 | [TestMethod] 66 | public void SpacesAround_AbsolutePath_BackwardSlash() 67 | { 68 | InputProducesExpected( 69 | "something is wrong in C:\\path\\file.ext that needs fixing.", 70 | "something is wrong in that needs fixing."); 71 | } 72 | 73 | [TestMethod] 74 | public void SingleQuoteAround_AbsolutePath_BackwardSlash() 75 | { 76 | InputProducesExpected( 77 | "something is wrong in 'C:\\path\\file.ext' that needs fixing.", 78 | "something is wrong in that needs fixing."); 79 | } 80 | 81 | [TestMethod] 82 | public void SpacesAround_RelativePath_BackwardSlash() 83 | { 84 | InputProducesExpected( 85 | "something is wrong in ..\\otherpath\\file.ext that needs fixing.", 86 | "something is wrong in that needs fixing."); 87 | } 88 | 89 | [TestMethod] 90 | public void SingleQuoteAround_RelativePath_BackwardSlash() 91 | { 92 | InputProducesExpected( 93 | "something is wrong in '..\\otherpath\\file.ext' that needs fixing.", 94 | "something is wrong in that needs fixing."); 95 | } 96 | 97 | [TestMethod] 98 | public void SpacesAround_Uri_NotRemoved() 99 | { 100 | InputProducesExpected( 101 | "something is wrong in http://example.com that needs fixing.", 102 | "something is wrong in http://example.com that needs fixing."); 103 | } 104 | 105 | [TestMethod] 106 | public void SingleQuoteAround_Uri_NotRemoved() 107 | { 108 | InputProducesExpected( 109 | "something is wrong in 'http://example.com' that needs fixing.", 110 | "something is wrong in 'http://example.com' that needs fixing."); 111 | } 112 | 113 | [TestMethod] 114 | public void QuotedUrlAtEndOfString_NoError() 115 | { 116 | InputProducesExpected( 117 | "something is wrong in \"http://example.com\"", 118 | "something is wrong in \"http://example.com\""); 119 | } 120 | 121 | [TestMethod] 122 | public void Issue13() 123 | { 124 | InputProducesExpected( 125 | "\"double Triangle::getArea(int x) const\" (declared at line 11 of \"C:\\Users\\Lars\\OneDrive\\programming\\geomalgorithms\\geomalgorithms\\triangle.h\")", 126 | "\"double Triangle::getArea(int x) const\" (declared at line 11 of \"\")"); 127 | } 128 | 129 | private void InputProducesExpected(string input, string expected) 130 | { 131 | var actual = SearchDescriptionWithoutPathsCommand.StripPaths(input); 132 | 133 | Assert.AreEqual(expected, actual); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/ErrorHelper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | Debug 11 | AnyCPU 12 | 2.0 13 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | {509E30AA-A6E4-44CD-A7E9-52E672D8A4D7} 15 | Library 16 | Properties 17 | ErrorHelper 18 | ErrorHelper 19 | v4.8 20 | true 21 | true 22 | true 23 | false 24 | false 25 | true 26 | true 27 | Program 28 | $(DevEnvDir)devenv.exe 29 | /rootsuffix Exp 30 | 31 | 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | ErrorHelper.ruleset 40 | 41 | 42 | pdbonly 43 | true 44 | bin\Release\ 45 | TRACE 46 | prompt 47 | 4 48 | ErrorHelper.ruleset 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Component 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | True 66 | True 67 | source.extension.vsixmanifest 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Designer 77 | VsixManifestGenerator 78 | source.extension.cs 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 3.1.2 93 | 94 | 95 | 2.22.0 96 | 97 | 98 | 6.1.0 99 | 100 | 101 | 4.7.36 102 | 103 | 104 | compile; build; native; contentfiles; analyzers; buildtransitive 105 | 106 | 107 | 2.20.20 108 | 109 | 110 | 9.0.1 111 | 112 | 113 | 1.7.0 114 | runtime; build; native; contentfiles; analyzers; buildtransitive 115 | all 116 | 117 | 118 | 119 | 120 | Menus.ctmenu 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | true 129 | PreserveNewest 130 | 131 | 132 | 133 | PreserveNewest 134 | true 135 | 136 | 137 | 138 | 139 | 140 | 147 | -------------------------------------------------------------------------------- /src/ErrorHelper.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/src/ErrorHelper.ico -------------------------------------------------------------------------------- /src/ErrorHelper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrlacey/ErrorHelper/20c0997c9d00f8e0277b88336d4c7e7759966024/src/ErrorHelper.png -------------------------------------------------------------------------------- /src/ErrorHelper.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 | 76 | -------------------------------------------------------------------------------- /src/ErrorHelper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31410.414 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorHelper", "ErrorHelper.csproj", "{509E30AA-A6E4-44CD-A7E9-52E672D8A4D7}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorHelper.Tests", "ErrorHelper.Tests\ErrorHelper.Tests.csproj", "{213B6CCA-C5AA-4BB4-A935-BA3F75491E57}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2C9AEA3-2E50-491C-B538-7D1B13FE1265}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\README.md = ..\README.md 13 | signvsix.targets = signvsix.targets 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {509E30AA-A6E4-44CD-A7E9-52E672D8A4D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {509E30AA-A6E4-44CD-A7E9-52E672D8A4D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {509E30AA-A6E4-44CD-A7E9-52E672D8A4D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {509E30AA-A6E4-44CD-A7E9-52E672D8A4D7}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {213B6CCA-C5AA-4BB4-A935-BA3F75491E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {213B6CCA-C5AA-4BB4-A935-BA3F75491E57}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {213B6CCA-C5AA-4BB4-A935-BA3F75491E57}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {213B6CCA-C5AA-4BB4-A935-BA3F75491E57}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {6D6A0BF5-371D-466E-9310-9ED6BB858F30} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/ErrorHelperPackage.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Runtime.InteropServices; 8 | using System.Threading; 9 | using Microsoft.ApplicationInsights; 10 | using Microsoft.ApplicationInsights.Extensibility; 11 | using Microsoft.VisualStudio.Shell; 12 | using Task = System.Threading.Tasks.Task; 13 | 14 | namespace ErrorHelper 15 | { 16 | [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.SolutionHasMultipleProjects, PackageAutoLoadFlags.BackgroundLoad)] 17 | [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.SolutionHasSingleProject, PackageAutoLoadFlags.BackgroundLoad)] 18 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 19 | [Guid(ErrorHelperPackage.PackageGuidString)] 20 | [ProvideMenuResource("Menus.ctmenu", 1)] 21 | [InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)] // Info on this package for Help/About 22 | [ProvideOptionPage(typeof(OptionPageGrid), Vsix.Name, "General", 0, 0, true)] 23 | [ProvideProfileAttribute(typeof(OptionPageGrid), Vsix.Name, "General", 106, 107, isToolsOptionPage: true)] 24 | public sealed class ErrorHelperPackage : AsyncPackage 25 | { 26 | public const string PackageGuidString = "3f9fa707-35b8-42f5-a623-8fa0ab37a05c"; 27 | 28 | public static ErrorHelperPackage Instance; 29 | 30 | public OptionPageGrid Options 31 | { 32 | get 33 | { 34 | return (OptionPageGrid)this.GetDialogPage(typeof(OptionPageGrid)); 35 | } 36 | } 37 | 38 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 39 | { 40 | // When initialized asynchronously, the current thread may be a background thread at this point. 41 | // Do any initialization that requires the UI thread after switching to the UI thread. 42 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 43 | 44 | OutputPane.Instance.WriteLine($"{Vsix.Name} v{Vsix.Version}"); 45 | 46 | ErrorHelperPackage.Instance = this; 47 | 48 | await CopyDescriptionCommand.InitializeAsync(this); 49 | await SearchDescriptionCommand.InitializeAsync(this); 50 | await SearchDescriptionWithoutPathsCommand.InitializeAsync(this); 51 | await OpenUrlCommand.InitializeAsync(this); 52 | 53 | await SponsorRequestHelper.CheckIfNeedToShowAsync(); 54 | 55 | TrackBasicUsageAnalytics(); 56 | } 57 | 58 | private static void TrackBasicUsageAnalytics() 59 | { 60 | #if !DEBUG 61 | try 62 | { 63 | if (string.IsNullOrWhiteSpace(AnalyticsConfig.TelemetryConnectionString)) 64 | { 65 | return; 66 | } 67 | 68 | var config = new TelemetryConfiguration 69 | { 70 | ConnectionString = AnalyticsConfig.TelemetryConnectionString, 71 | }; 72 | 73 | var client = new TelemetryClient(config); 74 | 75 | var properties = new Dictionary 76 | { 77 | { "VsixVersion", Vsix.Version }, 78 | { "VsVersion", Microsoft.VisualStudio.Telemetry.TelemetryService.DefaultSession?.GetSharedProperty("VS.Core.ExeVersion") }, 79 | { "Architecture", RuntimeInformation.ProcessArchitecture.ToString() }, 80 | { "MsInternal", Microsoft.VisualStudio.Telemetry.TelemetryService.DefaultSession?.IsUserMicrosoftInternal.ToString() }, 81 | }; 82 | 83 | client.TrackEvent(Vsix.Name, properties); 84 | } 85 | catch (Exception exc) 86 | { 87 | System.Diagnostics.Debug.WriteLine(exc); 88 | OutputPane.Instance.WriteLine("Error tracking usage analytics: " + exc.Message); 89 | } 90 | #endif 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ErrorHelperPackage.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 28 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2024 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 | -------------------------------------------------------------------------------- /src/OpenUrlCommand.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel.Design; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using Microsoft.VisualStudio.Shell; 11 | using Task = System.Threading.Tasks.Task; 12 | 13 | namespace ErrorHelper 14 | { 15 | public sealed class OpenUrlCommand : CommandBase 16 | { 17 | public const int CommandId = 4130; 18 | 19 | private OpenUrlCommand(AsyncPackage package, OleMenuCommandService commandService) 20 | : base(package) 21 | { 22 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 23 | 24 | var menuCommandID = new CommandID(CommandSet, CommandId); 25 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 26 | commandService.AddCommand(menuItem); 27 | } 28 | 29 | public static OpenUrlCommand Instance { get; private set; } 30 | 31 | public static async Task InitializeAsync(AsyncPackage package) 32 | { 33 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 34 | 35 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 36 | Instance = new OpenUrlCommand(package, commandService); 37 | } 38 | 39 | public static List ParseUrls(string input) 40 | { 41 | var result = new List(); 42 | 43 | result.AddRange(ParseUrlsWithPrefix(input, "https://")); 44 | result.AddRange(ParseUrlsWithPrefix(input, "http://")); 45 | 46 | return result; 47 | } 48 | 49 | public static List ParseUrlsWithPrefix(string input, string prefix) 50 | { 51 | var urls = new List(); 52 | 53 | var nextStart = input.IndexOf(prefix); 54 | 55 | var trimEndChars = new[] { '.', ',', '"', ':', ';', '\'', ')' }; 56 | 57 | while (nextStart > -1) 58 | { 59 | var end = input.IndexOfAny(new[] { ' ', "\r"[0], "\n"[0] }, nextStart); 60 | 61 | int nextSearchStart; 62 | 63 | if (end == -1) 64 | { 65 | // Will be true if domain ends at the end of the string 66 | urls.Add(input.Substring(nextStart).TrimEnd(trimEndChars)); 67 | nextSearchStart = nextStart + 1; // Need to set this to a valid value that will break the while loop 68 | } 69 | else 70 | { 71 | urls.Add(input.Substring(nextStart, end - nextStart).TrimEnd(trimEndChars)); 72 | nextSearchStart = end; 73 | } 74 | 75 | nextStart = input.IndexOf(prefix, nextSearchStart); 76 | } 77 | 78 | return urls; 79 | } 80 | 81 | #pragma warning disable VSTHRD100 // Avoid async void methods 82 | private async void Execute(object sender, EventArgs e) 83 | #pragma warning restore VSTHRD100 // Avoid async void methods 84 | { 85 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 86 | 87 | var desc = await this.GetDescriptionAsync(); 88 | 89 | if (!string.IsNullOrEmpty(desc)) 90 | { 91 | var urls = ParseUrls(desc); 92 | 93 | var urlCount = urls.Count; 94 | 95 | if (urlCount > 0) 96 | { 97 | if (urlCount > 1) 98 | { 99 | await this.ShowStatusBarMessageAsync("Multiple URLS found in description, opening first"); 100 | } 101 | 102 | var ps = new ProcessStartInfo(urls.First()) 103 | { 104 | UseShellExecute = true, 105 | Verb = "open", 106 | }; 107 | 108 | Process.Start(ps); 109 | } 110 | else 111 | { 112 | await this.ShowStatusBarMessageAsync("No URL found in description"); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/OptionPageGrid.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System.ComponentModel; 6 | using Microsoft.VisualStudio.Shell; 7 | 8 | namespace ErrorHelper 9 | { 10 | public class OptionPageGrid : DialogPage 11 | { 12 | [Category("Error Helper")] 13 | [DisplayName("Search Engine")] 14 | [Description("Where to do the search.")] 15 | public SearchEngine SearchEngine { get; set; } 16 | 17 | [Category("Error Helper")] 18 | [DisplayName("Custom search URL")] 19 | [Description("What URL prefix should be used with a \"Custom\" search.")] 20 | public string SearchUrl { get; set; } = "https://example.com/search?q="; 21 | 22 | ////[Category("Error Helper")] 23 | ////[DisplayName("Strip Paths Before Search")] 24 | ////[Description("Remove anything that looks like a file path from the value sent to the search engine.")] 25 | ////public bool StripPaths { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OutputPane.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using Microsoft.VisualStudio; 7 | using Microsoft.VisualStudio.Shell; 8 | using Microsoft.VisualStudio.Shell.Interop; 9 | 10 | namespace ErrorHelper 11 | { 12 | public class OutputPane 13 | { 14 | private static Guid ehPaneGuid = new Guid("40B9EC6F-E48A-408B-A4CC-BD73D08FEE0D"); 15 | 16 | private static OutputPane instance; 17 | 18 | private readonly IVsOutputWindowPane pane; 19 | 20 | private OutputPane() 21 | { 22 | ThreadHelper.ThrowIfNotOnUIThread(); 23 | 24 | if (ServiceProvider.GlobalProvider.GetService(typeof(SVsOutputWindow)) is IVsOutputWindow outWindow 25 | && (ErrorHandler.Failed(outWindow.GetPane(ref ehPaneGuid, out this.pane)) || this.pane == null)) 26 | { 27 | if (ErrorHandler.Failed(outWindow.CreatePane(ref ehPaneGuid, Vsix.Name, 1, 0))) 28 | { 29 | System.Diagnostics.Debug.WriteLine("Failed to create the Output window pane."); 30 | return; 31 | } 32 | 33 | if (ErrorHandler.Failed(outWindow.GetPane(ref ehPaneGuid, out this.pane)) || (this.pane == null)) 34 | { 35 | System.Diagnostics.Debug.WriteLine("Failed to get access to the Output window pane."); 36 | } 37 | } 38 | } 39 | 40 | public static OutputPane Instance => instance ?? (instance = new OutputPane()); 41 | 42 | public void Activate() 43 | { 44 | ThreadHelper.ThrowIfNotOnUIThread(); 45 | 46 | this.pane?.Activate(); 47 | } 48 | 49 | public void WriteLine(string message) 50 | { 51 | ThreadHelper.ThrowIfNotOnUIThread(); 52 | 53 | this.pane?.OutputStringThreadSafe($"{message}{Environment.NewLine}"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | using ErrorHelper; 8 | 9 | [assembly: AssemblyTitle(Vsix.Name)] 10 | [assembly: AssemblyDescription(Vsix.Description)] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("Matt Lacey Ltd.")] 13 | [assembly: AssemblyProduct(Vsix.Name)] 14 | [assembly: AssemblyCopyright("2022 Matt Lacey Ltd.")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | [assembly: ComVisible(false)] 19 | 20 | [assembly: AssemblyVersion(Vsix.Version)] 21 | [assembly: AssemblyFileVersion(Vsix.Version)] 22 | -------------------------------------------------------------------------------- /src/SearchDescriptionCommand.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using System.Diagnostics; 8 | using System.Net; 9 | using Microsoft.VisualStudio.Shell; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace ErrorHelper 13 | { 14 | public sealed class SearchDescriptionCommand : CommandBase 15 | { 16 | public const int CommandId = 4129; 17 | 18 | private SearchDescriptionCommand(AsyncPackage package, OleMenuCommandService commandService) 19 | : base(package) 20 | { 21 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 22 | 23 | var menuCommandID = new CommandID(CommandSet, CommandId); 24 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 25 | commandService.AddCommand(menuItem); 26 | } 27 | 28 | public static SearchDescriptionCommand Instance { get; private set; } 29 | 30 | public static async Task InitializeAsync(AsyncPackage package) 31 | { 32 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 33 | 34 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 35 | Instance = new SearchDescriptionCommand(package, commandService); 36 | } 37 | 38 | #pragma warning disable VSTHRD100 // Avoid async void methods 39 | private async void Execute(object sender, EventArgs e) 40 | #pragma warning restore VSTHRD100 // Avoid async void methods 41 | { 42 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 43 | 44 | try 45 | { 46 | var desc = await this.GetDescriptionAsync(); 47 | 48 | if (!string.IsNullOrEmpty(desc)) 49 | { 50 | string url = this.GetSearchUrlBase(); 51 | string query = desc; 52 | 53 | var ps = new ProcessStartInfo(url + WebUtility.UrlEncode(query)) 54 | { 55 | UseShellExecute = true, 56 | Verb = "open", 57 | }; 58 | 59 | Process.Start(ps); 60 | } 61 | } 62 | catch (Exception exc) 63 | { 64 | await this.ShowStatusBarMessageAsync("Unable to search for error description."); 65 | OutputPane.Instance.WriteLine($"ErrorHelper: {exc}"); 66 | OutputPane.Instance.Activate(); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/SearchDescriptionWithoutPathsCommand.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.ComponentModel.Design; 7 | using System.Diagnostics; 8 | using System.Net; 9 | using Microsoft.VisualStudio.Shell; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace ErrorHelper 13 | { 14 | public sealed class SearchDescriptionWithoutPathsCommand : CommandBase 15 | { 16 | public const int CommandId = 4131; 17 | 18 | private SearchDescriptionWithoutPathsCommand(AsyncPackage package, OleMenuCommandService commandService) 19 | : base(package) 20 | { 21 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 22 | 23 | var menuCommandID = new CommandID(CommandSet, CommandId); 24 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 25 | commandService.AddCommand(menuItem); 26 | } 27 | 28 | public static SearchDescriptionWithoutPathsCommand Instance { get; private set; } 29 | 30 | public static async Task InitializeAsync(AsyncPackage package) 31 | { 32 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 33 | 34 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 35 | Instance = new SearchDescriptionWithoutPathsCommand(package, commandService); 36 | } 37 | 38 | public static string StripPaths(string descriptionContainingPaths) 39 | { 40 | var result = descriptionContainingPaths ?? string.Empty; 41 | 42 | string WithoutAbsolutePath(string original, string identifier) 43 | { 44 | var res = original; 45 | 46 | if (res.Contains(identifier)) 47 | { 48 | var pathStartPos = res.IndexOf(identifier); 49 | 50 | if (char.IsLetter(res[pathStartPos - 1]) 51 | && !char.IsLetterOrDigit(res[pathStartPos - 2])) 52 | { 53 | pathStartPos -= 1; 54 | 55 | var endPos = res.IndexOf(res[pathStartPos - 1], pathStartPos); 56 | 57 | if (res[pathStartPos - 1] == '\'') 58 | { 59 | endPos = res.IndexOf("'", pathStartPos) + 1; 60 | pathStartPos -= 1; 61 | } 62 | 63 | var firstPart = res.Substring(0, pathStartPos); 64 | 65 | if (endPos > 0) 66 | { 67 | res = firstPart + res.Substring(endPos); 68 | } 69 | else 70 | { 71 | res = firstPart; 72 | } 73 | } 74 | } 75 | 76 | return res; 77 | } 78 | 79 | result = WithoutAbsolutePath(result, ":/"); 80 | result = WithoutAbsolutePath(result, ":\\"); 81 | 82 | string WithoutRelativePath(string original, string identifier) 83 | { 84 | var res = original; 85 | 86 | if (res.Contains(identifier)) 87 | { 88 | var pathStartPos = res.IndexOf(identifier); 89 | 90 | var endPos = res.IndexOf(" ", pathStartPos); 91 | 92 | if (res[pathStartPos - 1] == '\'') 93 | { 94 | endPos = res.IndexOf("'", pathStartPos) + 1; 95 | pathStartPos -= 1; 96 | } 97 | 98 | var firstPart = res.Substring(0, pathStartPos); 99 | 100 | res = firstPart + res.Substring(endPos); 101 | } 102 | 103 | return res; 104 | } 105 | 106 | result = WithoutRelativePath(result, "../"); 107 | result = WithoutRelativePath(result, "..\\"); 108 | 109 | return result; 110 | } 111 | 112 | #pragma warning disable VSTHRD100 // Avoid async void methods 113 | private async void Execute(object sender, EventArgs e) 114 | #pragma warning restore VSTHRD100 // Avoid async void methods 115 | { 116 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 117 | 118 | try 119 | { 120 | var desc = await this.GetDescriptionAsync(); 121 | 122 | if (!string.IsNullOrEmpty(desc)) 123 | { 124 | string url = this.GetSearchUrlBase(); 125 | string query = StripPaths(desc); 126 | 127 | var ps = new ProcessStartInfo(url + WebUtility.UrlEncode(query)) 128 | { 129 | UseShellExecute = true, 130 | Verb = "open", 131 | }; 132 | 133 | Process.Start(ps); 134 | } 135 | } 136 | catch (Exception exc) 137 | { 138 | await this.ShowStatusBarMessageAsync("Unable to search for error description."); 139 | OutputPane.Instance.WriteLine($"ErrorHelper: {exc}"); 140 | OutputPane.Instance.Activate(); 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/SearchEngine.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | namespace ErrorHelper 6 | { 7 | public enum SearchEngine 8 | { 9 | Ecosia, 10 | Bing, 11 | DuckDuckGo, 12 | Google, 13 | StackOverflow, 14 | Custom, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SponsorDetector.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace ErrorHelper 9 | { 10 | public class SponsorDetector 11 | { 12 | // This might be the code you see, but it's not what I compile into the extensions when built ;) 13 | public static async Task IsSponsorAsync() 14 | { 15 | return await Task.FromResult(false); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SponsorRequestHelper.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Matt Lacey Limited. All rights reserved. 3 | // 4 | 5 | using System; 6 | using Microsoft.VisualStudio.Shell; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace ErrorHelper 10 | { 11 | public class SponsorRequestHelper 12 | { 13 | public static async Task CheckIfNeedToShowAsync() 14 | { 15 | if (await SponsorDetector.IsSponsorAsync()) 16 | { 17 | if (new Random().Next(1, 10) == 2) 18 | { 19 | await ShowThanksForSponsorshipMessageAsync(); 20 | } 21 | } 22 | else 23 | { 24 | await ShowPromptForSponsorshipAsync(); 25 | } 26 | } 27 | 28 | private static async Task ShowThanksForSponsorshipMessageAsync() 29 | { 30 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 31 | 32 | OutputPane.Instance.WriteLine("Thank you for your sponsorship. It really helps."); 33 | OutputPane.Instance.WriteLine("If you have ideas for new features or suggestions for new features"); 34 | OutputPane.Instance.WriteLine("please raise an issue at https://github.com/mrlacey/ErrorHelper/issues"); 35 | OutputPane.Instance.WriteLine(string.Empty); 36 | } 37 | 38 | private static async Task ShowPromptForSponsorshipAsync() 39 | { 40 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 41 | 42 | OutputPane.Instance.WriteLine("********************************************************************************************************"); 43 | OutputPane.Instance.WriteLine("This is a free extension that is made possible thanks to the kind and generous donations of:"); 44 | OutputPane.Instance.WriteLine(""); 45 | OutputPane.Instance.WriteLine("Daniel, James, Mike, Bill, unicorns39283, Martin, Richard, Alan, Howard, Mike, Dave, Joe, "); 46 | OutputPane.Instance.WriteLine("Alvin, Anders, Melvyn, Nik, Kevin, Richard, Orien, Shmueli, Gabriel, Martin, Neil, Daniel, "); 47 | OutputPane.Instance.WriteLine("Victor, Uno, Paula, Tom, Nick, Niki, chasingcode, luatnt, holeow, logarrhythmic, kokolorix, "); 48 | OutputPane.Instance.WriteLine("Guiorgy, Jessé, pharmacyhalo, MXM-7, atexinspect, João, hals1010, WTD-leachA, andermikael, "); 49 | OutputPane.Instance.WriteLine("spudwa, Cleroth, relentless-dev-purchases & 20+ more"); 50 | OutputPane.Instance.WriteLine(""); 51 | OutputPane.Instance.WriteLine("Join them to show you appreciation and ensure future maintenance and development by becoming a sponsor."); 52 | OutputPane.Instance.WriteLine(""); 53 | OutputPane.Instance.WriteLine("Go to https://github.com/sponsors/mrlacey"); 54 | OutputPane.Instance.WriteLine(""); 55 | OutputPane.Instance.WriteLine("Any amount, as either a one-off or on a monthly basis, is appreciated more than you can imagine."); 56 | OutputPane.Instance.WriteLine(""); 57 | OutputPane.Instance.WriteLine("I'll also tell you how to hide this message too. ;)"); 58 | OutputPane.Instance.WriteLine(""); 59 | OutputPane.Instance.WriteLine(""); 60 | OutputPane.Instance.WriteLine("If you can't afford to support financially, you can always"); 61 | OutputPane.Instance.WriteLine("leave a positive review at https://marketplace.visualstudio.com/items?itemName=MattLaceyLtd.ErrorHelper&ssr=false#review-details"); 62 | OutputPane.Instance.WriteLine(""); 63 | OutputPane.Instance.WriteLine("********************************************************************************************************"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/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 | Error Helper 122 | 123 | 124 | General 125 | 126 | 127 | Error Helper 128 | 129 | 130 | Make it easy to copy or search for descriptions in the error list. 131 | 132 | -------------------------------------------------------------------------------- /src/filestosign.txt: -------------------------------------------------------------------------------- 1 | ErrorHelper.dll -------------------------------------------------------------------------------- /src/signvsix.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/source.extension.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by VSIX Synchronizer 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace ErrorHelper 7 | { 8 | internal sealed partial class Vsix 9 | { 10 | public const string Id = "ErrorHelper.e90a7159-7bf3-45f6-b3d5-e80fd5978133"; 11 | public const string Name = "Error Helper"; 12 | public const string Description = @"Helper for using the ErrorList Description."; 13 | public const string Language = "en-US"; 14 | public const string Version = "2.8.8"; 15 | public const string Author = "Matt Lacey"; 16 | public const string Tags = ""; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Error Helper 6 | Helper for using the ErrorList Description. 7 | https://github.com/mrlacey/ErrorHelper 8 | LICENSE.txt 9 | ErrorHelper.png 10 | ErrorHelper.png 11 | 12 | 13 | 14 | amd64 15 | 16 | 17 | arm64 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------