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

enable adding and navigating to custom marks in the buffer #158313

Merged
merged 66 commits into from Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
65dfef8
add buffer mark capability
meganrogge Aug 15, 2022
8f13bcb
Merge branch 'main' into merogge/buffer-mark
meganrogge Aug 16, 2022
89eb98e
alter decoration addon, add scrollToMark
meganrogge Aug 16, 2022
96d8811
fix issues
meganrogge Aug 16, 2022
152a1d3
CommandNavigationAddon -> MarkNavigationAddon
meganrogge Aug 16, 2022
6e985ea
get decorations to work
meganrogge Aug 16, 2022
89916df
get mark navigation to work
meganrogge Aug 16, 2022
5981457
get scroll to mark to work
meganrogge Aug 16, 2022
b6a6170
fix hidden property
meganrogge Aug 16, 2022
95164ac
add tests
meganrogge Aug 16, 2022
0d5a706
get rid of assert
meganrogge Aug 16, 2022
04ae814
improve JSdoc
meganrogge Aug 16, 2022
f02a472
fix description
meganrogge Aug 16, 2022
bf37a7d
stricter check
meganrogge Aug 16, 2022
6d82617
revert changes to iterm setMark
meganrogge Aug 16, 2022
94f15f2
Update src/vs/workbench/contrib/terminal/test/browser/xterm/shellInte…
meganrogge Aug 16, 2022
681ab27
Update src/vs/workbench/contrib/terminal/browser/terminal.ts
meganrogge Aug 16, 2022
495e3ec
Update src/vs/workbench/contrib/terminal/browser/terminal.ts
meganrogge Aug 16, 2022
0030b04
Update src/vs/workbench/contrib/terminal/browser/terminal.ts
meganrogge Aug 16, 2022
d87521f
handle generic marks as well
meganrogge Aug 16, 2022
aed2a1e
get decoration to show up
meganrogge Aug 16, 2022
c5fcf3f
Merge branch 'main' into merogge/buffer-mark
meganrogge Aug 16, 2022
18a1eac
Update src/vs/platform/terminal/common/capabilities/bufferMarkCapabil…
meganrogge Aug 17, 2022
92145cc
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
meganrogge Aug 17, 2022
0b80808
more cleanup
meganrogge Aug 17, 2022
ed095e1
implement scroll to closest marker
meganrogge Aug 17, 2022
7fc78e1
Merge branch 'main' into merogge/buffer-mark
meganrogge Aug 18, 2022
bbbcea6
remove export
meganrogge Aug 18, 2022
8236a61
Revert "try with task active event"
meganrogge Aug 18, 2022
20df8f8
Merge branch 'main' into merogge/buffer-mark
meganrogge Aug 19, 2022
e427efb
Revert "get decoration to show up"
meganrogge Aug 19, 2022
f126f53
add scroll to marker
meganrogge Aug 19, 2022
70b333f
get tasks to work
meganrogge Aug 19, 2022
c62ec62
get test to pass
meganrogge Aug 19, 2022
2a7cd4e
get it to work for generic markers
meganrogge Aug 19, 2022
a5cc851
Merge branch 'main' into merogge/buffer-mark
meganrogge Aug 19, 2022
014fefe
clean up
meganrogge Aug 19, 2022
6e2ed1d
use mark properties everywhere
meganrogge Aug 19, 2022
51578b5
fix hover
meganrogge Aug 19, 2022
369ccdc
Update src/vs/platform/terminal/common/capabilities/capabilities.ts
meganrogge Aug 19, 2022
65a8928
Update src/vs/platform/terminal/common/capabilities/capabilities.ts
meganrogge Aug 19, 2022
04e5544
fix issue
meganrogge Aug 19, 2022
a6320ce
adjust test because we now support SetMark with no ID
meganrogge Aug 19, 2022
a16a91e
add test
meganrogge Aug 24, 2022
cb13f90
cleanup
meganrogge Aug 24, 2022
4043bc8
Update src/vs/platform/terminal/common/capabilities/bufferMarkCapabil…
meganrogge Sep 6, 2022
154cf73
Update src/vs/workbench/contrib/terminal/browser/xterm/markNavigation…
meganrogge Sep 6, 2022
fe3169e
rename etc
meganrogge Sep 6, 2022
7918334
use iterator
meganrogge Sep 6, 2022
c065c18
parse args
meganrogge Sep 6, 2022
554e6d9
hidden -> true
meganrogge Sep 6, 2022
a506a07
get all markers
meganrogge Sep 6, 2022
58c29ab
Merge branch 'main' into merogge/buffer-mark
meganrogge Sep 6, 2022
8a21f75
Merge branch 'main' into merogge/buffer-mark
meganrogge Sep 6, 2022
f5467da
try to fix layer issue
meganrogge Sep 7, 2022
f0b8d58
fix import issues
meganrogge Sep 7, 2022
153251e
fix errors
meganrogge Sep 7, 2022
3b2e471
layer
meganrogge Sep 7, 2022
63c18da
add more tests
meganrogge Sep 7, 2022
2cbed9f
get markers to show up
meganrogge Sep 7, 2022
dfafeea
remove nav decorations appropriately
meganrogge Sep 7, 2022
51cd3dd
get multi line decorations to work
meganrogge Sep 7, 2022
5b1894e
Update src/vs/workbench/contrib/terminal/browser/xterm/markNavigation…
meganrogge Sep 7, 2022
fb5f6e2
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
meganrogge Sep 7, 2022
0dd2bc9
clean up
meganrogge Sep 7, 2022
ccaf3cd
remaining cleanup
meganrogge Sep 7, 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
@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Emitter } from 'vs/base/common/event';
import { IBufferMarkCapability, TerminalCapability, IMarkProperties } from 'vs/platform/terminal/common/capabilities/capabilities';
// Importing types is safe in any layer
// eslint-disable-next-line local/code-import-patterns
import type { IMarker, Terminal } from 'xterm-headless';

/**
* Manages "marks" in the buffer which are lines that are tracked when lines are added to or removed
* from the buffer.
*/
export class BufferMarkCapability implements IBufferMarkCapability {

readonly type = TerminalCapability.BufferMarkDetection;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved

private _idToMarkerMap: Map<string, IMarker> = new Map();
private _anonymousMarkers: IMarker[] = [];

private readonly _onMarkAdded = new Emitter<IMarkProperties>();
readonly onMarkAdded = this._onMarkAdded.event;

constructor(
private readonly _terminal: Terminal
) {
}

*markers(): IterableIterator<IMarker> {
for (const m of this._idToMarkerMap.values()) {
yield m;
}
for (const m of this._anonymousMarkers) {
yield m;
}
}

addMark(properties?: IMarkProperties): void {
const marker = properties?.marker || this._terminal.registerMarker();
const id = properties?.id;
if (!marker) {
return;
}
if (id) {
this._idToMarkerMap.set(id, marker);
marker.onDispose(() => this._idToMarkerMap.delete(id));
} else {
this._anonymousMarkers.push(marker);
marker.onDispose(() => this._anonymousMarkers.filter(m => m !== marker));
}
this._onMarkAdded.fire({ marker, id, hidden: properties?.hidden, hoverMessage: properties?.hoverMessage });
}

getMark(id: string): IMarker | undefined {
return this._idToMarkerMap.get(id);
}
}
56 changes: 50 additions & 6 deletions src/vs/platform/terminal/common/capabilities/capabilities.ts
Expand Up @@ -5,7 +5,7 @@

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

interface IEvent<T, U = void> {
(listener: (arg1: T, arg2: U) => any): IDisposable;
Expand Down Expand Up @@ -60,7 +60,14 @@ export const enum TerminalCapability {
* may not be so good at remembering the position of commands that ran in the past. This state
* may be enabled when something goes wrong or when using conpty for example.
*/
PartialCommandDetection
PartialCommandDetection,

/**
* Manages buffer marks that can be used for terminal navigation. The source of
* the request (task, debug, etc) provides an ID, optional marker, hoverMessage, and hidden property. When
* hidden is not provided, a generic decoration is added to the buffer and overview ruler.
*/
BufferMarkDetection
}

/**
Expand Down Expand Up @@ -103,6 +110,7 @@ export interface ITerminalCapabilityImplMap {
[TerminalCapability.CommandDetection]: ICommandDetectionCapability;
[TerminalCapability.NaiveCwdDetection]: INaiveCwdDetectionCapability;
[TerminalCapability.PartialCommandDetection]: IPartialCommandDetectionCapability;
[TerminalCapability.BufferMarkDetection]: IBufferMarkCapability;
}

export interface ICwdDetectionCapability {
Expand All @@ -122,6 +130,14 @@ export interface ICommandInvalidationRequest {
reason: CommandInvalidationReason;
}

export interface IBufferMarkCapability {
type: TerminalCapability.BufferMarkDetection;
markers(): IterableIterator<IMarker>;
onMarkAdded: Event<IMarkProperties>;
addMark(properties?: IMarkProperties): void;
getMark(id: string): IMarker | undefined;
}

export interface ICommandDetectionCapability {
readonly type: TerminalCapability.CommandDetection;
readonly commands: readonly ITerminalCommand[];
Expand All @@ -148,7 +164,6 @@ export interface ICommandDetectionCapability {
handleRightPromptStart(): void;
handleRightPromptEnd(): void;
handleCommandStart(options?: IHandleCommandOptions): void;
handleGenericCommand(options?: IHandleCommandOptions): void;
handleCommandExecuted(options?: IHandleCommandOptions): void;
handleCommandFinished(exitCode?: number, options?: IHandleCommandOptions): void;
invalidateCurrentCommand(request: ICommandInvalidationRequest): void;
Expand All @@ -170,10 +185,11 @@ export interface IHandleCommandOptions {
* The marker to use
*/
marker?: IMarker;

/**
* Properties for a generic mark
* Properties for the mark
*/
genericMarkProperties?: IGenericMarkProperties;
markProperties?: IMarkProperties;
}

export interface INaiveCwdDetectionCapability {
Expand All @@ -197,9 +213,9 @@ export interface ITerminalCommand {
endMarker?: IXtermMarker;
executedMarker?: IXtermMarker;
commandStartLineContent?: string;
markProperties?: IMarkProperties;
getOutput(): string | undefined;
hasOutput(): boolean;
genericMarkProperties?: IGenericMarkProperties;
}

/**
Expand All @@ -214,3 +230,31 @@ export interface IXtermMarker {
(listener: () => any): { dispose(): void };
};
}

export interface ISerializedCommand {
command: string;
cwd: string | undefined;
startLine: number | undefined;
startX: number | undefined;
endLine: number | undefined;
executedLine: number | undefined;
exitCode: number | undefined;
commandStartLineContent: string | undefined;
timestamp: number;
markProperties: IMarkProperties | undefined;
}
export interface IMarkProperties {
hoverMessage?: string;
disableCommandStorage?: boolean;
hidden?: boolean;
marker?: IMarker;
id?: string;
}
export interface ISerializedCommandDetectionCapability {
isWindowsPty: boolean;
commands: ISerializedCommand[];
}
export interface IPtyHostProcessReplayEvent {
events: ReplayEntry[];
commands: ISerializedCommandDetectionCapability;
}
Expand Up @@ -7,8 +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, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ISerializedCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason, ISerializedCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
// Importing types is safe in any layer
// eslint-disable-next-line local/code-import-patterns
import type { IBuffer, IBufferLine, IDisposable, IMarker, Terminal } from 'xterm-headless';
Expand Down Expand Up @@ -317,7 +316,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
}
this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX;
this._currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0);
this._onCommandStarted.fire({ marker: options?.marker || this._currentCommand.commandStartMarker, genericMarkProperties: options?.genericMarkProperties } as ITerminalCommand);
this._onCommandStarted.fire({ marker: options?.marker || this._currentCommand.commandStartMarker, markProperties: options?.markProperties } as ITerminalCommand);
this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line);
}

Expand Down Expand Up @@ -353,7 +352,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
}

handleGenericCommand(options?: IHandleCommandOptions): void {
if (options?.genericMarkProperties?.disableCommandStorage) {
if (options?.markProperties?.disableCommandStorage) {
this.setIsCommandStorageDisabled();
}
this.handlePromptStart(options);
Expand Down Expand Up @@ -459,7 +458,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
commandStartLineContent: this._currentCommand.commandStartLineContent,
hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line),
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer),
genericMarkProperties: options?.genericMarkProperties
markProperties: options?.markProperties
};
this._commands.push(newCommand);
this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand);
Expand Down Expand Up @@ -526,7 +525,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
cwd: e.cwd,
exitCode: e.exitCode,
commandStartLineContent: e.commandStartLineContent,
timestamp: e.timestamp
timestamp: e.timestamp,
markProperties: e.markProperties
};
});
if (this._currentCommand.commandStartMarker) {
Expand All @@ -540,6 +540,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
exitCode: undefined,
commandStartLineContent: undefined,
timestamp: 0,
markProperties: undefined
});
}
return {
Expand Down Expand Up @@ -581,7 +582,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
exitCode: e.exitCode,
hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker.line < endMarker.line),
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer),
genericMarkProperties: e.genericMarkProperties
markProperties: e.markProperties
};
this._commands.push(newCommand);
this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand);
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/terminal/common/terminal.ts
Expand Up @@ -7,8 +7,8 @@ import { Event } from 'vs/base/common/event';
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
import { URI, UriComponents } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ISerializableEnvironmentVariableCollections } from 'vs/platform/terminal/common/environmentVariable';

Expand Down
26 changes: 0 additions & 26 deletions src/vs/platform/terminal/common/terminalProcess.ts
Expand Up @@ -74,29 +74,3 @@ export interface ReplayEntry {
rows: number;
data: string;
}
export interface ISerializedCommand {
command: string;
cwd: string | undefined;
startLine: number | undefined;
startX: number | undefined;
endLine: number | undefined;
executedLine: number | undefined;
exitCode: number | undefined;
commandStartLineContent: string | undefined;
timestamp: number;
genericMarkProperties?: IGenericMarkProperties;
}

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

export interface ISerializedCommandDetectionCapability {
isWindowsPty: boolean;
commands: ISerializedCommand[];
}
export interface IPtyHostProcessReplayEvent {
events: ReplayEntry[];
commands: ISerializedCommandDetectionCapability;
}
3 changes: 2 additions & 1 deletion src/vs/platform/terminal/common/terminalRecorder.ts
Expand Up @@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IPtyHostProcessReplayEvent, ReplayEntry } from 'vs/platform/terminal/common/terminalProcess';
import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess';

const MAX_RECORDER_DATA_SIZE = 1024 * 1024; // 1MB

Expand Down
54 changes: 49 additions & 5 deletions src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
Expand Up @@ -8,15 +8,17 @@ import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/l
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';
import { ICommandDetectionCapability, ICwdDetectionCapability, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IBufferMarkCapability, ICommandDetectionCapability, ICwdDetectionCapability, ISerializedCommandDetectionCapability, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { PartialCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/partialCommandDetectionCapability';
import { ILogService } from 'vs/platform/log/common/log';
// Importing types is safe in any layer
// eslint-disable-next-line local/code-import-patterns
import type { ITerminalAddon, Terminal } from 'xterm-headless';
import { ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Emitter } from 'vs/base/common/event';
import { BufferMarkCapability } from 'vs/platform/terminal/common/capabilities/bufferMarkCapability';
// Importing types is safe in any layer
// eslint-disable-next-line local/code-import-patterns
import type { ITerminalAddon, Terminal } from 'xterm-headless';
import { URI } from 'vs/base/common/uri';


Expand Down Expand Up @@ -152,7 +154,16 @@ const enum VSCodeOscPt {
*
* WARNING: Any other properties may be changed and are not guaranteed to work in the future.
*/
Property = 'P'
Property = 'P',

/**
* Sets a mark/point-of-interest in the buffer. `OSC 633 ; SetMark [; Id=<string>] [; Hidden]`
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
* `Id` - The identifier of the mark that can be used to reference it
* `Hidden` - When set, the mark will be available to reference internally but will not visible
*
* WARNING: This sequence is unfinalized, DO NOT use this in your shell integration script.
*/
SetMark = 'SetMark',
}

/**
Expand Down Expand Up @@ -355,10 +366,19 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
return true;
}
case 'Task': {
this._createOrGetBufferMarkDetection(this._terminal);
this.capabilities.get(TerminalCapability.CommandDetection)?.setIsCommandStorageDisabled();
return true;
}
}
}
case VSCodeOscPt.SetMark: {
if (args.length > 2) {
return false;
}
this._createOrGetBufferMarkDetection(this._terminal).addMark(parseMarkSequence(args));
return true;
}
}

// Unrecognized sequence
Expand All @@ -379,7 +399,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
const [command] = data.split(';');
switch (command) {
case ITermOscPt.SetMark: {
this._createOrGetCommandDetection(this._terminal).handleGenericCommand({ genericMarkProperties: { disableCommandStorage: true } });
this._createOrGetBufferMarkDetection(this._terminal).addMark();
}
default: {
// Checking for known `<key>=<value>` pairs.
Expand Down Expand Up @@ -479,6 +499,15 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
}
return commandDetection;
}

protected _createOrGetBufferMarkDetection(terminal: Terminal): IBufferMarkCapability {
let bufferMarkDetection = this.capabilities.get(TerminalCapability.BufferMarkDetection);
if (!bufferMarkDetection) {
bufferMarkDetection = new BufferMarkCapability(terminal);
this.capabilities.add(TerminalCapability.BufferMarkDetection, bufferMarkDetection);
}
return bufferMarkDetection;
}
}

export function deserializeMessage(message: string): string {
Expand All @@ -505,3 +534,18 @@ export function parseKeyValueAssignment(message: string): { key: string; value:
value: deserialized.substring(1 + separatorIndex)
};
}


export function parseMarkSequence(sequence: string[]): { id?: string; hidden?: boolean } {
let id = undefined;
let hidden = false;
for (const property of sequence) {
if (property === 'Hidden') {
hidden = true;
}
if (property.startsWith('Id=')) {
id = property.substring(3);
}
}
return { id, hidden };
}