mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 01:00:29 -04:00
Merge from vscode 2cd495805cf99b31b6926f08ff4348124b2cf73d
This commit is contained in:
committed by
AzureDataStudio
parent
a8a7559229
commit
1388493cc1
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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);
|
||||
@@ -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 ?? []));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user