@@ -6,13 +6,13 @@ import * as os from 'os';
66import * as path from 'path' ;
77import { CancellationToken } from 'vs/base/common/cancellation' ;
88import { URI } from 'vs/base/common/uri' ;
9- import { Event } from 'vs/base/common/event' ;
9+ import { Emitter , Event } from 'vs/base/common/event' ;
1010import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
1111import { ILogService } from 'vs/platform/log/common/log' ;
1212import product from 'vs/platform/product/common/product' ;
1313import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment' ;
1414import { 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' ;
1616import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService' ;
1717import { ICreateTerminalProcessArguments , ICreateTerminalProcessResult , REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel' ;
1818import * as platform from 'vs/base/common/platform' ;
@@ -24,9 +24,14 @@ import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/termin
2424import { IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable' ;
2525import { getSystemShellSync } from 'vs/base/node/shell' ;
2626import { 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' ;
2828import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver' ;
2929import { 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' ;
3035
3136export function registerRemoteTerminal ( services : ServicesAccessor , channelServer : IPCServer < RemoteAgentConnectionContext > ) {
3237 const reconnectConstants = {
@@ -36,154 +41,233 @@ export function registerRemoteTerminal(services: ServicesAccessor, channelServer
3641 const configurationService = services . get ( IConfigurationService ) ;
3742 const logService = services . get ( ILogService ) ;
3843 const telemetryService = services . get ( ITelemetryService ) ;
44+ const rawURITransformerFactory = services . get ( IRawURITransformerFactory ) ;
3945 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+ }
4248
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+ }
4759
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 > {
6161
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 ) ;
7378 }
7479
75- } ) ;
76- }
80+ if ( command === '$sendCommandResult' ) {
81+ return this . sendCommandResult ( args [ 0 ] , args [ 1 ] , args [ 2 ] ) ;
82+ }
7783
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+ }
8191
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 } ) ;
92127
93- let lastActiveWorkspace : IWorkspaceFolder | undefined ;
94- if ( args . activeWorkspaceFolder ) {
95- lastActiveWorkspace = toWorkspaceFolder ( args . activeWorkspaceFolder ) ;
128+ return promise ;
96129 }
97130
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 ( ) ,
114210 variableResolver ,
115- services . logService ,
116- false
211+ lastActiveWorkspace ?. uri ,
212+ args . configuration [ 'terminal.integrated.cwd' ] ,
213+ this . logService
117214 ) ;
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' ] ,
121220 variableResolver ,
122- services . logService
221+ product . version ,
222+ args . configuration [ 'terminal.integrated.detectLocale' ] || 'auto' ,
223+ processEnv
123224 ) ;
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+ } ) ;
135233 }
234+ const mergedCollection = new MergedEnvironmentVariableCollection ( collection ) ;
235+ mergedCollection . applyToProcessEnvironment ( env , variableResolver ) ;
136236 }
137- }
138237
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+ ) ;
169247
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+ } ) ;
177265
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+ }
187271}
188272
189273/**
0 commit comments