Merge pull request #152995 from microsoft/tyriar/146587

Support (degraded) shell integration for powerlevel10k and other prompts by supporting the common protocol
This commit is contained in:
Daniel Imms 2022-06-23 12:33:56 -07:00 committed by GitHub
commit 4c60b2fd34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 7 deletions

View file

@ -107,7 +107,7 @@ export interface ICommandDetectionCapability {
handleContinuationEnd(): void;
handleRightPromptStart(): void;
handleRightPromptEnd(): void;
handleCommandStart(): void;
handleCommandStart(options?: IHandleCommandStartOptions): void;
handleCommandExecuted(): void;
handleCommandFinished(exitCode: number | undefined): void;
/**
@ -118,6 +118,14 @@ export interface ICommandDetectionCapability {
deserialize(serialized: ISerializedCommandDetectionCapability): void;
}
export interface IHandleCommandStartOptions {
/**
* Whether to allow an empty command to be registered. This should be used to support certain
* shell integration scripts/features where tracking the command line may not be possible.
*/
ignoreCommandLine?: boolean;
}
export interface INaiveCwdDetectionCapability {
readonly type: TerminalCapability.NaiveCwdDetection;
readonly onDidChangeCwd: Event<string>;

View file

@ -7,7 +7,7 @@ import { timeout } from 'vs/base/common/async';
import { debounce } from 'vs/base/common/decorators';
import { Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandStartOptions } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ISerializedCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
// Importing types is safe in any layer
// eslint-disable-next-line code-import-patterns
@ -61,6 +61,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
private _commandMarkers: IMarker[] = [];
private _dimensions: ITerminalDimensions;
private __isCommandStorageDisabled: boolean = false;
private _handleCommandStartOptions?: IHandleCommandStartOptions;
get commands(): readonly ITerminalCommand[] { return this._commands; }
get executingCommand(): string | undefined { return this._currentCommand.command; }
// TODO: as is unsafe here and it duplicates behavor of executingCommand
@ -300,7 +302,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
this._logService.debug('CommandDetectionCapability#handleRightPromptEnd', this._currentCommand.commandRightPromptEndX);
}
handleCommandStart(): void {
handleCommandStart(options?: IHandleCommandStartOptions): void {
this._handleCommandStartOptions = options;
// Only update the column if the line has already been set
if (this._currentCommand.commandStartMarker?.line === this._terminal.buffer.active.cursorY) {
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
@ -419,13 +422,13 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
return;
}
if (command !== undefined && !command.startsWith('\\')) {
if ((command !== undefined && !command.startsWith('\\')) || this._handleCommandStartOptions?.ignoreCommandLine) {
const buffer = this._terminal.buffer.active;
const timestamp = Date.now();
const executedMarker = this._currentCommand.commandExecutedMarker;
const endMarker = this._currentCommand.commandFinishedMarker;
const newCommand: ITerminalCommand = {
command,
command: this._handleCommandStartOptions?.ignoreCommandLine ? '' : (command || ''),
marker: this._currentCommand.commandStartMarker,
endMarker,
executedMarker,
@ -446,6 +449,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
}
this._currentCommand.previousCommandMarker = this._currentCommand.commandStartMarker;
this._currentCommand = {};
this._handleCommandStartOptions = undefined;
}
private _preHandleCommandFinishedWindows(): void {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IShellIntegration } from 'vs/platform/terminal/common/terminal';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
import { CwdDetectionCapability } from 'vs/platform/terminal/common/capabilities/cwdDetectionCapability';
@ -126,6 +126,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
readonly capabilities = new TerminalCapabilityStore();
private _hasUpdatedTelemetry: boolean = false;
private _activationTimeout: any;
private _commonProtocolDisposables: IDisposable[] = [];
constructor(
private readonly _disableTelemetry: boolean | undefined,
@ -133,16 +134,58 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
@ILogService private readonly _logService: ILogService
) {
super();
this._register(toDisposable(() => this._clearActivationTimeout()));
this._register(toDisposable(() => {
this._clearActivationTimeout();
this._disposeCommonProtocol();
}));
}
private _disposeCommonProtocol(): void {
dispose(this._commonProtocolDisposables);
this._commonProtocolDisposables.length = 0;
}
activate(xterm: Terminal) {
this._terminal = xterm;
this.capabilities.add(TerminalCapability.PartialCommandDetection, new PartialCommandDetectionCapability(this._terminal));
this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.VSCode, data => this._handleVSCodeSequence(data)));
this._commonProtocolDisposables.push(
xterm.parser.registerOscHandler(ShellIntegrationOscPs.FinalTerm, data => this._handleFinalTermSequence(data))
);
this._ensureCapabilitiesOrAddFailureTelemetry();
}
private _handleFinalTermSequence(data: string): boolean {
if (!this._terminal) {
return false;
}
// Pass the sequence along to the capability
// It was considered to disable the common protocol in order to not confuse the VS Code
// shell integration if both happen for some reason. This doesn't work for powerlevel10k
// when instant prompt is enabled though. If this does end up being a problem we could pass
// a type flag through the capability calls
const [command, ...args] = data.split(';');
switch (command) {
case 'A':
this._createOrGetCommandDetection(this._terminal).handlePromptStart();
return true;
case 'B':
// Ignore the command line for these sequences as it's unreliable for example in powerlevel10k
this._createOrGetCommandDetection(this._terminal).handleCommandStart({ ignoreCommandLine: true });
return true;
case 'C':
this._createOrGetCommandDetection(this._terminal).handleCommandExecuted();
return true;
case 'D': {
const exitCode = args.length === 1 ? parseInt(args[0]) : undefined;
this._createOrGetCommandDetection(this._terminal).handleCommandFinished(exitCode);
return true;
}
}
return false;
}
private _handleVSCodeSequence(data: string): boolean {
const didHandle = this._doHandleVSCodeSequence(data);
if (!this._hasUpdatedTelemetry && didHandle) {