mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 01:00:29 -04:00
Merge from vscode ad407028575a77ea387eb7cc219b323dc017b686
This commit is contained in:
committed by
Anthony Dresser
parent
404260b8a0
commit
4ad73d381c
@@ -21,10 +21,16 @@ export function convertLinkRangeToBuffer(lines: IBufferLine[], bufferWidth: numb
|
||||
// Shift start range right for each wide character before the link
|
||||
let startOffset = 0;
|
||||
const startWrappedLineCount = Math.ceil(range.startColumn / bufferWidth);
|
||||
for (let y = 0; y < startWrappedLineCount; y++) {
|
||||
for (let y = 0; y < Math.min(startWrappedLineCount); y++) {
|
||||
const lineLength = Math.min(bufferWidth, range.startColumn - y * bufferWidth);
|
||||
let lineOffset = 0;
|
||||
const line = lines[y];
|
||||
// Sanity check for line, apparently this can happen but it's not clear under what
|
||||
// circumstances this happens. Continue on, skipping the remainder of start offset if this
|
||||
// happens to minimize impact.
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
for (let x = 0; x < Math.min(bufferWidth, lineLength + lineOffset); x++) {
|
||||
const cell = line.getCell(x)!;
|
||||
const width = cell.getWidth();
|
||||
|
||||
@@ -16,11 +16,9 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
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, ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { 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';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TerminalProtocolLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider';
|
||||
import { TerminalValidatedLocalLinkProvider, lineAndColumnClause, unixLocalLinkClause, winLocalLinkClause, winDrivePrefix, winLineAndColumnMatchIndex, unixLineAndColumnMatchIndex, lineAndColumnClauseGroupCount } from 'vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider';
|
||||
import { TerminalWordLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider';
|
||||
@@ -44,24 +42,9 @@ interface IPath {
|
||||
export class TerminalLinkManager extends DisposableStore {
|
||||
private _widgetManager: TerminalWidgetManager | undefined;
|
||||
private _processCwd: string | undefined;
|
||||
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;
|
||||
|
||||
private readonly _onBeforeHandleLink = this.add(new Emitter<ITerminalBeforeHandleLinkEvent>({
|
||||
onFirstListenerAdd: () => this._hasBeforeHandleLinkListeners = true,
|
||||
onLastListenerRemove: () => this._hasBeforeHandleLinkListeners = false
|
||||
}));
|
||||
/**
|
||||
* Allows intercepting links and handling them outside of the default link handler. When fired
|
||||
* the listener has a set amount of time to handle the link or the default handler will fire.
|
||||
* This was designed to only be handled by a single listener.
|
||||
*/
|
||||
public get onBeforeHandleLink(): Event<ITerminalBeforeHandleLinkEvent> { return this._onBeforeHandleLink.event; }
|
||||
|
||||
constructor(
|
||||
private _xterm: Terminal,
|
||||
private readonly _processManager: ITerminalProcessManager,
|
||||
@@ -69,14 +52,13 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Protocol links
|
||||
const wrappedActivateCallback = this._wrapLinkHandler((_, link) => this._handleProtocolLink(link));
|
||||
const protocolProvider = this._instantiationService.createInstance(TerminalProtocolLinkProvider, this._xterm, wrappedActivateCallback, this._tooltipCallback2.bind(this));
|
||||
const protocolProvider = this._instantiationService.createInstance(TerminalProtocolLinkProvider, this._xterm, wrappedActivateCallback, this._tooltipCallback.bind(this));
|
||||
this._standardLinkProviders.push(protocolProvider);
|
||||
|
||||
// Validated local links
|
||||
@@ -87,19 +69,19 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
this._processManager.os || OS,
|
||||
wrappedTextLinkActivateCallback,
|
||||
this._wrapLinkHandler.bind(this),
|
||||
this._tooltipCallback2.bind(this),
|
||||
this._tooltipCallback.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));
|
||||
const wordProvider = this._instantiationService.createInstance(TerminalWordLinkProvider, this._xterm, this._wrapLinkHandler.bind(this), this._tooltipCallback.bind(this));
|
||||
this._standardLinkProviders.push(wordProvider);
|
||||
|
||||
this._registerStandardLinkProviders();
|
||||
}
|
||||
|
||||
private _tooltipCallback2(link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) {
|
||||
private _tooltipCallback(link: TerminalLink, viewportRange: IViewportRange, modifierDownCallback?: () => void, modifierUpCallback?: () => void) {
|
||||
if (!this._widgetManager) {
|
||||
return;
|
||||
}
|
||||
@@ -156,7 +138,7 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
}
|
||||
|
||||
public registerExternalLinkProvider(instance: ITerminalInstance, linkProvider: ITerminalExternalLinkProvider): IDisposable {
|
||||
const wrappedLinkProvider = this._instantiationService.createInstance(TerminalExternalLinkProviderAdapter, this._xterm, instance, linkProvider, this._wrapLinkHandler.bind(this), this._tooltipCallback2.bind(this));
|
||||
const wrappedLinkProvider = this._instantiationService.createInstance(TerminalExternalLinkProviderAdapter, this._xterm, instance, linkProvider, this._wrapLinkHandler.bind(this), this._tooltipCallback.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();
|
||||
@@ -173,38 +155,11 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow the link to be intercepted if there are listeners
|
||||
if (this._hasBeforeHandleLinkListeners) {
|
||||
const wasHandled = await this._triggerBeforeHandleLinkListeners(link);
|
||||
if (!wasHandled) {
|
||||
handler(event, link);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Just call the handler if there is no before listener
|
||||
handler(event, link);
|
||||
};
|
||||
}
|
||||
|
||||
private async _triggerBeforeHandleLinkListeners(link: string): Promise<boolean> {
|
||||
return new Promise<boolean>(r => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
canceled = true;
|
||||
this._logService.error(`An extension intecepted a terminal link but it timed out after ${TerminalLinkManager.LINK_INTERCEPT_THRESHOLD / 1000} seconds`);
|
||||
r(false);
|
||||
}, TerminalLinkManager.LINK_INTERCEPT_THRESHOLD);
|
||||
let canceled = false;
|
||||
const resolve = (handled: boolean) => {
|
||||
if (!canceled) {
|
||||
clearTimeout(timeoutId);
|
||||
r(handled);
|
||||
}
|
||||
};
|
||||
this._onBeforeHandleLink.fire({ link, resolve });
|
||||
});
|
||||
}
|
||||
|
||||
protected get _localLinkRegex(): RegExp {
|
||||
if (!this._processManager) {
|
||||
throw new Error('Process manager is required');
|
||||
@@ -369,7 +324,6 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
* @param link Url link which may contain line and column number.
|
||||
*/
|
||||
public extractLineColumnInfo(link: string): LineColumnInfo {
|
||||
|
||||
const matches: string[] | null = this._localLinkRegex.exec(link);
|
||||
const lineColumnInfo: LineColumnInfo = {
|
||||
lineNumber: 1,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Terminal as XTermTerminal } from 'xterm';
|
||||
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
|
||||
import { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11';
|
||||
import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl';
|
||||
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError, ITerminalNativeWindowsDelegate, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProcessEnvironment, Platform } from 'vs/base/common/platform';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
@@ -136,14 +136,6 @@ export interface ITerminalService {
|
||||
findNext(): void;
|
||||
findPrevious(): void;
|
||||
|
||||
/**
|
||||
* Link handlers can be registered here to allow intercepting links clicked in the terminal.
|
||||
* When a link is clicked, the link will be considered handled when the first interceptor
|
||||
* resolves with true. It will be considered not handled when _all_ link handlers resolve with
|
||||
* false, or 3 seconds have elapsed.
|
||||
*/
|
||||
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
|
||||
@@ -157,6 +149,12 @@ export interface ITerminalService {
|
||||
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
|
||||
manageWorkspaceShellPermissions(): void;
|
||||
|
||||
/**
|
||||
* Injects native Windows functionality into the service.
|
||||
*/
|
||||
setNativeWindowsDelegate(delegate: ITerminalNativeWindowsDelegate): void;
|
||||
setLinuxDistro(linuxDistro: LinuxDistro): void;
|
||||
|
||||
/**
|
||||
* Takes a path and returns the properly escaped path to send to the terminal.
|
||||
* On Windows, this included trying to prepare the path for WSL if needed.
|
||||
@@ -215,8 +213,6 @@ export enum WindowsShellType {
|
||||
}
|
||||
export type TerminalShellType = WindowsShellType | undefined;
|
||||
|
||||
export const LINK_INTERCEPT_THRESHOLD = 3000;
|
||||
|
||||
export interface ITerminalBeforeHandleLinkEvent {
|
||||
terminal?: ITerminalInstance;
|
||||
/** The text of the link */
|
||||
@@ -225,8 +221,6 @@ export interface ITerminalBeforeHandleLinkEvent {
|
||||
resolve(wasHandled: boolean): void;
|
||||
}
|
||||
|
||||
export type TerminalLinkHandlerCallback = (e: ITerminalBeforeHandleLinkEvent) => Promise<boolean>;
|
||||
|
||||
export interface ITerminalInstance {
|
||||
/**
|
||||
* The ID of the terminal instance, this is an arbitrary number only used to identify the
|
||||
@@ -289,15 +283,18 @@ export interface ITerminalInstance {
|
||||
*/
|
||||
onExit: Event<number | undefined>;
|
||||
|
||||
/**
|
||||
* Attach a listener to intercept and handle link clicks in the terminal.
|
||||
*/
|
||||
onBeforeHandleLink: Event<ITerminalBeforeHandleLinkEvent>;
|
||||
|
||||
readonly exitCode: number | undefined;
|
||||
|
||||
readonly areLinksReady: boolean;
|
||||
|
||||
/**
|
||||
* Returns an array of data events that have fired within the first 10 seconds. If this is
|
||||
* called 10 seconds after the terminal has existed the result will be undefined. This is useful
|
||||
* when objects that depend on the data events have delayed initialization, like extension
|
||||
* hosts.
|
||||
*/
|
||||
readonly initialDataEvents: string[] | undefined;
|
||||
|
||||
/** A promise that resolves when the terminal's pty/process have been created. */
|
||||
processReady: Promise<void>;
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
|
||||
private _charMeasureElement: HTMLElement | undefined;
|
||||
private _lastFontMeasurement: ITerminalFont | undefined;
|
||||
private _linuxDistro: LinuxDistro = LinuxDistro.Unknown;
|
||||
public config!: ITerminalConfiguration;
|
||||
|
||||
private readonly _onWorkspacePermissionsChanged = new Emitter<boolean>();
|
||||
public get onWorkspacePermissionsChanged(): Event<boolean> { return this._onWorkspacePermissionsChanged.event; }
|
||||
|
||||
public constructor(
|
||||
private readonly _linuxDistro: LinuxDistro,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@@ -62,6 +62,10 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'terminalConfigHelper/launchRecommendationsIgnore', version: 1 });
|
||||
}
|
||||
|
||||
public setLinuxDistro(linuxDistro: LinuxDistro) {
|
||||
this._linuxDistro = linuxDistro;
|
||||
}
|
||||
|
||||
private _updateConfig(): void {
|
||||
this.config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
|
||||
}
|
||||
|
||||
@@ -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, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, 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';
|
||||
@@ -98,6 +98,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private _titleReadyPromise: Promise<string>;
|
||||
private _titleReadyComplete: ((title: string) => any) | undefined;
|
||||
private _areLinksReady: boolean = false;
|
||||
private _initialDataEvents: string[] | undefined = [];
|
||||
|
||||
private _messageTitleDisposable: IDisposable | undefined;
|
||||
|
||||
@@ -131,6 +132,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
// 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 initialDataEvents(): string[] | undefined { return this._initialDataEvents; }
|
||||
public get exitCode(): number | undefined { return this._exitCode; }
|
||||
public get title(): string { return this._title; }
|
||||
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
|
||||
@@ -164,8 +166,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
public get onMaximumDimensionsChanged(): Event<void> { return this._onMaximumDimensionsChanged.event; }
|
||||
private readonly _onFocus = new Emitter<ITerminalInstance>();
|
||||
public get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
|
||||
private readonly _onBeforeHandleLink = new Emitter<ITerminalBeforeHandleLinkEvent>();
|
||||
public get onBeforeHandleLink(): Event<ITerminalBeforeHandleLinkEvent> { return this._onBeforeHandleLink.event; }
|
||||
|
||||
public constructor(
|
||||
private readonly _terminalFocusContextKey: IContextKey<boolean>,
|
||||
@@ -233,6 +233,20 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this.updateAccessibilitySupport();
|
||||
}
|
||||
}));
|
||||
|
||||
// Clear out initial data events after 10 seconds, hopefully extension hosts are up and
|
||||
// running at that point.
|
||||
let initialDataEventsTimeout: number | undefined = window.setTimeout(() => {
|
||||
initialDataEventsTimeout = undefined;
|
||||
this._initialDataEvents = undefined;
|
||||
}, 10000);
|
||||
this._register({
|
||||
dispose: () => {
|
||||
if (initialDataEventsTimeout) {
|
||||
window.clearTimeout(initialDataEventsTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addDisposable(disposable: IDisposable): void {
|
||||
@@ -419,10 +433,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
});
|
||||
}
|
||||
this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm, this._processManager!);
|
||||
this._linkManager.onBeforeHandleLink(e => {
|
||||
e.terminal = this;
|
||||
this._onBeforeHandleLink.fire(e);
|
||||
});
|
||||
this._areLinksReady = true;
|
||||
this._onLinksReady.fire(this);
|
||||
});
|
||||
@@ -868,11 +878,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
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.onProcessData(data => {
|
||||
this._initialDataEvents?.push(data);
|
||||
this._onData.fire(data);
|
||||
});
|
||||
this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e));
|
||||
this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e));
|
||||
this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e));
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, ITerminalLaunchError, ITerminalNativeWindowsDelegate } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
|
||||
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService } 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, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, 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';
|
||||
@@ -50,7 +50,6 @@ export class TerminalService implements ITerminalService {
|
||||
private _findState: FindReplaceState;
|
||||
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();
|
||||
|
||||
@@ -60,6 +59,7 @@ export class TerminalService implements ITerminalService {
|
||||
|
||||
private _configHelper: TerminalConfigHelper;
|
||||
private _terminalContainer: HTMLElement | undefined;
|
||||
private _nativeWindowsDelegate: ITerminalNativeWindowsDelegate | undefined;
|
||||
|
||||
public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
|
||||
|
||||
@@ -92,8 +92,6 @@ export class TerminalService implements ITerminalService {
|
||||
private readonly _onRequestAvailableShells = new Emitter<IAvailableShellsRequest>();
|
||||
public get onRequestAvailableShells(): Event<IAvailableShellsRequest> { return this._onRequestAvailableShells.event; }
|
||||
|
||||
private readonly _terminalNativeService: ITerminalNativeService | undefined;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService,
|
||||
@@ -105,34 +103,17 @@ export class TerminalService implements ITerminalService {
|
||||
@IQuickInputService private _quickInputService: IQuickInputService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IViewsService private _viewsService: IViewsService,
|
||||
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
|
||||
// HACK: Ideally TerminalNativeService would depend on TerminalService and inject the
|
||||
// additional native functionality into it.
|
||||
@optional(ITerminalNativeService) terminalNativeService: ITerminalNativeService
|
||||
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService
|
||||
) {
|
||||
// @optional could give undefined and properly typing it breaks service registration
|
||||
this._terminalNativeService = terminalNativeService as ITerminalNativeService | undefined;
|
||||
|
||||
this._activeTabIndex = 0;
|
||||
this._isShuttingDown = false;
|
||||
this._findState = new FindReplaceState();
|
||||
lifecycleService.onBeforeShutdown(async event => event.veto(this._onBeforeShutdown()));
|
||||
lifecycleService.onShutdown(() => this._onShutdown());
|
||||
if (this._terminalNativeService) {
|
||||
this._terminalNativeService.onRequestFocusActiveInstance(() => {
|
||||
if (this.terminalInstances.length > 0) {
|
||||
const terminal = this.getActiveInstance();
|
||||
if (terminal) {
|
||||
terminal.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
this._terminalNativeService.onOsResume(() => this._onOsResume());
|
||||
}
|
||||
this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService);
|
||||
this._terminalShellTypeContextKey = KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE.bindTo(this._contextKeyService);
|
||||
this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE.bindTo(this._contextKeyService);
|
||||
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this._terminalNativeService?.linuxDistro || LinuxDistro.Unknown);
|
||||
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper);
|
||||
this.onTabDisposed(tab => this._removeTab(tab));
|
||||
this.onActiveTabChanged(() => {
|
||||
const instance = this.getActiveInstance();
|
||||
@@ -143,6 +124,14 @@ export class TerminalService implements ITerminalService {
|
||||
this._handleContextKeys();
|
||||
}
|
||||
|
||||
public setNativeWindowsDelegate(delegate: ITerminalNativeWindowsDelegate): void {
|
||||
this._nativeWindowsDelegate = delegate;
|
||||
}
|
||||
|
||||
public setLinuxDistro(linuxDistro: LinuxDistro): void {
|
||||
this._configHelper.setLinuxDistro(linuxDistro);
|
||||
}
|
||||
|
||||
private _handleContextKeys(): void {
|
||||
const terminalIsOpenContext = KEYBINDING_CONTEXT_TERMINAL_IS_OPEN.bindTo(this._contextKeyService);
|
||||
|
||||
@@ -226,14 +215,6 @@ export class TerminalService implements ITerminalService {
|
||||
this.terminalInstances.forEach(instance => instance.dispose(true));
|
||||
}
|
||||
|
||||
private _onOsResume(): void {
|
||||
const activeTab = this.getActiveTab();
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
activeTab.terminalInstances.forEach(instance => instance.forceRedraw());
|
||||
}
|
||||
|
||||
public getTabLabels(): string[] {
|
||||
return this._terminalTabs.filter(tab => tab.terminalInstances.length > 0).map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`);
|
||||
}
|
||||
@@ -428,50 +409,6 @@ export class TerminalService implements ITerminalService {
|
||||
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));
|
||||
instance.addDisposable(instance.onBeforeHandleLink(async e => {
|
||||
// No link handlers have been registered
|
||||
const keys = Object.keys(this._linkHandlers);
|
||||
if (keys.length === 0) {
|
||||
e.resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire each link interceptor and wait for either a true, all false or the cancel time
|
||||
let resolved = false;
|
||||
const promises: Promise<boolean>[] = [];
|
||||
const timeout = setTimeout(() => {
|
||||
resolved = true;
|
||||
e.resolve(false);
|
||||
}, LINK_INTERCEPT_THRESHOLD);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const p = this._linkHandlers[keys[i]](e);
|
||||
p.then(handled => {
|
||||
if (!resolved && handled) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
e.resolve(true);
|
||||
}
|
||||
});
|
||||
promises.push(p);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
e.resolve(false);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable {
|
||||
this._linkHandlers[key] = callback;
|
||||
return {
|
||||
dispose: () => {
|
||||
if (this._linkHandlers[key] === callback) {
|
||||
delete this._linkHandlers[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable {
|
||||
@@ -588,8 +525,8 @@ export class TerminalService implements ITerminalService {
|
||||
return;
|
||||
}
|
||||
else if (shellType === WindowsShellType.Wsl) {
|
||||
if (this._terminalNativeService && this._terminalNativeService.getWindowsBuildNumber() >= 17063) {
|
||||
c(this._terminalNativeService.getWslPath(originalPath));
|
||||
if (this._nativeWindowsDelegate && this._nativeWindowsDelegate.getWindowsBuildNumber() >= 17063) {
|
||||
c(this._nativeWindowsDelegate.getWslPath(originalPath));
|
||||
} else {
|
||||
c(originalPath.replace(/\\/g, '/'));
|
||||
}
|
||||
@@ -603,9 +540,9 @@ export class TerminalService implements ITerminalService {
|
||||
}
|
||||
} else {
|
||||
const lowerExecutable = executable.toLowerCase();
|
||||
if (this._terminalNativeService && this._terminalNativeService.getWindowsBuildNumber() >= 17063 &&
|
||||
if (this._nativeWindowsDelegate && this._nativeWindowsDelegate.getWindowsBuildNumber() >= 17063 &&
|
||||
(lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) {
|
||||
c(this._terminalNativeService.getWslPath(originalPath));
|
||||
c(this._nativeWindowsDelegate.getWslPath(originalPath));
|
||||
return;
|
||||
} else if (hasSpace) {
|
||||
c('"' + originalPath + '"');
|
||||
|
||||
@@ -23,6 +23,7 @@ export class TerminalWidgetManager implements IDisposable {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.hideHovers();
|
||||
if (this._container && this._container.parentElement) {
|
||||
this._container.parentElement.removeChild(this._container);
|
||||
this._container = undefined;
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as nls from 'vs/nls';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
@@ -55,8 +54,6 @@ export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverM
|
||||
// trying to create the corressponding object on the ext host.
|
||||
export const EXT_HOST_CREATION_DELAY = 100;
|
||||
|
||||
export const ITerminalNativeService = createDecorator<ITerminalNativeService>('terminalNativeService');
|
||||
|
||||
export const TerminalCursorStyle = {
|
||||
BLOCK: 'block',
|
||||
LINE: 'line',
|
||||
@@ -230,18 +227,17 @@ export interface IShellLaunchConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to native or electron APIs to other terminal services.
|
||||
* Provides access to native Windows calls that can be injected into non-native layers.
|
||||
*/
|
||||
export interface ITerminalNativeService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly linuxDistro: LinuxDistro;
|
||||
|
||||
readonly onRequestFocusActiveInstance: Event<void>;
|
||||
readonly onOsResume: Event<void>;
|
||||
|
||||
export interface ITerminalNativeWindowsDelegate {
|
||||
/**
|
||||
* Gets the Windows build number, eg. this would be `19041` for Windows 10 version 2004
|
||||
*/
|
||||
getWindowsBuildNumber(): number;
|
||||
whenFileDeleted(path: URI): Promise<void>;
|
||||
/**
|
||||
* Converts a regular Windows path into the WSL path equivalent, eg. `C:\` -> `/mnt/c`
|
||||
* @param path The Windows path.
|
||||
*/
|
||||
getWslPath(path: string): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,22 +3,25 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/electron-browser/terminalInstanceService';
|
||||
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { TerminalNativeService } from 'vs/workbench/contrib/terminal/electron-browser/terminalNativeService';
|
||||
import { ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalNativeContribution } from 'vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
// This file contains additional desktop-only contributions on top of those in browser/
|
||||
|
||||
// Register services
|
||||
registerSingleton(ITerminalNativeService, TerminalNativeService, true);
|
||||
registerSingleton(ITerminalInstanceService, TerminalInstanceService, true);
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, LifecyclePhase.Ready);
|
||||
|
||||
// Register configurations
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
configurationRegistry.registerConfiguration(getTerminalShellConfiguration(getSystemShell));
|
||||
|
||||
@@ -5,40 +5,40 @@
|
||||
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
|
||||
import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { getWindowsBuildNumber, linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { execFile } from 'child_process';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerRemoteContributions } from 'vs/workbench/contrib/terminal/electron-browser/terminalRemote';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { INativeOpenFileRequest } from 'vs/platform/windows/node/window';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
|
||||
export class TerminalNativeService extends Disposable implements ITerminalNativeService {
|
||||
export class TerminalNativeContribution extends Disposable implements IWorkbenchContribution {
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
public get linuxDistro(): LinuxDistro { return linuxDistro; }
|
||||
|
||||
private readonly _onRequestFocusActiveInstance = this._register(new Emitter<void>());
|
||||
public get onRequestFocusActiveInstance(): Event<void> { return this._onRequestFocusActiveInstance.event; }
|
||||
private readonly _onOsResume = this._register(new Emitter<void>());
|
||||
public get onOsResume(): Event<void> { return this._onOsResume.event; }
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@IInstantiationService readonly instantiationService: IInstantiationService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IElectronService electronService: IElectronService
|
||||
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService,
|
||||
@IElectronService readonly electronService: IElectronService
|
||||
) {
|
||||
super();
|
||||
|
||||
ipcRenderer.on('vscode:openFiles', (event: unknown, request: IOpenFileRequest) => this._onOpenFileRequest(request));
|
||||
this._register(electronService.onOSResume(() => this._onOsResume.fire()));
|
||||
ipcRenderer.on('vscode:openFiles', (_: unknown, request: IOpenFileRequest) => this._onOpenFileRequest(request));
|
||||
this._register(electronService.onOSResume(() => this._onOsResume()));
|
||||
|
||||
this._terminalService.setLinuxDistro(linuxDistro);
|
||||
this._terminalService.setNativeWindowsDelegate({
|
||||
getWslPath: this._getWslPath.bind(this),
|
||||
getWindowsBuildNumber: this._getWindowsBuildNumber.bind(this)
|
||||
});
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection && connection.remoteAuthority) {
|
||||
@@ -46,18 +46,28 @@ export class TerminalNativeService extends Disposable implements ITerminalNative
|
||||
}
|
||||
}
|
||||
|
||||
private _onOsResume(): void {
|
||||
const activeTab = this._terminalService.getActiveTab();
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
activeTab.terminalInstances.forEach(instance => instance.forceRedraw());
|
||||
}
|
||||
|
||||
private async _onOpenFileRequest(request: INativeOpenFileRequest): Promise<void> {
|
||||
// if the request to open files is coming in from the integrated terminal (identified though
|
||||
// the termProgram variable) and we are instructed to wait for editors close, wait for the
|
||||
// marker file to get deleted and then focus back to the integrated terminal.
|
||||
if (request.termProgram === 'vscode' && request.filesToWait) {
|
||||
const waitMarkerFileUri = URI.revive(request.filesToWait.waitMarkerFileUri);
|
||||
await this.whenFileDeleted(waitMarkerFileUri);
|
||||
this._onRequestFocusActiveInstance.fire();
|
||||
await this._whenFileDeleted(waitMarkerFileUri);
|
||||
|
||||
// Focus active terminal
|
||||
this._terminalService.getActiveInstance()?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public whenFileDeleted(path: URI): Promise<void> {
|
||||
private _whenFileDeleted(path: URI): Promise<void> {
|
||||
// Complete when wait marker file is deleted
|
||||
return new Promise<void>(resolve => {
|
||||
let running = false;
|
||||
@@ -80,7 +90,7 @@ export class TerminalNativeService extends Disposable implements ITerminalNative
|
||||
* Converts a path to a path on WSL using the wslpath utility.
|
||||
* @param path The original path.
|
||||
*/
|
||||
public getWslPath(path: string): Promise<string> {
|
||||
private _getWslPath(path: string): Promise<string> {
|
||||
if (getWindowsBuildNumber() < 17063) {
|
||||
throw new Error('wslpath does not exist on Windows build < 17063');
|
||||
}
|
||||
@@ -92,7 +102,7 @@ export class TerminalNativeService extends Disposable implements ITerminalNative
|
||||
});
|
||||
}
|
||||
|
||||
public getWindowsBuildNumber(): number {
|
||||
private _getWindowsBuildNumber(): number {
|
||||
return getWindowsBuildNumber();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,13 @@ import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvir
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
// Writing large amounts of data can be corrupted for some reason, after looking into this is
|
||||
// appears to be a race condition around writing to the FD which may be based on how powerful the
|
||||
// hardware is. The workaround for this is to space out when large amounts of data is being written
|
||||
// to the terminal. See https://github.com/microsoft/vscode/issues/38137
|
||||
const WRITE_MAX_CHUNK_SIZE = 50;
|
||||
const WRITE_INTERVAL_MS = 5;
|
||||
|
||||
export class TerminalProcess extends Disposable implements ITerminalChildProcess {
|
||||
private _exitCode: number | undefined;
|
||||
private _exitMessage: string | undefined;
|
||||
@@ -27,6 +34,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
private _processStartupComplete: Promise<void> | undefined;
|
||||
private _isDisposed: boolean = false;
|
||||
private _titleInterval: NodeJS.Timer | null = null;
|
||||
private _writeQueue: string[] = [];
|
||||
private _writeTimeout: NodeJS.Timeout | undefined;
|
||||
private readonly _initialCwd: string;
|
||||
private readonly _ptyOptions: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions;
|
||||
|
||||
@@ -232,8 +241,37 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
if (this._isDisposed || !this._ptyProcess) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i <= Math.floor(data.length / WRITE_MAX_CHUNK_SIZE); i++) {
|
||||
this._writeQueue.push(data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE));
|
||||
}
|
||||
this._startWrite();
|
||||
}
|
||||
|
||||
private _startWrite(): void {
|
||||
// Don't write if it's already queued of is there is nothing to write
|
||||
if (this._writeTimeout !== undefined || this._writeQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._doWrite();
|
||||
|
||||
// Don't queue more writes if the queue is empty
|
||||
if (this._writeQueue.length === 0) {
|
||||
this._writeTimeout = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the next write
|
||||
this._writeTimeout = setTimeout(() => {
|
||||
this._writeTimeout = undefined;
|
||||
this._startWrite();
|
||||
}, WRITE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private _doWrite(): void {
|
||||
const data = this._writeQueue.shift()!;
|
||||
this._logService.trace('IPty#write', `${data.length} characters`);
|
||||
this._ptyProcess.write(data);
|
||||
this._ptyProcess!.write(data);
|
||||
}
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { TerminalLinkManager, XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
|
||||
import { Terminal as XtermTerminal } from 'xterm';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { TestPathService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
class TestTerminalLinkManager extends TerminalLinkManager {
|
||||
public get localLinkRegex(): RegExp {
|
||||
return this._localLinkRegex;
|
||||
}
|
||||
public preprocessPath(link: string): string | null {
|
||||
return this._preprocessPath(link);
|
||||
}
|
||||
protected _isLinkActivationModifierDown(event: MouseEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
public wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler {
|
||||
TerminalLinkManager._LINK_INTERCEPT_THRESHOLD = 0;
|
||||
return this._wrapLinkHandler((_, link) => handler(link));
|
||||
}
|
||||
}
|
||||
|
||||
class MockTerminalInstanceService implements ITerminalInstanceService {
|
||||
onRequestDefaultShellAndArgs?: Event<any> | undefined;
|
||||
getDefaultShellAndArgs(): Promise<{ shell: string; args: string | string[] | undefined; }> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
declare readonly _serviceBrand: undefined;
|
||||
getXtermConstructor(): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getXtermSearchConstructor(): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getXtermUnicode11Constructor(): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getXtermWebglConstructor(): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
createWindowsShellHelper(): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
createTerminalProcess(): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getMainProcessParentEnv(): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
suite('Workbench - TerminalLinkManager', () => {
|
||||
let instantiationService: TestInstantiationService;
|
||||
|
||||
setup(async () => {
|
||||
const configurationService = new TestConfigurationService();
|
||||
await configurationService.setUserConfiguration('terminal', { integrated: { enableFileLinks: true } });
|
||||
|
||||
instantiationService = new TestInstantiationService();
|
||||
instantiationService.stub(IEnvironmentService, TestEnvironmentService);
|
||||
instantiationService.stub(IPathService, new TestPathService());
|
||||
instantiationService.stub(ITerminalInstanceService, new MockTerminalInstanceService());
|
||||
instantiationService.stub(IConfigurationService, configurationService);
|
||||
});
|
||||
|
||||
suite('preprocessPath', () => {
|
||||
test('Windows', () => {
|
||||
const linkHandler: TestTerminalLinkManager = instantiationService.createInstance(TestTerminalLinkManager, new XtermTerminal() as any, {
|
||||
os: OperatingSystem.Windows,
|
||||
userHome: 'C:\\Users\\Me'
|
||||
} as any);
|
||||
linkHandler.processCwd = 'C:\\base';
|
||||
|
||||
assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1');
|
||||
assert.equal(linkHandler.preprocessPath('src\\file2'), 'C:\\base\\src\\file2');
|
||||
assert.equal(linkHandler.preprocessPath('~/src/file3'), 'C:\\Users\\Me\\src\\file3');
|
||||
assert.equal(linkHandler.preprocessPath('~\\src\\file4'), 'C:\\Users\\Me\\src\\file4');
|
||||
assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file5'), 'C:\\absolute\\path\\file5');
|
||||
assert.equal(linkHandler.preprocessPath('\\\\?\\C:\\absolute\\path\\extended\\file6'), 'C:\\absolute\\path\\extended\\file6');
|
||||
});
|
||||
test('Windows - spaces', () => {
|
||||
const linkHandler: TestTerminalLinkManager = instantiationService.createInstance(TestTerminalLinkManager, new XtermTerminal() as any, {
|
||||
os: OperatingSystem.Windows,
|
||||
userHome: 'C:\\Users\\M e'
|
||||
} as any);
|
||||
linkHandler.processCwd = 'C:\\base dir';
|
||||
|
||||
assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1');
|
||||
assert.equal(linkHandler.preprocessPath('src\\file2'), 'C:\\base dir\\src\\file2');
|
||||
assert.equal(linkHandler.preprocessPath('~/src/file3'), 'C:\\Users\\M e\\src\\file3');
|
||||
assert.equal(linkHandler.preprocessPath('~\\src\\file4'), 'C:\\Users\\M e\\src\\file4');
|
||||
assert.equal(linkHandler.preprocessPath('C:\\abso lute\\path\\file5'), 'C:\\abso lute\\path\\file5');
|
||||
});
|
||||
|
||||
test('Linux', () => {
|
||||
const linkHandler: TestTerminalLinkManager = instantiationService.createInstance(TestTerminalLinkManager, new XtermTerminal() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: '/home/me'
|
||||
} as any);
|
||||
linkHandler.processCwd = '/base';
|
||||
|
||||
assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1');
|
||||
assert.equal(linkHandler.preprocessPath('src/file2'), '/base/src/file2');
|
||||
assert.equal(linkHandler.preprocessPath('~/src/file3'), '/home/me/src/file3');
|
||||
assert.equal(linkHandler.preprocessPath('/absolute/path/file4'), '/absolute/path/file4');
|
||||
});
|
||||
|
||||
test('No Workspace', () => {
|
||||
const linkHandler: TestTerminalLinkManager = instantiationService.createInstance(TestTerminalLinkManager, new XtermTerminal() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: '/home/me'
|
||||
} as any);
|
||||
|
||||
assert.equal(linkHandler.preprocessPath('./src/file1'), null);
|
||||
assert.equal(linkHandler.preprocessPath('src/file2'), null);
|
||||
assert.equal(linkHandler.preprocessPath('~/src/file3'), '/home/me/src/file3');
|
||||
assert.equal(linkHandler.preprocessPath('/absolute/path/file4'), '/absolute/path/file4');
|
||||
});
|
||||
});
|
||||
|
||||
suite('wrapLinkHandler', () => {
|
||||
const nullMouseEvent: any = Object.freeze({ preventDefault: () => { } });
|
||||
|
||||
test('should allow intercepting of links with onBeforeHandleLink', async () => {
|
||||
const linkHandler: TestTerminalLinkManager = instantiationService.createInstance(TestTerminalLinkManager, new XtermTerminal() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: ''
|
||||
} as any);
|
||||
linkHandler.onBeforeHandleLink(e => {
|
||||
if (e.link === 'https://www.microsoft.com') {
|
||||
intercepted = true;
|
||||
e.resolve(true);
|
||||
}
|
||||
e.resolve(false);
|
||||
});
|
||||
const wrappedHandler = linkHandler.wrapLinkHandler(() => defaultHandled = true);
|
||||
|
||||
let defaultHandled = false;
|
||||
let intercepted = false;
|
||||
await wrappedHandler(nullMouseEvent, 'https://www.visualstudio.com');
|
||||
assert.equal(intercepted, false);
|
||||
assert.equal(defaultHandled, true);
|
||||
|
||||
defaultHandled = false;
|
||||
intercepted = false;
|
||||
await wrappedHandler(nullMouseEvent, 'https://www.microsoft.com');
|
||||
assert.equal(intercepted, true);
|
||||
assert.equal(defaultHandled, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
// const configurationService = new TestConfigurationService();
|
||||
// configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
// configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: 'bar' } });
|
||||
// const configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!);
|
||||
// const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!);
|
||||
// configHelper.panelContainer = fixture;
|
||||
// assert.equal(configHelper.getFont().fontFamily, 'bar', 'terminal.integrated.fontFamily should be selected over editor.fontFamily');
|
||||
// });
|
||||
@@ -30,7 +30,8 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } });
|
||||
const configHelper = new TerminalConfigHelper(LinuxDistro.Fedora, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.setLinuxDistro(LinuxDistro.Fedora);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontFamily, '\'DejaVu Sans Mono\', monospace', 'Fedora should have its font overridden when terminal.integrated.fontFamily not set');
|
||||
});
|
||||
@@ -39,7 +40,8 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } });
|
||||
const configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.setLinuxDistro(LinuxDistro.Ubuntu);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontFamily, '\'Ubuntu Mono\', monospace', 'Ubuntu should have its font overridden when terminal.integrated.fontFamily not set');
|
||||
});
|
||||
@@ -48,7 +50,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } });
|
||||
const configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set');
|
||||
});
|
||||
@@ -66,7 +68,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: 10
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 10, 'terminal.integrated.fontSize should be selected over editor.fontSize');
|
||||
|
||||
@@ -79,11 +81,12 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: 0
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.setLinuxDistro(LinuxDistro.Ubuntu);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 8, 'The minimum terminal font size (with adjustment) should be used when terminal.integrated.fontSize less than it');
|
||||
|
||||
configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 6, 'The minimum terminal font size should be used when terminal.integrated.fontSize less than it');
|
||||
|
||||
@@ -96,7 +99,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: 1500
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 25, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it');
|
||||
|
||||
@@ -109,11 +112,12 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: null
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(LinuxDistro.Ubuntu, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.setLinuxDistro(LinuxDistro.Ubuntu);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize + 2, 'The default editor font size (with adjustment) should be used when terminal.integrated.fontSize is not set');
|
||||
|
||||
configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize, 'The default editor font size should be used when terminal.integrated.fontSize is not set');
|
||||
});
|
||||
@@ -131,7 +135,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
lineHeight: 2
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().lineHeight, 2, 'terminal.integrated.lineHeight should be selected over editor.lineHeight');
|
||||
|
||||
@@ -145,7 +149,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
lineHeight: 0
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be 1 when terminal.integrated.lineHeight not set');
|
||||
});
|
||||
@@ -158,7 +162,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced');
|
||||
});
|
||||
@@ -170,7 +174,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontFamily: 'sans-serif'
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced');
|
||||
});
|
||||
@@ -182,7 +186,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontFamily: 'serif'
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced');
|
||||
});
|
||||
@@ -198,7 +202,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced');
|
||||
});
|
||||
@@ -214,7 +218,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced');
|
||||
});
|
||||
@@ -230,7 +234,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(LinuxDistro.Unknown, configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user