Skip to content
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

add generic marker sequence and markers/decorations for problems #152671

Merged
merged 31 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2bb881a
add markers for problems
meganrogge Jun 20, 2022
9d01a4f
fix tests
meganrogge Jun 20, 2022
5812177
actually fix tests
meganrogge Jun 20, 2022
1e58802
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
meganrogge Jun 20, 2022
b9579bf
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
meganrogge Jun 20, 2022
451b646
put marker in better spot
meganrogge Jun 20, 2022
4797afb
get much closer
meganrogge Jun 20, 2022
fed7318
Merge branch 'main' into merogge/problem-marker
meganrogge Jun 20, 2022
69a565c
Merge branch 'main' into merogge/problem-marker
meganrogge Jun 20, 2022
686b40c
Merge branch 'main' into merogge/problem-marker
meganrogge Jun 22, 2022
5a3f512
register it as a command
meganrogge Jun 22, 2022
b1a4f1a
enable a custom hover
meganrogge Jun 22, 2022
b98d81d
invalidate command
meganrogge Jun 22, 2022
7887b6a
use number of matches
meganrogge Jun 22, 2022
04092be
tidy
meganrogge Jun 22, 2022
df5e8b4
Merge branch 'main' into merogge/problem-marker
meganrogge Jun 22, 2022
7c9a7b3
Merge branch 'main' into merogge/problem-marker
meganrogge Jun 22, 2022
60a7c31
fix test
meganrogge Jun 22, 2022
23d13aa
Update src/vs/platform/terminal/common/capabilities/capabilities.ts
meganrogge Jun 22, 2022
5040888
Merge branch 'main' into merogge/problem-marker
meganrogge Jun 22, 2022
b82fe91
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
meganrogge Jun 23, 2022
7a0271e
Update src/vs/workbench/contrib/terminal/browser/xterm/decorationAddo…
meganrogge Jun 23, 2022
be9e602
add e to file name
meganrogge Jun 23, 2022
3cb3fcb
cleanup
meganrogge Jun 23, 2022
5f80b23
big refactor
meganrogge Jun 23, 2022
66a8c5c
fix merge conflicts
meganrogge Jun 23, 2022
88ae78c
refactor
meganrogge Jun 23, 2022
29c5f3c
copy marker to capabilities file
meganrogge Jun 23, 2022
5316b57
handle marker in taskTerminalStatus
meganrogge Jun 23, 2022
4040a3b
Update src/vs/workbench/contrib/terminal/browser/terminal.ts
meganrogge Jun 23, 2022
c10717a
h
meganrogge Jun 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 58 additions & 7 deletions src/vs/platform/terminal/common/capabilities/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,38 @@
*--------------------------------------------------------------------------------------------*/

import { Event } from 'vs/base/common/event';
import { ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IGenericMarkProperties, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';

interface IEvent<T, U = void> {
(listener: (arg1: T, arg2: U) => any): IDisposable;
}

export interface IMarker extends IDisposable {
/**
* A unique identifier for this marker.
*/
readonly id: number;

/**
* Whether this marker is disposed.
*/
readonly isDisposed: boolean;

/**
* The actual line index in the buffer at this point in time. This is set to
* -1 if the marker has been disposed.
*/
readonly line: number;

/**
* Event listener to get notified when the marker gets disposed. Automatic disposal
* might happen for a marker, that got invalidated by scrolling out or removal of
* a line from the buffer.
*/
onDispose: IEvent<void>;
}


/**
* Primarily driven by the shell integration feature, a terminal capability is the mechanism for
Expand Down Expand Up @@ -82,6 +113,15 @@ export interface ICwdDetectionCapability {
updateCwd(cwd: string): void;
}

export const enum CommandInvalidationReason {
Windows = 'windows',
NoProblemsReported = 'noProblemsReported'
}

export interface ICommandInvalidationRequest {
reason: CommandInvalidationReason;
}

export interface ICommandDetectionCapability {
readonly type: TerminalCapability.CommandDetection;
readonly commands: readonly ITerminalCommand[];
Expand All @@ -93,7 +133,7 @@ export interface ICommandDetectionCapability {
readonly onCommandStarted: Event<ITerminalCommand>;
readonly onCommandFinished: Event<ITerminalCommand>;
readonly onCommandInvalidated: Event<ITerminalCommand[]>;
readonly onCurrentCommandInvalidated: Event<void>;
readonly onCurrentCommandInvalidated: Event<ICommandInvalidationRequest>;
setCwd(value: string): void;
setIsWindowsPty(value: boolean): void;
setIsCommandStorageDisabled(): void;
Expand All @@ -102,14 +142,16 @@ export interface ICommandDetectionCapability {
* case the terminal's initial cwd should be used.
*/
getCwdForLine(line: number): string | undefined;
handlePromptStart(): void;
handlePromptStart(options?: IHandleCommandOptions): void;
handleContinuationStart(): void;
handleContinuationEnd(): void;
handleRightPromptStart(): void;
handleRightPromptEnd(): void;
handleCommandStart(options?: IHandleCommandStartOptions): void;
handleCommandExecuted(): void;
handleCommandFinished(exitCode: number | undefined): void;
handleCommandStart(options?: IHandleCommandOptions): void;
handleGenericCommand(options?: IHandleCommandOptions): void;
handleCommandExecuted(options?: IHandleCommandOptions): void;
handleCommandFinished(exitCode?: number, options?: IHandleCommandOptions): void;
invalidateCurrentCommand(request: ICommandInvalidationRequest): void;
/**
* Set the command line explicitly.
*/
Expand All @@ -118,12 +160,20 @@ export interface ICommandDetectionCapability {
deserialize(serialized: ISerializedCommandDetectionCapability): void;
}

export interface IHandleCommandStartOptions {
export interface IHandleCommandOptions {
/**
* 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;
/**
* The marker to use
*/
marker?: IMarker;
/**
* Properties for a generic mark
*/
genericMarkProperties?: IGenericMarkProperties;
}

export interface INaiveCwdDetectionCapability {
Expand All @@ -149,6 +199,7 @@ export interface ITerminalCommand {
commandStartLineContent?: string;
getOutput(): string | undefined;
hasOutput: boolean;
genericMarkProperties?: IGenericMarkProperties;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, IHandleCommandStartOptions } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason } 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
Expand Down Expand Up @@ -61,7 +61,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
private _commandMarkers: IMarker[] = [];
private _dimensions: ITerminalDimensions;
private __isCommandStorageDisabled: boolean = false;
private _handleCommandStartOptions?: IHandleCommandStartOptions;
private _handleCommandStartOptions?: IHandleCommandOptions;

get commands(): readonly ITerminalCommand[] { return this._commands; }
get executingCommand(): string | undefined { return this._currentCommand.command; }
Expand All @@ -82,7 +82,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
readonly onCommandFinished = this._onCommandFinished.event;
private readonly _onCommandInvalidated = new Emitter<ITerminalCommand[]>();
readonly onCommandInvalidated = this._onCommandInvalidated.event;
private readonly _onCurrentCommandInvalidated = new Emitter<void>();
private readonly _onCurrentCommandInvalidated = new Emitter<ICommandInvalidationRequest>();
readonly onCurrentCommandInvalidated = this._onCurrentCommandInvalidated.event;

constructor(
Expand Down Expand Up @@ -122,7 +122,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
if (this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY < this._currentCommand.commandStartMarker.line) {
this._clearCommandsInViewport();
this._currentCommand.isInvalid = true;
this._onCurrentCommandInvalidated.fire();
this._onCurrentCommandInvalidated.fire({ reason: CommandInvalidationReason.Windows });
}
}
}
Expand All @@ -139,7 +139,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
if (command.command.trim().toLowerCase() === 'clear' || command.command.trim().toLowerCase() === 'cls') {
this._clearCommandsInViewport();
this._currentCommand.isInvalid = true;
this._onCurrentCommandInvalidated.fire();
this._onCurrentCommandInvalidated.fire({ reason: CommandInvalidationReason.Windows });
}
}
});
Expand Down Expand Up @@ -266,8 +266,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
return reversed.find(c => c.marker!.line <= line - 1)?.cwd;
}

handlePromptStart(): void {
this._currentCommand.promptStartMarker = this._terminal.registerMarker(0);
handlePromptStart(options?: IHandleCommandOptions): void {
this._currentCommand.promptStartMarker = options?.marker || this._terminal.registerMarker(0);
this._logService.debug('CommandDetectionCapability#handlePromptStart', this._terminal.buffer.active.cursorX, this._currentCommand.promptStartMarker?.line);
}

Expand Down Expand Up @@ -302,9 +302,10 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
this._logService.debug('CommandDetectionCapability#handleRightPromptEnd', this._currentCommand.commandRightPromptEndX);
}

handleCommandStart(options?: IHandleCommandStartOptions): void {
handleCommandStart(options?: IHandleCommandOptions): void {
this._handleCommandStartOptions = options;
// Only update the column if the line has already been set
this._currentCommand.commandStartMarker = options?.marker || this._currentCommand.commandStartMarker;
if (this._currentCommand.commandStartMarker?.line === this._terminal.buffer.active.cursorY) {
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line);
Expand All @@ -315,8 +316,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
return;
}
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
this._currentCommand.commandStartMarker = this._terminal.registerMarker(0);
this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand);
this._currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0);
this._onCommandStarted.fire({ marker: options?.marker || this._currentCommand.commandStartMarker, genericMarkProperties: options?.genericMarkProperties } as ITerminalCommand);
this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line);
}

Expand Down Expand Up @@ -351,13 +352,23 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
});
}

handleCommandExecuted(): void {
handleGenericCommand(options?: IHandleCommandOptions): void {
if (options?.genericMarkProperties?.disableCommandStorage) {
this.setIsCommandStorageDisabled();
}
this.handlePromptStart(options);
this.handleCommandStart(options);
this.handleCommandExecuted(options);
this.handleCommandFinished(undefined, options);
}

handleCommandExecuted(options?: IHandleCommandOptions): void {
if (this._isWindowsPty) {
this._handleCommandExecutedWindows();
return;
}

this._currentCommand.commandExecutedMarker = this._terminal.registerMarker(0);
this._currentCommand.commandExecutedMarker = options?.marker || this._terminal.registerMarker(0);
this._currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX;
this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line);

Expand Down Expand Up @@ -396,12 +407,17 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line);
}

handleCommandFinished(exitCode: number | undefined): void {
invalidateCurrentCommand(request: ICommandInvalidationRequest): void {
this._currentCommand.isInvalid = true;
this._onCurrentCommandInvalidated.fire(request);
}
meganrogge marked this conversation as resolved.
Show resolved Hide resolved

handleCommandFinished(exitCode: number | undefined, options?: IHandleCommandOptions): void {
if (this._isWindowsPty) {
this._preHandleCommandFinishedWindows();
}

this._currentCommand.commandFinishedMarker = this._terminal.registerMarker(0);
this._currentCommand.commandFinishedMarker = options?.marker || this._terminal.registerMarker(0);
const command = this._currentCommand.command;
this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, this._currentCommand.commandFinishedMarker?.line, this._currentCommand.command, this._currentCommand);
this._exitCode = exitCode;
Expand Down Expand Up @@ -437,7 +453,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
exitCode: this._exitCode,
commandStartLineContent: this._currentCommand.commandStartLineContent,
hasOutput: !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line),
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer)
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer),
genericMarkProperties: options?.genericMarkProperties
};
this._commands.push(newCommand);
this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand);
Expand Down Expand Up @@ -558,7 +575,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
commandStartLineContent: e.commandStartLineContent,
exitCode: e.exitCode,
hasOutput: !!(executedMarker && endMarker && executedMarker.line < endMarker.line),
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer)
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer),
genericMarkProperties: e.genericMarkProperties
};
this._commands.push(newCommand);
this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand);
Expand Down
7 changes: 7 additions & 0 deletions src/vs/platform/terminal/common/terminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,14 @@ export interface ISerializedCommand {
exitCode: number | undefined;
commandStartLineContent: string | undefined;
timestamp: number;
genericMarkProperties?: IGenericMarkProperties;
}

export interface IGenericMarkProperties {
hoverMessage?: string;
disableCommandStorage?: boolean;
}

export interface ISerializedCommandDetectionCapability {
isWindowsPty: boolean;
commands: ISerializedCommand[];
Expand Down
27 changes: 27 additions & 0 deletions src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ const enum VSCodeOscPt {
Property = 'P'
}

/**
* ITerm sequences
*/
const enum ITermOscPt {
/**
* Based on ITerm's `OSC 1337 ; SetMark`, sets a mark on the scroll bar
*/
SetMark = 'SetMark'
}

/**
* The shell integration addon extends xterm by reading shell integration sequences and creating
* capabilities and passing along relevant sequences to the capabilities. This is meant to
Expand Down Expand Up @@ -149,6 +159,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
this._terminal = xterm;
this.capabilities.add(TerminalCapability.PartialCommandDetection, new PartialCommandDetectionCapability(this._terminal));
this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.VSCode, data => this._handleVSCodeSequence(data)));
this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.ITerm, data => this._doHandleITermSequence(data)));
this._commonProtocolDisposables.push(
xterm.parser.registerOscHandler(ShellIntegrationOscPs.FinalTerm, data => this._handleFinalTermSequence(data))
);
Expand Down Expand Up @@ -292,6 +303,22 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
return false;
}

private _doHandleITermSequence(data: string): boolean {
if (!this._terminal) {
return false;
}

const [command, hoverMessage, disableCommandStorage] = data.split(';');
switch (command) {
case ITermOscPt.SetMark: {
this._createOrGetCommandDetection(this._terminal).handleGenericCommand({ genericMarkProperties: { hoverMessage: hoverMessage || '', disableCommandStorage: disableCommandStorage === 'true' ? true : false } });
}
}

// Unrecognized sequence
return false;
}

serialize(): ISerializedCommandDetectionCapability {
if (!this._terminal || !this.capabilities.has(TerminalCapability.CommandDetection)) {
return {
Expand Down
15 changes: 14 additions & 1 deletion src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina
import { ITerminalStatus } from 'vs/workbench/contrib/terminal/browser/terminalStatusList';
import { MarkerSeverity } from 'vs/platform/markers/common/markers';
import { spinningLoading } from 'vs/platform/theme/common/iconRegistry';
import { IMarker } from 'vs/platform/terminal/common/capabilities/capabilities';

interface ITerminalData {
terminal: ITerminalInstance;
Expand All @@ -37,7 +38,7 @@ const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID

export class TaskTerminalStatus extends Disposable {
private terminalMap: Map<string, ITerminalData> = new Map();

private _marker: IMarker | undefined;
constructor(taskService: ITaskService) {
super();
this._register(taskService.onDidStateChange((event) => {
Expand All @@ -53,6 +54,18 @@ export class TaskTerminalStatus extends Disposable {
addTerminal(task: Task, terminal: ITerminalInstance, problemMatcher: AbstractProblemCollector) {
const status: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, severity: Severity.Info };
terminal.statusList.add(status);
problemMatcher.onDidFindFirstMatch(() => {
this._marker = terminal.registerMarker();
});
problemMatcher.onDidFindErrors(() => {
if (this._marker) {
terminal.addGenericMark(this._marker, { hoverMessage: nls.localize('task.watchFirstError', "First error"), disableCommandStorage: true });
}
});
problemMatcher.onDidRequestInvalidateLastMarker(() => {
this._marker?.dispose();
this._marker = undefined;
});
this.terminalMap.set(task._id, { terminal, task, status, problemMatcher, taskRunEnded: false });
}

Expand Down
7 changes: 4 additions & 3 deletions src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration';
import { Codicon } from 'vs/base/common/codicons';
import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences';

const taskShellIntegrationStartSequence = '\x1b]633;A\x07' + '\x1b]633;P;Task=\x07' + '\x1b]633;B\x07';
const taskShellIntegrationOutputSequence = '\x1b]633;C\x07';
const taskShellIntegrationStartSequence = VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, VSCodeOscProperty.Task) + VSCodeSequence(VSCodeOscPt.CommandStart);
const taskShellIntegrationOutputSequence = VSCodeSequence(VSCodeOscPt.CommandExecuted);

interface ITerminalData {
terminal: ITerminalInstance;
Expand Down Expand Up @@ -1713,6 +1714,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {

function taskShellIntegrationWaitOnExitSequence(message: string): (exitCode: number) => string {
return (exitCode) => {
return `\x1b]633;D;${exitCode}\x07${message}`;
return `${VSCodeSequence(VSCodeOscPt.CommandFinished, exitCode.toString())}${message}`;
};
}