mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-19 18:14:50 +01:00
add basic contextual commands (#160952)
This commit is contained in:
@@ -220,6 +220,7 @@ export interface ITerminalCommand {
|
||||
commandStartLineContent?: string;
|
||||
markProperties?: IMarkProperties;
|
||||
getOutput(): string | undefined;
|
||||
getOutputMatch(outputMatcher: { lineMatcher: string | RegExp; anchor?: 'top' | 'bottom'; offset?: number; length?: number }): RegExpMatchArray | undefined;
|
||||
hasOutput(): boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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, 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';
|
||||
@@ -485,6 +486,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),
|
||||
getOutputMatch: (outputMatcher?: { lineMatcher: string | RegExp; anchor?: 'top' | 'bottom'; offset?: number; length?: number }) => getOutputMatchForCommand(executedMarker, endMarker, buffer, this._terminal.cols, outputMatcher),
|
||||
markProperties: options?.markProperties
|
||||
};
|
||||
this._commands.push(newCommand);
|
||||
@@ -609,6 +611,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),
|
||||
getOutputMatch: (outputMatcher: { lineMatcher: string | RegExp; anchor?: 'top' | 'bottom'; offset?: number; length?: number }) => getOutputMatchForCommand(executedMarker, endMarker, buffer, this._terminal.cols, outputMatcher),
|
||||
markProperties: e.markProperties
|
||||
};
|
||||
this._commands.push(newCommand);
|
||||
@@ -639,3 +642,59 @@ function getOutputForCommand(executedMarker: IMarker | undefined, endMarker: IMa
|
||||
}
|
||||
return output === '' ? undefined : output;
|
||||
}
|
||||
|
||||
export function getOutputMatchForCommand(executedMarker: IMarker | undefined, endMarker: IMarker | undefined, buffer: IBuffer, cols: number, outputMatcher: { lineMatcher: string | RegExp; anchor?: 'top' | 'bottom'; offset?: number; length?: number } | undefined): RegExpMatchArray | undefined {
|
||||
if (!executedMarker || !endMarker) {
|
||||
return undefined;
|
||||
}
|
||||
const startLine = executedMarker.line;
|
||||
const endLine = endMarker.line;
|
||||
|
||||
if (startLine === endLine) {
|
||||
return undefined;
|
||||
}
|
||||
if (outputMatcher?.length && (endLine - startLine) < outputMatcher.length) {
|
||||
return undefined;
|
||||
}
|
||||
let output = '';
|
||||
let line: string | undefined;
|
||||
if (outputMatcher?.anchor === 'bottom') {
|
||||
for (let i = endLine - (outputMatcher.offset || 0); i >= startLine; i--) {
|
||||
line = getXtermLineContent(buffer, i, i, cols);
|
||||
output = line + output;
|
||||
const match = output.match(outputMatcher.lineMatcher);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = startLine + (outputMatcher?.offset || 0); i < endLine; i++) {
|
||||
line = getXtermLineContent(buffer, i, i, cols);
|
||||
output += line;
|
||||
if (outputMatcher) {
|
||||
const match = output.match(outputMatcher.lineMatcher);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number, cols: number): string {
|
||||
// Cap the maximum number of lines generated to prevent potential performance problems. This is
|
||||
// more of a sanity check as the wrapped line should already be trimmed down at this point.
|
||||
const maxLineLength = Math.max(2048 / cols * 2);
|
||||
lineEnd = Math.min(lineEnd, lineStart + maxLineLength);
|
||||
let content = '';
|
||||
for (let i = lineStart; i <= lineEnd; i++) {
|
||||
// Make sure only 0 to cols are considered as resizing when windows mode is enabled will
|
||||
// retain buffer data outside of the terminal width as reflow is disabled.
|
||||
const line = buffer.getLine(i);
|
||||
if (line) {
|
||||
content += line.translateToString(true, 0, cols);
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -331,6 +331,7 @@ export interface IPtyService extends IPtyHostController {
|
||||
reduceConnectionGraceTime(): Promise<void>;
|
||||
requestDetachInstance(workspaceId: string, instanceId: number): Promise<IProcessDetails | undefined>;
|
||||
acceptDetachInstanceReply(requestId: number, persistentProcessId?: number): Promise<void>;
|
||||
freePortKillProcess?(id: number, port: string): Promise<{ port: string; processId: string }>;
|
||||
/**
|
||||
* Serializes and returns terminal state.
|
||||
* @param ids The persistent terminal IDs to serialize.
|
||||
@@ -649,6 +650,11 @@ export interface ITerminalChildProcess {
|
||||
*/
|
||||
detach?(forcePersist?: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Frees the port and kills the process
|
||||
*/
|
||||
freePortKillProcess?(port: string): Promise<{ port: string; processId: string }>;
|
||||
|
||||
/**
|
||||
* Shutdown the terminal process.
|
||||
*
|
||||
|
||||
@@ -316,6 +316,13 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
return this._proxy.acceptDetachInstanceReply(requestId, persistentProcessId);
|
||||
}
|
||||
|
||||
async freePortKillProcess(id: number, port: string): Promise<{ port: string; processId: string }> {
|
||||
if (!this._proxy.freePortKillProcess) {
|
||||
throw new Error('freePortKillProcess does not exist on the pty proxy');
|
||||
}
|
||||
return this._proxy.freePortKillProcess(id, port);
|
||||
}
|
||||
|
||||
async serializeTerminalState(ids: number[]): Promise<string> {
|
||||
return this._proxy.serializeTerminalState(ids);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { execFile } from 'child_process';
|
||||
import { execFile, exec } from 'child_process';
|
||||
import { AutoOpenBarrier, ProcessTimeRunOnceScheduler, Promises, Queue } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -103,6 +103,66 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
this._detachInstanceRequestStore.acceptReply(requestId, processDetails);
|
||||
}
|
||||
|
||||
async freePortKillProcess(id: number, port: string): Promise<{ port: string; processId: string }> {
|
||||
let result: { port: string; processId: string } | undefined;
|
||||
if (!isWindows) {
|
||||
const stdout = await new Promise<string>((resolve, reject) => {
|
||||
exec(`lsof -nP -iTCP -sTCP:LISTEN | grep ${port}`, {}, (err, stdout) => {
|
||||
if (err) {
|
||||
return reject('Problem occurred when listing active processes');
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
const processesForPort = stdout.split('\n');
|
||||
if (processesForPort.length >= 1) {
|
||||
const capturePid = /\s+(\d+)\s+/;
|
||||
const processId = processesForPort[0].match(capturePid)?.[1];
|
||||
if (processId) {
|
||||
await new Promise<string>((resolve, reject) => {
|
||||
exec(`kill ${processId}`, {}, (err, stdout) => {
|
||||
if (err) {
|
||||
return reject(`Problem occurred when killing the process w ID: ${processId}`);
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
result = { port, processId };
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const stdout = await new Promise<string>((resolve, reject) => {
|
||||
exec(`netstat -ano | findstr "${port}"`, {}, (err, stdout) => {
|
||||
if (err) {
|
||||
return reject('Problem occurred when listing active processes');
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
const processesForPort = stdout.split('\n');
|
||||
if (processesForPort.length >= 1) {
|
||||
const capturePid = /LISTENING\s+(\d{3})/;
|
||||
const processId = processesForPort[0].match(capturePid)?.[1];
|
||||
if (processId) {
|
||||
await new Promise<string>((resolve, reject) => {
|
||||
exec(`Taskkill /F /PID ${processId}`, {}, (err, stdout) => {
|
||||
if (err) {
|
||||
return reject(`Problem occurred when killing the process w ID: ${processId}`);
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
result = { port, processId };
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
throw new Error(`Processes for port ${port} were not found`);
|
||||
}
|
||||
|
||||
async serializeTerminalState(ids: number[]): Promise<string> {
|
||||
const promises: Promise<ISerializedTerminalState>[] = [];
|
||||
for (const [persistentProcessId, persistentProcess] of this._ptys.entries()) {
|
||||
|
||||
@@ -147,6 +147,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
|
||||
case '$refreshProperty': return this._ptyService.refreshProperty.apply(this._ptyService, args);
|
||||
case '$requestDetachInstance': return this._ptyService.requestDetachInstance(args[0], args[1]);
|
||||
case '$acceptDetachedInstance': return this._ptyService.acceptDetachInstanceReply(args[0], args[1]);
|
||||
case '$freePortKillProcess': return this._ptyService.freePortKillProcess?.apply(args[0], args[1]);
|
||||
}
|
||||
|
||||
throw new Error(`IPC Command ${command} not found`);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { IViewportRange, IBufferRange, IBufferLine, IBuffer, IBufferCellPosition } from 'xterm';
|
||||
import type { IViewportRange, IBufferRange, IBufferLine, IBufferCellPosition, IBuffer } from 'xterm';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { IPath, posix, win32 } from 'vs/base/common/path';
|
||||
@@ -138,6 +138,7 @@ export function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd:
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
export function positionIsInRange(position: IBufferCellPosition, range: IBufferRange): boolean {
|
||||
if (position.y < range.start.y || position.y > range.end.y) {
|
||||
return false;
|
||||
|
||||
@@ -107,6 +107,13 @@ export class RemotePty extends Disposable implements ITerminalChildProcess {
|
||||
});
|
||||
}
|
||||
|
||||
freePortKillProcess(port: string): Promise<{ port: string; processId: string }> {
|
||||
if (!this._remoteTerminalChannel.freePortKillProcess) {
|
||||
throw new Error('freePortKillProcess does not exist on the local pty service');
|
||||
}
|
||||
return this._remoteTerminalChannel.freePortKillProcess(this.id, port);
|
||||
}
|
||||
|
||||
acknowledgeDataEvent(charCount: number): void {
|
||||
// Support flow control for server spawned processes
|
||||
if (this._inReplay) {
|
||||
|
||||
@@ -242,10 +242,6 @@ registerSendSequenceKeybinding(String.fromCharCode('A'.charCodeAt(0) - 64), {
|
||||
registerSendSequenceKeybinding(String.fromCharCode('E'.charCodeAt(0) - 64), {
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow }
|
||||
});
|
||||
// Break: ctrl+C
|
||||
registerSendSequenceKeybinding(String.fromCharCode('C'.charCodeAt(0) - 64), {
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyCode.Period }
|
||||
});
|
||||
// NUL: ctrl+shift+2
|
||||
registerSendSequenceKeybinding('\u0000', {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit2,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
@@ -17,6 +18,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { IEditableData } from 'vs/workbench/common/views';
|
||||
import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget';
|
||||
import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList';
|
||||
import { IContextualAction } from 'vs/workbench/contrib/terminal/browser/xterm/contextualActionAddon';
|
||||
import { INavigationMode, IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalFont, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { IMarker } from 'xterm';
|
||||
@@ -456,6 +458,8 @@ export interface ITerminalInstance {
|
||||
|
||||
readonly statusList: ITerminalStatusList;
|
||||
|
||||
contextualActions: IContextualAction | undefined;
|
||||
|
||||
readonly findWidget: TerminalFindWidget;
|
||||
|
||||
/**
|
||||
@@ -903,6 +907,36 @@ export interface ITerminalInstance {
|
||||
* Activates the most recent link of the given type.
|
||||
*/
|
||||
openRecentLink(type: 'localFile' | 'url'): Promise<void>;
|
||||
|
||||
/**
|
||||
* Registers contextual action listeners
|
||||
*/
|
||||
registerContextualActions(...options: ITerminalContextualActionOptions[]): void;
|
||||
|
||||
freePortKillProcess(port: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ITerminalContextualActionOptions {
|
||||
actionName: string | DynamicActionName;
|
||||
commandLineMatcher: string | RegExp;
|
||||
outputMatcher?: ITerminalOutputMatcher;
|
||||
getActions: ContextualActionCallback;
|
||||
exitCode?: number;
|
||||
}
|
||||
export type ContextualMatchResult = { commandLineMatch: RegExpMatchArray; outputMatch?: RegExpMatchArray | null };
|
||||
export type DynamicActionName = (matchResult: ContextualMatchResult) => string;
|
||||
export type ContextualActionCallback = (matchResult: ContextualMatchResult, command: ITerminalCommand) => ICommandAction[] | undefined;
|
||||
|
||||
export interface ICommandAction extends IAction {
|
||||
commandToRunInTerminal?: string;
|
||||
addNewLine?: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminalOutputMatcher {
|
||||
lineMatcher: string | RegExp;
|
||||
anchor?: 'top' | 'bottom';
|
||||
offset?: number;
|
||||
length?: number;
|
||||
}
|
||||
|
||||
export interface IXtermTerminal {
|
||||
|
||||
@@ -143,6 +143,26 @@ export function registerTerminalActions() {
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.QuickFix,
|
||||
title: { value: localize('workbench.action.terminal.quickFix', "Quick Fix"), original: 'Quick Fix' },
|
||||
f1: true,
|
||||
category,
|
||||
precondition: TerminalContextKeys.processSupported,
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Period,
|
||||
when: TerminalContextKeys.focus,
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
accessor.get(ITerminalService).activeInstance?.contextualActions?.showQuickFixMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Register new with profile command
|
||||
refreshTerminalActions([]);
|
||||
|
||||
@@ -350,7 +370,7 @@ export function registerTerminalActions() {
|
||||
return;
|
||||
}
|
||||
const output = command.getOutput();
|
||||
if (output) {
|
||||
if (output && typeof output === 'string') {
|
||||
await accessor.get(IClipboardService).writeText(output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ContextualMatchResult, ICommandAction, ITerminalContextualActionOptions, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ITerminalCommand } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export const GitCommandLineRegex = /git/;
|
||||
export const GitPushCommandLineRegex = /git\s+push/;
|
||||
export const AnyCommandLineRegex = /.{4,}/;
|
||||
export const GitSimilarOutputRegex = /most similar command is\s+([^\s]{3,})/;
|
||||
export const FreePortOutputRegex = /address already in use \d\.\d\.\d\.\d:(\d\d\d\d)\s+|Unable to bind [^ ]*:(\d+)|can't listen on port (\d+)|listen EADDRINUSE [^ ]*:(\d+)/;
|
||||
export const GitPushOutputRegex = /git push --set-upstream origin ([^\s]+)\s+/;
|
||||
export const GitCreatePrOutputRegex = /Create a pull request for \'([^\s]+)\' on GitHub by visiting:\s+remote:\s+(https:.+pull.+)\s+/;
|
||||
|
||||
export function gitSimilarCommand(): ITerminalContextualActionOptions {
|
||||
return {
|
||||
commandLineMatcher: GitCommandLineRegex,
|
||||
outputMatcher: { lineMatcher: GitSimilarOutputRegex, anchor: 'bottom' },
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Run git ${matchResult.outputMatch[1]}` : ``,
|
||||
exitCode: 1,
|
||||
getActions: (matchResult: ContextualMatchResult, command: ITerminalCommand) => {
|
||||
const actions: ICommandAction[] = [];
|
||||
const fixedCommand = matchResult?.outputMatch?.[1];
|
||||
if (!fixedCommand) {
|
||||
return;
|
||||
}
|
||||
const label = localize("terminal.gitSimilarCommand", "Run git {0}", fixedCommand);
|
||||
actions.push({
|
||||
class: undefined, tooltip: label, id: 'terminal.gitSimilarCommand', label, enabled: true,
|
||||
commandToRunInTerminal: `git ${fixedCommand}`,
|
||||
addNewLine: true,
|
||||
run: () => { }
|
||||
});
|
||||
return actions;
|
||||
}
|
||||
};
|
||||
}
|
||||
export function freePort(terminalInstance?: Partial<ITerminalInstance>): ITerminalContextualActionOptions {
|
||||
return {
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Free port ${matchResult.outputMatch[1]}` : '',
|
||||
commandLineMatcher: AnyCommandLineRegex,
|
||||
outputMatcher: !isWindows ? { lineMatcher: FreePortOutputRegex, anchor: 'bottom' } : undefined,
|
||||
getActions: (matchResult: ContextualMatchResult, command: ITerminalCommand) => {
|
||||
const port = matchResult?.outputMatch?.[1];
|
||||
if (!port) {
|
||||
return;
|
||||
}
|
||||
const actions: ICommandAction[] = [];
|
||||
const label = localize("terminal.freePort", "Free port {0}", port);
|
||||
actions.push({
|
||||
class: undefined, tooltip: label, id: 'terminal.freePort', label, enabled: true,
|
||||
run: async () => {
|
||||
await terminalInstance?.freePortKillProcess?.(port);
|
||||
},
|
||||
commandToRunInTerminal: command.command,
|
||||
addNewLine: false
|
||||
});
|
||||
return actions;
|
||||
}
|
||||
};
|
||||
}
|
||||
export function gitPushSetUpstream(): ITerminalContextualActionOptions {
|
||||
return {
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Git push ${matchResult.outputMatch[1]}` : '',
|
||||
commandLineMatcher: GitPushCommandLineRegex,
|
||||
outputMatcher: { lineMatcher: GitPushOutputRegex, anchor: 'bottom' },
|
||||
exitCode: 128,
|
||||
getActions: (matchResult: ContextualMatchResult, command: ITerminalCommand) => {
|
||||
const branch = matchResult?.outputMatch?.[1];
|
||||
if (!branch) {
|
||||
return;
|
||||
}
|
||||
const actions: ICommandAction[] = [];
|
||||
const label = localize("terminal.gitPush", "Git push {0}", branch);
|
||||
command.command = `git push --set-upstream origin ${branch}`;
|
||||
actions.push({
|
||||
class: undefined, tooltip: label, id: 'terminal.gitPush', label, enabled: true,
|
||||
commandToRunInTerminal: command.command,
|
||||
addNewLine: true,
|
||||
run: () => { }
|
||||
});
|
||||
return actions;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function gitCreatePr(openerService: IOpenerService): ITerminalContextualActionOptions {
|
||||
return {
|
||||
actionName: (matchResult: ContextualMatchResult) => matchResult.outputMatch ? `Create PR for ${matchResult.outputMatch[1]}` : '',
|
||||
commandLineMatcher: GitPushCommandLineRegex,
|
||||
outputMatcher: { lineMatcher: GitCreatePrOutputRegex, anchor: 'bottom' },
|
||||
exitCode: 0,
|
||||
getActions: (matchResult: ContextualMatchResult, command?: ITerminalCommand) => {
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
const branch = matchResult?.outputMatch?.[1];
|
||||
const link = matchResult?.outputMatch?.[2];
|
||||
if (!branch || !link) {
|
||||
return;
|
||||
}
|
||||
const actions: IAction[] = [];
|
||||
const label = localize("terminal.gitCreatePr", "Create PR");
|
||||
actions.push({
|
||||
class: undefined, tooltip: label, id: 'terminal.gitCreatePr', label, enabled: true,
|
||||
run: () => openerService.open(link)
|
||||
});
|
||||
return actions;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -59,8 +59,9 @@ import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/brows
|
||||
import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import { IDetectedLinks, TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
|
||||
import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick';
|
||||
import { IRequestAddInstanceToGroupEvent, ITerminalExternalLinkProvider, ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IRequestAddInstanceToGroupEvent, ITerminalContextualActionOptions, ITerminalExternalLinkProvider, ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
|
||||
import { gitSimilarCommand, gitCreatePr, gitPushSetUpstream, freePort } from 'vs/workbench/contrib/terminal/browser/terminalBaseContextualActions';
|
||||
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';
|
||||
@@ -72,6 +73,7 @@ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTy
|
||||
import { getTerminalResourcesFromDragEvent, getTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri';
|
||||
import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget';
|
||||
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
|
||||
import { ContextualActionAddon, IContextualAction } from 'vs/workbench/contrib/terminal/browser/xterm/contextualActionAddon';
|
||||
import { LineDataEventAddon } from 'vs/workbench/contrib/terminal/browser/xterm/lineDataEventAddon';
|
||||
import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/xterm/navigationModeAddon';
|
||||
import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal';
|
||||
@@ -138,10 +140,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
private readonly _scopedInstantiationService: IInstantiationService;
|
||||
|
||||
private readonly _processManager: ITerminalProcessManager;
|
||||
readonly _processManager: ITerminalProcessManager;
|
||||
private readonly _resource: URI;
|
||||
private _shutdownPersistentProcessId: number | undefined;
|
||||
|
||||
protected get processManager() { return this._processManager; }
|
||||
// Enables disposal of the xterm onKey
|
||||
// event when the CwdDetection capability
|
||||
// is added
|
||||
@@ -205,10 +207,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private _target?: TerminalLocation | undefined;
|
||||
private _disableShellIntegrationReporting: boolean | undefined;
|
||||
private _usedShellIntegrationInjection: boolean = false;
|
||||
private _contextualActionAddon: ContextualActionAddon | undefined;
|
||||
|
||||
readonly capabilities = new TerminalCapabilityStoreMultiplexer();
|
||||
readonly statusList: ITerminalStatusList;
|
||||
|
||||
/**
|
||||
* Enables opening the contextual actions, if any, that are available
|
||||
* and registering of command finished listeners
|
||||
*/
|
||||
get contextualActions(): IContextualAction | undefined { return this._contextualActionAddon; }
|
||||
|
||||
readonly findWidget: TerminalFindWidget;
|
||||
|
||||
xterm?: XtermTerminal;
|
||||
@@ -450,7 +459,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._scopedInstantiationService.invokeFunction(getDirectoryHistory)?.add(e, { remoteAuthority: this.remoteAuthority });
|
||||
});
|
||||
} else if (e === TerminalCapability.CommandDetection) {
|
||||
this.capabilities.get(TerminalCapability.CommandDetection)?.onCommandFinished(e => {
|
||||
const commandCapability = this.capabilities.get(TerminalCapability.CommandDetection);
|
||||
commandCapability?.onCommandFinished(e => {
|
||||
if (e.command.trim().length > 0) {
|
||||
this._scopedInstantiationService.invokeFunction(getCommandHistory)?.add(e.command, { shellType: this._shellType });
|
||||
}
|
||||
@@ -590,6 +600,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerContextualActions(...actionOptions: ITerminalContextualActionOptions[]): void {
|
||||
for (const actionOption of actionOptions) {
|
||||
this.contextualActions?.registerCommandFinishedListener(actionOption);
|
||||
}
|
||||
}
|
||||
|
||||
private _initDimensions(): void {
|
||||
// The terminal panel needs to have been created to get the real view dimensions
|
||||
if (!this._container) {
|
||||
@@ -702,6 +718,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
const xterm = this._scopedInstantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel, this.capabilities, this.disableShellIntegrationReporting);
|
||||
this.xterm = xterm;
|
||||
this._contextualActionAddon = this._scopedInstantiationService.createInstance(ContextualActionAddon, this.capabilities);
|
||||
this.xterm?.raw.loadAddon(this._contextualActionAddon);
|
||||
this.registerContextualActions(gitSimilarCommand(), gitCreatePr(this._openerService), gitPushSetUpstream(), freePort(this));
|
||||
this._register(this._contextualActionAddon.onDidRequestRerunCommand((e) => this.sendText(e.command, e.addNewLine || false)));
|
||||
const lineDataEventAddon = new LineDataEventAddon();
|
||||
this.xterm.raw.loadAddon(lineDataEventAddon);
|
||||
this.updateAccessibilitySupport();
|
||||
@@ -709,7 +729,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
if (e.copyAsHtml) {
|
||||
this.copySelection(true, e.command);
|
||||
} else {
|
||||
this.sendText(e.command.command, true);
|
||||
this.sendText(e.command.command, e.noNewLine ? false : true);
|
||||
}
|
||||
});
|
||||
// Write initial text, deferring onLineFeed listener when applicable to avoid firing
|
||||
@@ -1483,6 +1503,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this.xterm?.markTracker.scrollToClosestMarker(startMarkId, endMarkId, highlight);
|
||||
}
|
||||
|
||||
public async freePortKillProcess(port: string): Promise<void> {
|
||||
await this._processManager?.freePortKillProcess(port);
|
||||
}
|
||||
|
||||
private _onProcessData(ev: IProcessDataEvent): void {
|
||||
const messageId = ++this._latestXtermWriteData;
|
||||
if (ev.trackCommit) {
|
||||
|
||||
@@ -36,6 +36,8 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
/** The amount of time to consider terminal errors to be related to the launch */
|
||||
const LAUNCHING_DURATION = 500;
|
||||
@@ -136,7 +138,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@INotificationService private readonly _notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -171,6 +174,17 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
}
|
||||
}
|
||||
|
||||
async freePortKillProcess(port: string): Promise<void> {
|
||||
try {
|
||||
if (this._process?.freePortKillProcess) {
|
||||
const result = await this._process?.freePortKillProcess(port);
|
||||
this._notificationService.notify({ message: `Killed process w ID: ${result.processId} to free port ${result.port}`, severity: Severity.Info });
|
||||
}
|
||||
} catch (e) {
|
||||
this._notificationService.notify({ message: `Could not kill process for port ${port} wth error ${e}`, severity: Severity.Warning });
|
||||
}
|
||||
}
|
||||
|
||||
override dispose(immediate: boolean = false): void {
|
||||
this._isDisposed = true;
|
||||
if (this._process) {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
// Importing types is safe in any layer
|
||||
// eslint-disable-next-line local/code-import-patterns
|
||||
import type { ITerminalAddon } from 'xterm-headless';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICommandAction, ITerminalContextualActionOptions } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { DecorationSelector, updateLayout } from 'vs/workbench/contrib/terminal/browser/xterm/decorationStyles';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
|
||||
export interface IContextualAction {
|
||||
/**
|
||||
* Shows the quick fix menu
|
||||
*/
|
||||
showQuickFixMenu(): void;
|
||||
|
||||
/**
|
||||
* Registers a listener on onCommandFinished scoped to a particular command or regular
|
||||
* expression and provides a callback to be executed for commands that match.
|
||||
*/
|
||||
registerCommandFinishedListener(options: ITerminalContextualActionOptions): void;
|
||||
}
|
||||
|
||||
export interface IContextualActionAdddon extends IContextualAction {
|
||||
onDidRequestRerunCommand: Event<{ command: string; addNewLine?: boolean }>;
|
||||
}
|
||||
|
||||
export class ContextualActionAddon extends Disposable implements ITerminalAddon, IContextualActionAdddon {
|
||||
private readonly _onDidRequestRerunCommand = new Emitter<{ command: string; addNewLine?: boolean }>();
|
||||
readonly onDidRequestRerunCommand = this._onDidRequestRerunCommand.event;
|
||||
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
private _currentQuickFixElement: HTMLElement | undefined;
|
||||
|
||||
private _decorationMarkerIds = new Set<number>();
|
||||
|
||||
private _commandListeners: Map<string, ITerminalContextualActionOptions[]> = new Map();
|
||||
|
||||
private _matchActions: ICommandAction[] | undefined;
|
||||
|
||||
constructor(private readonly _capabilities: ITerminalCapabilityStore,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService) {
|
||||
super();
|
||||
const commandDetectionCapability = this._capabilities.get(TerminalCapability.CommandDetection);
|
||||
if (commandDetectionCapability) {
|
||||
this._registerCommandFinishedHandler();
|
||||
} else {
|
||||
this._capabilities.onDidAddCapability(c => {
|
||||
if (c === TerminalCapability.CommandDetection) {
|
||||
this._registerCommandFinishedHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
showQuickFixMenu(): void {
|
||||
this._currentQuickFixElement?.click();
|
||||
}
|
||||
|
||||
registerCommandFinishedListener(options: ITerminalContextualActionOptions): void {
|
||||
const matcherKey = options.commandLineMatcher.toString();
|
||||
const currentOptions = this._commandListeners.get(matcherKey) || [];
|
||||
currentOptions.push(options);
|
||||
this._commandListeners.set(matcherKey, currentOptions);
|
||||
}
|
||||
|
||||
private _registerCommandFinishedHandler(): void {
|
||||
const terminal = this._terminal;
|
||||
const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection);
|
||||
if (!terminal || !commandDetection) {
|
||||
return;
|
||||
}
|
||||
this._register(commandDetection.onCommandFinished(async command => {
|
||||
this._matchActions = getMatchActions(command, this._commandListeners, this._onDidRequestRerunCommand);
|
||||
}));
|
||||
// The buffer is not ready by the time command finish
|
||||
// is called. Add the decoration on command start using the actions, if any,
|
||||
// from the last command
|
||||
this._register(commandDetection.onCommandStarted(() => {
|
||||
if (this._matchActions) {
|
||||
this._registerContextualDecoration();
|
||||
this._matchActions = undefined;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _registerContextualDecoration(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
const marker = this._terminal.registerMarker();
|
||||
if (!marker) {
|
||||
return;
|
||||
}
|
||||
const actions = this._matchActions;
|
||||
const decoration = this._terminal.registerDecoration({ marker, layer: 'top' });
|
||||
decoration?.onRender((e: HTMLElement) => {
|
||||
if (!this._decorationMarkerIds.has(decoration.marker.id)) {
|
||||
this._currentQuickFixElement = e;
|
||||
e.classList.add(DecorationSelector.QuickFix, DecorationSelector.Codicon, DecorationSelector.CommandDecoration, DecorationSelector.XtermDecoration);
|
||||
e.style.color = '#ffcc00';
|
||||
updateLayout(this._configurationService, e);
|
||||
if (actions) {
|
||||
this._decorationMarkerIds.add(decoration.marker.id);
|
||||
dom.addDisposableListener(e, dom.EventType.CLICK, () => {
|
||||
this._contextMenuService.showContextMenu({ getAnchor: () => e, getActions: () => actions });
|
||||
this._contextMenuService.onDidHideContextMenu(() => decoration.dispose());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getMatchActions(command: ITerminalCommand, actionOptions: Map<string, ITerminalContextualActionOptions[]>, onDidRequestRerunCommand?: Emitter<{ command: string; addNewLine?: boolean }>): IAction[] | undefined {
|
||||
const matchActions: IAction[] = [];
|
||||
const newCommand = command.command;
|
||||
for (const options of actionOptions.values()) {
|
||||
for (const actionOption of options) {
|
||||
if (actionOption.exitCode !== undefined && command.exitCode !== actionOption.exitCode) {
|
||||
continue;
|
||||
}
|
||||
const commandLineMatch = newCommand.match(actionOption.commandLineMatcher);
|
||||
if (!commandLineMatch) {
|
||||
continue;
|
||||
}
|
||||
const outputMatcher = actionOption.outputMatcher;
|
||||
let outputMatch;
|
||||
if (outputMatcher) {
|
||||
outputMatch = command.getOutputMatch(outputMatcher);
|
||||
}
|
||||
const actions = actionOption.getActions({ commandLineMatch, outputMatch }, command);
|
||||
if (!actions) {
|
||||
return matchActions.length === 0 ? undefined : matchActions;
|
||||
}
|
||||
for (const a of actions) {
|
||||
matchActions.push({
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
class: a.class,
|
||||
enabled: a.enabled,
|
||||
run: async () => {
|
||||
await a.run();
|
||||
if (a.commandToRunInTerminal) {
|
||||
onDidRequestRerunCommand?.fire({ command: a.commandToRunInTerminal, addNewLine: a.addNewLine });
|
||||
}
|
||||
},
|
||||
tooltip: a.tooltip
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchActions.length === 0 ? undefined : matchActions;
|
||||
}
|
||||
@@ -28,22 +28,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { terminalDecorationError, terminalDecorationIncomplete, terminalDecorationMark, terminalDecorationSuccess } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
|
||||
import { TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
|
||||
const enum DecorationSelector {
|
||||
CommandDecoration = 'terminal-command-decoration',
|
||||
Hide = 'hide',
|
||||
ErrorColor = 'error',
|
||||
DefaultColor = 'default-color',
|
||||
Default = 'default',
|
||||
Codicon = 'codicon',
|
||||
XtermDecoration = 'xterm-decoration',
|
||||
OverviewRuler = '.xterm-decoration-overview-ruler'
|
||||
}
|
||||
|
||||
const enum DecorationStyles {
|
||||
DefaultDimension = 16,
|
||||
MarginLeft = -17,
|
||||
}
|
||||
import { DecorationSelector, updateLayout } from 'vs/workbench/contrib/terminal/browser/xterm/decorationStyles';
|
||||
|
||||
interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties }
|
||||
|
||||
@@ -170,9 +155,9 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
|
||||
}
|
||||
|
||||
public refreshLayouts(): void {
|
||||
this._updateLayout(this._placeholderDecoration?.element);
|
||||
updateLayout(this._configurationService, this._placeholderDecoration?.element);
|
||||
for (const decoration of this._decorations) {
|
||||
this._updateLayout(decoration[1].decoration.element);
|
||||
updateLayout(this._configurationService, decoration[1].decoration.element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +298,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
|
||||
}
|
||||
if (!element.classList.contains(DecorationSelector.Codicon) || command?.marker?.line === 0) {
|
||||
// first render or buffer was cleared
|
||||
this._updateLayout(element);
|
||||
updateLayout(this._configurationService, element);
|
||||
this._updateClasses(element, command?.exitCode, command?.markProperties || markProperties);
|
||||
}
|
||||
});
|
||||
@@ -329,23 +314,6 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
|
||||
return [this._createContextMenu(element, command), ...this._createHover(element, command)];
|
||||
}
|
||||
|
||||
private _updateLayout(element?: HTMLElement): void {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const fontSize = this._configurationService.inspect(TerminalSettingId.FontSize).value;
|
||||
const defaultFontSize = this._configurationService.inspect(TerminalSettingId.FontSize).defaultValue;
|
||||
const lineHeight = this._configurationService.inspect(TerminalSettingId.LineHeight).value;
|
||||
if (typeof fontSize === 'number' && typeof defaultFontSize === 'number' && typeof lineHeight === 'number') {
|
||||
const scalar = (fontSize / defaultFontSize) <= 1 ? (fontSize / defaultFontSize) : 1;
|
||||
// must be inlined to override the inlined styles from xterm
|
||||
element.style.width = `${scalar * DecorationStyles.DefaultDimension}px`;
|
||||
element.style.height = `${scalar * DecorationStyles.DefaultDimension * lineHeight}px`;
|
||||
element.style.fontSize = `${scalar * DecorationStyles.DefaultDimension}px`;
|
||||
element.style.marginLeft = `${scalar * DecorationStyles.MarginLeft}px`;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateClasses(element?: HTMLElement, exitCode?: number, markProperties?: IMarkProperties): void {
|
||||
if (!element) {
|
||||
return;
|
||||
@@ -444,7 +412,12 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
|
||||
const labelText = localize("terminal.copyOutput", 'Copy Output');
|
||||
actions.push({
|
||||
class: undefined, tooltip: labelText, id: 'terminal.copyOutput', label: labelText, enabled: true,
|
||||
run: () => this._clipboardService.writeText(command.getOutput()!)
|
||||
run: () => {
|
||||
const text = command.getOutput();
|
||||
if (typeof text === 'string') {
|
||||
this._clipboardService.writeText(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
const labelHtml = localize("terminal.copyOutputAsHtml", 'Copy Output as HTML');
|
||||
actions.push({
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
|
||||
const enum DecorationStyles {
|
||||
DefaultDimension = 16,
|
||||
MarginLeft = -17,
|
||||
}
|
||||
|
||||
export const enum DecorationSelector {
|
||||
CommandDecoration = 'terminal-command-decoration',
|
||||
Hide = 'hide',
|
||||
ErrorColor = 'error',
|
||||
DefaultColor = 'default-color',
|
||||
Default = 'default',
|
||||
Codicon = 'codicon',
|
||||
XtermDecoration = 'xterm-decoration',
|
||||
OverviewRuler = '.xterm-decoration-overview-ruler',
|
||||
QuickFix = 'codicon-light-bulb'
|
||||
}
|
||||
|
||||
export function updateLayout(configurationService: IConfigurationService, element?: HTMLElement): void {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const fontSize = configurationService.inspect(TerminalSettingId.FontSize).value;
|
||||
const defaultFontSize = configurationService.inspect(TerminalSettingId.FontSize).defaultValue;
|
||||
const lineHeight = configurationService.inspect(TerminalSettingId.LineHeight).value;
|
||||
if (typeof fontSize === 'number' && typeof defaultFontSize === 'number' && typeof lineHeight === 'number') {
|
||||
const scalar = (fontSize / defaultFontSize) <= 1 ? (fontSize / defaultFontSize) : 1;
|
||||
// must be inlined to override the inlined styles from xterm
|
||||
element.style.width = `${scalar * DecorationStyles.DefaultDimension}px`;
|
||||
element.style.height = `${scalar * DecorationStyles.DefaultDimension * lineHeight}px`;
|
||||
element.style.fontSize = `${scalar * DecorationStyles.DefaultDimension}px`;
|
||||
element.style.marginLeft = `${scalar * DecorationStyles.MarginLeft}px`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
|
||||
// Always on addons
|
||||
private _markNavigationAddon: MarkNavigationAddon;
|
||||
private _shellIntegrationAddon: ShellIntegrationAddon;
|
||||
|
||||
private _decorationAddon: DecorationAddon;
|
||||
|
||||
// Optional addons
|
||||
@@ -102,8 +103,10 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
|
||||
private _lastFindResult: { resultIndex: number; resultCount: number } | undefined;
|
||||
get findResult(): { resultIndex: number; resultCount: number } | undefined { return this._lastFindResult; }
|
||||
|
||||
private readonly _onDidRequestRunCommand = new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean }>();
|
||||
private readonly _onDidRequestRunCommand = new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>();
|
||||
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
|
||||
private readonly _onDidRequestFreePort = new Emitter<string>();
|
||||
readonly onDidRequestFreePort = this._onDidRequestFreePort.event;
|
||||
private readonly _onDidChangeFindResults = new Emitter<{ resultIndex: number; resultCount: number } | undefined>();
|
||||
readonly onDidChangeFindResults = this._onDidChangeFindResults.event;
|
||||
private readonly _onDidChangeSelection = new Emitter<void>();
|
||||
|
||||
@@ -58,7 +58,6 @@ export interface ICreateTerminalProcessResult {
|
||||
}
|
||||
|
||||
export class RemoteTerminalChannelClient implements IPtyHostController {
|
||||
|
||||
get onPtyHostExit(): Event<number> {
|
||||
return this._channel.listen<number>('$onPtyHostExitEvent');
|
||||
}
|
||||
@@ -238,7 +237,9 @@ export class RemoteTerminalChannelClient implements IPtyHostController {
|
||||
sendCommandResult(reqId: number, isError: boolean, payload: any): Promise<void> {
|
||||
return this._channel.call('$sendCommandResult', [reqId, isError, payload]);
|
||||
}
|
||||
|
||||
freePortKillProcess(id: number, port: string): Promise<{ port: string; processId: string }> {
|
||||
return this._channel.call('$freePortKillProcess', [id, port]);
|
||||
}
|
||||
installAutoReply(match: string, reply: string): Promise<void> {
|
||||
return this._channel.call('$installAutoReply', [match, reply]);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,13 @@ export interface IShellLaunchConfigResolveOptions {
|
||||
allowAutomationShell?: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminalOutputMatcher {
|
||||
lineMatcher: string | RegExp;
|
||||
anchor?: 'top' | 'bottom';
|
||||
offset?: number;
|
||||
length?: number;
|
||||
}
|
||||
|
||||
export interface ITerminalBackend {
|
||||
readonly remoteAuthority: string | undefined;
|
||||
|
||||
@@ -347,6 +354,7 @@ export interface ITerminalCommand {
|
||||
markProperties?: IMarkProperties;
|
||||
hasOutput(): boolean;
|
||||
getOutput(): string | undefined;
|
||||
getOutputMatch(outputMatcher: ITerminalOutputMatcher): RegExpMatchArray | undefined;
|
||||
}
|
||||
|
||||
export interface INavigationMode {
|
||||
@@ -413,6 +421,7 @@ export interface ITerminalProcessManager extends IDisposable {
|
||||
refreshProperty<T extends ProcessPropertyType>(type: T): Promise<IProcessPropertyMap[T]>;
|
||||
updateProperty<T extends ProcessPropertyType>(property: T, value: IProcessPropertyMap[T]): void;
|
||||
getBackendOS(): Promise<OperatingSystem>;
|
||||
freePortKillProcess(port: string): void;
|
||||
}
|
||||
|
||||
export const enum ProcessState {
|
||||
@@ -510,7 +519,7 @@ export const enum TerminalCommandId {
|
||||
ResizePaneLeft = 'workbench.action.terminal.resizePaneLeft',
|
||||
ResizePaneRight = 'workbench.action.terminal.resizePaneRight',
|
||||
ResizePaneUp = 'workbench.action.terminal.resizePaneUp',
|
||||
CreateWithProfileButton = 'workbench.action.terminal.createProfileButton',
|
||||
CreateWithProfileButton = 'workbench.action.terminal.gitCreateProfileButton',
|
||||
SizeToContentWidth = 'workbench.action.terminal.sizeToContentWidth',
|
||||
SizeToContentWidthInstance = 'workbench.action.terminal.sizeToContentWidthInstance',
|
||||
ResizePaneDown = 'workbench.action.terminal.resizePaneDown',
|
||||
@@ -552,6 +561,7 @@ export const enum TerminalCommandId {
|
||||
SelectToNextLine = 'workbench.action.terminal.selectToNextLine',
|
||||
ToggleEscapeSequenceLogging = 'toggleEscapeSequenceLogging',
|
||||
SendSequence = 'workbench.action.terminal.sendSequence',
|
||||
QuickFix = 'workbench.action.terminal.quickFix',
|
||||
ToggleFindRegex = 'workbench.action.terminal.toggleFindRegex',
|
||||
ToggleFindWholeWord = 'workbench.action.terminal.toggleFindWholeWord',
|
||||
ToggleFindCaseSensitive = 'workbench.action.terminal.toggleFindCaseSensitive',
|
||||
|
||||
@@ -76,6 +76,12 @@ export class LocalPty extends Disposable implements ITerminalChildProcess {
|
||||
}
|
||||
this._localPtyService.resize(this.id, cols, rows);
|
||||
}
|
||||
freePortKillProcess(port: string): Promise<{ port: string; processId: string }> {
|
||||
if (!this._localPtyService.freePortKillProcess) {
|
||||
throw new Error('freePortKillProcess does not exist on the local pty service');
|
||||
}
|
||||
return this._localPtyService.freePortKillProcess(this.id, port);
|
||||
}
|
||||
async getInitialCwd(): Promise<string> {
|
||||
return this._properties.initialCwd;
|
||||
}
|
||||
|
||||
+8
-2
@@ -7,8 +7,11 @@ import { deepStrictEqual, ok } from 'assert';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { Terminal } from 'xterm';
|
||||
import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||
import { ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
|
||||
async function writeP(terminal: Terminal, data: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
@@ -64,7 +67,10 @@ suite('CommandDetectionCapability', () => {
|
||||
|
||||
setup(() => {
|
||||
xterm = new Terminal({ allowProposedApi: true, cols: 80 });
|
||||
capability = new TestCommandDetectionCapability(xterm, new NullLogService());
|
||||
const instantiationService = new TestInstantiationService();
|
||||
instantiationService.stub(IContextMenuService, { showContextMenu(delegate: IContextMenuDelegate): void { } } as Partial<IContextMenuService>);
|
||||
instantiationService.stub(ILogService, new NullLogService());
|
||||
capability = instantiationService.createInstance(TestCommandDetectionCapability, xterm);
|
||||
addEvents = [];
|
||||
capability.onCommandFinished(e => addEvents.push(e));
|
||||
assertCommands([]);
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { strictEqual } from 'assert';
|
||||
import { OpenerService } from 'vs/editor/browser/services/openerService';
|
||||
import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
|
||||
import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
|
||||
import { ICommandAction, ITerminalInstance, ITerminalOutputMatcher } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { freePort, FreePortOutputRegex, gitCreatePr, GitCreatePrOutputRegex, GitPushOutputRegex, gitPushSetUpstream, gitSimilarCommand, GitSimilarOutputRegex } from 'vs/workbench/contrib/terminal/browser/terminalBaseContextualActions';
|
||||
import { ContextualActionAddon, getMatchActions } from 'vs/workbench/contrib/terminal/browser/xterm/contextualActionAddon';
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
suite('ContextualActionAddon', () => {
|
||||
let contextualActionAddon: ContextualActionAddon;
|
||||
let terminalInstance: Pick<ITerminalInstance, 'freePortKillProcess'>;
|
||||
let commandDetection: CommandDetectionCapability;
|
||||
let openerService: OpenerService;
|
||||
setup(() => {
|
||||
const instantiationService = new TestInstantiationService();
|
||||
const xterm = new Terminal({
|
||||
allowProposedApi: true,
|
||||
cols: 80,
|
||||
rows: 30
|
||||
});
|
||||
const capabilities = new TerminalCapabilityStore();
|
||||
instantiationService.stub(ILogService, new NullLogService());
|
||||
commandDetection = instantiationService.createInstance(CommandDetectionCapability, xterm);
|
||||
capabilities.add(TerminalCapability.CommandDetection, commandDetection);
|
||||
instantiationService.stub(IContextMenuService, instantiationService.createInstance(ContextMenuService));
|
||||
openerService = instantiationService.createInstance(OpenerService);
|
||||
instantiationService.stub(IOpenerService, openerService);
|
||||
terminalInstance = {
|
||||
async freePortKillProcess(port: string): Promise<void> { }
|
||||
} as Pick<ITerminalInstance, 'freePortKillProcess'>;
|
||||
contextualActionAddon = instantiationService.createInstance(ContextualActionAddon, capabilities);
|
||||
xterm.loadAddon(contextualActionAddon);
|
||||
});
|
||||
suite('registerCommandFinishedListener & getMatchActions', () => {
|
||||
suite('gitSimilarCommand', async () => {
|
||||
const expectedMap = new Map();
|
||||
const command = `git sttatus`;
|
||||
const output = `git: 'sttatus' is not a git command. See 'git --help'.
|
||||
|
||||
The most similar command is
|
||||
status`;
|
||||
const exitCode = 1;
|
||||
const actions = [
|
||||
{
|
||||
id: 'terminal.gitSimilarCommand',
|
||||
label: 'Run git status',
|
||||
run: true,
|
||||
tooltip: 'Run git status',
|
||||
enabled: true
|
||||
}
|
||||
];
|
||||
setup(() => {
|
||||
const command = gitSimilarCommand();
|
||||
expectedMap.set(command.commandLineMatcher.toString(), [command]);
|
||||
contextualActionAddon.registerCommandFinishedListener(command);
|
||||
});
|
||||
suite('returns undefined when', () => {
|
||||
test('output does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, `invalid output`, GitSimilarOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('command does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(`gt sttatus`, output, GitSimilarOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('exit code does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, output, GitSimilarOutputRegex, 2), expectedMap), undefined);
|
||||
});
|
||||
});
|
||||
test('returns actions', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(command, output, GitSimilarOutputRegex, exitCode), expectedMap), actions);
|
||||
});
|
||||
});
|
||||
suite('freePort', () => {
|
||||
const expected = new Map();
|
||||
const portCommand = `yarn start dev`;
|
||||
const output = `yarn run v1.22.17
|
||||
warning ../../package.json: No license field
|
||||
Error: listen EADDRINUSE: address already in use 0.0.0.0:3000
|
||||
at Server.setupListenHandle [as _listen2] (node:net:1315:16)
|
||||
at listenInCluster (node:net:1363:12)
|
||||
at doListen (node:net:1501:7)
|
||||
at processTicksAndRejections (node:internal/process/task_queues:84:21)
|
||||
Emitted 'error' event on WebSocketServer instance at:
|
||||
at Server.emit (node:events:394:28)
|
||||
at emitErrorNT (node:net:1342:8)
|
||||
at processTicksAndRejections (node:internal/process/task_queues:83:21) {
|
||||
}
|
||||
error Command failed with exit code 1.
|
||||
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.`;
|
||||
const actionOptions = [{
|
||||
id: 'terminal.freePort',
|
||||
label: 'Free port 3000',
|
||||
run: true,
|
||||
tooltip: 'Free port 3000',
|
||||
enabled: true
|
||||
}];
|
||||
setup(() => {
|
||||
const command = freePort(terminalInstance);
|
||||
expected.set(command.commandLineMatcher.toString(), [command]);
|
||||
contextualActionAddon.registerCommandFinishedListener(command);
|
||||
});
|
||||
suite('returns undefined when', () => {
|
||||
test('output does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(portCommand, `invalid output`, FreePortOutputRegex), expected), undefined);
|
||||
});
|
||||
});
|
||||
test('returns actions', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(portCommand, output, FreePortOutputRegex), expected), actionOptions);
|
||||
});
|
||||
});
|
||||
suite('gitPushSetUpstream', () => {
|
||||
const expectedMap = new Map();
|
||||
const command = `git push`;
|
||||
const output = `fatal: The current branch test22 has no upstream branch.
|
||||
To push the current branch and set the remote as upstream, use
|
||||
|
||||
git push --set-upstream origin test22 `;
|
||||
const exitCode = 128;
|
||||
const actions = [
|
||||
{
|
||||
id: 'terminal.gitPush',
|
||||
label: 'Git push test22',
|
||||
run: true,
|
||||
tooltip: 'Git push test22',
|
||||
enabled: true
|
||||
}
|
||||
];
|
||||
setup(() => {
|
||||
const command = gitPushSetUpstream();
|
||||
expectedMap.set(command.commandLineMatcher.toString(), [command]);
|
||||
contextualActionAddon.registerCommandFinishedListener(command);
|
||||
});
|
||||
suite('returns undefined when', () => {
|
||||
test('output does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, `invalid output`, GitPushOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('command does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(`git status`, output, GitPushOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('exit code does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, output, GitPushOutputRegex, 2), expectedMap), undefined);
|
||||
});
|
||||
});
|
||||
test('returns actions', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(command, output, GitPushOutputRegex, exitCode), expectedMap), actions);
|
||||
});
|
||||
});
|
||||
suite('gitCreatePr', () => {
|
||||
const expectedMap = new Map();
|
||||
const command = `git push`;
|
||||
const output = `Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
|
||||
remote:
|
||||
remote: Create a pull request for 'test22' on GitHub by visiting:
|
||||
remote: https://github.com/meganrogge/xterm.js/pull/new/test22
|
||||
remote:
|
||||
To https://github.com/meganrogge/xterm.js
|
||||
* [new branch] test22 -> test22
|
||||
Branch 'test22' set up to track remote branch 'test22' from 'origin'. `;
|
||||
const exitCode = 0;
|
||||
const actions = [
|
||||
{
|
||||
id: 'terminal.gitCreatePr',
|
||||
label: 'Create PR',
|
||||
run: true,
|
||||
tooltip: 'Create PR',
|
||||
enabled: true
|
||||
}
|
||||
];
|
||||
setup(() => {
|
||||
const command = gitCreatePr(openerService);
|
||||
expectedMap.set(command.commandLineMatcher.toString(), [command]);
|
||||
contextualActionAddon.registerCommandFinishedListener(command);
|
||||
});
|
||||
suite('returns undefined when', () => {
|
||||
test('output does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, `invalid output`, GitCreatePrOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('command does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(`git status`, output, GitCreatePrOutputRegex, exitCode), expectedMap), undefined);
|
||||
});
|
||||
test('exit code does not match', () => {
|
||||
strictEqual(getMatchActions(createCommand(command, output, GitCreatePrOutputRegex, 2), expectedMap), undefined);
|
||||
});
|
||||
});
|
||||
test('returns actions', () => {
|
||||
assertMatchOptions(getMatchActions(createCommand(command, output, GitCreatePrOutputRegex, exitCode), expectedMap), actions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createCommand(command: string, output: string, outputMatcher?: RegExp | string, exitCode?: number): ITerminalCommand {
|
||||
return {
|
||||
command,
|
||||
exitCode,
|
||||
getOutput: () => { return output; },
|
||||
getOutputMatch: (matcher: ITerminalOutputMatcher) => {
|
||||
if (outputMatcher) {
|
||||
return output.match(outputMatcher) ?? undefined;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
hasOutput: () => !!output
|
||||
};
|
||||
}
|
||||
|
||||
function assertMatchOptions(actual: ICommandAction[] | undefined, expected: { id: string; label: string; run: boolean; tooltip: string; enabled: boolean }[]): void {
|
||||
strictEqual(actual?.length, expected.length);
|
||||
let index = 0;
|
||||
for (const i of actual) {
|
||||
const j = expected[index];
|
||||
strictEqual(i.id, j.id, `ID`);
|
||||
strictEqual(i.enabled, j.enabled, `enabled`);
|
||||
strictEqual(i.label, j.label, `label`);
|
||||
strictEqual(!!i.run, j.run, `run`);
|
||||
strictEqual(i.tooltip, j.tooltip, `tooltip`);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import { TestContextService } from 'vs/workbench/test/common/workbenchTestServic
|
||||
import { Terminal } from 'xterm';
|
||||
import { IFileQuery, ISearchComplete, ISearchService } from 'vs/workbench/services/search/common/search';
|
||||
import { SearchService } from 'vs/workbench/services/search/common/searchService';
|
||||
import { ITerminalOutputMatcher } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export interface ITerminalLinkActivationResult {
|
||||
source: 'editor' | 'search';
|
||||
@@ -131,6 +132,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
|
||||
cwd: '/initial/cwd',
|
||||
timestamp: 0,
|
||||
getOutput() { return undefined; },
|
||||
getOutputMatch(outputMatcher: ITerminalOutputMatcher) { return undefined; },
|
||||
marker: {
|
||||
line: 0
|
||||
} as Partial<IXtermMarker> as any,
|
||||
@@ -267,6 +269,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
|
||||
cwd,
|
||||
timestamp: 0,
|
||||
getOutput() { return undefined; },
|
||||
getOutputMatch(outputMatcher: ITerminalOutputMatcher) { return undefined; },
|
||||
marker: {
|
||||
line: 0
|
||||
} as Partial<IXtermMarker> as any,
|
||||
@@ -351,6 +354,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
|
||||
cwd,
|
||||
timestamp: 0,
|
||||
getOutput() { return undefined; },
|
||||
getOutputMatch(outputMatcher: ITerminalOutputMatcher) { return undefined; },
|
||||
marker: {
|
||||
line: 0
|
||||
} as Partial<IXtermMarker> as any,
|
||||
|
||||
@@ -21,7 +21,7 @@ import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilitie
|
||||
import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
function createInstance(partial?: Partial<ITerminalInstance>): Pick<ITerminalInstance, 'shellLaunchConfig' | 'userHome' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'> {
|
||||
export function createInstance(partial?: Partial<ITerminalInstance>): Pick<ITerminalInstance, 'shellLaunchConfig' | 'userHome' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'> {
|
||||
const capabilities = new TerminalCapabilityStore();
|
||||
if (!isWindows) {
|
||||
capabilities.add(TerminalCapability.NaiveCwdDetection, null!);
|
||||
|
||||
@@ -58,7 +58,7 @@ suite('DecorationAddon', () => {
|
||||
instantiationService.stub(IConfigurationService, configurationService);
|
||||
instantiationService.stub(IContextMenuService, instantiationService.createInstance(ContextMenuService));
|
||||
const capabilities = new TerminalCapabilityStore();
|
||||
capabilities.add(TerminalCapability.CommandDetection, new CommandDetectionCapability(xterm, new NullLogService()));
|
||||
capabilities.add(TerminalCapability.CommandDetection, instantiationService.createInstance(CommandDetectionCapability, xterm));
|
||||
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
||||
decorationAddon = instantiationService.createInstance(DecorationAddon, capabilities);
|
||||
xterm.loadAddon(decorationAddon);
|
||||
|
||||
@@ -45,7 +45,7 @@ suite('ShellIntegrationAddon', () => {
|
||||
xterm = new Terminal({ allowProposedApi: true, cols: 80, rows: 30 });
|
||||
const instantiationService = new TestInstantiationService();
|
||||
instantiationService.stub(ILogService, NullLogService);
|
||||
shellIntegrationAddon = instantiationService.createInstance(TestShellIntegrationAddon, undefined, undefined);
|
||||
shellIntegrationAddon = instantiationService.createInstance(TestShellIntegrationAddon);
|
||||
xterm.loadAddon(shellIntegrationAddon);
|
||||
capabilities = shellIntegrationAddon.capabilities;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user