Merge from vscode 2cd495805cf99b31b6926f08ff4348124b2cf73d

This commit is contained in:
ADS Merger
2020-06-30 04:40:21 +00:00
committed by AzureDataStudio
parent a8a7559229
commit 1388493cc1
602 changed files with 16375 additions and 12940 deletions

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, IViewportRange, IBufferLine } from 'xterm';
import { getXtermLineContent, convertLinkRangeToBuffer } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers';
import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
import { ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
/**
* An adapter to convert a simple external link provider into an internal link provider that
* manages link lifecycle, hovers, etc. and gets registered in xterm.js.
*/
export class TerminalExternalLinkProviderAdapter extends TerminalBaseLinkProvider {
constructor(
private readonly _xterm: Terminal,
private readonly _instance: ITerminalInstance,
private readonly _externalLinkProvider: ITerminalExternalLinkProvider,
private readonly _tooltipCallback: (link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) => void,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
}
protected async _provideLinks(y: number): Promise<TerminalLink[]> {
let startLine = y - 1;
let endLine = startLine;
const lines: IBufferLine[] = [
this._xterm.buffer.active.getLine(startLine)!
];
while (this._xterm.buffer.active.getLine(startLine)?.isWrapped) {
lines.unshift(this._xterm.buffer.active.getLine(startLine - 1)!);
startLine--;
}
while (this._xterm.buffer.active.getLine(endLine + 1)?.isWrapped) {
lines.push(this._xterm.buffer.active.getLine(endLine + 1)!);
endLine++;
}
const lineContent = getXtermLineContent(this._xterm.buffer.active, startLine, endLine, this._xterm.cols);
const externalLinks = await this._externalLinkProvider.provideLinks(this._instance, lineContent);
if (!externalLinks) {
return [];
}
return externalLinks.map(link => {
const bufferRange = convertLinkRangeToBuffer(lines, this._xterm.cols, {
startColumn: link.startIndex + 1,
startLineNumber: 1,
endColumn: link.startIndex + link.length + 1,
endLineNumber: 1
}, startLine);
const matchingText = lineContent.substr(link.startIndex, link.length) || '';
return this._instantiationService.createInstance(TerminalLink, bufferRange, matchingText, this._xterm.buffer.active.viewportY, (_, text) => link.activate(text), this._tooltipCallback, true, link.label);
});
}
}

View File

@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -13,10 +13,10 @@ import { ITerminalProcessManager, ITerminalConfiguration, TERMINAL_CONFIG_SECTIO
import { ITextEditorSelection } from 'vs/platform/editor/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileService } from 'vs/platform/files/common/files';
import { Terminal, IViewportRange } from 'xterm';
import { Terminal, IViewportRange, ILinkProvider } from 'xterm';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { posix, win32 } from 'vs/base/common/path';
import { ITerminalBeforeHandleLinkEvent, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalBeforeHandleLinkEvent, LINK_INTERCEPT_THRESHOLD, ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { OperatingSystem, isMacintosh, OS } from 'vs/base/common/platform';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { Emitter, Event } from 'vs/base/common/event';
@@ -28,6 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
import { TerminalHover, ILinkHoverTargetOptions } from 'vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget';
import { TerminalLink } from 'vs/workbench/contrib/terminal/browser/links/terminalLink';
import { TerminalExternalLinkProviderAdapter } from 'vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter';
export type XtermLinkMatcherHandler = (event: MouseEvent | undefined, link: string) => Promise<void>;
export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
@@ -43,8 +44,9 @@ interface IPath {
export class TerminalLinkManager extends DisposableStore {
private _widgetManager: TerminalWidgetManager | undefined;
private _processCwd: string | undefined;
private _linkProviders: IDisposable[] = [];
private _hasBeforeHandleLinkListeners = false;
private _standardLinkProviders: ILinkProvider[] = [];
private _standardLinkProvidersDisposables: IDisposable[] = [];
protected static _LINK_INTERCEPT_THRESHOLD = LINK_INTERCEPT_THRESHOLD;
public static readonly LINK_INTERCEPT_THRESHOLD = TerminalLinkManager._LINK_INTERCEPT_THRESHOLD;
@@ -72,7 +74,29 @@ export class TerminalLinkManager extends DisposableStore {
) {
super();
this.registerLinkProvider();
// Protocol links
const wrappedActivateCallback = this._wrapLinkHandler((_, link) => this._handleProtocolLink(link));
const protocolProvider = this._instantiationService.createInstance(TerminalProtocolLinkProvider, this._xterm, wrappedActivateCallback, this._tooltipCallback2.bind(this));
this._standardLinkProviders.push(protocolProvider);
// Validated local links
if (this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).enableFileLinks) {
const wrappedTextLinkActivateCallback = this._wrapLinkHandler((_, link) => this._handleLocalLink(link));
const validatedProvider = this._instantiationService.createInstance(TerminalValidatedLocalLinkProvider,
this._xterm,
this._processManager.os || OS,
wrappedTextLinkActivateCallback,
this._wrapLinkHandler.bind(this),
this._tooltipCallback2.bind(this),
async (link, cb) => cb(await this._resolvePath(link)));
this._standardLinkProviders.push(validatedProvider);
}
// Word links
const wordProvider = this._instantiationService.createInstance(TerminalWordLinkProvider, this._xterm, this._wrapLinkHandler.bind(this), this._tooltipCallback2.bind(this));
this._standardLinkProviders.push(wordProvider);
this._registerStandardLinkProviders();
}
private _tooltipCallback2(link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) {
@@ -123,28 +147,20 @@ export class TerminalLinkManager extends DisposableStore {
this._processCwd = processCwd;
}
public registerLinkProvider(): void {
// Protocol links
const wrappedActivateCallback = this._wrapLinkHandler((_, link) => this._handleProtocolLink(link));
const protocolProvider = this._instantiationService.createInstance(TerminalProtocolLinkProvider, this._xterm, wrappedActivateCallback, this._tooltipCallback2.bind(this));
this._linkProviders.push(this._xterm.registerLinkProvider(protocolProvider));
// Validated local links
if (this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION).enableFileLinks) {
const wrappedTextLinkActivateCallback = this._wrapLinkHandler((_, link) => this._handleLocalLink(link));
const validatedProvider = this._instantiationService.createInstance(TerminalValidatedLocalLinkProvider,
this._xterm,
this._processManager.os || OS,
wrappedTextLinkActivateCallback,
this._wrapLinkHandler.bind(this),
this._tooltipCallback2.bind(this),
async (link, cb) => cb(await this._resolvePath(link)));
this._linkProviders.push(this._xterm.registerLinkProvider(validatedProvider));
private _registerStandardLinkProviders(): void {
dispose(this._standardLinkProvidersDisposables);
this._standardLinkProvidersDisposables = [];
for (const p of this._standardLinkProviders) {
this._standardLinkProvidersDisposables.push(this._xterm.registerLinkProvider(p));
}
}
// Word links
const wordProvider = this._instantiationService.createInstance(TerminalWordLinkProvider, this._xterm, this._wrapLinkHandler.bind(this), this._tooltipCallback2.bind(this));
this._linkProviders.push(this._xterm.registerLinkProvider(wordProvider));
public registerExternalLinkProvider(instance: ITerminalInstance, linkProvider: ITerminalExternalLinkProvider): IDisposable {
const wrappedLinkProvider = this._instantiationService.createInstance(TerminalExternalLinkProviderAdapter, this._xterm, instance, linkProvider, this._tooltipCallback2.bind(this));
const newLinkProvider = this._xterm.registerLinkProvider(wrappedLinkProvider);
// Re-register the standard link providers so they are a lower priority that the new one
this._registerStandardLinkProviders();
return newLinkProvider;
}
protected _wrapLinkHandler(handler: (event: MouseEvent | undefined, link: string) => void): XtermLinkMatcherHandler {
@@ -255,7 +271,8 @@ export class TerminalLinkManager extends DisposableStore {
}
}
return new MarkdownString(`[${label || nls.localize('followLink', "Follow Link")}](${uri}) (${clickLabel})`, true);
// Use 'undefined' when uri is '' so the link displays correctly
return new MarkdownString(`[${label || nls.localize('followLink', "Follow Link")}](${uri || 'undefined'}) (${clickLabel})`, true);
}
private get osPath(): IPath {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .pane-body.integrated-terminal .xterm-viewport {
/* Use the hack presented in http://stackoverflow.com/a/38748186/1156119 to get opacity transitions working on the scrollbar */
/* Use the hack presented in https://stackoverflow.com/a/38748186/1156119 to get opacity transitions working on the scrollbar */
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;

View File

@@ -144,6 +144,14 @@ export interface ITerminalService {
*/
addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable;
/**
* Registers a link provider that enables integrators to add links to the terminal.
* @param linkProvider When registered, the link provider is asked whenever a cell is hovered
* for links at that position. This lets the terminal know all links at a given area and also
* labels for what these links are going to do.
*/
registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable;
selectDefaultShell(): Promise<void>;
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
@@ -165,22 +173,37 @@ export interface ITerminalService {
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise<ITerminalLaunchError | undefined>;
}
/**
* Similar to xterm.js' ILinkProvider but using promises and hides xterm.js internals (like buffer
* positions, decorations, etc.) from the rest of vscode. This is the interface to use for
* workbench integrations.
*/
export interface ITerminalExternalLinkProvider {
provideLinks(instance: ITerminalInstance, line: string): Promise<ITerminalLink[] | undefined>;
}
export interface ITerminalLink {
/** The startIndex of the link in the line. */
startIndex: number;
/** The length of the link in the line. */
length: number;
/** The descriptive label for what the link does when activated. */
label?: string;
/**
* Activates the link.
* @param text The text of the link.
*/
activate(text: string): void;
}
export interface ISearchOptions {
/**
* Whether the find should be done as a regex.
*/
/** Whether the find should be done as a regex. */
regex?: boolean;
/**
* Whether only whole words should match.
*/
/** Whether only whole words should match. */
wholeWord?: boolean;
/**
* Whether find should pay attention to case.
*/
/** Whether find should pay attention to case. */
caseSensitive?: boolean;
/**
* Whether the search should start at the current search position (not the next row)
*/
/** Whether the search should start at the current search position (not the next row). */
incremental?: boolean;
}
@@ -234,6 +257,7 @@ export interface ITerminalInstance {
onFocused: Event<ITerminalInstance>;
onProcessIdReady: Event<ITerminalInstance>;
onLinksReady: Event<ITerminalInstance>;
onRequestExtHostProcess: Event<ITerminalInstance>;
onDimensionsChanged: Event<void>;
onMaximumDimensionsChanged: Event<void>;
@@ -272,6 +296,9 @@ export interface ITerminalInstance {
readonly exitCode: number | undefined;
readonly areLinksReady: boolean;
/** A promise that resolves when the terminal's pty/process have been created. */
processReady: Promise<void>;
/**
@@ -480,4 +507,9 @@ export interface ITerminalInstance {
getInitialCwd(): Promise<string>;
getCwd(): Promise<string>;
/**
* @throws when called before xterm.js is ready.
*/
registerLinkProvider(provider: ITerminalExternalLinkProvider): IDisposable;
}

View File

@@ -38,6 +38,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
import { localize } from 'vs/nls';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI | undefined> {
switch (configHelper.config.splitCwd) {
@@ -304,6 +305,8 @@ export class SelectDefaultShellWindowsTerminalAction extends Action {
}
}
const terminalIndexRe = /^([0-9]+): /;
export class SwitchTerminalAction extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.SWITCH_TERMINAL;
@@ -311,7 +314,9 @@ export class SwitchTerminalAction extends Action {
constructor(
id: string, label: string,
@ITerminalService private readonly _terminalService: ITerminalService
@ITerminalService private readonly _terminalService: ITerminalService,
@ITerminalContributionService private readonly _contributions: ITerminalContributionService,
@ICommandService private readonly _commands: ICommandService,
) {
super(id, label, 'terminal-action switch-terminal');
}
@@ -328,9 +333,20 @@ export class SwitchTerminalAction extends Action {
this._terminalService.refreshActiveTab();
return this._terminalService.selectDefaultShell();
}
const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1;
this._terminalService.setActiveTabByIndex(selectedTabIndex);
return this._terminalService.showPanel(true);
const indexMatches = terminalIndexRe.exec(item);
if (indexMatches) {
this._terminalService.setActiveTabByIndex(Number(indexMatches[1]) - 1);
return this._terminalService.showPanel(true);
}
const customType = this._contributions.terminalTypes.find(t => t.title === item);
if (customType) {
return this._commands.executeCommand(customType.command);
}
console.warn(`Unmatched terminal item: "${item}"`);
return Promise.resolve();
}
}
@@ -342,9 +358,10 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
action: IAction,
@ITerminalService private readonly _terminalService: ITerminalService,
@IThemeService private readonly _themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService
@ITerminalContributionService private readonly _contributions: ITerminalContributionService,
@IContextViewService contextViewService: IContextViewService,
) {
super(null, action, getTerminalSelectOpenItems(_terminalService), _terminalService.activeTabIndex, contextViewService, { ariaLabel: localize('terminals', 'Open Terminals.'), optionsAsChildren: true });
super(null, action, getTerminalSelectOpenItems(_terminalService, _contributions), _terminalService.activeTabIndex, contextViewService, { ariaLabel: localize('terminals', 'Open Terminals.'), optionsAsChildren: true });
this._register(_terminalService.onInstancesChanged(this._updateItems, this));
this._register(_terminalService.onActiveTabChanged(this._updateItems, this));
@@ -362,13 +379,18 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
}
private _updateItems(): void {
this.setOptions(getTerminalSelectOpenItems(this._terminalService), this._terminalService.activeTabIndex);
this.setOptions(getTerminalSelectOpenItems(this._terminalService, this._contributions), this._terminalService.activeTabIndex);
}
}
function getTerminalSelectOpenItems(terminalService: ITerminalService): ISelectOptionItem[] {
function getTerminalSelectOpenItems(terminalService: ITerminalService, contributions: ITerminalContributionService): ISelectOptionItem[] {
const items = terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true });
for (const contributed of contributions.terminalTypes) {
items.push({ text: contributed.title });
}
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
return items;
}

View File

@@ -214,7 +214,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
const shellArgsConfigValue = this._configurationService.inspect<string[]>(`terminal.integrated.shellArgs.${platformKey}`);
const envConfigValue = this._configurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`);
// Check if workspace setting exists and whether it's whitelisted
// Check if workspace setting exists and whether it's allowed
let isWorkspaceShellAllowed: boolean | undefined = false;
if (shellConfigValue.workspaceValue !== undefined || shellArgsConfigValue.workspaceValue !== undefined || envConfigValue.workspaceValue !== undefined) {
isWorkspaceShellAllowed = this.isWorkspaceShellAllowed(undefined);
@@ -226,7 +226,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
isWorkspaceShellAllowed = true;
}
// Check if the value is neither blacklisted (false) or whitelisted (true) and ask for
// Check if the value is neither on the blocklist (false) or allowlist (true) and ask for
// permission
if (isWorkspaceShellAllowed === undefined) {
let shellString: string | undefined;

View File

@@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, ITerminalBeforeHandleLinkEvent, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm';
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
@@ -95,6 +95,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _xtermReadyPromise: Promise<XTermTerminal>;
private _titleReadyPromise: Promise<string>;
private _titleReadyComplete: ((title: string) => any) | undefined;
private _areLinksReady: boolean = false;
private _messageTitleDisposable: IDisposable | undefined;
@@ -127,6 +128,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// TODO: How does this work with detached processes?
// TODO: Should this be an event as it can fire twice?
public get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
public get areLinksReady(): boolean { return this._areLinksReady; }
public get exitCode(): number | undefined { return this._exitCode; }
public get title(): string { return this._title; }
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
@@ -144,6 +146,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
public get onFocused(): Event<ITerminalInstance> { return this._onFocused.event; }
private readonly _onProcessIdReady = new Emitter<ITerminalInstance>();
public get onProcessIdReady(): Event<ITerminalInstance> { return this._onProcessIdReady.event; }
private readonly _onLinksReady = new Emitter<ITerminalInstance>();
public get onLinksReady(): Event<ITerminalInstance> { return this._onLinksReady.event; }
private readonly _onTitleChanged = new Emitter<ITerminalInstance>();
public get onTitleChanged(): Event<ITerminalInstance> { return this._onTitleChanged.event; }
private readonly _onData = new Emitter<string>();
@@ -201,7 +205,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig);
this._initDimensions();
this._createProcess();
this._createProcessManager();
this._xtermReadyPromise = this._createXterm();
this._xtermReadyPromise.then(() => {
@@ -209,6 +213,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (_container) {
this._attachToElement(_container);
}
this._createProcess();
});
this.addDisposable(this._configurationService.onDidChangeConfiguration(e => {
@@ -416,6 +421,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
e.terminal = this;
this._onBeforeHandleLink.fire(e);
});
this._areLinksReady = true;
this._onLinksReady.fire(this);
});
this._commandTrackerAddon = new CommandTrackerAddon();
@@ -868,9 +875,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._terminalHasTextContextKey.set(isActive && this.hasSelection());
}
protected _createProcess(): void {
protected _createProcessManager(): void {
this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper);
this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this));
this._processManager.onProcessReady(() => {
this._onProcessIdReady.fire(this);
});
this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
this._processManager.onProcessData(data => this._onData.fire(data));
this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e));
@@ -914,9 +923,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
});
});
}
}
// Create the process asynchronously to allow the terminal's container to be created so
// dimensions are accurate
private _createProcess(): void {
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
if (error) {
this._onProcessExit(error);
@@ -1108,13 +1117,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Launch the process unless this is only a renderer.
// In the renderer only cases, we still need to set the title correctly.
const oldTitle = this._title;
this._createProcess();
this._createProcessManager();
if (oldTitle !== this._title) {
this.setTitle(this._title, TitleEventSource.Process);
}
this._processManager.onProcessData(data => this._onProcessData(data));
this._createProcess();
}
public relaunch(): void {
@@ -1492,6 +1502,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
public getCwd(): Promise<string> {
return this._processManager.getCwd();
}
public registerLinkProvider(provider: ITerminalExternalLinkProvider): IDisposable {
if (!this._linkManager) {
throw new Error('TerminalInstance.registerLinkProvider before link manager was ready');
}
return this._linkManager.registerExternalLinkProvider(this, provider);
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {

View File

@@ -146,7 +146,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this.userHome = this._pathService.resolvedUserHome?.fsPath;
this.os = platform.OS;
if (launchRemotely) {
const userHomeUri = await this._pathService.userHome;
const userHomeUri = await this._pathService.userHome();
this.userHome = userHomeUri.path;
if (hasRemoteAuthority) {
const remoteEnv = await this._remoteAgentService.getEnvironment();

View File

@@ -14,7 +14,7 @@ import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, TerminalLinkHandlerCallback, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, TerminalLinkHandlerCallback, LINK_INTERCEPT_THRESHOLD, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
@@ -54,6 +54,8 @@ export class TerminalService implements ITerminalService {
private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {};
private _activeTabIndex: number;
private _linkHandlers: { [key: string]: TerminalLinkHandlerCallback } = {};
private _linkProviders: Set<ITerminalExternalLinkProvider> = new Set();
private _linkProviderDisposables: Map<ITerminalExternalLinkProvider, IDisposable[]> = new Map();
public get activeTabIndex(): number { return this._activeTabIndex; }
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
@@ -72,6 +74,8 @@ export class TerminalService implements ITerminalService {
public get onInstanceDisposed(): Event<ITerminalInstance> { return this._onInstanceDisposed.event; }
private readonly _onInstanceProcessIdReady = new Emitter<ITerminalInstance>();
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
private readonly _onInstanceLinksReady = new Emitter<ITerminalInstance>();
public get onInstanceLinksReady(): Event<ITerminalInstance> { return this._onInstanceLinksReady.event; }
private readonly _onInstanceRequestSpawnExtHostProcess = new Emitter<ISpawnExtHostProcessRequest>();
public get onInstanceRequestSpawnExtHostProcess(): Event<ISpawnExtHostProcessRequest> { return this._onInstanceRequestSpawnExtHostProcess.event; }
private readonly _onInstanceRequestStartExtensionTerminal = new Emitter<IStartExtensionTerminalRequest>();
@@ -128,6 +132,7 @@ export class TerminalService implements ITerminalService {
const instance = this.getActiveInstance();
this._onActiveInstanceChanged.fire(instance ? instance : undefined);
});
this.onInstanceLinksReady(instance => this._setInstanceLinkProviders(instance));
this._handleContextKeys();
}
@@ -429,6 +434,7 @@ export class TerminalService implements ITerminalService {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady));
instance.addDisposable(instance.onLinksReady(this._onInstanceLinksReady.fire, this._onInstanceLinksReady));
instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
@@ -478,6 +484,34 @@ export class TerminalService implements ITerminalService {
};
}
public registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable {
const disposables: IDisposable[] = [];
this._linkProviders.add(linkProvider);
for (const instance of this.terminalInstances) {
if (instance.areLinksReady) {
disposables.push(instance.registerLinkProvider(linkProvider));
}
}
this._linkProviderDisposables.set(linkProvider, disposables);
return {
dispose: () => {
const disposables = this._linkProviderDisposables.get(linkProvider) || [];
for (const disposable of disposables) {
disposable.dispose();
}
this._linkProviders.delete(linkProvider);
}
};
}
private _setInstanceLinkProviders(instance: ITerminalInstance): void {
for (const linkProvider of this._linkProviders) {
const disposables = this._linkProviderDisposables.get(linkProvider);
const provider = instance.registerLinkProvider(linkProvider);
disposables?.push(provider);
}
}
private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | undefined {
return find(this._terminalTabs, tab => tab.terminalInstances.indexOf(instance) !== -1);
}

View File

@@ -11,7 +11,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import * as dom from 'vs/base/browser/dom';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IHoverService, IHoverOptions } from 'vs/workbench/contrib/hover/browser/hover';
import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover';
export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWidget {
readonly id = 'env-var-info';

View File

@@ -9,7 +9,7 @@ import { Widget } from 'vs/base/browser/ui/widget';
import { ITerminalWidget } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
import * as dom from 'vs/base/browser/dom';
import { IViewportRange } from 'xterm';
import { IHoverTarget, IHoverService } from 'vs/workbench/contrib/hover/browser/hover';
import { IHoverTarget, IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorHoverHighlight } from 'vs/platform/theme/common/colorRegistry';

View File

@@ -12,6 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { OperatingSystem } from 'vs/base/common/platform';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry';
export const TERMINAL_VIEW_ID = 'workbench.panel.terminal';
@@ -625,3 +626,41 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
'workbench.action.quickOpenView',
'workbench.action.toggleMaximizedPanel'
];
export interface ITerminalContributions {
types?: ITerminalTypeContribution[];
}
export interface ITerminalTypeContribution {
title: string;
command: string;
}
export const terminalContributionsDescriptor: IExtensionPointDescriptor = {
extensionPoint: 'terminal',
defaultExtensionKind: 'workspace',
jsonSchema: {
description: nls.localize('vscode.extension.contributes.terminal', 'Contributes terminal functionality.'),
type: 'object',
properties: {
types: {
type: 'array',
description: nls.localize('vscode.extension.contributes.terminal.types', "Defines additional terminal types that the user can create."),
items: {
type: 'object',
required: ['command', 'title'],
properties: {
command: {
description: nls.localize('vscode.extension.contributes.terminal.types.command', "Command to execute when the user creates this type of terminal."),
type: 'string',
},
title: {
description: nls.localize('vscode.extension.contributes.terminal.types.title', "Title for this type of terminal."),
type: 'string',
},
},
},
},
},
},
};

View File

@@ -218,7 +218,7 @@ export const terminalConfiguration: IConfigurationNode = {
default: false
},
'terminal.integrated.commandsToSkipShell': {
markdownDescription: localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open. Use the command prefixed with `-` to remove default commands from the list.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')),
markdownDescription: localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by VS Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open. Use the command prefixed with `-` to remove default commands from the list.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')),
type: 'array',
items: {
type: 'string'

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalContributionService, TerminalContributionService } from './terminalExtensionPoints';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
registerSingleton(ITerminalContributionService, TerminalContributionService, true);

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ITerminalTypeContribution, ITerminalContributions, terminalContributionsDescriptor } from 'vs/workbench/contrib/terminal/common/terminal';
import { flatten } from 'vs/base/common/arrays';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
// terminal extension point
export const terminalsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<ITerminalContributions>(terminalContributionsDescriptor);
export interface ITerminalContributionService {
readonly _serviceBrand: undefined;
readonly terminalTypes: ReadonlyArray<ITerminalTypeContribution>;
}
export const ITerminalContributionService = createDecorator<ITerminalContributionService>('terminalContributionsService');
export class TerminalContributionService implements ITerminalContributionService {
public readonly _serviceBrand = undefined;
private _terminalTypes: ReadonlyArray<ITerminalTypeContribution> = [];
public get terminalTypes() {
return this._terminalTypes;
}
constructor() {
terminalsExtPoint.setHandler(contributions => {
this._terminalTypes = flatten(contributions.filter(c => c.description.enableProposedApi).map(c => c.value?.types ?? []));
});
}
}

View File

@@ -48,7 +48,7 @@ export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
// For macOS we want the "root" environment as shells by default run as login shells. It
// doesn't appear to be possible to get the "root" environment as `ps eww -o command` for
// PID 1 (the parent of the main process when launched from the dock/finder) returns no
// environment, because of this we will fill in the root environment using a whitelist of
// environment, because of this we will fill in the root environment using a allowlist of
// environment variables that we have.
if (isMacintosh) {
mainProcessParentEnv = {};
@@ -129,4 +129,4 @@ export async function findExecutable(command: string, cwd?: string, paths?: stri
}
const fullPath = path.join(cwd, command);
return await exists(fullPath) ? fullPath : undefined;
}
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import * as pty from 'node-pty';
@@ -53,7 +52,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
) {
super();
let name: string;
if (os.platform() === 'win32') {
if (platform.isWindows) {
name = path.basename(this._shellLaunchConfig.executable || '');
} else {
// Using 'xterm-256color' here helps ensure that the majority of Linux distributions will use a