├── .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 | [](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 | 
11 |
12 | Then use Ctrl+D, B or your keyboard shortcut to open and focus the Breakpoints window:
13 |
14 | 
15 |
16 | And then Ctrl+B or your keyboard shortcut to open the New Function Breakpoint dialog:
17 |
18 | 
19 |
20 | Then Ctrl+V to paste and Enter to accept, and you’re done!
21 |
22 | 
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 | 
7 |
8 | Then use Ctrl+D, B or your keyboard shortcut to open and focus the Breakpoints window:
9 |
10 | 
11 |
12 | And then Ctrl+B or your keyboard shortcut to open the New Function Breakpoint dialog:
13 |
14 | 
15 |
16 | Then Ctrl+V to paste and Enter to accept, and you’re done!
17 |
18 | 
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 |
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 |
--------------------------------------------------------------------------------