@@ -6,13 +6,13 @@ import * as os from 'os';
6
6
import * as path from 'path' ;
7
7
import { CancellationToken } from 'vs/base/common/cancellation' ;
8
8
import { URI } from 'vs/base/common/uri' ;
9
- import { Event } from 'vs/base/common/event' ;
9
+ import { Emitter , Event } from 'vs/base/common/event' ;
10
10
import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
11
11
import { ILogService } from 'vs/platform/log/common/log' ;
12
12
import product from 'vs/platform/product/common/product' ;
13
13
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment' ;
14
14
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
15
- import { IShellLaunchConfig , LocalReconnectConstants } from 'vs/platform/terminal/common/terminal' ;
15
+ import { IPtyService , IShellLaunchConfig , LocalReconnectConstants } from 'vs/platform/terminal/common/terminal' ;
16
16
import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService' ;
17
17
import { ICreateTerminalProcessArguments , ICreateTerminalProcessResult , REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel' ;
18
18
import * as platform from 'vs/base/common/platform' ;
@@ -24,9 +24,14 @@ import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/termin
24
24
import { IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable' ;
25
25
import { getSystemShellSync } from 'vs/base/node/shell' ;
26
26
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation' ;
27
- import { IPCServer } from 'vs/base/parts/ipc/common/ipc' ;
27
+ import { IPCServer , IServerChannel } from 'vs/base/parts/ipc/common/ipc' ;
28
28
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver' ;
29
29
import { TernarySearchTree } from 'vs/base/common/map' ;
30
+ import { CLIServerBase } from 'vs/workbench/api/node/extHostCLIServer' ;
31
+ import { createRandomIPCHandle } from 'vs/base/parts/ipc/node/ipc.net' ;
32
+ import { IRawURITransformerFactory } from 'vs/server/node/server.main' ;
33
+ import { IURITransformer , transformIncomingURIs , URITransformer } from 'vs/base/common/uriIpc' ;
34
+ import { cloneAndChange } from 'vs/base/common/objects' ;
30
35
31
36
export function registerRemoteTerminal ( services : ServicesAccessor , channelServer : IPCServer < RemoteAgentConnectionContext > ) {
32
37
const reconnectConstants = {
@@ -36,154 +41,233 @@ export function registerRemoteTerminal(services: ServicesAccessor, channelServer
36
41
const configurationService = services . get ( IConfigurationService ) ;
37
42
const logService = services . get ( ILogService ) ;
38
43
const telemetryService = services . get ( ITelemetryService ) ;
44
+ const rawURITransformerFactory = services . get ( IRawURITransformerFactory ) ;
39
45
const ptyHostService = new PtyHostService ( reconnectConstants , configurationService , logService , telemetryService ) ;
40
- const resolvedServices : Services = { logService, ptyHostService } ;
41
- channelServer . registerChannel ( REMOTE_TERMINAL_CHANNEL_NAME , {
46
+ channelServer . registerChannel ( REMOTE_TERMINAL_CHANNEL_NAME , new RemoteTerminalChannelServer ( rawURITransformerFactory , logService , ptyHostService ) ) ;
47
+ }
42
48
43
- call : async ( ctx : RemoteAgentConnectionContext , command : string , arg ?: any , cancellationToken ?: CancellationToken ) => {
44
- if ( command === '$createProcess' ) {
45
- return createProcess ( arg , resolvedServices ) ;
46
- }
49
+ function toWorkspaceFolder ( data : IWorkspaceFolderData ) : IWorkspaceFolder {
50
+ return {
51
+ uri : URI . revive ( data . uri ) ,
52
+ name : data . name ,
53
+ index : data . index ,
54
+ toResource : ( ) => {
55
+ throw new Error ( 'Not implemented' ) ;
56
+ }
57
+ } ;
58
+ }
47
59
48
- // Generic method handling for all other commands
49
- const serviceRecord = ptyHostService as unknown as Record < string , ( arg ?: any ) => Promise < any > > ;
50
- const serviceFunc = serviceRecord [ command . substring ( 1 ) ] ;
51
- if ( ! serviceFunc ) {
52
- logService . error ( 'Unknown command: ' + command ) ;
53
- return undefined ;
54
- }
55
- if ( Array . isArray ( arg ) ) {
56
- return serviceFunc . call ( ptyHostService , ...arg ) ;
57
- } else {
58
- return serviceFunc . call ( ptyHostService , arg ) ;
59
- }
60
- } ,
60
+ export class RemoteTerminalChannelServer implements IServerChannel < RemoteAgentConnectionContext > {
61
61
62
- listen : ( ctx : RemoteAgentConnectionContext , event : string ) => {
63
- if ( event === '$onExecuteCommand' ) {
64
- return Event . None ;
65
- }
66
- const serviceRecord = ptyHostService as unknown as Record < string , Event < any > > ;
67
- const result = serviceRecord [ event . substring ( 1 , event . endsWith ( 'Event' ) ? event . length - 'Event' . length : undefined ) ] ;
68
- if ( ! result ) {
69
- logService . error ( 'Unknown event: ' + event ) ;
70
- return Event . None ;
71
- }
72
- return result ;
62
+ private _lastRequestId = 0 ;
63
+ private _pendingRequests = new Map < number , { resolve : ( data : any ) => void , reject : ( error : any ) => void , uriTransformer : IURITransformer } > ( ) ;
64
+
65
+ private readonly _onExecuteCommand = new Emitter < { reqId : number , commandId : string , commandArgs : any [ ] } > ( ) ;
66
+ readonly onExecuteCommand = this . _onExecuteCommand . event ;
67
+
68
+ constructor (
69
+ private readonly rawURITransformerFactory : IRawURITransformerFactory ,
70
+ private readonly logService : ILogService ,
71
+ private readonly ptyService : IPtyService ,
72
+ ) {
73
+ }
74
+
75
+ public async call ( context : RemoteAgentConnectionContext , command : string , args : any , cancellationToken ?: CancellationToken | undefined ) : Promise < any > {
76
+ if ( command === '$createProcess' ) {
77
+ return this . createProcess ( context . remoteAuthority , args ) ;
73
78
}
74
79
75
- } ) ;
76
- }
80
+ if ( command === '$sendCommandResult' ) {
81
+ return this . sendCommandResult ( args [ 0 ] , args [ 1 ] , args [ 2 ] ) ;
82
+ }
77
83
78
- interface Services {
79
- ptyHostService : PtyHostService , logService : ILogService
80
- }
84
+ // Generic method handling for all other commands
85
+ const serviceRecord = this . ptyService as unknown as Record < string , ( arg ?: any ) => Promise < any > > ;
86
+ const serviceFunc = serviceRecord [ command . substring ( 1 ) ] ;
87
+ if ( ! serviceFunc ) {
88
+ this . logService . error ( 'Unknown command: ' + command ) ;
89
+ return ;
90
+ }
81
91
82
- async function createProcess ( args : ICreateTerminalProcessArguments , services : Services ) : Promise < ICreateTerminalProcessResult > {
83
- const shellLaunchConfigDto = args . shellLaunchConfig ;
84
- // See $spawnExtHostProcess in src/vs/workbench/api/node/extHostTerminalService.ts for a reference implementation
85
- const shellLaunchConfig : IShellLaunchConfig = {
86
- name : shellLaunchConfigDto . name ,
87
- executable : shellLaunchConfigDto . executable ,
88
- args : shellLaunchConfigDto . args ,
89
- cwd : typeof shellLaunchConfigDto . cwd === 'string' ? shellLaunchConfigDto . cwd : URI . revive ( shellLaunchConfigDto . cwd ) ,
90
- env : shellLaunchConfigDto . env
91
- } ;
92
+ if ( Array . isArray ( args ) ) {
93
+ return serviceFunc . call ( this . ptyService , ...args ) ;
94
+ } else {
95
+ return serviceFunc . call ( this . ptyService , args ) ;
96
+ }
97
+ }
98
+
99
+ public listen ( context : RemoteAgentConnectionContext , event : string , args : any ) : Event < any > {
100
+ if ( event === '$onExecuteCommand' ) {
101
+ return this . _onExecuteCommand . event ;
102
+ }
103
+
104
+ const serviceRecord = this . ptyService as unknown as Record < string , Event < any > > ;
105
+ const result = serviceRecord [ event . substring ( 1 , event . endsWith ( 'Event' ) ? event . length - 'Event' . length : undefined ) ] ;
106
+ if ( ! result ) {
107
+ this . logService . error ( 'Unknown event: ' + event ) ;
108
+ return Event . None ;
109
+ }
110
+ return result ;
111
+ }
112
+
113
+ private executeCommand ( uriTransformer : IURITransformer , id : string , args : any [ ] ) : Promise < any > {
114
+ let resolve : ( data : any ) => void , reject : ( error : any ) => void ;
115
+ const promise = new Promise < any > ( ( c , e ) => { resolve = c ; reject = e ; } ) ;
116
+
117
+ const reqId = ++ this . _lastRequestId ;
118
+ this . _pendingRequests . set ( reqId , { resolve : resolve ! , reject : reject ! , uriTransformer } ) ;
119
+
120
+ const commandArgs = cloneAndChange ( args , value => {
121
+ if ( value instanceof URI ) {
122
+ return uriTransformer . transformOutgoingURI ( value ) ;
123
+ }
124
+ return ;
125
+ } ) ;
126
+ this . _onExecuteCommand . fire ( { reqId, commandId : id , commandArgs } ) ;
92
127
93
- let lastActiveWorkspace : IWorkspaceFolder | undefined ;
94
- if ( args . activeWorkspaceFolder ) {
95
- lastActiveWorkspace = toWorkspaceFolder ( args . activeWorkspaceFolder ) ;
128
+ return promise ;
96
129
}
97
130
98
- const processEnv = { ...process . env , ...args . resolverEnv } as platform . IProcessEnvironment ;
99
- const configurationResolverService = new RemoteTerminalVariableResolverService (
100
- args . workspaceFolders . map ( toWorkspaceFolder ) ,
101
- args . resolvedVariables ,
102
- args . activeFileResource ? URI . revive ( args . activeFileResource ) : undefined ,
103
- processEnv
104
- ) ;
105
- const variableResolver = createVariableResolver ( lastActiveWorkspace , processEnv , configurationResolverService ) ;
106
-
107
- // Merge in shell and args from settings
108
- if ( ! shellLaunchConfig . executable ) {
109
- shellLaunchConfig . executable = getDefaultShell (
110
- key => args . configuration [ key ] ,
111
- getSystemShellSync ( platform . OS , process . env as platform . IProcessEnvironment ) ,
112
- process . env . hasOwnProperty ( 'PROCESSOR_ARCHITEW6432' ) ,
113
- process . env . windir ,
131
+ private async sendCommandResult ( reqId : number , isError : boolean , payload : any ) : Promise < any > {
132
+ const reqData = this . _pendingRequests . get ( reqId ) ;
133
+ if ( ! reqData ) {
134
+ return ;
135
+ }
136
+
137
+ this . _pendingRequests . delete ( reqId ) ;
138
+
139
+ const result = transformIncomingURIs ( payload , reqData . uriTransformer ) ;
140
+ if ( isError ) {
141
+ reqData . reject ( result ) ;
142
+ } else {
143
+ reqData . resolve ( result ) ;
144
+ }
145
+ }
146
+
147
+ private async createProcess ( remoteAuthority : string , args : ICreateTerminalProcessArguments ) : Promise < ICreateTerminalProcessResult > {
148
+ const uriTransformer = new URITransformer ( this . rawURITransformerFactory ( remoteAuthority ) ) ;
149
+
150
+ const shellLaunchConfigDto = args . shellLaunchConfig ;
151
+ // See $spawnExtHostProcess in src/vs/workbench/api/node/extHostTerminalService.ts for a reference implementation
152
+ const shellLaunchConfig : IShellLaunchConfig = {
153
+ name : shellLaunchConfigDto . name ,
154
+ executable : shellLaunchConfigDto . executable ,
155
+ args : shellLaunchConfigDto . args ,
156
+ cwd : typeof shellLaunchConfigDto . cwd === 'string' ? shellLaunchConfigDto . cwd : URI . revive ( shellLaunchConfigDto . cwd ) ,
157
+ env : shellLaunchConfigDto . env
158
+ } ;
159
+
160
+ let lastActiveWorkspace : IWorkspaceFolder | undefined ;
161
+ if ( args . activeWorkspaceFolder ) {
162
+ lastActiveWorkspace = toWorkspaceFolder ( args . activeWorkspaceFolder ) ;
163
+ }
164
+
165
+ const processEnv = { ...process . env , ...args . resolverEnv } as platform . IProcessEnvironment ;
166
+ const configurationResolverService = new RemoteTerminalVariableResolverService (
167
+ args . workspaceFolders . map ( toWorkspaceFolder ) ,
168
+ args . resolvedVariables ,
169
+ args . activeFileResource ? URI . revive ( args . activeFileResource ) : undefined ,
170
+ processEnv
171
+ ) ;
172
+ const variableResolver = createVariableResolver ( lastActiveWorkspace , processEnv , configurationResolverService ) ;
173
+
174
+ // Merge in shell and args from settings
175
+ if ( ! shellLaunchConfig . executable ) {
176
+ shellLaunchConfig . executable = getDefaultShell (
177
+ key => args . configuration [ key ] ,
178
+ getSystemShellSync ( platform . OS , process . env as platform . IProcessEnvironment ) ,
179
+ process . env . hasOwnProperty ( 'PROCESSOR_ARCHITEW6432' ) ,
180
+ process . env . windir ,
181
+ variableResolver ,
182
+ this . logService ,
183
+ false
184
+ ) ;
185
+ shellLaunchConfig . args = getDefaultShellArgs (
186
+ key => args . configuration [ key ] ,
187
+ false ,
188
+ variableResolver ,
189
+ this . logService
190
+ ) ;
191
+ } else if ( variableResolver ) {
192
+ shellLaunchConfig . executable = variableResolver ( shellLaunchConfig . executable ) ;
193
+ if ( shellLaunchConfig . args ) {
194
+ if ( Array . isArray ( shellLaunchConfig . args ) ) {
195
+ const resolvedArgs : string [ ] = [ ] ;
196
+ for ( const arg of shellLaunchConfig . args ) {
197
+ resolvedArgs . push ( variableResolver ( arg ) ) ;
198
+ }
199
+ shellLaunchConfig . args = resolvedArgs ;
200
+ } else {
201
+ shellLaunchConfig . args = variableResolver ( shellLaunchConfig . args ) ;
202
+ }
203
+ }
204
+ }
205
+
206
+ // Get the initial cwd
207
+ const initialCwd = getCwd (
208
+ shellLaunchConfig ,
209
+ os . homedir ( ) ,
114
210
variableResolver ,
115
- services . logService ,
116
- false
211
+ lastActiveWorkspace ?. uri ,
212
+ args . configuration [ 'terminal.integrated.cwd' ] ,
213
+ this . logService
117
214
) ;
118
- shellLaunchConfig . args = getDefaultShellArgs (
119
- key => args . configuration [ key ] ,
120
- false ,
215
+ shellLaunchConfig . cwd = initialCwd ;
216
+
217
+ const env = createTerminalEnvironment (
218
+ shellLaunchConfig ,
219
+ args . configuration [ 'terminal.integrated.env.linux' ] ,
121
220
variableResolver ,
122
- services . logService
221
+ product . version ,
222
+ args . configuration [ 'terminal.integrated.detectLocale' ] || 'auto' ,
223
+ processEnv
123
224
) ;
124
- } else if ( variableResolver ) {
125
- shellLaunchConfig . executable = variableResolver ( shellLaunchConfig . executable ) ;
126
- if ( shellLaunchConfig . args ) {
127
- if ( Array . isArray ( shellLaunchConfig . args ) ) {
128
- const resolvedArgs : string [ ] = [ ] ;
129
- for ( const arg of shellLaunchConfig . args ) {
130
- resolvedArgs . push ( variableResolver ( arg ) ) ;
131
- }
132
- shellLaunchConfig . args = resolvedArgs ;
133
- } else {
134
- shellLaunchConfig . args = variableResolver ( shellLaunchConfig . args ) ;
225
+
226
+ // Apply extension environment variable collections to the environment
227
+ if ( ! shellLaunchConfig . strictEnv ) {
228
+ const collection = new Map < string , IEnvironmentVariableCollection > ( ) ;
229
+ for ( const [ name , serialized ] of args . envVariableCollections ) {
230
+ collection . set ( name , {
231
+ map : deserializeEnvironmentVariableCollection ( serialized )
232
+ } ) ;
135
233
}
234
+ const mergedCollection = new MergedEnvironmentVariableCollection ( collection ) ;
235
+ mergedCollection . applyToProcessEnvironment ( env , variableResolver ) ;
136
236
}
137
- }
138
237
139
- // Get the initial cwd
140
- const initialCwd = getCwd (
141
- shellLaunchConfig ,
142
- os . homedir ( ) ,
143
- variableResolver ,
144
- lastActiveWorkspace ?. uri ,
145
- args . configuration [ 'terminal.integrated.cwd' ] , services . logService
146
- ) ;
147
- shellLaunchConfig . cwd = initialCwd ;
148
-
149
- const env = createTerminalEnvironment (
150
- shellLaunchConfig ,
151
- args . configuration [ 'terminal.integrated.env.linux' ] ,
152
- variableResolver ,
153
- product . version ,
154
- args . configuration [ 'terminal.integrated.detectLocale' ] || 'auto' ,
155
- processEnv
156
- ) ;
157
-
158
- // Apply extension environment variable collections to the environment
159
- if ( ! shellLaunchConfig . strictEnv ) {
160
- const collection = new Map < string , IEnvironmentVariableCollection > ( ) ;
161
- for ( const [ name , serialized ] of args . envVariableCollections ) {
162
- collection . set ( name , {
163
- map : deserializeEnvironmentVariableCollection ( serialized )
164
- } ) ;
165
- }
166
- const mergedCollection = new MergedEnvironmentVariableCollection ( collection ) ;
167
- mergedCollection . applyToProcessEnvironment ( env , variableResolver ) ;
168
- }
238
+ const ipcHandle = createRandomIPCHandle ( ) ;
239
+ env [ 'VSCODE_IPC_HOOK_CLI' ] = ipcHandle ;
240
+ const cliServer = new CLIServerBase (
241
+ {
242
+ executeCommand : ( id , ...args ) => this . executeCommand ( uriTransformer , id , args )
243
+ } ,
244
+ this . logService ,
245
+ ipcHandle
246
+ ) ;
169
247
170
- const persistentTerminalId = await services . ptyHostService . createProcess ( shellLaunchConfig , initialCwd , args . cols , args . rows ,
171
- env , processEnv , false , args . shouldPersistTerminal , args . workspaceId , args . workspaceName ) ;
172
- return {
173
- persistentTerminalId,
174
- resolvedShellLaunchConfig : shellLaunchConfig
175
- } ;
176
- }
248
+ const persistentTerminalId = await this . ptyService . createProcess (
249
+ shellLaunchConfig ,
250
+ initialCwd ,
251
+ args . cols ,
252
+ args . rows ,
253
+ env ,
254
+ processEnv ,
255
+ false ,
256
+ args . shouldPersistTerminal ,
257
+ args . workspaceId ,
258
+ args . workspaceName
259
+ ) ;
260
+ this . ptyService . onProcessExit ( e => {
261
+ if ( e . id === persistentTerminalId ) {
262
+ cliServer . dispose ( ) ;
263
+ }
264
+ } ) ;
177
265
178
- function toWorkspaceFolder ( data : IWorkspaceFolderData ) : IWorkspaceFolder {
179
- return {
180
- uri : URI . revive ( data . uri ) ,
181
- name : data . name ,
182
- index : data . index ,
183
- toResource : ( ) => {
184
- throw new Error ( 'Not implemented' ) ;
185
- }
186
- } ;
266
+ return {
267
+ persistentTerminalId,
268
+ resolvedShellLaunchConfig : shellLaunchConfig
269
+ } ;
270
+ }
187
271
}
188
272
189
273
/**
0 commit comments