-
Notifications
You must be signed in to change notification settings - Fork 2
/
vscode_uri.ts
97 lines (77 loc) · 3.03 KB
/
vscode_uri.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// deno-lint-ignore no-unused-vars
import { Node, Project, type SourceFile, ts } from "../deps/ts_morph.ts"
/**
* extended [VSCode URL](https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls)
* format to uniquely identify {@link Node} in {@link SourceFile}.
*
* format: `VSCode URL` + `kind` query parameter
*
* @privateRemarks
*
* RFC: encode project source file commit SHA for validation?
*/
export type VSCodeURI = `vscode://file/${string}?${string}`
/** Encode {@link Node} into {@link VSCodeURI} */
export const encodeVSCodeURI = (node: Node): VSCodeURI => {
const srcfile = node.getSourceFile()
const path = srcfile.getFilePath().replace("/", "")
const { line, column } = srcfile.getLineAndColumnAtPos(node.getStart())
const searchParams = new URLSearchParams({ kind: node.getKindName() })
if (Node.hasName(node)) searchParams.set("name", node.getName())
const hash = `L${node.getStartLineNumber()}-L${node.getEndLineNumber()}`
// console.log(node.getStart(), node.getText(), {
// line,
// column,
// kind: node.getKindName(),
// })
return `vscode://file/${path}:${line}:${column}?${searchParams}#${hash}`
}
/**
* Decode {@link VSCodeURI} into {@link Node}
*
* The project used to decode the URI must have
* identical source file state to the project used to encode the URI.
*/
export const mkDecodeVSCodeURI =
(project: Project) => (url: VSCodeURI): Node | undefined => {
const kind = new URL(url).searchParams.get("kind")
if (!kind || !isValidSyntaxKind(kind)) return undefined
const syntaxKind = ts.SyntaxKind[kind]
const match = vscodeURIPattern.captures(url)
if (!match) return undefined
const { path, line, column } = match
const srcfile = project.getSourceFile(path)
if (!srcfile) return undefined
// https://github.com/dsherret/ts-morph/issues/891#issuecomment-847972520
const pos = srcfile.compilerNode
.getPositionOfLineAndCharacter(+line - 1, +column - 1) // 1-indexed to 0-indexed
// console.log({
// pos,
// line,
// column,
// kind,
// child: srcfile.getDescendantAtPos(pos)?.getText(),
// actual: srcfile.getDescendantAtPos(pos)?.getAncestors()
// .find((x) => x.isKind(syntaxKind))?.getText(),
// })
return srcfile.getDescendantAtPos(pos)
?.getAncestors().find((x) => x.isKind(syntaxKind))
}
const vscodeURIPattern = typedRegEx(
"vscode:\\/\\/file/(?<path>.+):(?<line>\\d+):(?<column>\\d+)",
)
const isValidSyntaxKind = (kind: string): kind is keyof typeof ts.SyntaxKind =>
kind in ts.SyntaxKind
export const prettyPrintURI = (uri: VSCodeURI) => {
const { name, fullPath } = parseVSCodeURI(uri)
return `${name} (${fullPath})`
}
export const parseVSCodeURI = (uri: VSCodeURI) => {
const url = new URL(uri)
const fullPath = url.pathname.replace("vscode://file", "")
const [path, line, column] = fullPath.split(":")
const name = url.searchParams.get("name")
const kind = url.searchParams.get("kind")
return { uri, url, fullPath, path, line, column, name, kind }
}