Skip to content

Commit b18512f

Browse files
committed
Terminal cli commads working
1 parent dfaa255 commit b18512f

File tree

1 file changed

+215
-131
lines changed

1 file changed

+215
-131
lines changed

src/vs/server/node/remote-terminal.ts

Lines changed: 215 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import * as os from 'os';
66
import * as path from 'path';
77
import { CancellationToken } from 'vs/base/common/cancellation';
88
import { URI } from 'vs/base/common/uri';
9-
import { Event } from 'vs/base/common/event';
9+
import { Emitter, Event } from 'vs/base/common/event';
1010
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1111
import { ILogService } from 'vs/platform/log/common/log';
1212
import product from 'vs/platform/product/common/product';
1313
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
1414
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';
1616
import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService';
1717
import { ICreateTerminalProcessArguments, ICreateTerminalProcessResult, REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
1818
import * as platform from 'vs/base/common/platform';
@@ -24,9 +24,14 @@ import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/termin
2424
import { IEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
2525
import { getSystemShellSync } from 'vs/base/node/shell';
2626
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';
2828
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
2929
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';
3035

3136
export 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

Comments
 (0)