Skip to content

Use terminal data write event to figure out whether shell integration is working #22872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
// Licensed under the MIT License.

import { injectable, inject } from 'inversify';
import { IApplicationShell, ITerminalManager, IWorkspaceService } from '../../common/application/types';
import { EventEmitter } from 'vscode';
import {
IApplicationEnvironment,
IApplicationShell,
ITerminalManager,
IWorkspaceService,
} from '../../common/application/types';
import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector';
import { TerminalShellType } from '../../common/terminal/types';
import { IPersistentStateFactory } from '../../common/types';
import { IDisposableRegistry, IPersistentStateFactory } from '../../common/types';
import { createDeferred, sleep } from '../../common/utils/async';
import { cache } from '../../common/utils/decorators';
import { traceError, traceInfo, traceVerbose } from '../../logging';
Expand Down Expand Up @@ -34,12 +40,55 @@ export class ShellIntegrationService implements IShellIntegrationService {
*/
private readonly USE_COMMAND_APPROACH = false;

private isWorkingForShell = new Set<TerminalShellType>();

private readonly didChange = new EventEmitter<void>();

private isDataWriteEventWorking = true;

constructor(
@inject(ITerminalManager) private readonly terminalManager: ITerminalManager,
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
@inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory,
) {}
@inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment,
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
) {
try {
this.appShell.onDidWriteTerminalData(
(e) => {
if (e.data.includes('\x1b]633;A\x07')) {
let { shell } = this.appEnvironment;
if ('shellPath' in e.terminal.creationOptions && e.terminal.creationOptions.shellPath) {
shell = e.terminal.creationOptions.shellPath;
}
const shellType = identifyShellFromShellPath(shell);
const wasWorking = this.isWorkingForShell.has(shellType);
this.isWorkingForShell.add(shellType);
if (!wasWorking) {
// If it wasn't working previously, status has changed.
this.didChange.fire();
}
}
},
this,
this.disposables,
);
this.appEnvironment.onDidChangeShell(
async (shell: string) => {
this.createDummyHiddenTerminal(shell);
},
this,
this.disposables,
);
this.createDummyHiddenTerminal(this.appEnvironment.shell);
} catch (ex) {
this.isDataWriteEventWorking = false;
traceError('Unable to check if shell integration is active', ex);
}
}

public readonly onDidChangeStatus = this.didChange.event;

public async isWorking(shell: string): Promise<boolean> {
return this._isWorking(shell).catch((ex) => {
Expand All @@ -62,8 +111,20 @@ export class ShellIntegrationService implements IShellIntegrationService {
return false;
}
if (!this.USE_COMMAND_APPROACH) {
// For now, based on problems with using the command approach, assume it always works.
return true;
// For now, based on problems with using the command approach, use terminal data write event.
if (!this.isDataWriteEventWorking) {
// Assume shell integration is working, if data write event isn't working.
return true;
}
if (shellType === TerminalShellType.powershell || shellType === TerminalShellType.powershellCore) {
// Due to upstream bug: https://github.com/microsoft/vscode/issues/204616, assume shell integration is working for now.
return true;
}
if (!this.isWorkingForShell.has(shellType)) {
// Maybe data write event has not been processed yet, wait a bit.
await sleep(1000);
}
return this.isWorkingForShell.has(shellType);
}
const key = `${isShellIntegrationWorkingKey}_${shellType}`;
const persistedResult = this.persistentStateFactory.createGlobalPersistentState<boolean>(key);
Expand All @@ -76,6 +137,16 @@ export class ShellIntegrationService implements IShellIntegrationService {
return result;
}

/**
* Creates a dummy terminal so that we are guaranteed a data write event for this shell type.
*/
private createDummyHiddenTerminal(shell: string) {
this.terminalManager.createTerminal({
shellPath: shell,
hideFromUser: true,
});
}

private async checkIfWorkingByRunningCommand(shell: string): Promise<boolean> {
const shellType = identifyShellFromShellPath(shell);
const deferred = createDeferred<void>();
Expand Down
1 change: 1 addition & 0 deletions src/client/terminals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface ITerminalEnvVarCollectionService {

export const IShellIntegrationService = Symbol('IShellIntegrationService');
export interface IShellIntegrationService {
onDidChangeStatus: Event<void>;
isWorking(shell: string): Promise<boolean>;
}

Expand Down