@@ -10,9 +10,16 @@ const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10
10
const absRequire = ( createRequire || createRequireFromPath ) ( absPnpApiPath ) ;
11
11
12
12
const moduleWrapper = tsserver => {
13
+ if ( ! process . versions . pnp ) {
14
+ return tsserver ;
15
+ }
16
+
13
17
const { isAbsolute} = require ( `path` ) ;
14
18
const pnpApi = require ( `pnpapi` ) ;
15
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
+
16
23
const dependencyTreeRoots = new Set ( pnpApi . getDependencyTreeRoots ( ) . map ( locator => {
17
24
return `${ locator . name } @${ locator . reference } ` ;
18
25
} ) ) ;
@@ -23,9 +30,9 @@ const moduleWrapper = tsserver => {
23
30
24
31
function toEditorPath ( str ) {
25
32
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
26
- if ( isAbsolute ( str ) && ! str . match ( / ^ \^ z i p : / ) && ( str . match ( / \. z i p \/ / ) || str . match ( / \/ ( \$ \$ v i r t u a l | _ _ v i r t u a l _ _ ) \/ / ) ) ) {
33
+ if ( isAbsolute ( str ) && ! str . match ( / ^ \^ ? ( z i p : | \/ z i p \/ ) / ) && ( str . match ( / \. z i p \/ / ) || isVirtual ( str ) ) ) {
27
34
// We also take the opportunity to turn virtual paths into physical ones;
28
- // this makes is much easier to work with workspaces that list peer
35
+ // this makes it much easier to work with workspaces that list peer
29
36
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
30
37
// file instances instead of the real ones.
31
38
//
@@ -34,36 +41,83 @@ const moduleWrapper = tsserver => {
34
41
// with peer dep (otherwise jumping into react-dom would show resolution
35
42
// errors on react).
36
43
//
37
- const resolved = pnpApi . resolveVirtual ( str ) ;
44
+ const resolved = isVirtual ( str ) ? pnpApi . resolveVirtual ( str ) : str ;
38
45
if ( resolved ) {
39
46
const locator = pnpApi . findPackageLocator ( resolved ) ;
40
47
if ( locator && dependencyTreeRoots . has ( `${ locator . name } @${ locator . reference } ` ) ) {
41
- str = resolved ;
48
+ str = resolved ;
42
49
}
43
50
}
44
51
45
- str = str . replace ( / \\ / g, `/` )
46
- str = str . replace ( / ^ \/ ? / , `/` ) ;
52
+ str = normalize ( str ) ;
47
53
48
- // Absolute VSCode `Uri.fsPath`s need to start with a slash.
49
- // VSCode only adds it automatically for supported schemes,
50
- // so we have to do it manually for the `zip` scheme.
51
- // The path needs to start with a caret otherwise VSCode doesn't handle the protocol
52
- //
53
- // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
54
- //
55
54
if ( str . match ( / \. z i p \/ / ) ) {
56
- str = `${ isVSCode ? `^` : `` } zip:${ str } ` ;
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
+ }
57
95
}
58
96
}
59
97
60
98
return str ;
61
99
}
62
100
63
101
function fromEditorPath ( str ) {
64
- return process . platform === `win32`
65
- ? str . replace ( / ^ \^ ? z i p : \/ / , `` )
66
- : str . replace ( / ^ \^ ? z i p : / , `` ) ;
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
+ }
67
121
}
68
122
69
123
// Force enable 'allowLocalPluginLoads'
@@ -86,24 +140,33 @@ const moduleWrapper = tsserver => {
86
140
87
141
const Session = tsserver . server . Session ;
88
142
const { onMessage : originalOnMessage , send : originalSend } = Session . prototype ;
89
- let isVSCode = false ;
143
+ let hostInfo = `unknown` ;
90
144
91
- return Object . assign ( Session . prototype , {
92
- onMessage ( /** @type {string } */ message ) {
93
- const parsedMessage = JSON . parse ( message )
145
+ Object . assign ( Session . prototype , {
146
+ onMessage ( /** @type {string | object } */ message ) {
147
+ const isStringMessage = typeof message === 'string' ;
148
+ const parsedMessage = isStringMessage ? JSON . parse ( message ) : message ;
94
149
95
150
if (
96
151
parsedMessage != null &&
97
152
typeof parsedMessage === `object` &&
98
153
parsedMessage . arguments &&
99
- parsedMessage . arguments . hostInfo === `vscode `
154
+ typeof parsedMessage . arguments . hostInfo === `string `
100
155
) {
101
- isVSCode = true ;
156
+ hostInfo = parsedMessage . arguments . hostInfo ;
157
+ 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 ) \. / ) ) {
158
+ hostInfo += ` <1.61` ;
159
+ }
102
160
}
103
161
104
- return originalOnMessage . call ( this , JSON . stringify ( parsedMessage , ( key , value ) => {
105
- return typeof value === `string` ? fromEditorPath ( value ) : value ;
106
- } ) ) ;
162
+ const processedMessageJSON = JSON . stringify ( parsedMessage , ( key , value ) => {
163
+ return typeof value === 'string' ? fromEditorPath ( value ) : value ;
164
+ } ) ;
165
+
166
+ return originalOnMessage . call (
167
+ this ,
168
+ isStringMessage ? processedMessageJSON : JSON . parse ( processedMessageJSON )
169
+ ) ;
107
170
} ,
108
171
109
172
send ( /** @type {any } */ msg ) {
@@ -112,6 +175,8 @@ const moduleWrapper = tsserver => {
112
175
} ) ) ) ;
113
176
}
114
177
} ) ;
178
+
179
+ return tsserver ;
115
180
} ;
116
181
117
182
if ( existsSync ( absPnpApiPath ) ) {
0 commit comments