@@ -4,52 +4,173 @@ const {existsSync} = require(`fs`);
44const { createRequire, createRequireFromPath} = require ( `module` ) ;
55const { resolve} = require ( `path` ) ;
66
7- const relPnpApiPath = "../../../../.pnp.js " ;
7+ const relPnpApiPath = "../../../../.pnp.cjs " ;
88
99const absPnpApiPath = resolve ( __dirname , relPnpApiPath ) ;
1010const absRequire = ( createRequire || createRequireFromPath ) ( absPnpApiPath ) ;
1111
1212const moduleWrapper = tsserver => {
13+ if ( ! process . versions . pnp ) {
14+ return tsserver ;
15+ }
16+
17+ const { isAbsolute} = require ( `path` ) ;
18+ const pnpApi = require ( `pnpapi` ) ;
19+
20+ const isVirtual = str => str . match ( / \/ ( \$ \$ v i r t u a l | _ _ v i r t u a l _ _ ) \/ / ) ;
21+ const normalize = str => str . replace ( / \\ / g, `/` ) . replace ( / ^ \/ ? / , `/` ) ;
22+
23+ const dependencyTreeRoots = new Set ( pnpApi . getDependencyTreeRoots ( ) . map ( locator => {
24+ return `${ locator . name } @${ locator . reference } ` ;
25+ } ) ) ;
26+
1327 // VSCode sends the zip paths to TS using the "zip://" prefix, that TS
1428 // doesn't understand. This layer makes sure to remove the protocol
1529 // before forwarding it to TS, and to add it back on all returned paths.
1630
17- const { isAbsolute} = require ( `path` ) ;
31+ function toEditorPath ( str ) {
32+ // We add the `zip:` prefix to both `.zip/` paths and virtual paths
33+ if ( isAbsolute ( str ) && ! str . match ( / ^ \^ ? ( z i p : | \/ z i p \/ ) / ) && ( str . match ( / \. z i p \/ / ) || isVirtual ( str ) ) ) {
34+ // We also take the opportunity to turn virtual paths into physical ones;
35+ // this makes it much easier to work with workspaces that list peer
36+ // dependencies, since otherwise Ctrl+Click would bring us to the virtual
37+ // file instances instead of the real ones.
38+ //
39+ // We only do this to modules owned by the the dependency tree roots.
40+ // This avoids breaking the resolution when jumping inside a vendor
41+ // with peer dep (otherwise jumping into react-dom would show resolution
42+ // errors on react).
43+ //
44+ const resolved = isVirtual ( str ) ? pnpApi . resolveVirtual ( str ) : str ;
45+ if ( resolved ) {
46+ const locator = pnpApi . findPackageLocator ( resolved ) ;
47+ if ( locator && dependencyTreeRoots . has ( `${ locator . name } @${ locator . reference } ` ) ) {
48+ str = resolved ;
49+ }
50+ }
51+
52+ str = normalize ( str ) ;
53+
54+ if ( str . match ( / \. z i p \/ / ) ) {
55+ switch ( hostInfo ) {
56+ // Absolute VSCode `Uri.fsPath`s need to start with a slash.
57+ // VSCode only adds it automatically for supported schemes,
58+ // so we have to do it manually for the `zip` scheme.
59+ // The path needs to start with a caret otherwise VSCode doesn't handle the protocol
60+ //
61+ // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
62+ //
63+ // Update Oct 8 2021: VSCode changed their format in 1.61.
64+ // Before | ^zip:/c:/foo/bar.zip/package.json
65+ // After | ^/zip//c:/foo/bar.zip/package.json
66+ //
67+ case `vscode <1.61` : {
68+ str = `^zip:${ str } ` ;
69+ } break ;
70+
71+ case `vscode` : {
72+ str = `^/zip/${ str } ` ;
73+ } break ;
74+
75+ // To make "go to definition" work,
76+ // We have to resolve the actual file system path from virtual path
77+ // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
78+ case `coc-nvim` : {
79+ str = normalize ( resolved ) . replace ( / \. z i p \/ / , `.zip::` ) ;
80+ str = resolve ( `zipfile:${ str } ` ) ;
81+ } break ;
82+
83+ // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
84+ // We have to resolve the actual file system path from virtual path,
85+ // everything else is up to neovim
86+ case `neovim` : {
87+ str = normalize ( resolved ) . replace ( / \. z i p \/ / , `.zip::` ) ;
88+ str = `zipfile:${ str } ` ;
89+ } break ;
90+
91+ default : {
92+ str = `zip:${ str } ` ;
93+ } break ;
94+ }
95+ }
96+ }
97+
98+ return str ;
99+ }
100+
101+ function fromEditorPath ( str ) {
102+ switch ( hostInfo ) {
103+ case `coc-nvim` :
104+ case `neovim` : {
105+ str = str . replace ( / \. z i p : : / , `.zip/` ) ;
106+ // The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
107+ // So in order to convert it back, we use .* to match all the thing
108+ // before `zipfile:`
109+ return process . platform === `win32`
110+ ? str . replace ( / ^ .* z i p f i l e : \/ / , `` )
111+ : str . replace ( / ^ .* z i p f i l e : / , `` ) ;
112+ } break ;
113+
114+ case `vscode` :
115+ default : {
116+ return process . platform === `win32`
117+ ? str . replace ( / ^ \^ ? ( z i p : | \/ z i p ) \/ + / , `` )
118+ : str . replace ( / ^ \^ ? ( z i p : | \/ z i p ) \/ + / , `/` ) ;
119+ } break ;
120+ }
121+ }
122+
123+ // Force enable 'allowLocalPluginLoads'
124+ // TypeScript tries to resolve plugins using a path relative to itself
125+ // which doesn't work when using the global cache
126+ // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
127+ // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
128+ // TypeScript already does local loads and if this code is running the user trusts the workspace
129+ // https://github.com/microsoft/vscode/issues/45856
130+ const ConfiguredProject = tsserver . server . ConfiguredProject ;
131+ const { enablePluginsWithOptions : originalEnablePluginsWithOptions } = ConfiguredProject . prototype ;
132+ ConfiguredProject . prototype . enablePluginsWithOptions = function ( ) {
133+ this . projectService . allowLocalPluginLoads = true ;
134+ return originalEnablePluginsWithOptions . apply ( this , arguments ) ;
135+ } ;
136+
137+ // And here is the point where we hijack the VSCode <-> TS communications
138+ // by adding ourselves in the middle. We locate everything that looks
139+ // like an absolute path of ours and normalize it.
18140
19141 const Session = tsserver . server . Session ;
20142 const { onMessage : originalOnMessage , send : originalSend } = Session . prototype ;
143+ let hostInfo = `unknown` ;
21144
22- return Object . assign ( Session . prototype , {
145+ Object . assign ( Session . prototype , {
23146 onMessage ( /** @type {string } */ message ) {
24- return originalOnMessage . call ( this , JSON . stringify ( JSON . parse ( message ) , ( key , value ) => {
25- return typeof value === 'string' ? removeZipPrefix ( value ) : value ;
147+ const parsedMessage = JSON . parse ( message )
148+
149+ if (
150+ parsedMessage != null &&
151+ typeof parsedMessage === `object` &&
152+ parsedMessage . arguments &&
153+ typeof parsedMessage . arguments . hostInfo === `string`
154+ ) {
155+ hostInfo = parsedMessage . arguments . hostInfo ;
156+ if ( hostInfo === `vscode` && process . env . VSCODE_IPC_HOOK && process . env . VSCODE_IPC_HOOK . match ( / C o d e \/ 1 \. ( [ 1 - 5 ] [ 0 - 9 ] | 6 0 ) \. / ) ) {
157+ hostInfo += ` <1.61` ;
158+ }
159+ }
160+
161+ return originalOnMessage . call ( this , JSON . stringify ( parsedMessage , ( key , value ) => {
162+ return typeof value === `string` ? fromEditorPath ( value ) : value ;
26163 } ) ) ;
27164 } ,
28165
29166 send ( /** @type {any } */ msg ) {
30167 return originalSend . call ( this , JSON . parse ( JSON . stringify ( msg , ( key , value ) => {
31- return typeof value === ' string' ? addZipPrefix ( value ) : value ;
168+ return typeof value === ` string` ? toEditorPath ( value ) : value ;
32169 } ) ) ) ;
33170 }
34171 } ) ;
35172
36- function addZipPrefix ( str ) {
37- // We add the `zip:` prefix to both `.zip/` paths and virtual paths
38- if ( isAbsolute ( str ) && ! str . match ( / ^ z i p : / ) && ( str . match ( / \. z i p \/ / ) || str . match ( / \$ \$ v i r t u a l \/ / ) ) ) {
39- // Absolute VSCode `Uri.fsPath`s need to start with a slash.
40- // VSCode only adds it automatically for supported schemes,
41- // so we have to do it manually for the `zip` scheme.
42- return `zip:${ str . replace ( / ^ \/ ? / , `/` ) } ` ;
43- } else {
44- return str ;
45- }
46- }
47-
48- function removeZipPrefix ( str ) {
49- return process . platform === 'win32'
50- ? str . replace ( / ^ z i p : \/ / , `` )
51- : str . replace ( / ^ z i p : / , `` ) ;
52- }
173+ return tsserver ;
53174} ;
54175
55176if ( existsSync ( absPnpApiPath ) ) {
0 commit comments