diff --git a/extensions/npm/package.json b/extensions/npm/package.json index be86fa4fcaa..dbebc670d68 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -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 } } ] diff --git a/extensions/npm/src/npmMain.ts b/extensions/npm/src/npmMain.ts index 4c815b8e689..26c6412b2d7 100644 --- a/extensions/npm/src/npmMain.ts +++ b/extensions/npm/src/npmMain.ts @@ -78,7 +78,7 @@ export async function activate(context: vscode.ExtensionContext): Promise } 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 } 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; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 7b01801888e..e7da8ff4177 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -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 }; - } } } } diff --git a/src/vs/platform/terminal/common/xterm/terminalQuickFix.ts b/src/vs/platform/terminal/common/xterm/terminalQuickFix.ts index 765d58f104b..aa8ce275226 100644 --- a/src/vs/platform/terminal/common/xterm/terminalQuickFix.ts +++ b/src/vs/platform/terminal/common/xterm/terminalQuickFix.ts @@ -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 { diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index d287275ad98..023613fe87c 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -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 }; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a5afe3dcd78..48e67749ad8 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -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, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 16d4acefd80..703cf3adbde 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -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 }[]; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 046564d8b92..7737164a7ce 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -679,7 +679,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } - public async $provideTerminalQuickFixes(id: string, matchResult: vscode.TerminalCommandMatchResult): Promise { + 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 { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5f41e8b8b9c..afb0d03fe11 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -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, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts index 73017bfad3e..dcf9371ace5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts @@ -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 (? // it's safe to assume it's a github pull request if the URL includes `/pull/` export const GitCreatePrOutputRegex = /remote:\s*(?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): 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 }; } }; diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts b/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts index fe7a3ef3144..f3b21ffe206 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts @@ -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 }; } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts index 8d5b0f929f6..64d399b1d7e 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts @@ -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); } } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 0556052baa8..d7ff6e52367 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -741,7 +741,8 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor 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 (?[^\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; } - 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 } }