Fix async in terminal ext host and custom pty impl terminals
Fixes #129240
This commit is contained in:
parent
b9c8788542
commit
5b5766d77d
4 changed files with 67 additions and 56 deletions
|
@ -669,7 +669,7 @@ import { assertNoRpc } from '../utils';
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('environmentVariableCollection', () => {
|
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
|
// Text to match on before passing the test
|
||||||
const expectedText = [
|
const expectedText = [
|
||||||
'~a2~',
|
'~a2~',
|
||||||
|
@ -697,7 +697,7 @@ import { assertNoRpc } from '../utils';
|
||||||
collection.replace('A', '~a2~');
|
collection.replace('A', '~a2~');
|
||||||
collection.append('B', '~b2~');
|
collection.append('B', '~b2~');
|
||||||
collection.prepend('C', '~c2~');
|
collection.prepend('C', '~c2~');
|
||||||
const terminal = await window.createTerminal({
|
const terminal = window.createTerminal({
|
||||||
env: {
|
env: {
|
||||||
A: 'a1',
|
A: 'a1',
|
||||||
B: 'b1',
|
B: 'b1',
|
||||||
|
@ -714,7 +714,7 @@ import { assertNoRpc } from '../utils';
|
||||||
terminal.sendText('echo $C');
|
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
|
// Text to match on before passing the test
|
||||||
const expectedText = [
|
const expectedText = [
|
||||||
'~a2~',
|
'~a2~',
|
||||||
|
@ -742,7 +742,7 @@ import { assertNoRpc } from '../utils';
|
||||||
collection.replace('A', '~a2~');
|
collection.replace('A', '~a2~');
|
||||||
collection.append('B', '~b2~');
|
collection.append('B', '~b2~');
|
||||||
collection.prepend('C', '~c2~');
|
collection.prepend('C', '~c2~');
|
||||||
const terminal = await window.createTerminal({
|
const terminal = window.createTerminal({
|
||||||
env: {
|
env: {
|
||||||
A: null,
|
A: null,
|
||||||
B: null,
|
B: null,
|
||||||
|
@ -759,7 +759,7 @@ import { assertNoRpc } from '../utils';
|
||||||
terminal.sendText('echo $C');
|
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
|
// Text to match on before passing the test
|
||||||
const expectedText = [
|
const expectedText = [
|
||||||
'~a1~',
|
'~a1~',
|
||||||
|
@ -786,7 +786,7 @@ import { assertNoRpc } from '../utils';
|
||||||
collection.replace('A', '~a2~');
|
collection.replace('A', '~a2~');
|
||||||
collection.replace('B', '~a2~');
|
collection.replace('B', '~a2~');
|
||||||
collection.clear();
|
collection.clear();
|
||||||
const terminal = await window.createTerminal({
|
const terminal = window.createTerminal({
|
||||||
env: {
|
env: {
|
||||||
A: '~a1~',
|
A: '~a1~',
|
||||||
B: '~b1~'
|
B: '~b1~'
|
||||||
|
@ -800,7 +800,7 @@ import { assertNoRpc } from '../utils';
|
||||||
terminal.sendText('echo $B');
|
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
|
// Text to match on before passing the test
|
||||||
const expectedText = [
|
const expectedText = [
|
||||||
'~a1~',
|
'~a1~',
|
||||||
|
@ -827,7 +827,7 @@ import { assertNoRpc } from '../utils';
|
||||||
collection.replace('A', '~a2~');
|
collection.replace('A', '~a2~');
|
||||||
collection.replace('B', '~b2~');
|
collection.replace('B', '~b2~');
|
||||||
collection.delete('A');
|
collection.delete('A');
|
||||||
const terminal = await window.createTerminal({
|
const terminal = window.createTerminal({
|
||||||
env: {
|
env: {
|
||||||
A: '~a1~',
|
A: '~a1~',
|
||||||
B: '~b2~'
|
B: '~b2~'
|
||||||
|
|
|
@ -30,7 +30,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||||
* to a numeric terminal id (an id generated on the renderer side)
|
* 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
|
* 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 _toDispose = new DisposableStore();
|
||||||
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
||||||
private readonly _profileProviders = new Map<string, IDisposable>();
|
private readonly _profileProviders = new Map<string, IDisposable>();
|
||||||
|
@ -109,19 +109,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||||
this._proxy.$acceptDefaultProfile(...await Promise.all([defaultProfile, defaultAutomationProfile]));
|
this._proxy.$acceptDefaultProfile(...await Promise.all([defaultProfile, defaultAutomationProfile]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getTerminalId(id: TerminalIdentifier): number | undefined {
|
private async _getTerminalInstance(id: TerminalIdentifier): Promise<ITerminalInstance | undefined> {
|
||||||
if (typeof id === 'number') {
|
if (typeof id === 'string') {
|
||||||
return id;
|
return this._extHostTerminals.get(id);
|
||||||
}
|
}
|
||||||
return this._extHostTerminalIds.get(id);
|
return this._terminalService.getInstanceFromId(id);
|
||||||
}
|
|
||||||
|
|
||||||
private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined {
|
|
||||||
const rendererId = this._getTerminalId(id);
|
|
||||||
if (typeof rendererId === 'number') {
|
|
||||||
return this._terminalService.getInstanceFromId(rendererId);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise<void> {
|
public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise<void> {
|
||||||
|
@ -141,11 +133,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||||
customPtyImplementation: launchConfig.isExtensionCustomPtyTerminal
|
customPtyImplementation: launchConfig.isExtensionCustomPtyTerminal
|
||||||
? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService)
|
? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService)
|
||||||
: undefined,
|
: undefined,
|
||||||
extHostTerminalId: extHostTerminalId,
|
extHostTerminalId,
|
||||||
isFeatureTerminal: launchConfig.isFeatureTerminal,
|
isFeatureTerminal: launchConfig.isFeatureTerminal,
|
||||||
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
|
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
|
||||||
useShellEnvironment: launchConfig.useShellEnvironment
|
useShellEnvironment: launchConfig.useShellEnvironment
|
||||||
};
|
};
|
||||||
|
this._extHostTerminals.set(extHostTerminalId, new Promise(async r => {
|
||||||
let terminal: ITerminalInstance | undefined;
|
let terminal: ITerminalInstance | undefined;
|
||||||
if (launchConfig.isSplitTerminal) {
|
if (launchConfig.isSplitTerminal) {
|
||||||
const activeInstance = this._terminalService.getInstanceHost(launchConfig.target).activeInstance;
|
const activeInstance = this._terminalService.getInstanceHost(launchConfig.target).activeInstance;
|
||||||
|
@ -159,11 +152,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||||
target: launchConfig.target
|
target: launchConfig.target
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._extHostTerminalIds.set(extHostTerminalId, terminal.instanceId);
|
r(terminal);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public $show(id: TerminalIdentifier, preserveFocus: boolean): void {
|
public async $show(id: TerminalIdentifier, preserveFocus: boolean): Promise<void> {
|
||||||
const terminalInstance = this._getTerminalInstance(id);
|
const terminalInstance = await this._getTerminalInstance(id);
|
||||||
if (terminalInstance) {
|
if (terminalInstance) {
|
||||||
this._terminalService.setActiveInstance(terminalInstance);
|
this._terminalService.setActiveInstance(terminalInstance);
|
||||||
if (terminalInstance.target === TerminalLocation.Editor) {
|
if (terminalInstance.target === TerminalLocation.Editor) {
|
||||||
|
@ -174,20 +168,21 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public $hide(id: TerminalIdentifier): void {
|
public async $hide(id: TerminalIdentifier): Promise<void> {
|
||||||
const rendererId = this._getTerminalId(id);
|
const instanceToHide = await this._getTerminalInstance(id);
|
||||||
const instance = this._terminalService.activeInstance;
|
const activeInstance = this._terminalService.activeInstance;
|
||||||
if (instance && instance.instanceId === rendererId && instance.target !== TerminalLocation.Editor) {
|
if (activeInstance && activeInstance.instanceId === instanceToHide?.instanceId && activeInstance.target !== TerminalLocation.Editor) {
|
||||||
this._terminalGroupService.hidePanel();
|
this._terminalGroupService.hidePanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public $dispose(id: TerminalIdentifier): void {
|
public async $dispose(id: TerminalIdentifier): Promise<void> {
|
||||||
this._getTerminalInstance(id)?.dispose();
|
(await this._getTerminalInstance(id))?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void {
|
public async $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): Promise<void> {
|
||||||
this._getTerminalInstance(id)?.sendText(text, addNewLine);
|
const instance = await this._getTerminalInstance(id);
|
||||||
|
await instance?.sendText(text, addNewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $startSendingDataEvents(): void {
|
public $startSendingDataEvents(): void {
|
||||||
|
|
|
@ -618,7 +618,7 @@ export interface ITerminalInstance {
|
||||||
* required to run a command in the terminal. The character(s) added are \n or \r\n
|
* 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`.
|
* 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. */
|
/** Scroll the terminal buffer down 1 line. */
|
||||||
scrollDownLine(): void;
|
scrollDownLine(): void;
|
||||||
|
|
|
@ -229,8 +229,9 @@ export class TerminalService implements ITerminalService {
|
||||||
lifecycleService.onWillShutdown(e => this._onWillShutdown(e));
|
lifecycleService.onWillShutdown(e => this._onWillShutdown(e));
|
||||||
|
|
||||||
this._configurationService.onDidChangeConfiguration(async e => {
|
this._configurationService.onDidChangeConfiguration(async e => {
|
||||||
if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + this._getPlatformKey()) ||
|
const platformKey = await this._getPlatformKey();
|
||||||
e.affectsConfiguration(TerminalSettingPrefix.Profiles + this._getPlatformKey()) ||
|
if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + platformKey) ||
|
||||||
|
e.affectsConfiguration(TerminalSettingPrefix.Profiles + platformKey) ||
|
||||||
e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) {
|
e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) {
|
||||||
this._refreshAvailableProfiles();
|
this._refreshAvailableProfiles();
|
||||||
}
|
}
|
||||||
|
@ -957,7 +958,6 @@ export class TerminalService implements ITerminalService {
|
||||||
return; // Should never happen
|
return; // Should never happen
|
||||||
} else if ('id' in value.profile) {
|
} else if ('id' in value.profile) {
|
||||||
// extension contributed profile
|
// extension contributed profile
|
||||||
console.log(value.profile.title);
|
|
||||||
await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.title, ConfigurationTarget.USER);
|
await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.title, ConfigurationTarget.USER);
|
||||||
|
|
||||||
this._registerContributedProfile(value.profile.extensionIdentifier, value.profile.id, value.profile.title, {
|
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 };
|
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) {
|
if (shellLaunchConfigOrProfile && 'profileName' in shellLaunchConfigOrProfile) {
|
||||||
const profile = shellLaunchConfigOrProfile;
|
const profile = shellLaunchConfigOrProfile;
|
||||||
if (!profile.path) {
|
if (!profile.path) {
|
||||||
|
@ -1084,8 +1084,8 @@ export class TerminalService implements ITerminalService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shell launch config was provided
|
// A shell launch config was provided
|
||||||
if (shellLaunchConfigOrProfile && 'cwd' in shellLaunchConfigOrProfile) {
|
if (shellLaunchConfigOrProfile) {
|
||||||
if (cwd) {
|
if (cwd) {
|
||||||
shellLaunchConfigOrProfile.cwd = cwd;
|
shellLaunchConfigOrProfile.cwd = cwd;
|
||||||
}
|
}
|
||||||
|
@ -1109,11 +1109,27 @@ export class TerminalService implements ITerminalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTerminal(options?: ICreateTerminalOptions): Promise<ITerminalInstance> {
|
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);
|
// Get the contributed profile if it was provided
|
||||||
if (contributedDefaultProfile) {
|
let contributedProfile = config && 'extensionIdentifier' in config ? config : undefined;
|
||||||
await this.createContributedTerminalProfile(contributedDefaultProfile.extensionIdentifier, contributedDefaultProfile.id, { isSplitTerminal: false, icon: contributedDefaultProfile.icon });
|
|
||||||
|
// 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];
|
return this._terminalGroupService.instances[this._terminalGroupService.instances.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue