├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── Readme.md ├── azure-pipelines.yml ├── build.ps1 ├── build ├── Release.ps1 ├── ReleasePipeline.ps1 └── Update-Version.ps1 ├── resources ├── MarketplaceOverview.md ├── VsixPublishManifest.json ├── context-menu.png ├── created-breakpoint.png ├── new-function-breakpoint-dialog.png └── new-function-breakpoint-menu.png └── src ├── CopyFunctionBreakpointName.Tests ├── AnnotatedSourceUtils.cs ├── CopyFunctionBreakpointName.Tests.csproj ├── FunctionBreakpointUtilsTests.cs └── Properties │ └── AssemblyInfo.cs ├── CopyFunctionBreakpointName.sln └── CopyFunctionBreakpointName ├── CopyFunctionBreakpointName.csproj ├── CopyFunctionBreakpointNamePackage.cs ├── CopyFunctionBreakpointNamePackage.vsct ├── CopyFunctionBreakpointNameService.cs ├── Extensions.cs ├── FunctionBreakpointNameFactory.cs ├── FunctionBreakpointUtils.cs ├── Properties └── AssemblyInfo.cs ├── VSPackage.resx └── source.extension.vsixmanifest /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | charset = utf-8 6 | end_of_line = crlf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{cs,vsixmanifest}] 14 | indent_size = 4 15 | 16 | [*] 17 | csharp_indent_case_contents_when_block = false 18 | csharp_style_conditional_delegate_call = true:error 19 | dotnet_sort_system_directives_first = true 20 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 21 | dotnet_style_predefined_type_for_member_access = true:error 22 | 23 | # Override ReSharper defaults 24 | csharp_space_after_cast = false 25 | resharper_csharp_space_within_single_line_array_initializer_braces = true # https://www.jetbrains.com/help/resharper/EditorConfig_CSHARP_SpacesPageSchema.html#resharper_csharp_space_within_single_line_array_initializer_braces 26 | 27 | # The first matching rule wins, more specific rules at the top 28 | # dotnet_naming_rule.*.symbols does not yet support a comma-separated list https://github.com/dotnet/roslyn/issues/20891 29 | # dotnet_naming_symbols.*.applicable_kinds does not yet support namespace, type_parameter or local https://github.com/dotnet/roslyn/issues/18121 30 | 31 | dotnet_naming_style.interfaces.required_prefix = I 32 | dotnet_naming_style.interfaces.capitalization = pascal_case # Needed or VS ignores all naming rules https://github.com/dotnet/roslyn/issues/20895 33 | 34 | dotnet_naming_symbols.interfaces.applicable_kinds = interface 35 | dotnet_naming_rule.interfaces.severity = error 36 | dotnet_naming_rule.interfaces.symbols = interfaces 37 | dotnet_naming_rule.interfaces.style = interfaces 38 | 39 | 40 | dotnet_naming_style.pascal_case.capitalization = pascal_case 41 | 42 | dotnet_naming_symbols.namespaces_types_and_non_field_members.applicable_kinds = namespace, class, struct, enum, interface, delegate, type_parameter, method, property, event 43 | dotnet_naming_rule.namespaces_types_and_non_field_members.severity = error 44 | dotnet_naming_rule.namespaces_types_and_non_field_members.symbols = namespaces_types_and_non_field_members 45 | dotnet_naming_rule.namespaces_types_and_non_field_members.style = pascal_case 46 | 47 | dotnet_naming_symbols.non_private_fields.applicable_kinds = field 48 | dotnet_naming_symbols.non_private_fields.applicable_accessibilities = public, protected, protected_internal, internal 49 | dotnet_naming_rule.non_private_fields.severity = error 50 | dotnet_naming_rule.non_private_fields.symbols = non_private_fields 51 | dotnet_naming_rule.non_private_fields.style = pascal_case 52 | 53 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 54 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 55 | dotnet_naming_rule.static_readonly_fields.severity = error 56 | dotnet_naming_rule.static_readonly_fields.symbols = static_readonly_fields 57 | dotnet_naming_rule.static_readonly_fields.style = pascal_case 58 | 59 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 60 | dotnet_naming_symbols.constant_fields.required_modifiers = const 61 | dotnet_naming_rule.constant_fields.severity = error 62 | dotnet_naming_rule.constant_fields.symbols = constant_fields 63 | dotnet_naming_rule.constant_fields.style = pascal_case 64 | 65 | 66 | dotnet_naming_style.camel_case.capitalization = camel_case 67 | 68 | dotnet_naming_symbols.other_fields_parameters_and_locals.applicable_kinds = field, parameter, local 69 | dotnet_naming_rule.other_fields_parameters_and_locals.severity = error 70 | dotnet_naming_rule.other_fields_parameters_and_locals.symbols = other_fields_parameters_and_locals 71 | dotnet_naming_rule.other_fields_parameters_and_locals.style = camel_case 72 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2018 Joseph Musser 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Copy Function Breakpoint Name extension 2 | 3 | [![Build Status](https://jnm2.visualstudio.com/CopyFunctionBreakpointName/_apis/build/status/CI)](https://jnm2.visualstudio.com/CopyFunctionBreakpointName/_build/latest?definitionId=7) 4 | 5 | This Visual Studio extension enables you to quickly copy a name to the clipboard which the New Function Breakpoint dialog understands. 6 | This is useful when you cannot place a breakpoint directly in the current source view; for example, when viewing metadata or decompiled source, or when you’re in a separate instance of Visual Studio from the one in which you want to set the breakpoint. 7 | 8 | Start by right-clicking the member inside which you want to break: 9 | 10 | ![Context menu screenshot](resources/context-menu.png) 11 | 12 | Then use Ctrl+D, B or your keyboard shortcut to open and focus the Breakpoints window: 13 | 14 | ![New function breakpoint menu](resources/new-function-breakpoint-menu.png) 15 | 16 | And then Ctrl+B or your keyboard shortcut to open the New Function Breakpoint dialog: 17 | 18 | ![New function breakpoint dialog](resources/new-function-breakpoint-dialog.png) 19 | 20 | Then Ctrl+V to paste and Enter to accept, and you’re done! 21 | 22 | ![Created breakpoint](resources/created-breakpoint.png) 23 | 24 | Currently requires Visual Studio 2017 Update 7 or later. The extension can be installed in Visual Studio 2019 but it has not been tested there. 25 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: VS2017-Win2016 3 | 4 | trigger: 5 | branches: 6 | include: [ '*' ] 7 | exclude: [ 'refs/tags/*' ] 8 | 9 | steps: 10 | 11 | - powershell: | 12 | . 'build/Update-Version.ps1' 13 | 14 | Update-Version $env:BUILD_BUILDID ` 15 | -CommitHash $env:BUILD_SOURCEVERSION.Substring(0, 8) ` 16 | -UpdateBuildNumber { 17 | param([string] $BuildNumber) 18 | Write-Host "##vso[build.updatebuildnumber]$BuildNumber" 19 | } 20 | displayName: 'Update version' 21 | 22 | - task: VSBuild@1 23 | displayName: Build 24 | inputs: 25 | solution: src 26 | msbuildArgs: /restore /p:DeployExtension=false 27 | configuration: Release 28 | 29 | - task: VSTest@2 30 | displayName: Test 31 | 32 | - task: PublishBuildArtifacts@1 33 | displayName: Save VSIX artifact 34 | inputs: 35 | PathtoPublish: src\CopyFunctionBreakpointName\bin\Release\CopyFunctionBreakpointName.vsix 36 | ArtifactName: VSIX 37 | 38 | - task: PublishBuildArtifacts@1 39 | displayName: Save symbols 40 | inputs: 41 | PathtoPublish: src\CopyFunctionBreakpointName\bin\Release\CopyFunctionBreakpointName.pdb 42 | ArtifactName: Symbols 43 | 44 | - task: CopyFiles@2 45 | displayName: Stage marketplace publish artifacts 46 | inputs: 47 | SourceFolder: '$(Build.SourcesDirectory)\resources' 48 | Contents: | 49 | VsixPublishManifest.json 50 | MarketplaceOverview.md 51 | context-menu.png 52 | created-breakpoint.png 53 | new-function-breakpoint-dialog.png 54 | new-function-breakpoint-menu.png 55 | TargetFolder: $(Build.ArtifactStagingDirectory)\Marketplace 56 | 57 | - task: PublishBuildArtifacts@1 58 | displayName: Save marketplace publish artifacts 59 | inputs: 60 | PathtoPublish: $(Build.ArtifactStagingDirectory)\Marketplace 61 | ArtifactName: Marketplace 62 | 63 | - task: CopyFiles@2 64 | displayName: Stage release pipeline source artifacts 65 | inputs: 66 | SourceFolder: '$(Build.SourcesDirectory)\build' 67 | Contents: | 68 | Release.ps1 69 | ReleasePipeline.ps1 70 | TargetFolder: $(Build.ArtifactStagingDirectory)\ReleasePipelineSource 71 | 72 | - task: PublishBuildArtifacts@1 73 | displayName: Save release pipeline source artifacts 74 | inputs: 75 | PathtoPublish: $(Build.ArtifactStagingDirectory)\ReleasePipelineSource 76 | ArtifactName: ReleasePipelineSource 77 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop"; 2 | 3 | . 'build\Update-Version.ps1' 4 | Update-Version 5 | 6 | $visualStudioInstallation = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath 7 | $configuration = 'Release' 8 | $targetFramework = 'net472' 9 | 10 | $msbuild = Join-Path $visualStudioInstallation 'MSBuild\15.0\Bin\MSBuild.exe' 11 | & $msbuild src /restore /p:Configuration=$configuration /v:minimal 12 | 13 | $vstest = join-path $visualStudioInstallation 'Common7\IDE\CommonExtensions\Microsoft\TestWindow\VSTest.Console.exe' 14 | & $vstest src\CopyFunctionBreakpointName.Tests\bin\$configuration\$targetFramework\CopyFunctionBreakpointName.Tests.dll 15 | -------------------------------------------------------------------------------- /build/Release.ps1: -------------------------------------------------------------------------------- 1 | function PublishToMarketplace( 2 | [Parameter(Mandatory=$true)] [string] $VsixPath, 3 | [Parameter(Mandatory=$true)] [string] $VsixPublishManifestPath, 4 | [Parameter(Mandatory=$true)] [string] $VsixMarketplaceAccessToken 5 | ) { 6 | $visualStudioInstallation = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VSSDK -property installationPath 7 | 8 | $vsixPublisher = Join-Path $visualStudioInstallation 'VSSDK\VisualStudioIntegration\Tools\Bin\VsixPublisher.exe' 9 | & $vsixPublisher publish -payload $VsixPath -publishManifest $VsixPublishManifestPath -personalAccessToken $VsixMarketplaceAccessToken 10 | } 11 | 12 | function CreateGitHubRelease( 13 | [Parameter(Mandatory=$true)] [string] $GitHubAccessToken, 14 | [Parameter(Mandatory=$true)] [string] $Owner, 15 | [Parameter(Mandatory=$true)] [string] $Repository, 16 | [Parameter(Mandatory=$true)] [string] $Commit, 17 | [Parameter(Mandatory=$true)] [string] $Tag, 18 | [Parameter(Mandatory=$true)] [string] $Name, 19 | [string] $Body, 20 | [switch] $Draft, 21 | [switch] $Prerelease, 22 | [string[]] $Assets 23 | ) { 24 | [System.Net.ServicePointManager]::SecurityProtocol = 'tls12' 25 | 26 | $headers = @{ 27 | Accept = 'application/vnd.github.v3+json' 28 | Authorization = "token $GitHubAccessToken" 29 | 'User-Agent' = 'PowerShell' 30 | } 31 | 32 | # https://developer.github.com/v3/repos/releases/#create-a-release 33 | $creationResult = Invoke-RestMethod ` 34 | -Method 'post' ` 35 | -Uri "https://api.github.com/repos/$Owner/$Repository/releases" ` 36 | -Headers $headers ` 37 | -Body (ConvertTo-Json @{ 38 | tag_name = $Tag 39 | target_commitish = $Commit 40 | name = $Name 41 | body = $Body 42 | draft = $Draft.IsPresent 43 | prerelease = $Prerelease.IsPresent 44 | }) 45 | 46 | foreach ($asset in $Assets) { 47 | # https://developer.github.com/v3/repos/releases/#upload-a-release-asset 48 | $filename = Split-Path $asset -leaf 49 | $uploadUrl = $creationResult.upload_url -replace "\{\?[^}]+\}", "?name=$filename" 50 | 51 | $null = Invoke-RestMethod ` 52 | -Method 'post' ` 53 | -Uri $uploadUrl ` 54 | -Headers $headers ` 55 | -ContentType 'application/zip' ` 56 | -InFile $asset 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /build/ReleasePipeline.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Until release pipelines have YAML support (https://dev.azure.com/mseng/Azure%20DevOps%20Roadmap/_workitems/edit/1221170), 3 | as much as possible goes here. 4 | 5 | Setup instructions: 6 | 7 | 1. Create secret release variables: 8 | 9 | - VsixMarketplaceAccessToken, value obtained from Azure DevOps user settings (Security/Personal Access Tokens). 10 | Organization *must* be changed to ‘All accessible organizations’ at creation time. Editing later doesn’t work. 11 | Under Scopes, choose ‘Custom defined,’ ‘Show all scopes,’ and click Publish under the Marketplace group. 12 | 13 | - GitHubReleaseAccessToken, value obtained from https://github.com/settings/tokens. 14 | Only scope needed is public_repo. 15 | 16 | 2. Add PowerShell task to the release pipeline job and set: 17 | 18 | - Display name: Delegate to source-controlled file 19 | 20 | - Script path: $(System.DefaultWorkingDirectory)\CI\ReleasePipelineSource\ReleasePipeline.ps1 21 | This assumes the build producing the artifacts is named CI and that one of the artifacts contains this file 22 | and is named ReleasePipelineSource. Refer to azure_pipelines.yml to see how the artifacts are created. 23 | 24 | - Arguments: "$(VsixMarketplaceAccessToken)" "$(GitHubReleaseAccessToken)" 25 | 26 | 3. If you like, set Options > General > Release name format: Release $(Build.BuildNumber) 27 | Should be good to go! 28 | #> 29 | 30 | # Secret release variables are injected 31 | Param( 32 | [Parameter(Mandatory=$true)] [string] $VsixMarketplaceAccessToken, 33 | [Parameter(Mandatory=$true)] [string] $GitHubReleaseAccessToken 34 | ) 35 | 36 | . "$PSScriptRoot\Release.ps1" 37 | 38 | # Calculate release version by scripping the build metadata from the build version 39 | # (of the form ‘1.0.0+build.1234.commit.abcdef12’) 40 | $releaseVersion = $env:BUILD_BUILDNUMBER.Substring(0, $env:BUILD_BUILDNUMBER.IndexOfAny(('-', '+'))) 41 | 42 | PublishToMarketplace ` 43 | "$env:SYSTEM_ARTIFACTSDIRECTORY\CI\VSIX\CopyFunctionBreakpointName.vsix" ` 44 | "$env:SYSTEM_ARTIFACTSDIRECTORY\CI\Marketplace\VsixPublishManifest.json" ` 45 | $VsixMarketplaceAccessToken 46 | 47 | CreateGitHubRelease ` 48 | -GitHubAccessToken $GitHubReleaseAccessToken ` 49 | -Owner 'jnm2' ` 50 | -Repository 'CopyFunctionBreakpointName' ` 51 | -Commit $env:BUILD_SOURCEVERSION ` 52 | -Tag "v$releaseVersion" ` 53 | -Name $releaseVersion ` 54 | -Body 'https://marketplace.visualstudio.com/items?itemName=jnm2.CopyFunctionBreakpointName' ` 55 | -Assets ( 56 | "$env:SYSTEM_ARTIFACTSDIRECTORY\CI\VSIX\CopyFunctionBreakpointName.vsix", 57 | "$env:SYSTEM_ARTIFACTSDIRECTORY\CI\Symbols\CopyFunctionBreakpointName.pdb" 58 | ) 59 | -------------------------------------------------------------------------------- /build/Update-Version.ps1: -------------------------------------------------------------------------------- 1 | $vsixmanifestPath = 'src\CopyFunctionBreakpointName\source.extension.vsixmanifest' 2 | $assemblyInfoPath = 'src\CopyFunctionBreakpointName\Properties\AssemblyInfo.cs' 3 | 4 | function XmlPeek( 5 | [Parameter(Mandatory=$true)] [string] $FilePath, 6 | [Parameter(Mandatory=$true)] [string] $XPath, 7 | [HashTable] $NamespaceUrisByPrefix 8 | ) { 9 | $document = [xml](Get-Content $FilePath) 10 | $namespaceManager = [System.Xml.XmlNamespaceManager]::new($document.NameTable) 11 | 12 | if ($null -ne $NamespaceUrisByPrefix) { 13 | foreach ($prefix in $NamespaceUrisByPrefix.Keys) { 14 | $namespaceManager.AddNamespace($prefix, $NamespaceUrisByPrefix[$prefix]); 15 | } 16 | } 17 | 18 | return $document.SelectSingleNode($XPath, $namespaceManager).Value 19 | } 20 | 21 | function XmlPoke( 22 | [Parameter(Mandatory=$true)] [string] $FilePath, 23 | [Parameter(Mandatory=$true)] [string] $XPath, 24 | [Parameter(Mandatory=$true)] [string] $Value, 25 | [HashTable] $NamespaceUrisByPrefix 26 | ) { 27 | $document = [System.Xml.XmlDocument]::new() 28 | $document.PreserveWhitespace = $true 29 | $document.Load((Resolve-Path $FilePath)) 30 | 31 | $namespaceManager = [System.Xml.XmlNamespaceManager]::new($document.NameTable) 32 | 33 | if ($null -ne $NamespaceUrisByPrefix) { 34 | foreach ($prefix in $NamespaceUrisByPrefix.Keys) { 35 | $namespaceManager.AddNamespace($prefix, $NamespaceUrisByPrefix[$prefix]); 36 | } 37 | } 38 | 39 | $document.SelectSingleNode($XPath, $namespaceManager).Value = $Value 40 | $document.Save((Resolve-Path $FilePath)) 41 | } 42 | 43 | function Get-AutomaticCiVersion { 44 | function Get-VersionPrefix([Parameter(Mandatory=$true)] [string] $Tag) { 45 | # Start the search at index 6, skipping 1 for the `v` and 5 because no valid semantic version can have a suffix sooner than `N.N.N`. 46 | $suffixStart = $Tag.IndexOfAny(('-', '+'), 6) 47 | 48 | return [version] $( 49 | if ($suffixStart -eq -1) { 50 | $Tag.Substring(1) 51 | } else { 52 | $Tag.Substring(1, $suffixStart - 1) 53 | }) 54 | } 55 | 56 | $currentTags = @(git tag --list v* --points-at head --sort=-v:refname) 57 | if ($currentTags.Count -gt 0) { 58 | # Head is tagged, so the tag is the intended CI version for this build. 59 | return Get-VersionPrefix $currentTags[0] 60 | } 61 | 62 | $previousTags = @(git tag --list v* --sort=-v:refname) 63 | if ($previousTags.Count -gt 0) { 64 | # Head is not tagged, so it would be greater than the most recent tagged version. 65 | $previousVersion = Get-VersionPrefix $previousTags[0] 66 | return [version]::new($previousVersion.Major, $previousVersion.Minor, $previousVersion.Build + 1) 67 | } 68 | 69 | # No release has been tagged, so the initial version should be whatever the source files currently contain. 70 | } 71 | 72 | function Update-Version([string] $BuildNumber, [string] $CommitHash, [ScriptBlock] $UpdateBuildNumber) { 73 | $vsixManifestVersionPath = '/vsx:PackageManifest/vsx:Metadata/vsx:Identity/@Version' 74 | $vsixNamespaces = @{ vsx = 'http://schemas.microsoft.com/developer/vsx-schema/2011' } 75 | 76 | $extensionVersion = [version](XmlPeek $vsixmanifestPath $vsixManifestVersionPath $vsixNamespaces) 77 | 78 | $automaticCiVersion = Get-AutomaticCiVersion 79 | if ($extensionVersion -lt $automaticCiVersion) { $extensionVersion = $automaticCiVersion } 80 | 81 | $assemblyProductVersion = [string] $extensionVersion; 82 | $suffix = @() 83 | if ($BuildNumber) { $suffix += "build.$BuildNumber" } 84 | if ($CommitHash) { $suffix += "commit.$CommitHash" } 85 | if ($suffix) { $assemblyProductVersion += '+' + ($suffix -Join '.') } 86 | 87 | if ($UpdateBuildNumber) { $UpdateBuildNumber.Invoke($assemblyProductVersion) } 88 | 89 | XmlPoke $vsixmanifestPath $vsixManifestVersionPath -Value $extensionVersion $vsixNamespaces 90 | 91 | $assemblyInfo = [System.IO.File]::ReadAllText((Resolve-Path $assemblyInfoPath)) 92 | 93 | $assemblyInfo = $assemblyInfo ` 94 | -replace '(?<=\[assembly: AssemblyVersion\(")[^"]*(?="\)\])', $extensionVersion ` 95 | -replace '(?<=\[assembly: AssemblyInformationalVersion\(")[^"]*(?="\)\])', $assemblyProductVersion 96 | 97 | [System.IO.File]::WriteAllText((Resolve-Path $assemblyInfoPath), $assemblyInfo) 98 | } 99 | -------------------------------------------------------------------------------- /resources/MarketplaceOverview.md: -------------------------------------------------------------------------------- 1 | This extension enables you to quickly copy a name to the clipboard which the New Function Breakpoint dialog understands. 2 | This is useful when you cannot place a breakpoint directly in the current source view; for example, when viewing metadata or decompiled source, or when you’re in a separate instance of Visual Studio from the one in which you want to set the breakpoint. 3 | 4 | Start by right-clicking the member inside which you want to break: 5 | 6 | ![Context menu screenshot](context-menu.png) 7 | 8 | Then use Ctrl+D, B or your keyboard shortcut to open and focus the Breakpoints window: 9 | 10 | ![New function breakpoint menu](new-function-breakpoint-menu.png) 11 | 12 | And then Ctrl+B or your keyboard shortcut to open the New Function Breakpoint dialog: 13 | 14 | ![New function breakpoint dialog](new-function-breakpoint-dialog.png) 15 | 16 | Then Ctrl+V to paste and Enter to accept, and you’re done! 17 | 18 | ![Created breakpoint](created-breakpoint.png) 19 | 20 | Currently requires Visual Studio 2017 Update 7 or later. The extension can be installed in Visual Studio 2019 but it has not been tested there. 21 | -------------------------------------------------------------------------------- /resources/VsixPublishManifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vsix-publish", 3 | "identity": { 4 | "internalName": "CopyFunctionBreakpointName" 5 | }, 6 | "categories": [ "other", "coding" ], 7 | "overview": "MarketplaceOverview.md", 8 | "assetFiles": [ 9 | { 10 | "pathOnDisk": "context-menu.png", 11 | "targetPath": "context-menu.png" 12 | }, 13 | { 14 | "pathOnDisk": "created-breakpoint.png", 15 | "targetPath": "created-breakpoint.png" 16 | }, 17 | { 18 | "pathOnDisk": "new-function-breakpoint-dialog.png", 19 | "targetPath": "new-function-breakpoint-dialog.png" 20 | }, 21 | { 22 | "pathOnDisk": "new-function-breakpoint-menu.png", 23 | "targetPath": "new-function-breakpoint-menu.png" 24 | } 25 | ], 26 | "publisher": "jnm2", 27 | "repo": "https://github.com/jnm2/CopyFunctionBreakpointName" 28 | } 29 | -------------------------------------------------------------------------------- /resources/context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnm2/CopyFunctionBreakpointName/0dc5885e4e38c2ff9384175419dbbb117c1652a8/resources/context-menu.png -------------------------------------------------------------------------------- /resources/created-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnm2/CopyFunctionBreakpointName/0dc5885e4e38c2ff9384175419dbbb117c1652a8/resources/created-breakpoint.png -------------------------------------------------------------------------------- /resources/new-function-breakpoint-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnm2/CopyFunctionBreakpointName/0dc5885e4e38c2ff9384175419dbbb117c1652a8/resources/new-function-breakpoint-dialog.png -------------------------------------------------------------------------------- /resources/new-function-breakpoint-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnm2/CopyFunctionBreakpointName/0dc5885e4e38c2ff9384175419dbbb117c1652a8/resources/new-function-breakpoint-menu.png -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName.Tests/AnnotatedSourceUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis.Text; 3 | 4 | namespace CopyFunctionBreakpointName.Tests 5 | { 6 | internal static class AnnotatedSourceUtils 7 | { 8 | public const string AnnotationStartMarker = "[|"; 9 | public const string AnnotationEndMarker = "|]"; 10 | 11 | public static (string source, TextSpan span) Parse(string annotatedSource, string paramName) 12 | { 13 | if (!TryParse(annotatedSource, out var source, out var span)) 14 | { 15 | throw new ArgumentException( 16 | "The source must be annotated with \"" + AnnotationStartMarker + "\" and \"" + AnnotationEndMarker + "\" around the selected text.", 17 | paramName); 18 | } 19 | 20 | return (source, span); 21 | } 22 | 23 | public static bool TryParse(string annotatedSource, out string unannotatedSource, out TextSpan span) 24 | { 25 | if (TrySingleIndexOf(annotatedSource, AnnotationStartMarker, out var annotationStart) 26 | && TrySingleIndexOf(annotatedSource, AnnotationEndMarker, out var annotationEnd)) 27 | { 28 | var innerSubstringStart = annotationStart + AnnotationStartMarker.Length; 29 | var innerSubstringLength = annotationEnd - innerSubstringStart; 30 | 31 | if (innerSubstringLength >= 0) 32 | { 33 | unannotatedSource = 34 | annotatedSource.Substring(0, annotationStart) 35 | + annotatedSource.Substring(innerSubstringStart, innerSubstringLength) 36 | + annotatedSource.Substring(annotationEnd + AnnotationEndMarker.Length); 37 | 38 | span = new TextSpan(annotationStart, innerSubstringLength); 39 | 40 | return true; 41 | } 42 | } 43 | 44 | unannotatedSource = null; 45 | span = default; 46 | return false; 47 | } 48 | 49 | private static bool TrySingleIndexOf(string str, string value, out int index) 50 | { 51 | if (str == null) throw new ArgumentNullException(nameof(str)); 52 | if (value == null) throw new ArgumentNullException(nameof(value)); 53 | 54 | index = str.IndexOf(value, StringComparison.Ordinal); 55 | if (index == -1) return false; 56 | 57 | if (str.IndexOf(value, index + value.Length, StringComparison.Ordinal) == -1) return true; 58 | 59 | index = -1; 60 | return false; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName.Tests/CopyFunctionBreakpointName.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | latest 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName.Tests/FunctionBreakpointUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.Text; 8 | using NUnit.Framework; 9 | using Shouldly; 10 | 11 | #pragma warning disable VSTHRD200, UseAsyncSuffix // Test methods don’t need async suffix 12 | 13 | namespace CopyFunctionBreakpointName.Tests 14 | { 15 | public static class FunctionBreakpointUtilsTests 16 | { 17 | [Test] 18 | public static async Task Null_syntax_root_argument_exception() 19 | { 20 | var ex = await Should.ThrowAsync( 21 | () => FunctionBreakpointUtils.GetFunctionBreakpointNameFactoryAsync(syntaxRoot: null, new TextSpan(0, 0), c => null, default)); 22 | 23 | ex.ParamName.ShouldBe("syntaxRoot"); 24 | } 25 | 26 | [Test] 27 | public static async Task Null_semantic_model_accessor_argument_exception() 28 | { 29 | var ex = await Should.ThrowAsync( 30 | () => FunctionBreakpointUtils.GetFunctionBreakpointNameFactoryAsync(SyntaxFactory.IdentifierName(""), new TextSpan(0, 0), semanticModelAccessor: null, default)); 31 | 32 | ex.ParamName.ShouldBe("semanticModelAccessor"); 33 | } 34 | 35 | [Test] 36 | public static async Task Namespace_not_needed() 37 | { 38 | await AssertFunctionBreakpointName(@" 39 | class A 40 | { 41 | void [|B|]() { } 42 | }", "A.B"); 43 | } 44 | 45 | [Test] 46 | public static async Task Simple_namespace() 47 | { 48 | await AssertFunctionBreakpointName(@" 49 | namespace A 50 | { 51 | class B 52 | { 53 | void [|C|]() { } 54 | } 55 | }", "A.B.C"); 56 | } 57 | 58 | [Test] 59 | public static async Task Dotted_namespace() 60 | { 61 | await AssertFunctionBreakpointName(@" 62 | namespace A.B 63 | { 64 | class C 65 | { 66 | void [|D|]() { } 67 | } 68 | }", "A.B.C.D"); 69 | } 70 | 71 | [Test] 72 | public static async Task Nested_namespace() 73 | { 74 | await AssertFunctionBreakpointName(@" 75 | namespace A 76 | { 77 | namespace B1 { } 78 | 79 | namespace B2 80 | { 81 | class C 82 | { 83 | void [|D|]() { } 84 | } 85 | } 86 | }", "A.B2.C.D"); 87 | } 88 | 89 | [Test] 90 | public static async Task Nested_dotted_namespace() 91 | { 92 | await AssertFunctionBreakpointName(@" 93 | namespace A.B 94 | { 95 | namespace C.D 96 | { 97 | class E 98 | { 99 | void [|F|]() { } 100 | } 101 | } 102 | }", "A.B.C.D.E.F"); 103 | } 104 | 105 | [Test] 106 | public static async Task Nested_classes() 107 | { 108 | await AssertFunctionBreakpointName(@" 109 | class A 110 | { 111 | class B 112 | { 113 | void [|C|]() { } 114 | } 115 | }", "A.B.C"); 116 | } 117 | 118 | [Test] 119 | public static async Task Struct() 120 | { 121 | await AssertFunctionBreakpointName(@" 122 | struct A 123 | { 124 | void [|B|]() { } 125 | }", "A.B"); 126 | } 127 | 128 | [Test] 129 | public static async Task Nested_structs() 130 | { 131 | await AssertFunctionBreakpointName(@" 132 | struct A 133 | { 134 | struct B 135 | { 136 | void [|C|]() { } 137 | } 138 | }", "A.B.C"); 139 | } 140 | 141 | [Test] 142 | public static async Task Generics_with_name_selected() 143 | { 144 | await AssertFunctionBreakpointName(@" 145 | public static class A 146 | { 147 | public struct B 148 | { 149 | public static void [|C|]() 150 | { 151 | } 152 | } 153 | }", "A.B.C"); 154 | } 155 | 156 | [Test] 157 | public static async Task Generics_with_cursor_between_name_and_type_parameters() 158 | { 159 | await AssertFunctionBreakpointName(@" 160 | public static class A 161 | { 162 | public struct B 163 | { 164 | public static void C[||]() 165 | { 166 | } 167 | } 168 | }", "A.B.C"); 169 | } 170 | 171 | [Test] 172 | public static async Task Generics_with_name_selection_in_whitespace_not_touching_type_parameters() 173 | { 174 | await AssertFunctionBreakpointName(@" 175 | public static class A 176 | { 177 | public struct B 178 | { 179 | public static void [|C |] () 180 | { 181 | } 182 | } 183 | }", "A.B.C"); 184 | } 185 | 186 | [Test] 187 | public static async Task Generics_with_name_and_type_parameters_selected() 188 | { 189 | await AssertFunctionBreakpointName(@" 190 | public static class A 191 | { 192 | public struct B 193 | { 194 | public static void [|C|]() 195 | { 196 | } 197 | } 198 | }", "A.B.C"); 199 | } 200 | 201 | [Test] 202 | public static async Task Generics_with_only_type_parameters_selected_returns_nothing() 203 | { 204 | await AssertFunctionBreakpointName(@" 205 | public static class A 206 | { 207 | public struct B 208 | { 209 | public static void C[||]() 210 | { 211 | } 212 | } 213 | }", null); 214 | } 215 | 216 | [Test] 217 | public static async Task Namespace_identifier_selection_returns_nothing() 218 | { 219 | await AssertFunctionBreakpointName(@" 220 | namespace [|A|] 221 | { 222 | class B 223 | { 224 | void C() { } 225 | } 226 | }", null); 227 | } 228 | 229 | [Test] 230 | public static async Task Class_identifier_selection_returns_nothing() 231 | { 232 | await AssertFunctionBreakpointName(@" 233 | class [|A|] 234 | { 235 | void B() { } 236 | }", null); 237 | } 238 | 239 | [Test] 240 | public static async Task Zero_width_selection_at_start_of_method_name() 241 | { 242 | await AssertFunctionBreakpointName(@" 243 | class A 244 | { 245 | void [||]B() { } 246 | }", "A.B"); 247 | } 248 | 249 | [Test] 250 | public static async Task Zero_width_selection_at_end_of_method_name() 251 | { 252 | await AssertFunctionBreakpointName(@" 253 | class A 254 | { 255 | void B[||]() { } 256 | }", "A.B"); 257 | } 258 | 259 | [Test] 260 | public static async Task Zero_width_selection_inside_method_name() 261 | { 262 | await AssertFunctionBreakpointName(@" 263 | class A 264 | { 265 | void B[||]B() { } 266 | }", "A.BB"); 267 | } 268 | 269 | [Test] 270 | public static async Task Selection_past_end_of_method_name_returns_nothing() 271 | { 272 | await AssertFunctionBreakpointName(@" 273 | class A 274 | { 275 | void [|B(|]) { } 276 | }", null); 277 | } 278 | 279 | [Test] 280 | public static async Task Selection_before_start_of_method_name_returns_nothing() 281 | { 282 | await AssertFunctionBreakpointName(@" 283 | class A 284 | { 285 | void[| B|]() { } 286 | }", null); 287 | } 288 | 289 | [Test] 290 | public static async Task Class_instance_constructor() 291 | { 292 | await AssertFunctionBreakpointName(@" 293 | class A 294 | { 295 | [|A|]() 296 | { 297 | } 298 | }", "A.A"); 299 | } 300 | 301 | [Test] 302 | public static async Task Class_static_constructor() 303 | { 304 | await AssertFunctionBreakpointName(@" 305 | class A 306 | { 307 | static [|A|]() 308 | { 309 | } 310 | }", "A.cctor"); 311 | } 312 | 313 | [Test] 314 | public static async Task Struct_instance_constructor() 315 | { 316 | await AssertFunctionBreakpointName(@" 317 | struct A 318 | { 319 | [|A|](int x) 320 | { 321 | } 322 | }", "A.A"); 323 | } 324 | 325 | [Test(Description = "There does not appear to be a way to set a breakpoint on a struct static constructor by function name.")] 326 | public static async Task Struct_static_constructor() 327 | { 328 | await AssertFunctionBreakpointName(@" 329 | struct A 330 | { 331 | static [|A|]() 332 | { 333 | } 334 | }", null); 335 | } 336 | 337 | [Test] 338 | public static async Task Finalizer() 339 | { 340 | await AssertFunctionBreakpointName(@" 341 | class A 342 | { 343 | ~[|A|]() 344 | { 345 | } 346 | }", "A.Finalize"); 347 | } 348 | 349 | [Test] 350 | public static async Task Local_function_returns_nothing() 351 | { 352 | await AssertFunctionBreakpointName(@" 353 | class A 354 | { 355 | void B() 356 | { 357 | void [|C|]() { } 358 | } 359 | }", null); 360 | } 361 | 362 | [Test] 363 | public static async Task Entire_property() 364 | { 365 | await AssertFunctionBreakpointName(@" 366 | class A 367 | { 368 | int [|B|] { get; set; } 369 | }", "A.B"); 370 | } 371 | 372 | [Test] 373 | public static async Task Entire_property_expression() 374 | { 375 | await AssertFunctionBreakpointName(@" 376 | class A 377 | { 378 | int [|B|] => 0; 379 | }", "A.B"); 380 | } 381 | 382 | [Test] 383 | public static async Task Get_accessor_auto() 384 | { 385 | await AssertFunctionBreakpointName(@" 386 | class A 387 | { 388 | int B { [|get|]; } 389 | }", "A.get_B"); 390 | } 391 | 392 | [Test] 393 | public static async Task Get_accessor_expression() 394 | { 395 | await AssertFunctionBreakpointName(@" 396 | class A 397 | { 398 | int B { [|get|] => 0; } 399 | }", "A.get_B"); 400 | } 401 | 402 | [Test] 403 | public static async Task Get_accessor_statement() 404 | { 405 | await AssertFunctionBreakpointName(@" 406 | class A 407 | { 408 | int B { [|get|] { return 0; } } 409 | }", "A.get_B"); 410 | } 411 | 412 | [Test] 413 | public static async Task Set_accessor_auto() 414 | { 415 | await AssertFunctionBreakpointName(@" 416 | class A 417 | { 418 | int B { get; [|set|]; } 419 | }", "A.set_B"); 420 | } 421 | 422 | [Test] 423 | public static async Task Set_accessor_expression() 424 | { 425 | await AssertFunctionBreakpointName(@" 426 | class A 427 | { 428 | int B { [|set|] => _ = 0; } 429 | }", "A.set_B"); 430 | } 431 | 432 | [Test] 433 | public static async Task Set_accessor_statement() 434 | { 435 | await AssertFunctionBreakpointName(@" 436 | class A 437 | { 438 | int B { [|set|] { } } 439 | }", "A.set_B"); 440 | } 441 | 442 | [Test] 443 | public static async Task Entire_indexed_property() 444 | { 445 | await AssertFunctionBreakpointName(@" 446 | class A 447 | { 448 | int [|this|][int index] { get => 0; set { } } 449 | }", "A.Item"); 450 | } 451 | 452 | [Test] 453 | public static async Task Entire_indexed_property_expression() 454 | { 455 | await AssertFunctionBreakpointName(@" 456 | class A 457 | { 458 | int [|this|][int index] => 0; 459 | }", "A.Item"); 460 | } 461 | 462 | [Test] 463 | public static async Task Indexed_get_accessor_expression() 464 | { 465 | await AssertFunctionBreakpointName(@" 466 | class A 467 | { 468 | int this[int index] { [|get|] => 0; } 469 | }", "A.get_Item"); 470 | } 471 | 472 | [Test] 473 | public static async Task Indexed_get_accessor_statement() 474 | { 475 | await AssertFunctionBreakpointName(@" 476 | class A 477 | { 478 | int this[int index] { [|get|] { return 0; } } 479 | }", "A.get_Item"); 480 | } 481 | 482 | [Test] 483 | public static async Task Indexed_set_accessor_expression() 484 | { 485 | await AssertFunctionBreakpointName(@" 486 | class A 487 | { 488 | int this[int index] { [|set|] => _ = 0; } 489 | }", "A.set_Item"); 490 | } 491 | 492 | [Test] 493 | public static async Task Indexed_set_accessor_statement() 494 | { 495 | await AssertFunctionBreakpointName(@" 496 | class A 497 | { 498 | int this[int index] { [|set|] { } } 499 | }", "A.set_Item"); 500 | } 501 | 502 | [Test] 503 | public static async Task Named_indexed_property() 504 | { 505 | await AssertFunctionBreakpointName(@" 506 | using System.Runtime.CompilerServices; 507 | 508 | class A 509 | { 510 | [IndexerName(""B"")] 511 | int [|this|][int index] => 0; 512 | }", "A.B"); 513 | } 514 | 515 | [Test] 516 | public static async Task Named_indexed_get_accessor() 517 | { 518 | await AssertFunctionBreakpointName(@" 519 | using System.Runtime.CompilerServices; 520 | 521 | class A 522 | { 523 | [IndexerName(""B"")] 524 | int this[int index] { [|get|] => 0; } 525 | }", "A.get_B"); 526 | } 527 | 528 | [Test] 529 | public static async Task Named_indexed_set_accessor() 530 | { 531 | await AssertFunctionBreakpointName(@" 532 | using System.Runtime.CompilerServices; 533 | 534 | class A 535 | { 536 | [IndexerName(""B"")] 537 | int this[int index] { [|set|] { } } 538 | }", "A.set_B"); 539 | } 540 | 541 | [Test] 542 | public static async Task Entire_event_returns_nothing() 543 | { 544 | await AssertFunctionBreakpointName(@" 545 | class A 546 | { 547 | event System.Action [|B|]; 548 | }", null); 549 | } 550 | 551 | [Test] 552 | public static async Task Entire_event_custom_returns_nothing() 553 | { 554 | await AssertFunctionBreakpointName(@" 555 | class A 556 | { 557 | event System.Action [|B|] { add { } remove { } } 558 | }", null); 559 | } 560 | 561 | [Test] 562 | public static async Task Add_accessor_expression() 563 | { 564 | await AssertFunctionBreakpointName(@" 565 | class A 566 | { 567 | event System.Action B { [|add|] => _ = value; remove => _ = value; } 568 | }", "A.add_B"); 569 | } 570 | 571 | [Test] 572 | public static async Task Add_accessor_statement() 573 | { 574 | await AssertFunctionBreakpointName(@" 575 | class A 576 | { 577 | event System.Action B { [|add|] { } remove { } } 578 | }", "A.add_B"); 579 | } 580 | 581 | [Test] 582 | public static async Task Remove_accessor_expression() 583 | { 584 | await AssertFunctionBreakpointName(@" 585 | class A 586 | { 587 | event System.Action B { add => _ = value; [|remove|] => _ = value; } 588 | }", "A.remove_B"); 589 | } 590 | 591 | [Test] 592 | public static async Task Remove_accessor_statement() 593 | { 594 | await AssertFunctionBreakpointName(@" 595 | class A 596 | { 597 | event System.Action B { add { } [|remove|] { } } 598 | }", "A.remove_B"); 599 | } 600 | 601 | [Test] 602 | public static async Task Operator_select_only_token() 603 | { 604 | await AssertFunctionBreakpointName(@" 605 | class A 606 | { 607 | public static A operator [|+|](A a) => a; 608 | }", "A.op_UnaryPlus"); 609 | } 610 | 611 | [Test] 612 | public static async Task Operator_select_before_token() 613 | { 614 | await AssertFunctionBreakpointName(@" 615 | class A 616 | { 617 | public static A operator [||]+(A a) => a; 618 | }", "A.op_UnaryPlus"); 619 | } 620 | 621 | [Test] 622 | public static async Task Operator_select_after_token() 623 | { 624 | await AssertFunctionBreakpointName(@" 625 | class A 626 | { 627 | public static A operator +[||](A a) => a; 628 | }", "A.op_UnaryPlus"); 629 | } 630 | 631 | [Test] 632 | public static async Task Operator_select_before_keyword() 633 | { 634 | await AssertFunctionBreakpointName(@" 635 | class A 636 | { 637 | public static A [||]operator +(A a) => a; 638 | }", "A.op_UnaryPlus"); 639 | } 640 | 641 | [Test] 642 | public static async Task Operator_select_after_keyword() 643 | { 644 | await AssertFunctionBreakpointName(@" 645 | class A 646 | { 647 | public static A operator[||] +(A a) => a; 648 | }", "A.op_UnaryPlus"); 649 | } 650 | 651 | [Test] 652 | public static async Task Operator_select_keyword_and_token() 653 | { 654 | await AssertFunctionBreakpointName(@" 655 | class A 656 | { 657 | public static A [|operator +|](A a) => a; 658 | }", "A.op_UnaryPlus"); 659 | } 660 | 661 | [Test] 662 | public static async Task Operator_select_partial_token_and_space_after_keyword() 663 | { 664 | await AssertFunctionBreakpointName(@" 665 | class A 666 | { 667 | public static A operator[| +|]+(A a) => a; 668 | }", "A.op_Increment"); 669 | } 670 | 671 | [Test] 672 | public static async Task Operator_select_partial_keyword_and_space_before_token() 673 | { 674 | await AssertFunctionBreakpointName(@" 675 | class A 676 | { 677 | public static A operato[|r |]+(A a) => a; 678 | }", "A.op_UnaryPlus"); 679 | } 680 | 681 | [Test] 682 | public static async Task Operator_nonzero_whitespace_selection_between_keyword_and_token_returns_nothing() 683 | { 684 | await AssertFunctionBreakpointName(@" 685 | class A 686 | { 687 | public static A operator[| |]+(A a) => a; 688 | }", null); 689 | } 690 | 691 | [Test] 692 | public static async Task Operator_zero_whitespace_selection_touching_neither_keyword_nor_token_returns_nothing() 693 | { 694 | await AssertFunctionBreakpointName(@" 695 | class A 696 | { 697 | public static A operator [||] +(A a) => a; 698 | }", null); 699 | } 700 | 701 | [Test] 702 | public static async Task Operator_zero_whitespace_selection_touching_both_keyword_and_token() 703 | { 704 | await AssertFunctionBreakpointName(@" 705 | class A 706 | { 707 | public static A operator[||]+(A a) => a; 708 | }", "A.op_UnaryPlus"); 709 | } 710 | 711 | [Test] 712 | public static async Task Operator_character_before_keyword() 713 | { 714 | await AssertFunctionBreakpointName(@" 715 | class A 716 | { 717 | public static A[| operator|] +(A a) => a; 718 | }", null); 719 | } 720 | 721 | [Test] 722 | public static async Task Conversion_operator_character_before_keyword() 723 | { 724 | await AssertFunctionBreakpointName(@" 725 | class A 726 | { 727 | public static A[| operator|] +(A a) => a; 728 | }", null); 729 | } 730 | 731 | [Test] 732 | public static async Task Operator_character_after_token() 733 | { 734 | await AssertFunctionBreakpointName(@" 735 | class A 736 | { 737 | public static A operator [|+(|]A a) => a; 738 | }", null); 739 | } 740 | 741 | [Test] 742 | public static async Task Conversion_operator_type_selection_returns_nothing() 743 | { 744 | 745 | await AssertFunctionBreakpointName(@" 746 | class A 747 | { 748 | public static implicit operator [|bool|](A a) => true; 749 | }", null); 750 | } 751 | 752 | [Test] 753 | public static async Task Conversion_operator_implicit_keyword_selection_returns_nothing() 754 | { 755 | 756 | await AssertFunctionBreakpointName(@" 757 | class A 758 | { 759 | public static [|implicit|] operator bool(A a) => true; 760 | }", null); 761 | } 762 | 763 | [Test] 764 | public static async Task Operator_Explicit() 765 | { 766 | await AssertFunctionBreakpointName(@" 767 | class A 768 | { 769 | public static explicit [|operator|] bool(A a) => true; 770 | }", "A.op_Explicit"); 771 | } 772 | 773 | [Test] 774 | public static async Task Operator_Implicit() 775 | { 776 | 777 | await AssertFunctionBreakpointName(@" 778 | class A 779 | { 780 | public static implicit [|operator|] bool(A a) => true; 781 | }", "A.op_Implicit"); 782 | } 783 | 784 | [Test] 785 | public static async Task Operator_UnaryPlus() 786 | { 787 | await AssertFunctionBreakpointName(@" 788 | class A 789 | { 790 | public static A [|operator|] +(A a) => a; 791 | }", "A.op_UnaryPlus"); 792 | } 793 | 794 | [Test] 795 | public static async Task Operator_UnaryNegation() 796 | { 797 | await AssertFunctionBreakpointName(@" 798 | class A 799 | { 800 | public static A [|operator|] -(A a) => a; 801 | }", "A.op_UnaryNegation"); 802 | } 803 | 804 | [Test] 805 | public static async Task Operator_LogicalNot() 806 | { 807 | await AssertFunctionBreakpointName(@" 808 | class A 809 | { 810 | public static A [|operator|] !(A a) => a; 811 | }", "A.op_LogicalNot"); 812 | } 813 | 814 | [Test] 815 | public static async Task Operator_OnesComplement() 816 | { 817 | await AssertFunctionBreakpointName(@" 818 | class A 819 | { 820 | public static A [|operator|] ~(A a) => a; 821 | }", "A.op_OnesComplement"); 822 | } 823 | 824 | [Test] 825 | public static async Task Operator_Increment() 826 | { 827 | await AssertFunctionBreakpointName(@" 828 | class A 829 | { 830 | public static A [|operator|] ++(A a) => a; 831 | }", "A.op_Increment"); 832 | } 833 | 834 | [Test] 835 | public static async Task Operator_Decrement() 836 | { 837 | await AssertFunctionBreakpointName(@" 838 | class A 839 | { 840 | public static A [|operator|] --(A a) => a; 841 | }", "A.op_Decrement"); 842 | } 843 | 844 | [Test] 845 | public static async Task Operator_True() 846 | { 847 | await AssertFunctionBreakpointName(@" 848 | class A 849 | { 850 | public static bool [|operator|] true(A a) => true; 851 | public static bool operator false(A a) => false; 852 | }", "A.op_True"); 853 | } 854 | 855 | [Test] 856 | public static async Task Operator_False() 857 | { 858 | await AssertFunctionBreakpointName(@" 859 | class A 860 | { 861 | public static bool [|operator|] false(A a) => false; 862 | public static bool operator true(A a) => true; 863 | }", "A.op_False"); 864 | } 865 | 866 | [Test] 867 | public static async Task Operator_Addition() 868 | { 869 | await AssertFunctionBreakpointName(@" 870 | class A 871 | { 872 | public static A [|operator|] +(A left, A right) => left; 873 | }", "A.op_Addition"); 874 | } 875 | 876 | [Test] 877 | public static async Task Operator_Subtraction() 878 | { 879 | await AssertFunctionBreakpointName(@" 880 | class A 881 | { 882 | public static A [|operator|] -(A left, A right) => left; 883 | }", "A.op_Subtraction"); 884 | } 885 | 886 | [Test] 887 | public static async Task Operator_Multiply() 888 | { 889 | await AssertFunctionBreakpointName(@" 890 | class A 891 | { 892 | public static A [|operator|] *(A left, A right) => left; 893 | }", "A.op_Multiply"); 894 | } 895 | 896 | [Test] 897 | public static async Task Operator_Division() 898 | { 899 | await AssertFunctionBreakpointName(@" 900 | class A 901 | { 902 | public static A [|operator|] /(A left, A right) => left; 903 | }", "A.op_Division"); 904 | } 905 | 906 | [Test] 907 | public static async Task Operator_Modulus() 908 | { 909 | await AssertFunctionBreakpointName(@" 910 | class A 911 | { 912 | public static A [|operator|] %(A left, A right) => left; 913 | }", "A.op_Modulus"); 914 | } 915 | 916 | [Test] 917 | public static async Task Operator_BitwiseAnd() 918 | { 919 | await AssertFunctionBreakpointName(@" 920 | class A 921 | { 922 | public static A [|operator|] &(A left, A right) => left; 923 | }", "A.op_BitwiseAnd"); 924 | } 925 | 926 | [Test] 927 | public static async Task Operator_BitwiseOr() 928 | { 929 | await AssertFunctionBreakpointName(@" 930 | class A 931 | { 932 | public static A [|operator|] |(A left, A right) => left; 933 | }", "A.op_BitwiseOr"); 934 | } 935 | 936 | [Test] 937 | public static async Task Operator_ExclusiveOr() 938 | { 939 | await AssertFunctionBreakpointName(@" 940 | class A 941 | { 942 | public static A [|operator|] ^(A left, A right) => left; 943 | }", "A.op_ExclusiveOr"); 944 | } 945 | 946 | [Test] 947 | public static async Task Operator_LeftShift() 948 | { 949 | await AssertFunctionBreakpointName(@" 950 | class A 951 | { 952 | public static A [|operator|] <<(A a, int shift) => a; 953 | }", "A.op_LeftShift"); 954 | } 955 | 956 | [Test] 957 | public static async Task Operator_RightShift() 958 | { 959 | await AssertFunctionBreakpointName(@" 960 | class A 961 | { 962 | public static A [|operator|] >>(A a, int shift) => a; 963 | }", "A.op_RightShift"); 964 | } 965 | 966 | [Test] 967 | public static async Task Operator_Equality() 968 | { 969 | await AssertFunctionBreakpointName(@" 970 | class A 971 | { 972 | public static bool [|operator|] ==(A left, A right) => true; 973 | public static bool operator !=(A left, A right) => false; 974 | }", "A.op_Equality"); 975 | } 976 | 977 | [Test] 978 | public static async Task Operator_Inequality() 979 | { 980 | await AssertFunctionBreakpointName(@" 981 | class A 982 | { 983 | public static bool [|operator|] !=(A left, A right) => true; 984 | public static bool operator ==(A left, A right) => false; 985 | }", "A.op_Inequality"); 986 | } 987 | 988 | [Test] 989 | public static async Task Operator_LessThan() 990 | { 991 | await AssertFunctionBreakpointName(@" 992 | class A 993 | { 994 | public static bool [|operator|] <(A left, A right) => true; 995 | public static bool operator >(A left, A right) => false; 996 | }", "A.op_LessThan"); 997 | } 998 | 999 | [Test] 1000 | public static async Task Operator_GreaterThan() 1001 | { 1002 | await AssertFunctionBreakpointName(@" 1003 | class A 1004 | { 1005 | public static bool [|operator|] >(A left, A right) => true; 1006 | public static bool operator <(A left, A right) => false; 1007 | }", "A.op_GreaterThan"); 1008 | } 1009 | 1010 | [Test] 1011 | public static async Task Operator_LessThanOrEqual() 1012 | { 1013 | await AssertFunctionBreakpointName(@" 1014 | class A 1015 | { 1016 | public static bool [|operator|] <=(A left, A right) => true; 1017 | public static bool operator >=(A left, A right) => false; 1018 | }", "A.op_LessThanOrEqual"); 1019 | } 1020 | 1021 | [Test] 1022 | public static async Task Operator_GreaterThanOrEqual() 1023 | { 1024 | await AssertFunctionBreakpointName(@" 1025 | class A 1026 | { 1027 | public static bool [|operator|] >=(A left, A right) => true; 1028 | public static bool operator <=(A left, A right) => false; 1029 | }", "A.op_GreaterThanOrEqual"); 1030 | } 1031 | 1032 | [Test] 1033 | public static async Task Explicit_method_returns_nothing() 1034 | { 1035 | await AssertFunctionBreakpointName(@" 1036 | interface ITest 1037 | { 1038 | void Test(); 1039 | } 1040 | 1041 | class A : ITest 1042 | { 1043 | void ITest.[|Test|]() { } 1044 | }", null); 1045 | } 1046 | 1047 | [Test] 1048 | public static async Task Explicit_property_returns_nothing() 1049 | { 1050 | await AssertFunctionBreakpointName(@" 1051 | interface ITest 1052 | { 1053 | int Test { get; } 1054 | } 1055 | 1056 | class A : ITest 1057 | { 1058 | int ITest.[|Test|] => 0; 1059 | }", null); 1060 | } 1061 | 1062 | [Test] 1063 | public static async Task Explicit_property_getter_returns_nothing() 1064 | { 1065 | await AssertFunctionBreakpointName(@" 1066 | interface ITest 1067 | { 1068 | int Test { get; } 1069 | } 1070 | 1071 | class A : ITest 1072 | { 1073 | int ITest.Test { [|get|] => 0; } 1074 | }", null); 1075 | } 1076 | 1077 | [Test] 1078 | public static async Task Explicit_indexed_property_returns_nothing() 1079 | { 1080 | await AssertFunctionBreakpointName(@" 1081 | interface ITest 1082 | { 1083 | int this[int index] { get; } 1084 | } 1085 | 1086 | class A : ITest 1087 | { 1088 | int ITest.[|this|][int index] => 0; 1089 | }", null); 1090 | } 1091 | 1092 | [Test] 1093 | public static async Task Explicit_indexed_property_getter_returns_nothing() 1094 | { 1095 | await AssertFunctionBreakpointName(@" 1096 | interface ITest 1097 | { 1098 | int this[int index] { get; } 1099 | } 1100 | 1101 | class A : ITest 1102 | { 1103 | int ITest.this[int index] { [|get|] => 0; } 1104 | }", null); 1105 | } 1106 | 1107 | [Test] 1108 | public static async Task Explicit_event_add_accessor_returns_nothing() 1109 | { 1110 | await AssertFunctionBreakpointName(@" 1111 | interface ITest 1112 | { 1113 | event System.EventHandler Test; 1114 | } 1115 | 1116 | class A : ITest 1117 | { 1118 | event System.EventHandler ITest.Test { [|add|] { } remove { } } 1119 | }", null); 1120 | } 1121 | 1122 | private static async Task AssertFunctionBreakpointName(string annotatedSource, string expected) 1123 | { 1124 | Assert.That(await GetFunctionBreakpointNameAsync(annotatedSource), Is.EqualTo(expected)); 1125 | } 1126 | 1127 | private static async Task GetFunctionBreakpointNameAsync(string annotatedSource, bool permitCompilationErrors = false) 1128 | { 1129 | var (source, span) = AnnotatedSourceUtils.Parse(annotatedSource, nameof(annotatedSource)); 1130 | 1131 | var syntaxTree = CSharpSyntaxTree.ParseText(source); 1132 | 1133 | var compilation = new Lazy(() => 1134 | CSharpCompilation.Create( 1135 | nameof(GetFunctionBreakpointNameAsync), 1136 | new[] { syntaxTree }, 1137 | new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, 1138 | new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))); 1139 | 1140 | if (!permitCompilationErrors) 1141 | { 1142 | Assert.That(compilation.Value.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error), Is.Empty); 1143 | } 1144 | 1145 | var factory = await FunctionBreakpointUtils.GetFunctionBreakpointNameFactoryAsync( 1146 | await syntaxTree.GetRootAsync().ConfigureAwait(false), 1147 | span, 1148 | c => Task.FromResult(compilation.Value.GetSemanticModel(syntaxTree, ignoreAccessibility: false)), 1149 | CancellationToken.None).ConfigureAwait(false); 1150 | 1151 | return factory?.ToString(); 1152 | } 1153 | } 1154 | } 1155 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | [assembly: Parallelizable(ParallelScope.Children)] 4 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopyFunctionBreakpointName", "CopyFunctionBreakpointName\CopyFunctionBreakpointName.csproj", "{42E140AF-0200-4D66-AC68-77018A417B32}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CopyFunctionBreakpointName.Tests", "CopyFunctionBreakpointName.Tests\CopyFunctionBreakpointName.Tests.csproj", "{A2F57BBE-C981-4DE7-BF19-7435DA77C338}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {42E140AF-0200-4D66-AC68-77018A417B32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {42E140AF-0200-4D66-AC68-77018A417B32}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {42E140AF-0200-4D66-AC68-77018A417B32}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {42E140AF-0200-4D66-AC68-77018A417B32}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {A2F57BBE-C981-4DE7-BF19-7435DA77C338}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {A2F57BBE-C981-4DE7-BF19-7435DA77C338}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {A2F57BBE-C981-4DE7-BF19-7435DA77C338}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {A2F57BBE-C981-4DE7-BF19-7435DA77C338}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {4E504F95-8F3A-448F-8D3F-066288D8BE20} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/CopyFunctionBreakpointName.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Debug 4 | AnyCPU 5 | Library 6 | CopyFunctionBreakpointName 7 | CopyFunctionBreakpointName 8 | 15.0 9 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 10 | true 11 | bin\$(Configuration)\ 12 | $(Configuration.ToUpperInvariant());TRACE 13 | true 14 | pdbonly 15 | false 16 | true 17 | prompt 18 | {42E140AF-0200-4D66-AC68-77018A417B32} 19 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 20 | 21 | 22 | 23 | v4.6 24 | latest 25 | true 26 | true 27 | false 28 | true 29 | true 30 | true 31 | Program 32 | $(DevEnvDir)devenv.exe 33 | /rootsuffix Exp 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 2.7.0 42 | 43 | 44 | 2.7.0 45 | 46 | 47 | 15.7.68-pre 48 | 49 | 50 | 15.7.109 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/CopyFunctionBreakpointNamePackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using Microsoft.VisualStudio.ComponentModelHost; 6 | using Microsoft.VisualStudio.Editor; 7 | using Microsoft.VisualStudio.Shell; 8 | using Microsoft.VisualStudio.Shell.Interop; 9 | using Microsoft.VisualStudio.TextManager.Interop; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace CopyFunctionBreakpointName 13 | { 14 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 15 | [ProvideMenuResource("Menus.ctmenu", 1)] 16 | [Guid(PackageGuidString)] 17 | [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)] 18 | [ProvideUIContextRule(UIContextGuid, 19 | name: "C# editor window", 20 | expression: "CSharpEditorWindow", 21 | termNames: new[] { "CSharpEditorWindow" }, 22 | termValues: new[] { "ActiveEditorContentType:cs" })] 23 | public sealed class CopyFunctionBreakpointNamePackage : AsyncPackage 24 | { 25 | public const string PackageGuidString = "b78d32a2-8af1-4838-858e-6f865bd7b291"; 26 | public const string UIContextGuid = "91909bfb-2bff-4d68-ae11-e0f8478b4c46"; 27 | 28 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 29 | { 30 | var componentModel = (IComponentModel)await GetServiceAsync(typeof(SComponentModel)); 31 | var editorAdaptersFactoryService = componentModel.GetService(); 32 | 33 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 34 | 35 | var textManager = (IVsTextManager)await GetServiceAsync(typeof(SVsTextManager)); 36 | var menuCommandService = (IMenuCommandService)await GetServiceAsync(typeof(IMenuCommandService)); 37 | var statusBar = (IVsStatusbar)await GetServiceAsync(typeof(SVsStatusbar)); 38 | 39 | _ = new CopyFunctionBreakpointNameService( 40 | textManager, 41 | editorAdaptersFactoryService, 42 | menuCommandService, 43 | statusBar, 44 | JoinableTaskFactory); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/CopyFunctionBreakpointNamePackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/CopyFunctionBreakpointNameService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | using Microsoft.CodeAnalysis.Text; 7 | using Microsoft.VisualStudio; 8 | using Microsoft.VisualStudio.Editor; 9 | using Microsoft.VisualStudio.Shell; 10 | using Microsoft.VisualStudio.Shell.Interop; 11 | using Microsoft.VisualStudio.TextManager.Interop; 12 | using Microsoft.VisualStudio.Threading; 13 | using TextSpan = Microsoft.CodeAnalysis.Text.TextSpan; 14 | 15 | namespace CopyFunctionBreakpointName 16 | { 17 | public sealed class CopyFunctionBreakpointNameService 18 | { 19 | private static readonly CommandID MenuCommand = new CommandID(new Guid("840b69a0-a468-4950-8c25-16bb7a846a58"), 0x0100); 20 | 21 | private readonly IVsTextManager textManager; 22 | private readonly IVsEditorAdaptersFactoryService editorAdaptersFactoryService; 23 | private readonly IVsStatusbar statusBar; 24 | private readonly JoinableTaskFactory joinableTaskFactory; 25 | 26 | public CopyFunctionBreakpointNameService(IVsTextManager textManager, 27 | IVsEditorAdaptersFactoryService editorAdaptersFactoryService, 28 | IMenuCommandService menuCommandService, 29 | IVsStatusbar statusBar, 30 | JoinableTaskFactory joinableTaskFactory) 31 | { 32 | if (menuCommandService == null) throw new ArgumentNullException(nameof(menuCommandService)); 33 | 34 | this.textManager = textManager ?? throw new ArgumentNullException(nameof(textManager)); 35 | this.editorAdaptersFactoryService = editorAdaptersFactoryService ?? throw new ArgumentNullException(nameof(editorAdaptersFactoryService)); 36 | this.statusBar = statusBar ?? throw new ArgumentNullException(nameof(statusBar)); 37 | this.joinableTaskFactory = joinableTaskFactory ?? throw new ArgumentNullException(nameof(joinableTaskFactory)); 38 | 39 | menuCommandService.AddCommand( 40 | new OleMenuCommand(OnMenuCommandInvoked, changeHandler: null, UpdateMenuCommandStatus, MenuCommand)); 41 | } 42 | 43 | private void OnMenuCommandInvoked(object sender, EventArgs e) 44 | { 45 | joinableTaskFactory.Run( 46 | "Copy function breakpoint name", 47 | "Copying the function breakpoint name to the clipboard...", 48 | async (progress, cancellationToken) => 49 | { 50 | var factory = await GetFunctionBreakpointNameFactoryAsync(cancellationToken); 51 | 52 | await joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 53 | 54 | if (factory == null) 55 | { 56 | statusBar.SetText("Could not determine a function breakpoint name for the selected syntax"); 57 | } 58 | else 59 | { 60 | var clipboardContent = factory.Value.ToString(); 61 | Clipboard.SetText(clipboardContent); 62 | statusBar.SetText($"Copied “{clipboardContent}” to the clipboard"); 63 | } 64 | }); 65 | } 66 | 67 | private void UpdateMenuCommandStatus(object sender, EventArgs e) 68 | { 69 | var source = new CancellationTokenSource(); 70 | try 71 | { 72 | var task = GetFunctionBreakpointNameFactoryAsync(source.Token); 73 | 74 | ((MenuCommand)sender).Visible = 75 | !task.TryGetResult(out var factory) 76 | || factory != null; 77 | } 78 | finally 79 | { 80 | source.Cancel(); 81 | } 82 | } 83 | 84 | private async Task GetFunctionBreakpointNameFactoryAsync(CancellationToken cancellationToken) 85 | { 86 | ErrorHandler.ThrowOnFailure(textManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, out var view)); 87 | var activeViewSelection = editorAdaptersFactoryService.GetWpfTextView(view).Selection; 88 | var document = activeViewSelection.Start.Position.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); 89 | 90 | return await FunctionBreakpointUtils.GetFunctionBreakpointNameFactoryAsync( 91 | await document.GetSyntaxRootAsync(cancellationToken), 92 | TextSpan.FromBounds( 93 | activeViewSelection.Start.Position.Position, 94 | activeViewSelection.End.Position.Position), 95 | document.GetSemanticModelAsync, 96 | cancellationToken); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace CopyFunctionBreakpointName 4 | { 5 | internal static class Extensions 6 | { 7 | public static bool TryGetResult(this Task task, out T result) 8 | { 9 | var awaiter = task.GetAwaiter(); 10 | if (awaiter.IsCompleted) 11 | { 12 | #pragma warning disable VSTHRD002 // This is guaranteed not to block. 13 | result = awaiter.GetResult(); 14 | #pragma warning restore VSTHRD002 15 | return true; 16 | } 17 | 18 | result = default; 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/FunctionBreakpointNameFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | 7 | namespace CopyFunctionBreakpointName 8 | { 9 | public readonly struct FunctionBreakpointNameFactory 10 | { 11 | private readonly CSharpSyntaxNode member; 12 | private readonly SyntaxToken memberIdentifier; 13 | private readonly AccessorDeclarationSyntax accessor; 14 | private readonly TypeParameterListSyntax typeParameters; 15 | 16 | public FunctionBreakpointNameFactory(CSharpSyntaxNode member, SyntaxToken memberIdentifier, AccessorDeclarationSyntax accessor = null, TypeParameterListSyntax typeParameters = null) 17 | { 18 | this.member = member; 19 | this.memberIdentifier = memberIdentifier; 20 | this.accessor = accessor; 21 | this.typeParameters = typeParameters; 22 | } 23 | 24 | public override string ToString() 25 | { 26 | var reverseSegments = new List(); 27 | 28 | var current = member.Parent; 29 | 30 | while (current is TypeDeclarationSyntax type) 31 | { 32 | if (type.TypeParameterList != null) 33 | { 34 | var parametersBuilder = new StringBuilder(type.Identifier.ValueText); 35 | WriteTypeParameterSegments(parametersBuilder, type.TypeParameterList); 36 | reverseSegments.Add(parametersBuilder.ToString()); 37 | } 38 | else 39 | { 40 | reverseSegments.Add(type.Identifier.ValueText); 41 | } 42 | 43 | current = current.Parent; 44 | } 45 | 46 | while (current is NamespaceDeclarationSyntax @namespace) 47 | { 48 | var currentName = @namespace.Name; 49 | 50 | while (currentName is QualifiedNameSyntax qualified) 51 | { 52 | reverseSegments.Add(qualified.Right.Identifier.ValueText); 53 | currentName = qualified.Left; 54 | } 55 | 56 | reverseSegments.Add(((IdentifierNameSyntax)currentName).Identifier.ValueText); 57 | 58 | current = current.Parent; 59 | } 60 | 61 | var sb = new StringBuilder(); 62 | 63 | for (var i = reverseSegments.Count - 1; i >= 0; i--) 64 | { 65 | sb.Append(reverseSegments[i]).Append('.'); 66 | } 67 | 68 | switch (accessor?.Parent.Parent) 69 | { 70 | case PropertyDeclarationSyntax _: 71 | case IndexerDeclarationSyntax _: 72 | case EventDeclarationSyntax _: 73 | sb.Append(accessor.Keyword.ValueText).Append('_').Append(memberIdentifier.ValueText); 74 | break; 75 | default: 76 | sb.Append(memberIdentifier.ValueText); 77 | if (typeParameters != null) 78 | WriteTypeParameterSegments(sb, typeParameters); 79 | break; 80 | } 81 | 82 | return sb.ToString(); 83 | } 84 | 85 | private static void WriteTypeParameterSegments(StringBuilder sb, TypeParameterListSyntax list) 86 | { 87 | sb.Append(list.LessThanToken.ValueText); 88 | 89 | for (var i = 0; i < list.Parameters.Count; i++) 90 | { 91 | if (i != 0) sb.Append(", "); 92 | sb.Append(list.Parameters[i].Identifier.ValueText); 93 | } 94 | 95 | sb.Append(list.GreaterThanToken.ValueText); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/FunctionBreakpointUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Text; 8 | 9 | namespace CopyFunctionBreakpointName 10 | { 11 | public static class FunctionBreakpointUtils 12 | { 13 | public static async Task GetFunctionBreakpointNameFactoryAsync( 14 | SyntaxNode syntaxRoot, 15 | TextSpan selectionRange, 16 | Func> semanticModelAccessor, 17 | CancellationToken cancellationToken) 18 | { 19 | if (syntaxRoot == null) throw new ArgumentNullException(nameof(syntaxRoot)); 20 | if (semanticModelAccessor == null) throw new ArgumentNullException(nameof(semanticModelAccessor)); 21 | 22 | if (selectionRange.IsEmpty) 23 | { 24 | return await GetFunctionBreakpointNameFactoryAsync(syntaxRoot, new TextSpan(selectionRange.Start, 1), semanticModelAccessor, cancellationToken).ConfigureAwait(false) 25 | ?? await GetFunctionBreakpointNameFactoryAsync(syntaxRoot, new TextSpan(selectionRange.Start - 1, 1), semanticModelAccessor, cancellationToken).ConfigureAwait(false); 26 | } 27 | 28 | if (!(syntaxRoot is CSharpSyntaxNode csharpSyntaxRoot)) return null; 29 | 30 | switch (csharpSyntaxRoot.FindNode(selectionRange)) 31 | { 32 | case MethodDeclarationSyntax method when method.ExplicitInterfaceSpecifier == null 33 | && IsFunctionNameSpan(method, selectionRange): 34 | { 35 | return new FunctionBreakpointNameFactory(method, method.Identifier, accessor: null, method.TypeParameterList); 36 | } 37 | 38 | case ConstructorDeclarationSyntax constructor when constructor.Identifier.Span.Contains(selectionRange): 39 | { 40 | var isStatic = constructor.Modifiers.Any(SyntaxKind.StaticKeyword); 41 | 42 | if (isStatic && constructor.Parent is StructDeclarationSyntax) return null; 43 | 44 | return new FunctionBreakpointNameFactory( 45 | constructor, 46 | isStatic ? SyntaxFactory.Identifier("cctor") : constructor.Identifier, 47 | accessor: null); 48 | } 49 | 50 | case DestructorDeclarationSyntax destructor when destructor.Identifier.Span.Contains(selectionRange): 51 | { 52 | return new FunctionBreakpointNameFactory(destructor, SyntaxFactory.Identifier("Finalize")); 53 | } 54 | 55 | case PropertyDeclarationSyntax property when property.ExplicitInterfaceSpecifier == null 56 | && property.Identifier.Span.Contains(selectionRange): 57 | { 58 | return new FunctionBreakpointNameFactory(property, property.Identifier); 59 | } 60 | 61 | case IndexerDeclarationSyntax indexer when indexer.ExplicitInterfaceSpecifier == null 62 | && indexer.ThisKeyword.Span.Contains(selectionRange): 63 | { 64 | var semanticModel = await semanticModelAccessor.Invoke(cancellationToken).ConfigureAwait(false); 65 | var metadataName = GetMetadataName(indexer, semanticModel); 66 | return new FunctionBreakpointNameFactory(indexer, metadataName); 67 | } 68 | 69 | case AccessorDeclarationSyntax accessor when accessor.Keyword.Span.Contains(selectionRange): 70 | switch (accessor.Parent.Parent) 71 | { 72 | case PropertyDeclarationSyntax property when property.ExplicitInterfaceSpecifier == null: 73 | { 74 | return new FunctionBreakpointNameFactory(property, property.Identifier, accessor); 75 | } 76 | 77 | case IndexerDeclarationSyntax indexer when indexer.ExplicitInterfaceSpecifier == null: 78 | { 79 | var semanticModel = await semanticModelAccessor.Invoke(cancellationToken).ConfigureAwait(false); 80 | var metadataName = GetMetadataName(indexer, semanticModel); 81 | return new FunctionBreakpointNameFactory(indexer, metadataName, accessor); 82 | } 83 | 84 | case EventDeclarationSyntax @event when @event.ExplicitInterfaceSpecifier == null: 85 | { 86 | return new FunctionBreakpointNameFactory(@event, @event.Identifier, accessor); 87 | } 88 | 89 | default: 90 | return null; 91 | } 92 | 93 | case OperatorDeclarationSyntax op when IsFunctionNameSpan(op, selectionRange): 94 | { 95 | var semanticModel = await semanticModelAccessor.Invoke(cancellationToken).ConfigureAwait(false); 96 | var metadataName = GetMetadataName(op, semanticModel); 97 | return new FunctionBreakpointNameFactory(op, metadataName); 98 | } 99 | 100 | case ConversionOperatorDeclarationSyntax op when op.OperatorKeyword.Span.Contains(selectionRange): 101 | { 102 | return new FunctionBreakpointNameFactory( 103 | op, 104 | SyntaxFactory.Identifier(op.ImplicitOrExplicitKeyword.IsKind(SyntaxKind.ExplicitKeyword) 105 | ? WellKnownMemberNames.ExplicitConversionName 106 | : WellKnownMemberNames.ImplicitConversionName), 107 | accessor: null); 108 | } 109 | 110 | // New Function Breakpoint window does not add breakpoints for event accessors by event name. 111 | 112 | default: 113 | return null; 114 | } 115 | } 116 | 117 | private static SyntaxToken GetMetadataName(MemberDeclarationSyntax syntax, SemanticModel semanticModel) 118 | { 119 | var metadataName = semanticModel.GetDeclaredSymbol(syntax).MetadataName; 120 | 121 | return SyntaxFactory.Identifier(metadataName); 122 | } 123 | 124 | private static bool IsFunctionNameSpan(MethodDeclarationSyntax syntax, TextSpan span) 125 | { 126 | if (syntax.TypeParameterList != null) 127 | { 128 | return span.End <= syntax.TypeParameterList.Span.End 129 | && syntax.Identifier.Span.Contains(span.Start); 130 | } 131 | 132 | return syntax.Identifier.Span.Contains(span); 133 | } 134 | 135 | private static bool IsFunctionNameSpan(OperatorDeclarationSyntax syntax, TextSpan span) 136 | { 137 | if (syntax.OperatorKeyword.Span.Contains(span) || syntax.OperatorToken.Span.Contains(span)) 138 | { 139 | return true; 140 | } 141 | 142 | if (span.Start < syntax.OperatorKeyword.Span.Start || syntax.OperatorToken.Span.End < span.End) 143 | { 144 | return false; 145 | } 146 | 147 | return span.Start < syntax.OperatorKeyword.Span.End || syntax.OperatorToken.Span.Start < span.End; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyCompany("Joseph N. Musser II")] 5 | [assembly: AssemblyProduct("CopyFunctionBreakpointName")] 6 | [assembly: AssemblyCopyright("Copyright © 2018 Joseph N. Musser II")] 7 | 8 | [assembly: AssemblyVersion("1.0.0")] 9 | [assembly: AssemblyInformationalVersion("1.0.0")] 10 | 11 | [assembly: AssemblyTitle("Visual Studio extension")] 12 | 13 | [assembly: ComVisible(false)] 14 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/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 | -------------------------------------------------------------------------------- /src/CopyFunctionBreakpointName/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Copy Function Breakpoint Name 6 | Enables you to right-click and copy a name which the New Function Breakpoint dialog understands. This is useful when you cannot place a breakpoint directly in the current source view, e.g. metadata or decompiled source or a separate instance of Visual Studio. 7 | https://github.com/jnm2/CopyFunctionBreakpointName#copyfunctionbreakpointname-extension 8 | LICENSE.txt 9 | breakpoint;function;name;clipboard;copy;metadata;decompiled;source 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------