Skip to content

Commit 4030717

Browse files
author
Kartik Raj
authored
Use terminal data write event to figure out whether shell integration is working (#22872)
Closes #22439 Blocked on microsoft/vscode#204616
1 parent d674a17 commit 4030717

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

src/client/terminals/envCollectionActivation/shellIntegrationService.ts

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
// Licensed under the MIT License.
33

44
import { injectable, inject } from 'inversify';
5-
import { IApplicationShell, ITerminalManager, IWorkspaceService } from '../../common/application/types';
5+
import { EventEmitter } from 'vscode';
6+
import {
7+
IApplicationEnvironment,
8+
IApplicationShell,
9+
ITerminalManager,
10+
IWorkspaceService,
11+
} from '../../common/application/types';
612
import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector';
713
import { TerminalShellType } from '../../common/terminal/types';
8-
import { IPersistentStateFactory } from '../../common/types';
14+
import { IDisposableRegistry, IPersistentStateFactory } from '../../common/types';
915
import { createDeferred, sleep } from '../../common/utils/async';
1016
import { cache } from '../../common/utils/decorators';
1117
import { traceError, traceInfo, traceVerbose } from '../../logging';
@@ -34,12 +40,55 @@ export class ShellIntegrationService implements IShellIntegrationService {
3440
*/
3541
private readonly USE_COMMAND_APPROACH = false;
3642

43+
private isWorkingForShell = new Set<TerminalShellType>();
44+
45+
private readonly didChange = new EventEmitter<void>();
46+
47+
private isDataWriteEventWorking = true;
48+
3749
constructor(
3850
@inject(ITerminalManager) private readonly terminalManager: ITerminalManager,
3951
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
4052
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
4153
@inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory,
42-
) {}
54+
@inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment,
55+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
56+
) {
57+
try {
58+
this.appShell.onDidWriteTerminalData(
59+
(e) => {
60+
if (e.data.includes('\x1b]633;A\x07')) {
61+
let { shell } = this.appEnvironment;
62+
if ('shellPath' in e.terminal.creationOptions && e.terminal.creationOptions.shellPath) {
63+
shell = e.terminal.creationOptions.shellPath;
64+
}
65+
const shellType = identifyShellFromShellPath(shell);
66+
const wasWorking = this.isWorkingForShell.has(shellType);
67+
this.isWorkingForShell.add(shellType);
68+
if (!wasWorking) {
69+
// If it wasn't working previously, status has changed.
70+
this.didChange.fire();
71+
}
72+
}
73+
},
74+
this,
75+
this.disposables,
76+
);
77+
this.appEnvironment.onDidChangeShell(
78+
async (shell: string) => {
79+
this.createDummyHiddenTerminal(shell);
80+
},
81+
this,
82+
this.disposables,
83+
);
84+
this.createDummyHiddenTerminal(this.appEnvironment.shell);
85+
} catch (ex) {
86+
this.isDataWriteEventWorking = false;
87+
traceError('Unable to check if shell integration is active', ex);
88+
}
89+
}
90+
91+
public readonly onDidChangeStatus = this.didChange.event;
4392

4493
public async isWorking(shell: string): Promise<boolean> {
4594
return this._isWorking(shell).catch((ex) => {
@@ -62,8 +111,20 @@ export class ShellIntegrationService implements IShellIntegrationService {
62111
return false;
63112
}
64113
if (!this.USE_COMMAND_APPROACH) {
65-
// For now, based on problems with using the command approach, assume it always works.
66-
return true;
114+
// For now, based on problems with using the command approach, use terminal data write event.
115+
if (!this.isDataWriteEventWorking) {
116+
// Assume shell integration is working, if data write event isn't working.
117+
return true;
118+
}
119+
if (shellType === TerminalShellType.powershell || shellType === TerminalShellType.powershellCore) {
120+
// Due to upstream bug: https://github.com/microsoft/vscode/issues/204616, assume shell integration is working for now.
121+
return true;
122+
}
123+
if (!this.isWorkingForShell.has(shellType)) {
124+
// Maybe data write event has not been processed yet, wait a bit.
125+
await sleep(1000);
126+
}
127+
return this.isWorkingForShell.has(shellType);
67128
}
68129
const key = `${isShellIntegrationWorkingKey}_${shellType}`;
69130
const persistedResult = this.persistentStateFactory.createGlobalPersistentState<boolean>(key);
@@ -76,6 +137,16 @@ export class ShellIntegrationService implements IShellIntegrationService {
76137
return result;
77138
}
78139

140+
/**
141+
* Creates a dummy terminal so that we are guaranteed a data write event for this shell type.
142+
*/
143+
private createDummyHiddenTerminal(shell: string) {
144+
this.terminalManager.createTerminal({
145+
shellPath: shell,
146+
hideFromUser: true,
147+
});
148+
}
149+
79150
private async checkIfWorkingByRunningCommand(shell: string): Promise<boolean> {
80151
const shellType = identifyShellFromShellPath(shell);
81152
const deferred = createDeferred<void>();

src/client/terminals/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface ITerminalEnvVarCollectionService {
4444

4545
export const IShellIntegrationService = Symbol('IShellIntegrationService');
4646
export interface IShellIntegrationService {
47+
onDidChangeStatus: Event<void>;
4748
isWorking(shell: string): Promise<boolean>;
4849
}
4950

0 commit comments

Comments
 (0)