Fix async in terminal ext host and custom pty impl terminals

Fixes #129240
This commit is contained in:
Daniel Imms 2021-07-23 06:15:00 -07:00
parent b9c8788542
commit 5b5766d77d
4 changed files with 67 additions and 56 deletions

View file

@ -669,7 +669,7 @@ import { assertNoRpc } from '../utils';
});
suite('environmentVariableCollection', () => {
test('should have collection variables apply to terminals immediately after setting', async (done) => {
test('should have collection variables apply to terminals immediately after setting', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a2~',
@ -697,7 +697,7 @@ import { assertNoRpc } from '../utils';
collection.replace('A', '~a2~');
collection.append('B', '~b2~');
collection.prepend('C', '~c2~');
const terminal = await window.createTerminal({
const terminal = window.createTerminal({
env: {
A: 'a1',
B: 'b1',
@ -714,7 +714,7 @@ import { assertNoRpc } from '../utils';
terminal.sendText('echo $C');
});
test('should have collection variables apply to environment variables that don\'t exist', async (done) => {
test('should have collection variables apply to environment variables that don\'t exist', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a2~',
@ -742,7 +742,7 @@ import { assertNoRpc } from '../utils';
collection.replace('A', '~a2~');
collection.append('B', '~b2~');
collection.prepend('C', '~c2~');
const terminal = await window.createTerminal({
const terminal = window.createTerminal({
env: {
A: null,
B: null,
@ -759,7 +759,7 @@ import { assertNoRpc } from '../utils';
terminal.sendText('echo $C');
});
test('should respect clearing entries', async (done) => {
test('should respect clearing entries', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a1~',
@ -786,7 +786,7 @@ import { assertNoRpc } from '../utils';
collection.replace('A', '~a2~');
collection.replace('B', '~a2~');
collection.clear();
const terminal = await window.createTerminal({
const terminal = window.createTerminal({
env: {
A: '~a1~',
B: '~b1~'
@ -800,7 +800,7 @@ import { assertNoRpc } from '../utils';
terminal.sendText('echo $B');
});
test('should respect deleting entries', async (done) => {
test('should respect deleting entries', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a1~',
@ -827,7 +827,7 @@ import { assertNoRpc } from '../utils';
collection.replace('A', '~a2~');
collection.replace('B', '~b2~');
collection.delete('A');
const terminal = await window.createTerminal({
const terminal = window.createTerminal({
env: {
A: '~a1~',
B: '~b2~'

View file

@ -30,7 +30,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
* to a numeric terminal id (an id generated on the renderer side)
* This comes in play only when dealing with terminals created on the extension host side
*/
private _extHostTerminalIds = new Map<string, number>();
private _extHostTerminals = new Map<string, Promise<ITerminalInstance>>();
private readonly _toDispose = new DisposableStore();
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
private readonly _profileProviders = new Map<string, IDisposable>();
@ -109,19 +109,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptDefaultProfile(...await Promise.all([defaultProfile, defaultAutomationProfile]));
}
private _getTerminalId(id: TerminalIdentifier): number | undefined {
if (typeof id === 'number') {
return id;
private async _getTerminalInstance(id: TerminalIdentifier): Promise<ITerminalInstance | undefined> {
if (typeof id === 'string') {
return this._extHostTerminals.get(id);
}
return this._extHostTerminalIds.get(id);
}
private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined {
const rendererId = this._getTerminalId(id);
if (typeof rendererId === 'number') {
return this._terminalService.getInstanceFromId(rendererId);
}
return undefined;
return this._terminalService.getInstanceFromId(id);
}
public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise<void> {
@ -141,29 +133,31 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
customPtyImplementation: launchConfig.isExtensionCustomPtyTerminal
? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService)
: undefined,
extHostTerminalId: extHostTerminalId,
extHostTerminalId,
isFeatureTerminal: launchConfig.isFeatureTerminal,
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
useShellEnvironment: launchConfig.useShellEnvironment
};
let terminal: ITerminalInstance | undefined;
if (launchConfig.isSplitTerminal) {
const activeInstance = this._terminalService.getInstanceHost(launchConfig.target).activeInstance;
if (activeInstance) {
terminal = withNullAsUndefined(await this._terminalService.splitInstance(activeInstance, shellLaunchConfig));
this._extHostTerminals.set(extHostTerminalId, new Promise(async r => {
let terminal: ITerminalInstance | undefined;
if (launchConfig.isSplitTerminal) {
const activeInstance = this._terminalService.getInstanceHost(launchConfig.target).activeInstance;
if (activeInstance) {
terminal = withNullAsUndefined(await this._terminalService.splitInstance(activeInstance, shellLaunchConfig));
}
}
}
if (!terminal) {
terminal = await this._terminalService.createTerminal({
config: shellLaunchConfig,
target: launchConfig.target
});
}
this._extHostTerminalIds.set(extHostTerminalId, terminal.instanceId);
if (!terminal) {
terminal = await this._terminalService.createTerminal({
config: shellLaunchConfig,
target: launchConfig.target
});
}
r(terminal);
}));
}
public $show(id: TerminalIdentifier, preserveFocus: boolean): void {
const terminalInstance = this._getTerminalInstance(id);
public async $show(id: TerminalIdentifier, preserveFocus: boolean): Promise<void> {
const terminalInstance = await this._getTerminalInstance(id);
if (terminalInstance) {
this._terminalService.setActiveInstance(terminalInstance);
if (terminalInstance.target === TerminalLocation.Editor) {
@ -174,20 +168,21 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
}
public $hide(id: TerminalIdentifier): void {
const rendererId = this._getTerminalId(id);
const instance = this._terminalService.activeInstance;
if (instance && instance.instanceId === rendererId && instance.target !== TerminalLocation.Editor) {
public async $hide(id: TerminalIdentifier): Promise<void> {
const instanceToHide = await this._getTerminalInstance(id);
const activeInstance = this._terminalService.activeInstance;
if (activeInstance && activeInstance.instanceId === instanceToHide?.instanceId && activeInstance.target !== TerminalLocation.Editor) {
this._terminalGroupService.hidePanel();
}
}
public $dispose(id: TerminalIdentifier): void {
this._getTerminalInstance(id)?.dispose();
public async $dispose(id: TerminalIdentifier): Promise<void> {
(await this._getTerminalInstance(id))?.dispose();
}
public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void {
this._getTerminalInstance(id)?.sendText(text, addNewLine);
public async $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): Promise<void> {
const instance = await this._getTerminalInstance(id);
await instance?.sendText(text, addNewLine);
}
public $startSendingDataEvents(): void {

View file

@ -618,7 +618,7 @@ export interface ITerminalInstance {
* required to run a command in the terminal. The character(s) added are \n or \r\n
* depending on the platform. This defaults to `true`.
*/
sendText(text: string, addNewLine: boolean): void;
sendText(text: string, addNewLine: boolean): Promise<void>;
/** Scroll the terminal buffer down 1 line. */
scrollDownLine(): void;

View file

@ -229,8 +229,9 @@ export class TerminalService implements ITerminalService {
lifecycleService.onWillShutdown(e => this._onWillShutdown(e));
this._configurationService.onDidChangeConfiguration(async e => {
if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + this._getPlatformKey()) ||
e.affectsConfiguration(TerminalSettingPrefix.Profiles + this._getPlatformKey()) ||
const platformKey = await this._getPlatformKey();
if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + platformKey) ||
e.affectsConfiguration(TerminalSettingPrefix.Profiles + platformKey) ||
e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) {
this._refreshAvailableProfiles();
}
@ -957,7 +958,6 @@ export class TerminalService implements ITerminalService {
return; // Should never happen
} else if ('id' in value.profile) {
// extension contributed profile
console.log(value.profile.title);
await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.title, ConfigurationTarget.USER);
this._registerContributedProfile(value.profile.extensionIdentifier, value.profile.id, value.profile.title, {
@ -1067,7 +1067,7 @@ export class TerminalService implements ITerminalService {
return { label, description: profile.path, profile, buttons };
}
private _convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile | IExtensionTerminalProfile, cwd?: string | URI): IShellLaunchConfig {
private _convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig {
if (shellLaunchConfigOrProfile && 'profileName' in shellLaunchConfigOrProfile) {
const profile = shellLaunchConfigOrProfile;
if (!profile.path) {
@ -1084,8 +1084,8 @@ export class TerminalService implements ITerminalService {
};
}
// Shell launch config was provided
if (shellLaunchConfigOrProfile && 'cwd' in shellLaunchConfigOrProfile) {
// A shell launch config was provided
if (shellLaunchConfigOrProfile) {
if (cwd) {
shellLaunchConfigOrProfile.cwd = cwd;
}
@ -1109,11 +1109,27 @@ export class TerminalService implements ITerminalService {
}
async createTerminal(options?: ICreateTerminalOptions): Promise<ITerminalInstance> {
const shellLaunchConfig = this._convertProfileToShellLaunchConfig(options?.config || options);
const config = options?.config;
const shellLaunchConfig = config && 'extensionIdentifier' in config
? {}
: this._convertProfileToShellLaunchConfig(config || {});
const contributedDefaultProfile = await this._getContributedDefaultProfile(shellLaunchConfig);
if (contributedDefaultProfile) {
await this.createContributedTerminalProfile(contributedDefaultProfile.extensionIdentifier, contributedDefaultProfile.id, { isSplitTerminal: false, icon: contributedDefaultProfile.icon });
// Get the contributed profile if it was provided
let contributedProfile = config && 'extensionIdentifier' in config ? config : undefined;
// Get the default profile as a contributed profile if it exists
if (!contributedProfile && !options) {
contributedProfile = await this._getContributedDefaultProfile(shellLaunchConfig);
}
// Launch the contributed profile
if (contributedProfile) {
// TODO: createContributedTerminalProfile should be private if we want all terminal creation to go through createTerminal
await this.createContributedTerminalProfile(contributedProfile.extensionIdentifier, contributedProfile.id, {
isSplitTerminal: false,
icon: contributedProfile.icon
});
// TODO: The extension terminal may be created in the editor area
return this._terminalGroupService.instances[this._terminalGroupService.instances.length - 1];
}