Skip to content

Commit e243427

Browse files
committed
move matching to extension host
1 parent 8278e31 commit e243427

File tree

8 files changed

+102
-56
lines changed

8 files changed

+102
-56
lines changed

src/vs/platform/terminal/common/capabilities/capabilities.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { Event } from 'vs/base/common/event';
77
import { IDisposable } from 'vs/base/common/lifecycle';
88
import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess';
9-
import { ITerminalOutputMatch } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
9+
import { ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
1010

1111
interface IEvent<T, U = void> {
1212
(listener: (arg1: T, arg2: U) => any): IDisposable;
@@ -226,37 +226,6 @@ export interface ITerminalCommand {
226226
hasOutput(): boolean;
227227
}
228228

229-
230-
/**
231-
* A matcher that runs on a sub-section of a terminal command's output
232-
*/
233-
export interface ITerminalOutputMatcher {
234-
/**
235-
* A string or regex to match against the unwrapped line. If this is a regex with the multiline
236-
* flag, it will scan an amount of lines equal to `\n` instances in the regex + 1.
237-
*/
238-
lineMatcher: string | RegExp;
239-
/**
240-
* Which side of the output to anchor the {@link offset} and {@link length} against.
241-
*/
242-
anchor: 'top' | 'bottom';
243-
/**
244-
* The number of rows above or below the {@link anchor} to start matching against.
245-
*/
246-
offset: number;
247-
/**
248-
* The number of rows to match against, this should be as small as possible for performance
249-
* reasons. This is capped at 40.
250-
*/
251-
length: number;
252-
253-
/**
254-
* If multiple matches are expected - this will result in {@link outputLines} being returned
255-
* when there's a {@link regexMatch} from {@link offset} to {@link length}
256-
*/
257-
multipleMatches?: boolean;
258-
}
259-
260229
/**
261230
* A clone of the IMarker from xterm which cannot be imported from common
262231
*/

src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { timeout } from 'vs/base/common/async';
77
import { debounce } from 'vs/base/common/decorators';
88
import { Emitter } from 'vs/base/common/event';
99
import { ILogService } from 'vs/platform/log/common/log';
10-
import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason, ISerializedCommand, ISerializedCommandDetectionCapability, ITerminalOutputMatcher } from 'vs/platform/terminal/common/capabilities/capabilities';
11-
import { ITerminalOutputMatch } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
10+
import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason, ISerializedCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
11+
import { ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
1212

1313
// Importing types is safe in any layer
1414
// eslint-disable-next-line local/code-import-patterns
@@ -701,6 +701,51 @@ export function getOutputMatchForCommand(executedMarker: IMarker | undefined, en
701701
return match ? { regexMatch: match, outputLines: lines } : undefined;
702702
}
703703

704+
export function getLinesForCommand(buffer: IBuffer, command: ITerminalCommand, cols: number, outputMatcher?: ITerminalOutputMatcher): string[] | undefined {
705+
if (!outputMatcher) {
706+
return undefined;
707+
}
708+
const executedMarker = command.executedMarker;
709+
const endMarker = command.endMarker;
710+
if (!executedMarker || !endMarker) {
711+
throw new Error('No marker for command');
712+
}
713+
const startLine = executedMarker.line;
714+
const endLine = endMarker.line;
715+
716+
const linesToCheck = outputMatcher.length;
717+
const lines: string[] = [];
718+
if (outputMatcher.anchor === 'bottom') {
719+
for (let i = endLine - (outputMatcher.offset || 0); i >= startLine; i--) {
720+
let wrappedLineStart = i;
721+
const wrappedLineEnd = i;
722+
while (wrappedLineStart >= startLine && buffer.getLine(wrappedLineStart)?.isWrapped) {
723+
wrappedLineStart--;
724+
}
725+
i = wrappedLineStart;
726+
lines.unshift(getXtermLineContent(buffer, wrappedLineStart, wrappedLineEnd, cols));
727+
if (lines.length > linesToCheck) {
728+
lines.pop();
729+
}
730+
}
731+
} else {
732+
for (let i = startLine + (outputMatcher.offset || 0); i < endLine; i++) {
733+
const wrappedLineStart = i;
734+
let wrappedLineEnd = i;
735+
while (wrappedLineEnd + 1 < endLine && buffer.getLine(wrappedLineEnd + 1)?.isWrapped) {
736+
wrappedLineEnd++;
737+
}
738+
i = wrappedLineEnd;
739+
lines.push(getXtermLineContent(buffer, wrappedLineStart, wrappedLineEnd, cols));
740+
if (lines.length === linesToCheck) {
741+
lines.shift();
742+
}
743+
}
744+
}
745+
return lines;
746+
}
747+
748+
704749
function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number, cols: number): string {
705750
// Cap the maximum number of lines generated to prevent potential performance problems. This is
706751
// more of a sanity check as the wrapped line should already be trimmed down at this point.

src/vs/platform/terminal/common/xterm/terminalQuickFix.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@
77
import { IAction } from 'vs/base/common/actions';
88
import { CancellationToken } from 'vs/base/common/cancellation';
99
import { UriComponents } from 'vs/base/common/uri';
10-
import { ITerminalOutputMatcher, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
10+
import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
1111
import { ITerminalProfileContribution } from 'vs/platform/terminal/common/terminal';
12-
// Importing types is safe in any layer
13-
// eslint-disable-next-line local/code-import-patterns
14-
import { Terminal } from 'xterm-headless';
1512

1613
export interface ITerminalCommandSelector {
1714
id: string;
@@ -57,7 +54,7 @@ export interface ITerminalCommandSelector {
5754

5855
export type TerminalQuickFixActionInternal = IAction | ITerminalQuickFixCommandAction | ITerminalQuickFixOpenerAction;
5956
export type TerminalQuickFixCallback = (matchResult: ITerminalCommandMatchResult) => TerminalQuickFixActionInternal[] | TerminalQuickFixActionInternal | undefined;
60-
export type TerminalQuickFixCallbackExtension = (terminalCommand: ITerminalCommand, terminal: Terminal, option: ITerminalQuickFixOptions, token: CancellationToken) => Promise<ITerminalQuickFix[] | ITerminalQuickFix | undefined>;
57+
export type TerminalQuickFixCallbackExtension = (terminalCommand: ITerminalCommand, lines: string[] | undefined, option: ITerminalQuickFixOptions, token: CancellationToken) => Promise<ITerminalQuickFix[] | ITerminalQuickFix | undefined>;
6158

6259
export interface ITerminalQuickFixProvider {
6360
/**
@@ -66,7 +63,7 @@ export interface ITerminalQuickFixProvider {
6663
* @param token A cancellation token indicating the result is no longer needed
6764
* @return Terminal quick fix(es) if any
6865
*/
69-
provideTerminalQuickFixes(terminalCommand: ITerminalCommand, terminal: Terminal, option: ITerminalQuickFixOptions, token: CancellationToken): Promise<ITerminalQuickFix[] | ITerminalQuickFix | undefined>;
66+
provideTerminalQuickFixes(terminalCommand: ITerminalCommand, lines: string[] | undefined, option: ITerminalQuickFixOptions, token: CancellationToken): Promise<ITerminalQuickFix[] | ITerminalQuickFix | undefined>;
7067
}
7168
export interface ITerminalCommandMatchResult {
7269
commandLine: string;
@@ -102,3 +99,34 @@ export interface ITerminalContributions {
10299
export interface IExtensionTerminalQuickFix extends ITerminalQuickFixOptions {
103100
extensionIdentifier: string;
104101
}
102+
103+
104+
/**
105+
* A matcher that runs on a sub-section of a terminal command's output
106+
*/
107+
export interface ITerminalOutputMatcher {
108+
/**
109+
* A string or regex to match against the unwrapped line. If this is a regex with the multiline
110+
* flag, it will scan an amount of lines equal to `\n` instances in the regex + 1.
111+
*/
112+
lineMatcher: string | RegExp;
113+
/**
114+
* Which side of the output to anchor the {@link offset} and {@link length} against.
115+
*/
116+
anchor: 'top' | 'bottom';
117+
/**
118+
* The number of rows above or below the {@link anchor} to start matching against.
119+
*/
120+
offset: number;
121+
/**
122+
* The number of rows to match against, this should be as small as possible for performance
123+
* reasons. This is capped at 40.
124+
*/
125+
length: number;
126+
127+
/**
128+
* If multiple matches are expected - this will result in {@link outputLines} being returned
129+
* when there's a {@link regexMatch} from {@link offset} to {@link length}
130+
*/
131+
multipleMatches?: boolean;
132+
}

src/vs/workbench/api/browser/mainThreadTerminalService.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,8 @@ import { OperatingSystem, OS } from 'vs/base/common/platform';
2323
import { TerminalEditorLocationOptions } from 'vscode';
2424
import { Promises } from 'vs/base/common/async';
2525
import { CancellationToken } from 'vs/base/common/cancellation';
26-
// Importing types is safe in any layer
27-
// eslint-disable-next-line local/code-import-patterns
28-
import { Terminal } from 'xterm-headless';
2926
import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
30-
import { getOutputMatchForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
31-
import { ITerminalQuickFixOptions } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
27+
import { ITerminalOutputMatch, ITerminalOutputMatcher, ITerminalQuickFixOptions } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
3228

3329
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
3430
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
@@ -255,7 +251,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
255251
public async $registerQuickFixProvider(id: string): Promise<void> {
256252
this._quickFixProviders.set(id, this._terminalQuickFixService.registerQuickFixProvider(id,
257253
{
258-
provideTerminalQuickFixes: async (terminalCommand: ITerminalCommand, terminal: Terminal, option: ITerminalQuickFixOptions, token: CancellationToken) => {
254+
provideTerminalQuickFixes: async (terminalCommand: ITerminalCommand, lines: string[], option: ITerminalQuickFixOptions, token: CancellationToken) => {
259255
if (token.isCancellationRequested) {
260256
return;
261257
}
@@ -270,7 +266,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
270266
const outputMatcher = option.outputMatcher;
271267
let outputMatch;
272268
if (outputMatcher) {
273-
outputMatch = getOutputMatchForCommand(terminalCommand.executedMarker, terminalCommand.endMarker, terminal.buffer.active, terminal.cols, outputMatcher);
269+
outputMatch = getOutputMatchForLines(lines, outputMatcher);
274270
}
275271
if (!outputMatch) {
276272
return;
@@ -452,3 +448,8 @@ class ExtensionTerminalLinkProvider implements ITerminalExternalLinkProvider {
452448
}));
453449
}
454450
}
451+
452+
export function getOutputMatchForLines(lines: string[], outputMatcher: ITerminalOutputMatcher): ITerminalOutputMatch | undefined {
453+
const match: RegExpMatchArray | null | undefined = lines.join('\n').match(outputMatcher.lineMatcher);
454+
return match ? { regexMatch: match, outputLines: outputMatcher.multipleMatches ? lines : undefined } : undefined;
455+
}

src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget';
2727
import { TerminalQuickFix, TerminalQuickFixType, toMenuItems } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems';
2828
import { ITerminalQuickFixProviderSelector, ITerminalQuickFixService } from 'vs/workbench/contrib/terminal/common/terminal';
2929
import { ITerminalQuickFixOptions, IResolvedExtensionOptions, IUnresolvedExtensionOptions, ITerminalCommandSelector, ITerminalQuickFix, IInternalOptions, ITerminalQuickFixCommandAction, ITerminalQuickFixOpenerAction } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
30+
import { getLinesForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
3031

3132
const quickFixTelemetryTitle = 'terminal/quick-fix';
3233
type QuickFixResultTelemetryEvent = {
@@ -177,15 +178,15 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
177178
if (command.command !== '') {
178179
this._disposeQuickFix();
179180
}
180-
const resolver = async (selector: ITerminalQuickFixOptions) => {
181+
const resolver = async (selector: ITerminalQuickFixOptions, lines?: string[]) => {
181182
const id = selector.id;
182183
await this._extensionService.activateByEvent(`onTerminalQuickFixRequest:${id}`);
183184
const provider = this._quickFixService.providers.get(id);
184185
if (!provider) {
185186
this._logService.warn('No provider when trying to resolve terminal quick fix for provider: ', id);
186187
return;
187188
}
188-
return provider.provideTerminalQuickFixes(command, terminal, { type: 'resolved', commandLineMatcher: selector.commandLineMatcher, outputMatcher: selector.outputMatcher, exitStatus: selector.exitStatus, id: selector.id }, new CancellationTokenSource().token);
189+
return provider.provideTerminalQuickFixes(command, lines, { type: 'resolved', commandLineMatcher: selector.commandLineMatcher, outputMatcher: selector.outputMatcher, exitStatus: selector.exitStatus, id: selector.id }, new CancellationTokenSource().token);
189190
};
190191
const result = await getQuickFixesForCommand(terminal, command, this._commandListeners, this._openerService, this._onDidRequestRerunCommand, resolver);
191192
if (!result) {
@@ -299,7 +300,7 @@ export async function getQuickFixesForCommand(
299300
quickFixOptions: Map<string, ITerminalQuickFixOptions[]>,
300301
openerService: IOpenerService,
301302
onDidRequestRerunCommand?: Emitter<{ command: string; addNewLine?: boolean }>,
302-
getResolvedFixes?: (selector: ITerminalQuickFixOptions) => Promise<ITerminalQuickFix | ITerminalQuickFix[] | undefined>
303+
getResolvedFixes?: (selector: ITerminalQuickFixOptions, lines?: string[]) => Promise<ITerminalQuickFix | ITerminalQuickFix[] | undefined>
303304
): Promise<{ fixes: IAction[]; onDidRunQuickFix: Event<string>; expectedCommands?: string[] } | undefined> {
304305
const onDidRunQuickFixEmitter = new Emitter<string>();
305306
const onDidRunQuickFix = onDidRunQuickFixEmitter.event;
@@ -314,12 +315,12 @@ export async function getQuickFixesForCommand(
314315
const id = option.id;
315316
let quickFixes;
316317
if (option.type === 'resolved') {
317-
quickFixes = await (option as IResolvedExtensionOptions).getQuickFixes(terminalCommand, terminal, option, new CancellationTokenSource().token);
318+
quickFixes = await (option as IResolvedExtensionOptions).getQuickFixes(terminalCommand, getLinesForCommand(terminal.buffer.active, terminalCommand, terminal.cols, option.outputMatcher), option, new CancellationTokenSource().token);
318319
} else if (option.type === 'unresolved') {
319320
if (!getResolvedFixes) {
320321
throw new Error('No resolved fix provider');
321322
}
322-
quickFixes = await getResolvedFixes(option);
323+
quickFixes = await getResolvedFixes(option, option.outputMatcher ? getLinesForCommand(terminal.buffer.active, terminalCommand, terminal.cols, option.outputMatcher) : undefined);
323324
} else if (option.type === 'internal') {
324325
const commandLineMatch = newCommand.match(option.commandLineMatcher);
325326
if (!commandLineMatch) {

src/vs/workbench/contrib/terminal/common/terminal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/e
1313
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1414
import { URI } from 'vs/base/common/uri';
1515
import { Registry } from 'vs/platform/registry/common/platform';
16-
import { IMarkProperties, ISerializedCommandDetectionCapability, ITerminalCapabilityStore, ITerminalOutputMatcher, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities';
16+
import { IMarkProperties, ISerializedCommandDetectionCapability, ITerminalCapabilityStore, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities';
1717
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
1818
import { IProcessDetails } from 'vs/platform/terminal/common/terminalProcess';
19-
import { ITerminalQuickFixProvider, ITerminalCommandSelector, ITerminalOutputMatch } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
19+
import { ITerminalQuickFixProvider, ITerminalCommandSelector, ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
2020

2121
export const TERMINAL_VIEW_ID = 'terminal';
2222

src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
1717
import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
1818
import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminal/browser/links/links';
1919
import { TerminalLocalFileLinkOpener, TerminalLocalFolderInWorkspaceLinkOpener, TerminalSearchLinkOpener } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners';
20-
import { TerminalCapability, ITerminalCommand, IXtermMarker, ITerminalOutputMatcher } from 'vs/platform/terminal/common/capabilities/capabilities';
20+
import { TerminalCapability, ITerminalCommand, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities';
2121
import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
2222
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
2323
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
2424
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
2525
import { Terminal } from 'xterm';
2626
import { IFileQuery, ISearchComplete, ISearchService } from 'vs/workbench/services/search/common/search';
2727
import { SearchService } from 'vs/workbench/services/search/common/searchService';
28+
import { ITerminalOutputMatcher } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
2829

2930
export interface ITerminalLinkActivationResult {
3031
source: 'editor' | 'search';

src/vs/workbench/contrib/terminal/test/browser/quickFixAddon.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
1414
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
1515
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
1616
import { IOpenerService } from 'vs/platform/opener/common/opener';
17-
import { ITerminalCommand, ITerminalOutputMatcher, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
17+
import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
1818
import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
1919
import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
2020
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
@@ -25,6 +25,7 @@ import { Terminal } from 'xterm';
2525
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
2626
import { ITerminalQuickFixService } from 'vs/workbench/contrib/terminal/common/terminal';
2727
import { Emitter } from 'vs/base/common/event';
28+
import { ITerminalOutputMatcher } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
2829

2930
suite('QuickFixAddon', () => {
3031
let quickFixAddon: TerminalQuickFixAddon;

0 commit comments

Comments
 (0)