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

move builtin terminal quick fixes to contribution model #164099

Merged
merged 30 commits into from Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2fdae24
add stuff
meganrogge Oct 20, 2022
1899843
tweak
meganrogge Oct 20, 2022
ce05e51
Revert "add stuff"
meganrogge Oct 20, 2022
55bac09
Revert "tweak"
meganrogge Oct 20, 2022
0f5d4df
delete proposed api
meganrogge Oct 20, 2022
b8c2800
add contribution point
meganrogge Oct 20, 2022
6849d34
don't register another terminal contrib point
meganrogge Oct 20, 2022
6f03181
combine contrib points
meganrogge Oct 20, 2022
7f24e91
adopt contributed quick fixes
meganrogge Oct 20, 2022
0d97708
contribute git push quick fix as test
meganrogge Oct 20, 2022
5491e28
delete logs, replace all
meganrogge Oct 20, 2022
074e3c1
Revert "contribute git push quick fix as test"
meganrogge Oct 20, 2022
331c596
return [] if resolved variable is not in expected command/link
meganrogge Oct 20, 2022
0cdc829
add group: prefix to variables
meganrogge Oct 20, 2022
ca231dd
Merge branch 'main' into merogge/provider-quick-fix
meganrogge Oct 20, 2022
701c201
allow multiple groups to be resolved
meganrogge Oct 20, 2022
d34164e
improve docs
meganrogge Oct 20, 2022
45f9614
improve description
meganrogge Oct 20, 2022
19daf71
fix test
meganrogge Oct 20, 2022
33c66e4
Merge branch 'main' into merogge/provider-quick-fix
meganrogge Oct 24, 2022
34a85a4
fix test
meganrogge Oct 24, 2022
0cc0ae8
fix #163965
meganrogge Oct 24, 2022
f58c431
move 3 fixes over to extension contrib model
meganrogge Oct 24, 2022
09264dc
add proposed api placeholder file
meganrogge Oct 24, 2022
2a65a5c
fix test
meganrogge Oct 24, 2022
cbd24be
rename
meganrogge Oct 24, 2022
b091327
reinsert notebook proposal
meganrogge Oct 24, 2022
f6b1ddb
revert change bc args were lost w git similar
meganrogge Oct 24, 2022
db9ff8b
Merge branch 'main' into merogge/provider-quick-fix
meganrogge Oct 24, 2022
476360f
revert accidental deletion
meganrogge Oct 24, 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
16 changes: 15 additions & 1 deletion src/vs/platform/terminal/common/terminal.ts
Expand Up @@ -7,7 +7,7 @@ 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 { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/capabilities';
import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore, ITerminalOutputMatcher } 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 Expand Up @@ -797,6 +797,16 @@ export interface ITerminalProfileSource extends IBaseUnresolvedTerminalProfile {

export interface ITerminalContributions {
profiles?: ITerminalProfileContribution[];
quickFixes?: ITerminalQuickFixContribution[];
}

export interface ITerminalQuickFixContribution {
id: string;
commandLineMatcher: string | RegExp;
outputMatcher: ITerminalOutputMatcher;
exitStatus?: boolean;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
commandToRun?: string;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
linkToOpen?: string;
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
}

export interface ITerminalProfileContribution {
Expand All @@ -810,6 +820,10 @@ export interface IExtensionTerminalProfile extends ITerminalProfileContribution
extensionIdentifier: string;
}

export interface IExtensionTerminalQuickFix extends ITerminalQuickFixContribution {
extensionIdentifier: string;
}

export type ITerminalProfileObject = ITerminalExecutable | ITerminalProfileSource | IExtensionTerminalProfile | null;
export type ITerminalProfileType = ITerminalProfile | IExtensionTerminalProfile;

Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/contrib/terminal/browser/terminal.ts
Expand Up @@ -949,8 +949,9 @@ export interface ITerminalInstance {

/**
* Attempts to detect and kill the process listening on specified port.
* If successful, places commandToRun on the command line
*/
freePortKillProcess(port: string): Promise<void>;
freePortKillProcess(port: string, commandToRun: string): Promise<void>;
}

export interface ITerminalQuickFixOptions {
Expand All @@ -959,6 +960,7 @@ export interface ITerminalQuickFixOptions {
outputMatcher?: ITerminalOutputMatcher;
getQuickFixes: TerminalQuickFixCallback;
exitStatus?: boolean;
source: string;
}
export type TerminalQuickFixMatchResult = { commandLineMatch: RegExpMatchArray; outputMatch?: RegExpMatchArray | null };
export type TerminalQuickFixAction = IAction | ITerminalQuickFixCommandAction | ITerminalQuickFixOpenerAction;
Expand Down
7 changes: 4 additions & 3 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Expand Up @@ -62,7 +62,7 @@ import { IDetectedLinks, TerminalLinkManager } from 'vs/workbench/contrib/termin
import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick';
import { IRequestAddInstanceToGroupEvent, ITerminalQuickFixOptions, ITerminalExternalLinkProvider, ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { gitSimilarCommand, gitCreatePr, gitPushSetUpstream, freePort, gitTwoDashes } from 'vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions';
import { freePort, gitTwoDashes } from 'vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget';
Expand Down Expand Up @@ -731,7 +731,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this.xterm = xterm;
this._quickFixAddon = this._scopedInstantiationService.createInstance(TerminalQuickFixAddon, this.capabilities);
this.xterm?.raw.loadAddon(this._quickFixAddon);
this.registerQuickFixProvider(gitSimilarCommand(), gitTwoDashes(), gitCreatePr(), gitPushSetUpstream(), freePort(this));
this.registerQuickFixProvider(gitTwoDashes(), freePort(this));
this._register(this._quickFixAddon.onDidRequestRerunCommand(async (e) => await this.runCommand(e.command, e.addNewLine || false)));
const lineDataEventAddon = new LineDataEventAddon();
this.xterm.raw.loadAddon(lineDataEventAddon);
Expand Down Expand Up @@ -1541,8 +1541,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this.xterm?.markTracker.scrollToClosestMarker(startMarkId, endMarkId, highlight);
}

public async freePortKillProcess(port: string): Promise<void> {
public async freePortKillProcess(port: string, command: string): Promise<void> {
await this._processManager?.freePortKillProcess(port);
this.runCommand(command, false);
}

private _onProcessData(ev: IProcessDataEvent): void {
Expand Down
Expand Up @@ -6,21 +6,21 @@
import { localize } from 'vs/nls';
import { TerminalQuickFixMatchResult, ITerminalQuickFixOptions, ITerminalInstance, TerminalQuickFixAction } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalCommand } from 'vs/workbench/contrib/terminal/common/terminal';
import { URI } from 'vs/base/common/uri';

import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal';
export const GitCommandLineRegex = /git/;
export const GitPushCommandLineRegex = /git\s+push/;
export const GitTwoDashesRegex = /error: did you mean `--(.+)` \(with two dashes\)\?/;
export const AnyCommandLineRegex = /.+/;
export const GitSimilarOutputRegex = /(?:(most similar (command|commands) (is|are)))((\n\s*[^\s]+)+)/m;
export const GitSimilarOutputRegex = /(?:(most similar (command|commands) (is|are)))((\n\s*(?<fixedCommand>[^\s]+))+)/m;
export const FreePortOutputRegex = /address already in use (0\.0\.0\.0|127\.0\.0\.1|localhost|::):(?<portNumber>\d{4,5})|Unable to bind [^ ]*:(\d{4,5})|can't listen on port (\d{4,5})|listen EADDRINUSE [^ ]*:(\d{4,5})/;
export const GitPushOutputRegex = /git push --set-upstream origin ([^\s]+)/;
export const GitPushOutputRegex = /git push --set-upstream origin (?<branchName>[^\s]+)/;
// The previous line starts with "Create a pull request for \'([^\s]+)\' on GitHub by visiting:\s*"
// 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 GitCreatePrOutputRegex = /remote:\s*(?<link>https:\/\/github\.com\/.+\/.+\/pull\/new\/.+)/;

export function gitSimilarCommand(): ITerminalQuickFixOptions {
export function gitSimilar(): ITerminalQuickFixOptions {
return {
source: 'builtin',
id: 'Git Similar',
commandLineMatcher: GitCommandLineRegex,
outputMatcher: {
Expand Down Expand Up @@ -50,8 +50,10 @@ export function gitSimilarCommand(): ITerminalQuickFixOptions {
}
};
}

export function gitTwoDashes(): ITerminalQuickFixOptions {
return {
source: 'builtin',
id: 'Git Two Dashes',
commandLineMatcher: GitCommandLineRegex,
outputMatcher: {
Expand All @@ -76,6 +78,7 @@ export function gitTwoDashes(): ITerminalQuickFixOptions {
}
export function freePort(terminalInstance?: Partial<ITerminalInstance>): ITerminalQuickFixOptions {
return {
source: 'builtin',
id: 'Free Port',
commandLineMatcher: AnyCommandLineRegex,
outputMatcher: {
Expand All @@ -97,12 +100,15 @@ export function freePort(terminalInstance?: Partial<ITerminalInstance>): ITermin
id: 'terminal.freePort',
label,
enabled: true,
run: async () => terminalInstance?.freePortKillProcess?.(port)
run: async () => {
await terminalInstance?.freePortKillProcess?.(port, command.command);
}
};
}
};
}
export function gitPushSetUpstream(): ITerminalQuickFixOptions {

export function gitPushSetUpstream(): IExtensionTerminalQuickFix {
return {
id: 'Git Push Set Upstream',
commandLineMatcher: GitPushCommandLineRegex,
Expand All @@ -113,21 +119,12 @@ export function gitPushSetUpstream(): ITerminalQuickFixOptions {
length: 5
},
exitStatus: false,
getQuickFixes: (matchResult: TerminalQuickFixMatchResult, command: ITerminalCommand) => {
const branch = matchResult?.outputMatch?.[1];
if (!branch) {
return;
}
return {
type: 'command',
command: `git push --set-upstream origin ${branch}`,
addNewLine: true
};
}
commandToRun: 'git push --set-upstream origin ${group:branchName}',
extensionIdentifier: 'git'
};
}

export function gitCreatePr(): ITerminalQuickFixOptions {
export function gitCreatePr(): IExtensionTerminalQuickFix {
return {
id: 'Git Create Pr',
commandLineMatcher: GitPushCommandLineRegex,
Expand All @@ -138,18 +135,7 @@ export function gitCreatePr(): ITerminalQuickFixOptions {
length: 5
},
exitStatus: true,
getQuickFixes: (matchResult: TerminalQuickFixMatchResult, command?: ITerminalCommand) => {
if (!command) {
return;
}
const link = matchResult?.outputMatch?.[1];
if (!link) {
return;
}
return {
type: 'opener',
uri: URI.parse(link)
};
}
linkToOpen: '${group:link}',
extensionIdentifier: 'git'
};
}
74 changes: 73 additions & 1 deletion src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts
Expand Up @@ -16,7 +16,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
import { ITerminalQuickFixOptions } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalQuickFixOpenerAction, ITerminalQuickFixOptions, TerminalQuickFixAction, TerminalQuickFixMatchResult } from 'vs/workbench/contrib/terminal/browser/terminal';
import { DecorationSelector, TerminalDecorationHoverManager, updateLayout } from 'vs/workbench/contrib/terminal/browser/xterm/decorationStyles';
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
import { TERMINAL_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
Expand All @@ -28,6 +28,10 @@ import { IDecoration, Terminal } from 'xterm';
import type { ITerminalAddon } from 'xterm-headless';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ILogService } from 'vs/platform/log/common/log';
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal';
import { URI } from 'vs/base/common/uri';
import { gitCreatePr, gitPushSetUpstream, gitSimilar } from 'vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions';
const quickFixTelemetryTitle = 'terminal/quick-fix';
type QuickFixResultTelemetryEvent = {
id: string;
Expand Down Expand Up @@ -75,6 +79,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
constructor(private readonly _capabilities: ITerminalCapabilityStore,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService,
@IInstantiationService instantiationService: IInstantiationService,
@IAudioCueService private readonly _audioCueService: IAudioCueService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
Expand All @@ -94,6 +99,12 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
});
}
this._terminalDecorationHoverService = instantiationService.createInstance(TerminalDecorationHoverManager);
for (const quickFix of this._terminalContributionService.quickFixes) {
this.registerCommandFinishedListener(convertToQuickFixOptions(quickFix));
}
this.registerCommandFinishedListener(gitSimilar());
this.registerCommandFinishedListener(convertToQuickFixOptions(gitCreatePr()));
this.registerCommandFinishedListener(convertToQuickFixOptions(gitPushSetUpstream()));
}

activate(terminal: Terminal): void {
Expand Down Expand Up @@ -331,3 +342,64 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.QuickFix} { background-color: ${backgroundColor.toString()}; } `);
}
});

export function convertToQuickFixOptions(quickFix: IExtensionTerminalQuickFix): ITerminalQuickFixOptions {
const type = quickFix.commandToRun ? 'command' : quickFix.linkToOpen ? 'opener' : undefined;
const options = {
id: quickFix.id,
commandLineMatcher: quickFix.commandLineMatcher,
outputMatcher: quickFix.outputMatcher,
type,
getQuickFixes: type === 'command' ? (matchResult: TerminalQuickFixMatchResult) => {
const matches = matchResult.outputMatch;
const commandToRun = quickFix.commandToRun;
if (!matches || !commandToRun) {
return;
}
const groups = matches.groups;
if (!groups) {
return;
}
const actions: TerminalQuickFixAction[] = [];
let fixedCommand = commandToRun;
for (const [key, value] of Object.entries(groups)) {
const varToResolve = '${group:' + `${key}` + '}';
meganrogge marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can probably do '${group:' + key + '}' since key is already a string?

if (!commandToRun.includes(varToResolve)) {
return [];
}
fixedCommand = fixedCommand.replaceAll(varToResolve, value);
}
if (fixedCommand) {
actions.push({
type: 'command',
command: fixedCommand,
addNewLine: true
});
return actions;
}
return;
} : (matchResult: TerminalQuickFixMatchResult) => {
const matches = matchResult.outputMatch;
const linkToOpen = quickFix.linkToOpen;
if (!matches || !linkToOpen) {
return;
}
const groups = matches.groups;
if (!groups) {
return;
}
let link = linkToOpen;
for (const [key, value] of Object.entries(groups)) {
const varToResolve = '${group:' + `${key}` + '}';
if (!linkToOpen?.includes(varToResolve)) {
return [];
}
link = link.replaceAll(varToResolve, value);
}
return link ? { type: 'opener', uri: URI.parse(link) } as ITerminalQuickFixOpenerAction : [];
},
exitStatus: quickFix.exitStatus,
source: quickFix.extensionIdentifier
};
return options;
}
58 changes: 58 additions & 0 deletions src/vs/workbench/contrib/terminal/common/terminal.ts
Expand Up @@ -717,6 +717,64 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor = {
description: nls.localize('vscode.extension.contributes.terminal', 'Contributes terminal functionality.'),
type: 'object',
properties: {
quickFixes: {
type: 'array',
description: nls.localize('vscode.extension.contributes.terminal.quickFixes', "Defines quick fixes for terminals with shell integration enabled."),
items: {
type: 'object',
required: ['id', 'commandLineMatcher', 'outputMatcher'],
defaultSnippets: [{
body: {
id: '$1',
commandLineMatcher: '$2',
outputMatcher: '$3',
commandToRun: '$4',
linkToOpen: '$5'
}
}],
properties: {
id: {
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.id', "The ID of the quick fix."),
type: 'string',
},
commandLineMatcher: {
description: nls.localize('vscode.extension.contributes.terminal.quickFixes.commandLineMatcher', "The command line to match."),
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."),
type: 'object',
required: ['lineMatcher', 'anchor', 'offset', 'length'],
properties: {
lineMatcher: {
description: 'The command line to match',
type: 'string'
},
anchor: {
description: 'Which side of the output to anchor the offset and length against',
enum: ['top', 'bottom']
},
offset: {
description: 'How far from either the top or the bottom of the butter to start matching against.',
type: 'number'
},
length: {
description: 'The number of rows to match against, this should be as small as possible for performance reasons',
type: 'number'
}
}
},
commandToRun: {
description: 'The command to run in the terminal for this match. Refer to a group found in the outputMatcher via ${group:group_name}. When provided, will take precedence over linkToOpen.',
type: 'string'
},
linkToOpen: {
description: 'The link to open for this match. Refer to a group found in the outputMatcher via ${group:group_name}. If a commandToRun is provided, this will be ignored.',
type: 'string'
}
},
}
},
profiles: {
type: 'array',
description: nls.localize('vscode.extension.contributes.terminal.profiles', "Defines additional terminal profiles that the user can create."),
Expand Down