Skip to content

Commit ecb18d0

Browse files
committed
Add task copy_xpath.ps1 for vscode [skip pch]
1 parent b9f3930 commit ecb18d0

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ P.S. You can also install just [git-aliases-extra](https://github.com/PhysShell/
116116
- Windows PowerShell 5.1
117117
- PowerShell 7
118118

119+
## VSCode utils (vscode_stuff directory)
120+
121+
1. copy_xpath.ps1 - copies XPath for XML element under cursor
122+
- Example:
123+
```xml
124+
<Root>
125+
<P1>
126+
<P2>
127+
<P3>
128+
<TargetElementName id="x">hello</TargetElementName>
129+
</P3>
130+
</P2>
131+
</P1>
132+
</Root>
133+
```
134+
- Suppose cursor is on TargetElementName
135+
- Gives Root->P1->P2->P3->TargetElementName
136+
- See copy_xpath.ps1 for example of setting up tasks.json & keybindings.json
137+
- P.S although there's some extensions that already provide described functionality I didn't want to use extensions for that specific purpose alone
138+
119139
## License
120140

121141
WTFPL. See `LICENSE`.

vscode_stuff/copy_xpath.ps1

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Example in VSCode tasks.json (you can use either pwsh.exe or powershell.exe depending on your preference and setup)
2+
# {
3+
# "label": "XML: Copy absolute element path",
4+
# "type": "process",
5+
# "command": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
6+
# "args": [
7+
# "-NoProfile",
8+
# "-NonInteractive",
9+
# "-File",
10+
# "your\\path\\to\\copy_xpath.ps1",
11+
# "-File",
12+
# "${file}",
13+
# "-Line",
14+
# "${lineNumber}",
15+
# "-Column",
16+
# "${columnNumber}"
17+
# ],
18+
# "problemMatcher": [],
19+
# "presentation": {
20+
# "reveal": "never",
21+
# "panel": "shared"
22+
# }
23+
# }
24+
25+
# Example in VSCode keybindings.json
26+
# {
27+
# "key": "ctrl+alt+x",
28+
# "command": "workbench.action.tasks.runTask",
29+
# "args": "XML: Copy absolute element path",
30+
# "when": "editorTextFocus"
31+
# }
32+
33+
param(
34+
[Parameter(Mandatory = $true)]
35+
[string]$File,
36+
37+
[Parameter(Mandatory = $true)]
38+
[int]$Line,
39+
40+
[Parameter(Mandatory = $true)]
41+
[int]$Column,
42+
43+
[switch]$IncludeIndexes
44+
)
45+
46+
Set-StrictMode -Version Latest
47+
$ErrorActionPreference = 'Stop'
48+
49+
function Compare-Position {
50+
param(
51+
[int]$LineA,
52+
[int]$ColA,
53+
[int]$LineB,
54+
[int]$ColB
55+
)
56+
57+
if ($LineA -lt $LineB) { return -1 }
58+
if ($LineA -gt $LineB) { return 1 }
59+
if ($ColA -lt $ColB) { return -1 }
60+
if ($ColA -gt $ColB) { return 1 }
61+
return 0
62+
}
63+
64+
function Get-SiblingIndex {
65+
param(
66+
[System.Collections.ArrayList]$Stack,
67+
[string]$Name
68+
)
69+
70+
if ($Stack.Count -eq 0) {
71+
return 1
72+
}
73+
74+
$parent = $Stack[$Stack.Count - 1]
75+
if (-not $parent.ContainsKey('ChildCounters')) {
76+
$parent['ChildCounters'] = @{}
77+
}
78+
79+
if (-not $parent['ChildCounters'].ContainsKey($Name)) {
80+
$parent['ChildCounters'][$Name] = 0
81+
}
82+
83+
$parent['ChildCounters'][$Name]++
84+
return [int]$parent['ChildCounters'][$Name]
85+
}
86+
87+
if (-not (Test-Path -LiteralPath $File)) {
88+
throw ("File not found: {0}" -f $File)
89+
}
90+
91+
$settings = [System.Xml.XmlReaderSettings]::new()
92+
$settings.DtdProcessing = [System.Xml.DtdProcessing]::Ignore
93+
$settings.IgnoreComments = $false
94+
$settings.IgnoreProcessingInstructions = $false
95+
$settings.IgnoreWhitespace = $false
96+
97+
$stack = [System.Collections.ArrayList]::new()
98+
$bestPath = $null
99+
100+
$reader = [System.Xml.XmlReader]::Create($File, $settings)
101+
102+
try {
103+
while ($reader.Read()) {
104+
$lineInfo = [System.Xml.IXmlLineInfo]$reader
105+
$nodeLine = if ($lineInfo.HasLineInfo()) { $lineInfo.LineNumber } else { 0 }
106+
$nodeCol = if ($lineInfo.HasLineInfo()) { $lineInfo.LinePosition } else { 0 }
107+
108+
$cmp = Compare-Position -LineA $nodeLine -ColA $nodeCol -LineB $Line -ColB $Column
109+
110+
switch ($reader.NodeType) {
111+
([System.Xml.XmlNodeType]::Element) {
112+
if ($cmp -gt 0) {
113+
break
114+
}
115+
116+
$index = Get-SiblingIndex -Stack $stack -Name $reader.Name
117+
118+
$entry = @{
119+
Name = $reader.Name
120+
Index = $index
121+
StartLine = $nodeLine
122+
StartColumn = $nodeCol
123+
ChildCounters = @{}
124+
}
125+
126+
[void]$stack.Add($entry)
127+
128+
$bestPath = @($stack | ForEach-Object { $_ })
129+
130+
if ($reader.IsEmptyElement) {
131+
[void]$stack.RemoveAt($stack.Count - 1)
132+
}
133+
}
134+
135+
([System.Xml.XmlNodeType]::EndElement) {
136+
if ($cmp -gt 0) {
137+
break
138+
}
139+
140+
if ($stack.Count -gt 0) {
141+
[void]$stack.RemoveAt($stack.Count - 1)
142+
}
143+
144+
$bestPath = @($stack | ForEach-Object { $_ })
145+
}
146+
147+
default {
148+
if ($cmp -gt 0) {
149+
break
150+
}
151+
152+
$bestPath = @($stack | ForEach-Object { $_ })
153+
}
154+
}
155+
}
156+
}
157+
finally {
158+
$reader.Dispose()
159+
}
160+
161+
if (-not $bestPath -or $bestPath.Count -eq 0) {
162+
throw ("Could not determine XML element for cursor position {0}:{1}" -f $Line, $Column)
163+
}
164+
165+
$parts = foreach ($item in $bestPath) {
166+
if ($IncludeIndexes) {
167+
'{0}[{1}]' -f $item.Name, $item.Index
168+
}
169+
else {
170+
$item.Name
171+
}
172+
}
173+
174+
$result = $parts -join '->'
175+
176+
try {
177+
Set-Clipboard -Value $result
178+
}
179+
catch {
180+
Write-Warning ("Could not copy to clipboard automatically: {0}" -f $_.Exception.Message)
181+
}
182+
183+
Write-Output $result

0 commit comments

Comments
 (0)