quick fix API polish (#168688)

* fix #167649

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

* fix #167652

* add builtin everywhere for consistency

* fix  #167621 and fix #167646

* get rid of type in api

* fix  #167584

* fix #167587

* fix #167587

* fix #167590 and fix #167557

* Revert "fix #167652"

This reverts commit cf4cc72790.

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

Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>

* Update src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts

Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>

* use const enum + more

* use classes

* update npm package.json

* add example for outputMatcher

* improve description

Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
This commit is contained in:
Megan Rogge 2022-12-13 12:32:54 -06:00 committed by GitHub
parent f99a293cef
commit 3e9bfdddc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 122 additions and 98 deletions

View file

@ -346,13 +346,12 @@
{
"id": "ms-vscode.npm-command",
"commandLineMatcher": "npm",
"exitStatus": false,
"commandExitResult": "error",
"outputMatcher": {
"anchor": "bottom",
"length": 8,
"lineMatcher": "Did you mean (?:this|one of these)\\?((?:\\n.+?npm .+ #.+)+)",
"offset": 2,
"multipleMatches": true
"offset": 2
}
}
]

View file

@ -78,7 +78,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
}
const lines = outputMatch.regexMatch[1];
const fixes: vscode.TerminalQuickFixCommandAction[] = [];
const fixes: vscode.TerminalQuickFixCommand[] = [];
for (const line of lines.split('\n')) {
// search from the second char, since the lines might be prefixed with
// "npm ERR!" which comes before the actual command suggestion.
@ -88,10 +88,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
}
const end = line.lastIndexOf('#');
fixes.push({
type: vscode.TerminalQuickFixType.command,
terminalCommand: line.slice(begin, end === -1 ? undefined : end - 1)
});
fixes.push({ terminalCommand: line.slice(begin, end === -1 ? undefined : end - 1) });
}
return fixes;

View file

@ -674,9 +674,6 @@ function getOutputMatchForCommand(executedMarker: IMarker | undefined, endMarker
}
if (!match) {
match = lines.join('\n').match(matcher);
if (!outputMatcher.multipleMatches && match) {
return { regexMatch: match };
}
}
}
} else {
@ -693,9 +690,6 @@ function getOutputMatchForCommand(executedMarker: IMarker | undefined, endMarker
}
if (!match) {
match = lines.join('\n').match(matcher);
if (!outputMatcher.multipleMatches && match) {
return { regexMatch: match };
}
}
}
}

View file

@ -9,6 +9,12 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
export enum TerminalQuickFixType {
Command = 0,
Opener = 1,
Port = 2
}
export interface ITerminalCommandSelector {
id: string;
commandLineMatcher: string | RegExp;
@ -26,19 +32,19 @@ export interface ITerminalQuickFixOptions {
}
export interface ITerminalQuickFix {
type: 'command' | 'opener';
type: TerminalQuickFixType;
id: string;
source: string;
}
export interface ITerminalQuickFixCommandAction extends ITerminalQuickFix {
type: 'command';
type: TerminalQuickFixType.Command;
terminalCommand: string;
// TODO: Should this depend on whether alt is held?
addNewLine?: boolean;
}
export interface ITerminalQuickFixOpenerAction extends ITerminalQuickFix {
type: 'opener';
type: TerminalQuickFixType.Opener;
uri: URI;
}
@ -69,7 +75,7 @@ export interface ITerminalCommandMatchResult {
export interface ITerminalOutputMatch {
regexMatch: RegExpMatchArray;
outputLines?: string[];
outputLines: string[];
}
export interface IInternalOptions extends ITerminalQuickFixOptions {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, TerminalLaunchConfig, ITerminalDimensionsDto, ExtHostTerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, TerminalLaunchConfig, ITerminalDimensionsDto, ExtHostTerminalIdentifier, TerminalQuickFix } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
@ -20,13 +20,14 @@ import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminal
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { OperatingSystem, OS } from 'vs/base/common/platform';
import { TerminalEditorLocationOptions, TerminalQuickFix, TerminalQuickFixOpenerAction } from 'vscode';
import { TerminalEditorLocationOptions } from 'vscode';
import { Promises } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ITerminalOutputMatch, ITerminalOutputMatcher, ITerminalQuickFix, ITerminalQuickFixOptions } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
import { TerminalQuickFixType } from 'vs/workbench/api/common/extHostTypes';
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
export class MainThreadTerminalService implements MainThreadTerminalServiceShape {
@ -456,12 +457,14 @@ class ExtensionTerminalLinkProvider implements ITerminalExternalLinkProvider {
export function getOutputMatchForLines(lines: string[], outputMatcher: ITerminalOutputMatcher): ITerminalOutputMatch | undefined {
const match: RegExpMatchArray | null | undefined = lines.join('\n').match(outputMatcher.lineMatcher);
return match ? { regexMatch: match, outputLines: outputMatcher.multipleMatches ? lines : undefined } : undefined;
return match ? { regexMatch: match, outputLines: lines } : undefined;
}
function parseQuickFix(id: string, source: string, fix: TerminalQuickFix): ITerminalQuickFix {
if (fix.type === TerminalQuickFixType.opener) {
(fix as TerminalQuickFixOpenerAction).uri = URI.revive((fix as TerminalQuickFixOpenerAction).uri);
let type = TerminalQuickFixType.Command;
if ('uri' in fix) {
fix.uri = URI.revive(fix.uri);
type = TerminalQuickFixType.Opener;
}
return { id, source, ...fix };
return { id, type, source, ...fix };
}

View file

@ -1315,6 +1315,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TaskRevealKind: extHostTypes.TaskRevealKind,
TaskScope: extHostTypes.TaskScope,
TerminalLink: extHostTypes.TerminalLink,
TerminalQuickFixCommand: extHostTypes.TerminalQuickFixCommand,
TerminalQuickFixOpener: extHostTypes.TerminalQuickFixOpener,
TerminalLocation: extHostTypes.TerminalLocation,
TerminalProfile: extHostTypes.TerminalProfile,
TerminalExitReason: extHostTypes.TerminalExitReason,

View file

@ -70,7 +70,10 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplore
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import * as search from 'vs/workbench/services/search/common/search';
import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions';
import { TerminalCommandMatchResult, TerminalQuickFix } from 'vscode';
import { TerminalCommandMatchResult, TerminalQuickFixCommand, TerminalQuickFixOpener } from 'vscode';
export type TerminalQuickFix = TerminalQuickFixCommand | TerminalQuickFixOpener;
export interface IWorkspaceData extends IStaticWorkspaceData {
folders: { uri: UriComponents; name: string; index: number }[];

View file

@ -679,7 +679,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
});
}
public async $provideTerminalQuickFixes(id: string, matchResult: vscode.TerminalCommandMatchResult): Promise<vscode.TerminalQuickFix[] | vscode.TerminalQuickFix | undefined> {
public async $provideTerminalQuickFixes(id: string, matchResult: vscode.TerminalCommandMatchResult): Promise<(vscode.TerminalQuickFixOpener | vscode.TerminalQuickFixCommand)[] | vscode.TerminalQuickFixOpener | vscode.TerminalQuickFixCommand | undefined> {
const token = new CancellationTokenSource().token;
if (token.isCancellationRequested) {
return;
@ -688,7 +688,12 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
if (!provider) {
return;
}
return provider.provideTerminalQuickFixes(matchResult, token);
const quickFixes = await provider.provideTerminalQuickFixes(matchResult, token);
if (quickFixes === null) {
return undefined;
} else {
return quickFixes;
}
}
public async $createContributedProfileTerminal(id: string, options: ICreateContributedTerminalProfileOptions): Promise<void> {

View file

@ -46,13 +46,13 @@ function es5ClassCompat(target: Function): any {
}
export enum TerminalOutputAnchor {
top = 'top',
bottom = 'bottom'
Top = 0,
Bottom = 1
}
export enum TerminalQuickFixType {
command = 'command',
opener = 'opener'
Command = 0,
Opener = 1
}
@es5ClassCompat
@ -1926,6 +1926,20 @@ export class TerminalLink implements vscode.TerminalLink {
}
}
export class TerminalQuickFixOpener {
uri: vscode.Uri;
constructor(uri: vscode.Uri) {
this.uri = uri;
}
}
export class TerminalQuickFixCommand {
terminalCommand: string;
constructor(terminalCommand: string) {
this.terminalCommand = terminalCommand;
}
}
export enum TerminalLocation {
Panel = 1,
Editor = 2,

View file

@ -5,9 +5,8 @@
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IInternalOptions, ITerminalCommandMatchResult, TerminalQuickFixActionInternal } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
import { IInternalOptions, ITerminalCommandMatchResult, TerminalQuickFixActionInternal, TerminalQuickFixType } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalQuickFixType } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems';
export const GitCommandLineRegex = /git/;
export const GitPushCommandLineRegex = /git\s+push/;
@ -20,6 +19,10 @@ export const GitPushOutputRegex = /git push --set-upstream origin (?<branchName>
// it's safe to assume it's a github pull request if the URL includes `/pull/`
export const GitCreatePrOutputRegex = /remote:\s*(?<link>https:\/\/github\.com\/.+\/.+\/pull\/new\/.+)/;
export const enum QuickFixSource {
Builtin = 'builtin'
}
export function gitSimilar(): IInternalOptions {
return {
id: 'Git Similar',
@ -46,7 +49,7 @@ export function gitSimilar(): IInternalOptions {
type: TerminalQuickFixType.Command,
terminalCommand: matchResult.commandLine.replace(/git\s+[^\s]+/, () => `git ${fixedCommand}`),
addNewLine: true,
source: 'builtin'
source: QuickFixSource.Builtin
});
}
}
@ -77,7 +80,7 @@ export function gitTwoDashes(): IInternalOptions {
id: 'Git Two Dashes',
terminalCommand: matchResult.commandLine.replace(` -${problemArg}`, () => ` --${problemArg}`),
addNewLine: true,
source: 'builtin'
source: QuickFixSource.Builtin
};
}
};
@ -101,11 +104,13 @@ export function freePort(terminalInstance?: Partial<ITerminalInstance>): IIntern
}
const label = localize("terminal.freePort", "Free port {0}", port);
return {
class: TerminalQuickFixType.Port,
type: TerminalQuickFixType.Port,
class: undefined,
tooltip: label,
id: 'Free Port',
label,
enabled: true,
source: QuickFixSource.Builtin,
run: async () => {
await terminalInstance?.freePortKillProcess?.(port, matchResult.commandLine);
}
@ -147,11 +152,11 @@ export function gitPushSetUpstream(): IInternalOptions {
}
if (fixedCommand) {
actions.push({
type: 'command',
type: TerminalQuickFixType.Command,
id: 'Git Push Set Upstream',
terminalCommand: fixedCommand,
addNewLine: true,
source: 'builtin'
source: QuickFixSource.Builtin
});
return actions;
}
@ -179,14 +184,12 @@ export function gitCreatePr(): IInternalOptions {
}
const label = localize("terminal.createPR", "Create PR {0}", link);
return {
class: undefined,
tooltip: label,
id: 'Git Create Pr',
label,
enabled: true,
type: 'opener',
type: TerminalQuickFixType.Opener,
uri: URI.parse(link),
run: () => { }
source: QuickFixSource.Builtin
};
}
};

View file

@ -9,21 +9,16 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/common/types';
import { localize } from 'vs/nls';
import { ActionListItemKind, IListMenuItem } from 'vs/platform/actionWidget/browser/actionList';
import { IActionItem } from 'vs/platform/actionWidget/common/actionWidget';
import { TerminalQuickFixType } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
import { ITerminalAction } from 'vs/workbench/contrib/terminal/browser/xterm/quickFixAddon';
export const enum TerminalQuickFixType {
Command = 'command',
Opener = 'opener',
Port = 'port'
}
export class TerminalQuickFix implements IActionItem {
action: ITerminalAction;
type: string;
type: TerminalQuickFixType;
disabled?: boolean;
title?: string;
source: string;
constructor(action: ITerminalAction, type: string, source: string, title?: string, disabled?: boolean) {
constructor(action: ITerminalAction, type: TerminalQuickFixType, source: string, title?: string, disabled?: boolean) {
this.action = action;
this.disabled = disabled;
this.title = title;
@ -72,5 +67,4 @@ function getQuickFixIcon(quickFix: TerminalQuickFix): { codicon: Codicon } {
case TerminalQuickFixType.Port:
return { codicon: Codicon.debugDisconnect };
}
return { codicon: Codicon.lightBulb };
}

View file

@ -23,9 +23,9 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService';
import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget';
import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget';
import { TerminalQuickFix, TerminalQuickFixType, toMenuItems } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems';
import { TerminalQuickFix, toMenuItems } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems';
import { ITerminalQuickFixProviderSelector, ITerminalQuickFixService } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalQuickFixOptions, IResolvedExtensionOptions, IUnresolvedExtensionOptions, ITerminalCommandSelector, ITerminalQuickFix, IInternalOptions, ITerminalQuickFixCommandAction, ITerminalQuickFixOpenerAction } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
import { ITerminalQuickFixOptions, IResolvedExtensionOptions, IUnresolvedExtensionOptions, ITerminalCommandSelector, ITerminalQuickFix, IInternalOptions, ITerminalQuickFixCommandAction, ITerminalQuickFixOpenerAction, TerminalQuickFixType } from 'vs/platform/terminal/common/xterm/terminalQuickFix';
import { getLinesForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { ILabelService } from 'vs/platform/label/common/label';
@ -117,7 +117,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
}
// TODO: What's documentation do? Need a vscode command?
const actions = this._currentRenderContext.quickFixes.map(f => new TerminalQuickFix(f, f.class || TerminalQuickFixType.Command, f.source, f.label));
const actions = this._currentRenderContext.quickFixes.map(f => new TerminalQuickFix(f, f.type, f.source, f.label));
const documentation = this._currentRenderContext.quickFixes.map(f => { return { id: f.source, title: f.label, tooltip: f.source }; });
const actionSet = {
// TODO: Documentation and actions are separate?
@ -280,6 +280,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
}
export interface ITerminalAction extends IAction {
type: TerminalQuickFixType;
source: string;
uri?: URI;
command?: string;
@ -336,10 +337,11 @@ export async function getQuickFixesForCommand(
const fix = quickFix as ITerminalQuickFixCommandAction;
const label = localize('quickFix.command', 'Run: {0}', fix.terminalCommand);
action = {
type: TerminalQuickFixType.Command,
class: undefined,
source: quickFix.source,
id: quickFix.id,
label,
class: quickFix.type,
enabled: true,
run: () => {
onDidRequestRerunCommand?.fire({
@ -364,7 +366,8 @@ export async function getQuickFixesForCommand(
source: quickFix.source,
id: quickFix.id,
label,
class: quickFix.type,
type: TerminalQuickFixType.Opener,
class: undefined,
enabled: true,
run: () => openerService.open(fix.uri),
tooltip: label,
@ -372,23 +375,26 @@ export async function getQuickFixesForCommand(
};
break;
}
case TerminalQuickFixType.Port: {
const fix = quickFix as ITerminalAction;
action = {
source: 'builtin',
type: fix.type,
id: fix.id,
label: fix.label,
class: fix.class,
enabled: fix.enabled,
run: () => {
fix.run();
},
tooltip: fix.tooltip
};
break;
}
}
if (action) {
fixes.push(action);
}
} else {
const fix = quickFix as ITerminalAction;
action = {
source: 'builtin',
id: fix.id,
label: fix.label,
class: fix.class,
enabled: fix.enabled,
run: () => {
fix.run();
},
tooltip: fix.tooltip
};
}
if (action) {
fixes.push(action);
}
}
}

View file

@ -741,7 +741,8 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor<ITermina
description: nls.localize('vscode.extension.contributes.terminal.quickFixes', "Defines quick fixes for terminals with shell integration enabled."),
items: {
type: 'object',
required: ['id', 'commandLineMatcher', 'outputMatcher', 'exitStatus'],
additionalProperties: false,
required: ['id', 'commandLineMatcher', 'outputMatcher', 'commandExitResult'],
defaultSnippets: [{
body: {
id: '$1',
@ -752,15 +753,15 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor<ITermina
}],
properties: {
id: {
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.id', "The ID of the quick fix."),
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.id', "The ID of the quick fix provider."),
type: 'string',
},
commandLineMatcher: {
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.commandLineMatcher', "The command line to match."),
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.commandLineMatcher', "The regular expression to test the command line against."),
type: 'string',
},
outputMatcher: {
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.outputMatcher', "The output to match, which provides groups of the form <group_name> to be referenced via ${group:group_name} in commandToRun and linkToOpen."),
markdownDescription: nls.localize('vscode.extension.contributes.terminal.quickFixes.outputMatcher', "The regular expression to test the output against, which provides groups to be referenced in terminalCommand and uri.\n\nFor example:\n\n `lineMatcher: /git push --set-upstream origin (?<branchName>[^\s]+)/;`\n\n`terminalCommand: 'git push --set-upstream origin ${group:branchName}';`\n"),
type: 'object',
required: ['lineMatcher', 'anchor', 'offset', 'length'],
properties: {
@ -769,11 +770,11 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor<ITermina
type: 'string'
},
anchor: {
description: 'Which side of the output to anchor the offset and length against',
description: 'Where the search should begin in the buffer',
enum: ['top', 'bottom']
},
offset: {
description: 'How far from either the top or the bottom of the butter to start matching against.',
description: 'The number of lines vertically from the anchor in the buffer to start matching against',
type: 'number'
},
length: {
@ -783,7 +784,7 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor<ITermina
}
},
commandExitResult: {
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.commandExitResult', "The command result to match on"),
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.commandExitResult', "The command exit result to match on"),
enum: ['success', 'error'],
enumDescriptions: [
'The command exited with an exit code of zero.',

View file

@ -12,19 +12,16 @@ declare module 'vscode' {
* @param token A cancellation token indicating the result is no longer needed
* @return Terminal quick fix(es) if any
*/
provideTerminalQuickFixes(commandMatchResult: TerminalCommandMatchResult, token: CancellationToken): TerminalQuickFix[] | TerminalQuickFix | undefined;
provideTerminalQuickFixes(commandMatchResult: TerminalCommandMatchResult, token: CancellationToken): ProviderResult<(TerminalQuickFixCommand | TerminalQuickFixOpener)[] | TerminalQuickFixCommand | TerminalQuickFixOpener>;
}
interface TerminalQuickFix {
type: TerminalQuickFixType;
}
export interface TerminalCommandMatchResult {
commandLine: string;
commandLineMatch: RegExpMatchArray;
outputMatch?: {
regexMatch: RegExpMatchArray;
outputLines?: string[];
outputLines: string[];
};
}
@ -36,13 +33,19 @@ declare module 'vscode' {
export function registerTerminalQuickFixProvider(id: string, provider: TerminalQuickFixProvider): Disposable;
}
export interface TerminalQuickFixCommandAction extends TerminalQuickFix {
type: TerminalQuickFixType.command;
export class TerminalQuickFixCommand {
/**
* The terminal command to run
*/
terminalCommand: string;
constructor(terminalCommand: string);
}
export interface TerminalQuickFixOpenerAction extends TerminalQuickFix {
type: TerminalQuickFixType.opener;
export class TerminalQuickFixOpener {
/**
* The uri to open
*/
uri: Uri;
constructor(uri: Uri);
}
/**
@ -67,21 +70,15 @@ declare module 'vscode' {
* reasons. This is capped at 40.
*/
length: number;
/**
* If multiple matches are expected - this will result in {@link outputLines} being returned
* when there's a {@link regexMatch} from {@link offset} to {@link length}
*/
multipleMatches?: boolean;
}
enum TerminalOutputAnchor {
top = 'top',
bottom = 'bottom'
Top = 0,
Bottom = 1
}
enum TerminalQuickFixType {
command = 'command',
opener = 'opener'
Command = 0,
Opener = 1
}
}