mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 17:20:28 -04:00
Merge from vscode 52dcb723a39ae75bee1bd56b3312d7fcdc87aeed (#6719)
This commit is contained in:
@@ -3,9 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Terminal, IMarker } from 'xterm';
|
||||
import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Terminal, IMarker, ITerminalAddon } from 'xterm';
|
||||
import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
/**
|
||||
* The minimum size of the prompt in which to assume the line is a command.
|
||||
@@ -22,15 +21,15 @@ export const enum ScrollPosition {
|
||||
Middle
|
||||
}
|
||||
|
||||
export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposable {
|
||||
export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon {
|
||||
private _currentMarker: IMarker | Boundary = Boundary.Bottom;
|
||||
private _selectionStart: IMarker | Boundary | null = null;
|
||||
private _isDisposable: boolean = false;
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor(
|
||||
private _xterm: Terminal
|
||||
) {
|
||||
this._xterm.onKey(e => this._onKey(e.key));
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
terminal.onKey(e => this._onKey(e.key));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -48,116 +47,138 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa
|
||||
}
|
||||
|
||||
private _onEnter(): void {
|
||||
if (this._xterm.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) {
|
||||
this._xterm.addMarker(0);
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (this._terminal.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) {
|
||||
this._terminal.addMarker(0);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollToPreviousCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (!retainSelection) {
|
||||
this._selectionStart = null;
|
||||
}
|
||||
|
||||
let markerIndex;
|
||||
if (this._currentMarker === Boundary.Bottom) {
|
||||
markerIndex = this._xterm.markers.length - 1;
|
||||
markerIndex = this._terminal.markers.length - 1;
|
||||
} else if (this._currentMarker === Boundary.Top) {
|
||||
markerIndex = -1;
|
||||
} else if (this._isDisposable) {
|
||||
markerIndex = this._findPreviousCommand();
|
||||
markerIndex = this._findPreviousCommand(this._terminal);
|
||||
this._currentMarker.dispose();
|
||||
this._isDisposable = false;
|
||||
} else {
|
||||
markerIndex = this._xterm.markers.indexOf(this._currentMarker) - 1;
|
||||
markerIndex = this._terminal.markers.indexOf(this._currentMarker) - 1;
|
||||
}
|
||||
|
||||
if (markerIndex < 0) {
|
||||
this._currentMarker = Boundary.Top;
|
||||
this._xterm.scrollToTop();
|
||||
this._terminal.scrollToTop();
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentMarker = this._xterm.markers[markerIndex];
|
||||
this._currentMarker = this._terminal.markers[markerIndex];
|
||||
this._scrollToMarker(this._currentMarker, scrollPosition);
|
||||
}
|
||||
|
||||
public scrollToNextCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (!retainSelection) {
|
||||
this._selectionStart = null;
|
||||
}
|
||||
|
||||
let markerIndex;
|
||||
if (this._currentMarker === Boundary.Bottom) {
|
||||
markerIndex = this._xterm.markers.length;
|
||||
markerIndex = this._terminal.markers.length;
|
||||
} else if (this._currentMarker === Boundary.Top) {
|
||||
markerIndex = 0;
|
||||
} else if (this._isDisposable) {
|
||||
markerIndex = this._findNextCommand();
|
||||
markerIndex = this._findNextCommand(this._terminal);
|
||||
this._currentMarker.dispose();
|
||||
this._isDisposable = false;
|
||||
} else {
|
||||
markerIndex = this._xterm.markers.indexOf(this._currentMarker) + 1;
|
||||
markerIndex = this._terminal.markers.indexOf(this._currentMarker) + 1;
|
||||
}
|
||||
|
||||
if (markerIndex >= this._xterm.markers.length) {
|
||||
if (markerIndex >= this._terminal.markers.length) {
|
||||
this._currentMarker = Boundary.Bottom;
|
||||
this._xterm.scrollToBottom();
|
||||
this._terminal.scrollToBottom();
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentMarker = this._xterm.markers[markerIndex];
|
||||
this._currentMarker = this._terminal.markers[markerIndex];
|
||||
this._scrollToMarker(this._currentMarker, scrollPosition);
|
||||
}
|
||||
|
||||
private _scrollToMarker(marker: IMarker, position: ScrollPosition): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
let line = marker.line;
|
||||
if (position === ScrollPosition.Middle) {
|
||||
line = Math.max(line - Math.floor(this._xterm.rows / 2), 0);
|
||||
line = Math.max(line - Math.floor(this._terminal.rows / 2), 0);
|
||||
}
|
||||
this._xterm.scrollToLine(line);
|
||||
this._terminal.scrollToLine(line);
|
||||
}
|
||||
|
||||
public selectToPreviousCommand(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (this._selectionStart === null) {
|
||||
this._selectionStart = this._currentMarker;
|
||||
}
|
||||
this.scrollToPreviousCommand(ScrollPosition.Middle, true);
|
||||
this._selectLines(this._currentMarker, this._selectionStart);
|
||||
this._selectLines(this._terminal, this._currentMarker, this._selectionStart);
|
||||
}
|
||||
|
||||
public selectToNextCommand(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (this._selectionStart === null) {
|
||||
this._selectionStart = this._currentMarker;
|
||||
}
|
||||
this.scrollToNextCommand(ScrollPosition.Middle, true);
|
||||
this._selectLines(this._currentMarker, this._selectionStart);
|
||||
this._selectLines(this._terminal, this._currentMarker, this._selectionStart);
|
||||
}
|
||||
|
||||
public selectToPreviousLine(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (this._selectionStart === null) {
|
||||
this._selectionStart = this._currentMarker;
|
||||
}
|
||||
|
||||
this.scrollToPreviousLine(ScrollPosition.Middle, true);
|
||||
this._selectLines(this._currentMarker, this._selectionStart);
|
||||
this.scrollToPreviousLine(this._terminal, ScrollPosition.Middle, true);
|
||||
this._selectLines(this._terminal, this._currentMarker, this._selectionStart);
|
||||
}
|
||||
|
||||
public selectToNextLine(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
if (this._selectionStart === null) {
|
||||
this._selectionStart = this._currentMarker;
|
||||
}
|
||||
|
||||
this.scrollToNextLine(ScrollPosition.Middle, true);
|
||||
this._selectLines(this._currentMarker, this._selectionStart);
|
||||
this.scrollToNextLine(this._terminal, ScrollPosition.Middle, true);
|
||||
this._selectLines(this._terminal, this._currentMarker, this._selectionStart);
|
||||
}
|
||||
|
||||
private _selectLines(start: IMarker | Boundary, end: IMarker | Boundary | null): void {
|
||||
private _selectLines(xterm: Terminal, start: IMarker | Boundary, end: IMarker | Boundary | null): void {
|
||||
if (end === null) {
|
||||
end = Boundary.Bottom;
|
||||
}
|
||||
|
||||
let startLine = this._getLine(start);
|
||||
let endLine = this._getLine(end);
|
||||
let startLine = this._getLine(xterm, start);
|
||||
let endLine = this._getLine(xterm, end);
|
||||
|
||||
if (startLine > endLine) {
|
||||
const temp = startLine;
|
||||
@@ -169,13 +190,13 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa
|
||||
// command in the selection for the current command
|
||||
endLine -= 1;
|
||||
|
||||
this._xterm.selectLines(startLine, endLine);
|
||||
xterm.selectLines(startLine, endLine);
|
||||
}
|
||||
|
||||
private _getLine(marker: IMarker | Boundary): number {
|
||||
private _getLine(xterm: Terminal, marker: IMarker | Boundary): number {
|
||||
// Use the _second last_ row as the last row is likely the prompt
|
||||
if (marker === Boundary.Bottom) {
|
||||
return this._xterm.buffer.baseY + this._xterm.rows - 1;
|
||||
return xterm.buffer.baseY + xterm.rows - 1;
|
||||
}
|
||||
|
||||
if (marker === Boundary.Top) {
|
||||
@@ -185,74 +206,74 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa
|
||||
return marker.line;
|
||||
}
|
||||
|
||||
public scrollToPreviousLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
|
||||
public scrollToPreviousLine(xterm: Terminal, scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
|
||||
if (!retainSelection) {
|
||||
this._selectionStart = null;
|
||||
}
|
||||
|
||||
if (this._currentMarker === Boundary.Top) {
|
||||
this._xterm.scrollToTop();
|
||||
xterm.scrollToTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentMarker === Boundary.Bottom) {
|
||||
this._currentMarker = this._xterm.addMarker(this._getOffset() - 1);
|
||||
this._currentMarker = xterm.addMarker(this._getOffset(xterm) - 1);
|
||||
} else {
|
||||
const offset = this._getOffset();
|
||||
const offset = this._getOffset(xterm);
|
||||
if (this._isDisposable) {
|
||||
this._currentMarker.dispose();
|
||||
}
|
||||
this._currentMarker = this._xterm.addMarker(offset - 1);
|
||||
this._currentMarker = xterm.addMarker(offset - 1);
|
||||
}
|
||||
this._isDisposable = true;
|
||||
this._scrollToMarker(this._currentMarker, scrollPosition);
|
||||
}
|
||||
|
||||
public scrollToNextLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
|
||||
public scrollToNextLine(xterm: Terminal, scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void {
|
||||
if (!retainSelection) {
|
||||
this._selectionStart = null;
|
||||
}
|
||||
|
||||
if (this._currentMarker === Boundary.Bottom) {
|
||||
this._xterm.scrollToBottom();
|
||||
xterm.scrollToBottom();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentMarker === Boundary.Top) {
|
||||
this._currentMarker = this._xterm.addMarker(this._getOffset() + 1);
|
||||
this._currentMarker = xterm.addMarker(this._getOffset(xterm) + 1);
|
||||
} else {
|
||||
const offset = this._getOffset();
|
||||
const offset = this._getOffset(xterm);
|
||||
if (this._isDisposable) {
|
||||
this._currentMarker.dispose();
|
||||
}
|
||||
this._currentMarker = this._xterm.addMarker(offset + 1);
|
||||
this._currentMarker = xterm.addMarker(offset + 1);
|
||||
}
|
||||
this._isDisposable = true;
|
||||
this._scrollToMarker(this._currentMarker, scrollPosition);
|
||||
}
|
||||
|
||||
private _getOffset(): number {
|
||||
private _getOffset(xterm: Terminal): number {
|
||||
if (this._currentMarker === Boundary.Bottom) {
|
||||
return 0;
|
||||
} else if (this._currentMarker === Boundary.Top) {
|
||||
return 0 - (this._xterm.buffer.baseY + this._xterm.buffer.cursorY);
|
||||
return 0 - (xterm.buffer.baseY + xterm.buffer.cursorY);
|
||||
} else {
|
||||
let offset = this._getLine(this._currentMarker);
|
||||
offset -= this._xterm.buffer.baseY + this._xterm.buffer.cursorY;
|
||||
let offset = this._getLine(xterm, this._currentMarker);
|
||||
offset -= xterm.buffer.baseY + xterm.buffer.cursorY;
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
private _findPreviousCommand(): number {
|
||||
private _findPreviousCommand(xterm: Terminal): number {
|
||||
if (this._currentMarker === Boundary.Top) {
|
||||
return 0;
|
||||
} else if (this._currentMarker === Boundary.Bottom) {
|
||||
return this._xterm.markers.length - 1;
|
||||
return xterm.markers.length - 1;
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = this._xterm.markers.length - 1; i >= 0; i--) {
|
||||
if (this._xterm.markers[i].line < this._currentMarker.line) {
|
||||
for (i = xterm.markers.length - 1; i >= 0; i--) {
|
||||
if (xterm.markers[i].line < this._currentMarker.line) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -260,20 +281,20 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa
|
||||
return -1;
|
||||
}
|
||||
|
||||
private _findNextCommand(): number {
|
||||
private _findNextCommand(xterm: Terminal): number {
|
||||
if (this._currentMarker === Boundary.Top) {
|
||||
return 0;
|
||||
} else if (this._currentMarker === Boundary.Bottom) {
|
||||
return this._xterm.markers.length - 1;
|
||||
return xterm.markers.length - 1;
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = 0; i < this._xterm.markers.length; i++) {
|
||||
if (this._xterm.markers[i].line > this._currentMarker.line) {
|
||||
for (i = 0; i < xterm.markers.length; i++) {
|
||||
if (xterm.markers[i].line > this._currentMarker.line) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return this._xterm.markers.length;
|
||||
return xterm.markers.length;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { INavigationMode } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export class NavigationModeAddon implements INavigationMode, ITerminalAddon {
|
||||
private _terminal: Terminal;
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor(
|
||||
private _navigationModeContextKey: IContextKey<boolean>
|
||||
@@ -22,11 +22,18 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon {
|
||||
dispose() { }
|
||||
|
||||
exitNavigationMode(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
this._terminal.scrollToBottom();
|
||||
this._terminal.focus();
|
||||
}
|
||||
|
||||
focusPreviousLine(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus previous row if a row is already focused
|
||||
if (document.activeElement && document.activeElement.parentElement && document.activeElement.parentElement.classList.contains('xterm-accessibility-tree')) {
|
||||
const element = <HTMLElement | null>document.activeElement.previousElementSibling;
|
||||
@@ -66,6 +73,10 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon {
|
||||
}
|
||||
|
||||
focusNextLine(): void {
|
||||
if (!this._terminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus previous row if a row is already focused
|
||||
if (document.activeElement && document.activeElement.parentElement && document.activeElement.parentElement.classList.contains('xterm-accessibility-tree')) {
|
||||
const element = <HTMLElement | null>document.activeElement.nextElementSibling;
|
||||
@@ -103,4 +114,4 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon {
|
||||
});
|
||||
this._navigationModeContextKey.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,21 @@ configurationRegistry.registerConfiguration({
|
||||
title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'terminal.integrated.automationShell.linux': {
|
||||
markdownDescription: nls.localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`', '`env`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
'terminal.integrated.automationShell.osx': {
|
||||
markdownDescription: nls.localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`', '`env`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
'terminal.integrated.automationShell.windows': {
|
||||
markdownDescription: nls.localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`', '`env`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
'terminal.integrated.shellArgs.linux': {
|
||||
markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
@@ -450,11 +465,11 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTe
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, {
|
||||
primary: 0,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }
|
||||
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Previous Command', category);
|
||||
}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Previous Command', category);
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, {
|
||||
primary: 0,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }
|
||||
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Next Command', category);
|
||||
}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Next Command', category);
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, {
|
||||
primary: 0,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }
|
||||
|
||||
@@ -30,10 +30,10 @@ export interface ITerminalInstanceService {
|
||||
createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper;
|
||||
createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess;
|
||||
|
||||
getDefaultShellAndArgs(platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>;
|
||||
getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>;
|
||||
getMainProcessParentEnv(): Promise<IProcessEnvironment>;
|
||||
}
|
||||
|
||||
export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper {
|
||||
panelContainer: HTMLElement;
|
||||
panelContainer: HTMLElement | undefined;
|
||||
}
|
||||
|
||||
@@ -1168,7 +1168,7 @@ export class ScrollToPreviousCommandAction extends Action {
|
||||
|
||||
public run(): Promise<any> {
|
||||
const instance = this.terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance && instance.commandTracker) {
|
||||
instance.commandTracker.scrollToPreviousCommand();
|
||||
instance.focus();
|
||||
}
|
||||
@@ -1189,7 +1189,7 @@ export class ScrollToNextCommandAction extends Action {
|
||||
|
||||
public run(): Promise<any> {
|
||||
const instance = this.terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance && instance.commandTracker) {
|
||||
instance.commandTracker.scrollToNextCommand();
|
||||
instance.focus();
|
||||
}
|
||||
@@ -1210,7 +1210,7 @@ export class SelectToPreviousCommandAction extends Action {
|
||||
|
||||
public run(): Promise<any> {
|
||||
const instance = this.terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance && instance.commandTracker) {
|
||||
instance.commandTracker.selectToPreviousCommand();
|
||||
instance.focus();
|
||||
}
|
||||
@@ -1231,7 +1231,7 @@ export class SelectToNextCommandAction extends Action {
|
||||
|
||||
public run(): Promise<any> {
|
||||
const instance = this.terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance && instance.commandTracker) {
|
||||
instance.commandTracker.selectToNextCommand();
|
||||
instance.focus();
|
||||
}
|
||||
@@ -1252,7 +1252,7 @@ export class SelectToPreviousLineAction extends Action {
|
||||
|
||||
public run(): Promise<any> {
|
||||
const instance = this.terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance && instance.commandTracker) {
|
||||
instance.commandTracker.selectToPreviousLine();
|
||||
instance.focus();
|
||||
}
|
||||
@@ -1273,7 +1273,7 @@ export class SelectToNextLineAction extends Action {
|
||||
|
||||
public run(): Promise<any> {
|
||||
const instance = this.terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance && instance.commandTracker) {
|
||||
instance.commandTracker.selectToNextLine();
|
||||
instance.focus();
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ const MAXIMUM_FONT_SIZE = 25;
|
||||
* specific test cases can be written.
|
||||
*/
|
||||
export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
public panelContainer: HTMLElement;
|
||||
public panelContainer: HTMLElement | undefined;
|
||||
|
||||
private _charMeasureElement: HTMLElement;
|
||||
private _lastFontMeasurement: ITerminalFont;
|
||||
public config: ITerminalConfiguration;
|
||||
private _charMeasureElement: HTMLElement | undefined;
|
||||
private _lastFontMeasurement: ITerminalFont | undefined;
|
||||
public config!: ITerminalConfiguration;
|
||||
|
||||
private readonly _onWorkspacePermissionsChanged = new Emitter<boolean>();
|
||||
public get onWorkspacePermissionsChanged(): Event<boolean> { return this._onWorkspacePermissionsChanged.event; }
|
||||
@@ -55,49 +55,55 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
}
|
||||
|
||||
public configFontIsMonospace(): boolean {
|
||||
this._createCharMeasureElementIfNecessary();
|
||||
const fontSize = 15;
|
||||
const fontFamily = this.config.fontFamily || this._configurationService.getValue<IEditorOptions>('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
|
||||
const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
|
||||
const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
|
||||
|
||||
const invalidBounds = !i_rect.width || !w_rect.width;
|
||||
if (invalidBounds) {
|
||||
// There is no reason to believe the font is not Monospace.
|
||||
// Check for invalid bounds, there is no reason to believe the font is not monospace
|
||||
if (!i_rect || !w_rect || !i_rect.width || !w_rect.width) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return i_rect.width === w_rect.width;
|
||||
}
|
||||
|
||||
private _createCharMeasureElementIfNecessary() {
|
||||
private _createCharMeasureElementIfNecessary(): HTMLElement {
|
||||
if (!this.panelContainer) {
|
||||
throw new Error('Cannot measure element when terminal is not attached');
|
||||
}
|
||||
// Create charMeasureElement if it hasn't been created or if it was orphaned by its parent
|
||||
if (!this._charMeasureElement || !this._charMeasureElement.parentElement) {
|
||||
this._charMeasureElement = document.createElement('div');
|
||||
this.panelContainer.appendChild(this._charMeasureElement);
|
||||
}
|
||||
return this._charMeasureElement;
|
||||
}
|
||||
|
||||
private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect {
|
||||
const style = this._charMeasureElement.style;
|
||||
private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect | undefined {
|
||||
let charMeasureElement: HTMLElement;
|
||||
try {
|
||||
charMeasureElement = this._createCharMeasureElementIfNecessary();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const style = charMeasureElement.style;
|
||||
style.display = 'inline-block';
|
||||
style.fontFamily = fontFamily;
|
||||
style.fontSize = fontSize + 'px';
|
||||
style.lineHeight = 'normal';
|
||||
this._charMeasureElement.innerText = char;
|
||||
const rect = this._charMeasureElement.getBoundingClientRect();
|
||||
charMeasureElement.innerText = char;
|
||||
const rect = charMeasureElement.getBoundingClientRect();
|
||||
style.display = 'none';
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
|
||||
this._createCharMeasureElementIfNecessary();
|
||||
|
||||
const rect = this._getBoundingRectFor('X', fontFamily, fontSize);
|
||||
|
||||
// Bounding client rect was invalid, use last font measurement if available.
|
||||
if (this._lastFontMeasurement && !rect.width && !rect.height) {
|
||||
if (this._lastFontMeasurement && (!rect || !rect.width || !rect.height)) {
|
||||
return this._lastFontMeasurement;
|
||||
}
|
||||
|
||||
@@ -106,8 +112,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
fontSize,
|
||||
letterSpacing,
|
||||
lineHeight,
|
||||
charWidth: rect.width,
|
||||
charHeight: Math.ceil(rect.height)
|
||||
charWidth: rect && rect.width ? rect.width : 0,
|
||||
charHeight: rect && rect.height ? Math.ceil(rect.height) : 0
|
||||
};
|
||||
return this._lastFontMeasurement;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget';
|
||||
import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -78,4 +78,14 @@ export class TerminalFindWidget extends SimpleFindWidget {
|
||||
protected onFindInputFocusTrackerBlur() {
|
||||
this._findInputFocused.reset();
|
||||
}
|
||||
|
||||
public findFirst() {
|
||||
const instance = this._terminalService.getActiveInstance();
|
||||
if (instance) {
|
||||
if (instance.hasSelection()) {
|
||||
instance.clearSelection();
|
||||
}
|
||||
instance.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -30,13 +30,13 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR
|
||||
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler';
|
||||
import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { ITerminalInstanceService } 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';
|
||||
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
|
||||
import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon';
|
||||
|
||||
// How long in milliseconds should an average frame take to render for a notification to appear
|
||||
@@ -165,15 +165,15 @@ interface IGridDimensions {
|
||||
rows: number;
|
||||
}
|
||||
|
||||
export class TerminalInstance implements ITerminalInstance {
|
||||
export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private static readonly EOL_REGEX = /\r?\n/g;
|
||||
|
||||
private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
|
||||
private static _lastKnownGridDimensions: IGridDimensions | undefined;
|
||||
private static _idCounter = 1;
|
||||
|
||||
private _processManager: ITerminalProcessManager | undefined;
|
||||
private _pressAnyKeyToCloseListener: lifecycle.IDisposable | undefined;
|
||||
private _processManager!: ITerminalProcessManager;
|
||||
private _pressAnyKeyToCloseListener: IDisposable | undefined;
|
||||
|
||||
private _id: number;
|
||||
private _isExiting: boolean;
|
||||
@@ -181,27 +181,26 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
private _isVisible: boolean;
|
||||
private _isDisposed: boolean;
|
||||
private _skipTerminalCommands: string[];
|
||||
private _title: string;
|
||||
private _wrapperElement: HTMLDivElement;
|
||||
private _xterm: XTermTerminal;
|
||||
private _title: string = '';
|
||||
private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
|
||||
private _xterm: XTermTerminal | undefined;
|
||||
private _xtermSearch: SearchAddon | undefined;
|
||||
private _xtermElement: HTMLDivElement;
|
||||
private _xtermElement: HTMLDivElement | undefined;
|
||||
private _terminalHasTextContextKey: IContextKey<boolean>;
|
||||
private _terminalA11yTreeFocusContextKey: IContextKey<boolean>;
|
||||
private _cols: number;
|
||||
private _rows: number;
|
||||
private _cols: number = 0;
|
||||
private _rows: number = 0;
|
||||
private _dimensionsOverride: ITerminalDimensions | undefined;
|
||||
private _windowsShellHelper: IWindowsShellHelper | undefined;
|
||||
private _xtermReadyPromise: Promise<void>;
|
||||
private _xtermReadyPromise: Promise<XTermTerminal>;
|
||||
private _titleReadyPromise: Promise<string>;
|
||||
private _titleReadyComplete: (title: string) => any;
|
||||
private _titleReadyComplete: ((title: string) => any) | undefined;
|
||||
|
||||
private readonly _disposables = new lifecycle.DisposableStore();
|
||||
private _messageTitleDisposable: lifecycle.IDisposable | undefined;
|
||||
private _messageTitleDisposable: IDisposable | undefined;
|
||||
|
||||
private _widgetManager: TerminalWidgetManager;
|
||||
private _linkHandler: TerminalLinkHandler;
|
||||
private _commandTracker: TerminalCommandTracker;
|
||||
private _widgetManager: TerminalWidgetManager | undefined;
|
||||
private _linkHandler: TerminalLinkHandler | undefined;
|
||||
private _commandTrackerAddon: CommandTrackerAddon | undefined;
|
||||
private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
|
||||
|
||||
public disableLayout: boolean;
|
||||
@@ -221,15 +220,15 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
public get maxCols(): number { return this._cols; }
|
||||
public get maxRows(): number { return this._rows; }
|
||||
// TODO: Ideally processId would be merged into processReady
|
||||
public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; }
|
||||
public get processId(): number | undefined { return this._processManager.shellProcessId; }
|
||||
// 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 ? this._processManager.ptyProcessReady : Promise.resolve(undefined); }
|
||||
public get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
|
||||
public get title(): string { return this._title; }
|
||||
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
|
||||
public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
|
||||
public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
|
||||
public get commandTracker(): TerminalCommandTracker { return this._commandTracker; }
|
||||
public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; }
|
||||
public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; }
|
||||
|
||||
private readonly _onExit = new Emitter<number>();
|
||||
@@ -246,8 +245,6 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
public get onData(): Event<string> { return this._onData.event; }
|
||||
private readonly _onLineData = new Emitter<string>();
|
||||
public get onLineData(): Event<string> { return this._onLineData.event; }
|
||||
private readonly _onRendererInput = new Emitter<string>();
|
||||
public get onRendererInput(): Event<string> { return this._onRendererInput.event; }
|
||||
private readonly _onRequestExtHostProcess = new Emitter<ITerminalInstance>();
|
||||
public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
|
||||
private readonly _onDimensionsChanged = new Emitter<void>();
|
||||
@@ -275,6 +272,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._skipTerminalCommands = [];
|
||||
this._isExiting = false;
|
||||
this._hadFocusOnExit = false;
|
||||
@@ -293,11 +292,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig);
|
||||
|
||||
this._initDimensions();
|
||||
if (!this.shellLaunchConfig.isRendererOnly) {
|
||||
this._createProcess();
|
||||
} else {
|
||||
this.setTitle(this._shellLaunchConfig.name, false);
|
||||
}
|
||||
this._createProcess();
|
||||
|
||||
this._xtermReadyPromise = this._createXterm();
|
||||
this._xtermReadyPromise.then(() => {
|
||||
@@ -321,8 +316,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}));
|
||||
}
|
||||
|
||||
public addDisposable(disposable: lifecycle.IDisposable): void {
|
||||
this._disposables.add(disposable);
|
||||
public addDisposable(disposable: IDisposable): void {
|
||||
this._register(disposable);
|
||||
}
|
||||
|
||||
private _initDimensions(): void {
|
||||
@@ -455,11 +450,11 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
/**
|
||||
* Create xterm.js instance and attach data listeners.
|
||||
*/
|
||||
protected async _createXterm(): Promise<void> {
|
||||
protected async _createXterm(): Promise<XTermTerminal> {
|
||||
const Terminal = await this._getXtermConstructor();
|
||||
const font = this._configHelper.getFont(undefined, true);
|
||||
const config = this._configHelper.config;
|
||||
this._xterm = new Terminal({
|
||||
const xterm = new Terminal({
|
||||
scrollback: config.scrollback,
|
||||
theme: this._getXtermTheme(),
|
||||
drawBoldTextInBrightColors: config.drawBoldTextInBrightColors,
|
||||
@@ -476,53 +471,48 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
// TODO: Guess whether to use canvas or dom better
|
||||
rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType
|
||||
});
|
||||
this._xterm = xterm;
|
||||
this.updateAccessibilitySupport();
|
||||
this._terminalInstanceService.getXtermSearchConstructor().then(Addon => {
|
||||
this._xtermSearch = new Addon();
|
||||
this._xterm.loadAddon(this._xtermSearch);
|
||||
xterm.loadAddon(this._xtermSearch);
|
||||
});
|
||||
if (this._shellLaunchConfig.initialText) {
|
||||
this._xterm.writeln(this._shellLaunchConfig.initialText);
|
||||
}
|
||||
this._xterm.onLineFeed(() => this._onLineFeed());
|
||||
this._xterm.onKey(e => this._onKey(e.key, e.domEvent));
|
||||
this._xterm.onSelectionChange(async () => this._onSelectionChange());
|
||||
|
||||
if (this._processManager) {
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
this._xterm.onData(data => this._processManager!.write(data));
|
||||
// TODO: How does the cwd work on detached processes?
|
||||
this.processReady.then(async () => {
|
||||
this._linkHandler.processCwd = await this._processManager!.getInitialCwd();
|
||||
});
|
||||
// Init winpty compat and link handler after process creation as they rely on the
|
||||
// underlying process OS
|
||||
this._processManager.onProcessReady(() => {
|
||||
if (!this._processManager) {
|
||||
return;
|
||||
}
|
||||
if (this._processManager.os === platform.OperatingSystem.Windows) {
|
||||
this._xterm.setOption('windowsMode', true);
|
||||
// Force line data to be sent when the cursor is moved, the main purpose for
|
||||
// this is because ConPTY will often not do a line feed but instead move the
|
||||
// cursor, in which case we still want to send the current line's data to tasks.
|
||||
this._xterm.addCsiHandler('H', () => {
|
||||
this._onCursorMove();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager);
|
||||
});
|
||||
} else if (this.shellLaunchConfig.isRendererOnly) {
|
||||
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined);
|
||||
}
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
this._xterm.onData(data => this._processManager.write(data));
|
||||
// TODO: How does the cwd work on detached processes?
|
||||
this.processReady.then(async () => {
|
||||
if (this._linkHandler) {
|
||||
this._linkHandler.processCwd = await this._processManager.getInitialCwd();
|
||||
}
|
||||
});
|
||||
// Init winpty compat and link handler after process creation as they rely on the
|
||||
// underlying process OS
|
||||
this._processManager.onProcessReady(() => {
|
||||
if (this._processManager.os === platform.OperatingSystem.Windows) {
|
||||
xterm.setOption('windowsMode', true);
|
||||
// Force line data to be sent when the cursor is moved, the main purpose for
|
||||
// this is because ConPTY will often not do a line feed but instead move the
|
||||
// cursor, in which case we still want to send the current line's data to tasks.
|
||||
xterm.addCsiHandler('H', () => {
|
||||
this._onCursorMove();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager, this._configHelper);
|
||||
});
|
||||
|
||||
// Register listener to trigger the onInput ext API if the terminal is a renderer only
|
||||
if (this._shellLaunchConfig.isRendererOnly) {
|
||||
this._xterm.onData(data => this._sendRendererInput(data));
|
||||
}
|
||||
this._commandTrackerAddon = new CommandTrackerAddon();
|
||||
this._xterm.loadAddon(this._commandTrackerAddon);
|
||||
this._register(this._themeService.onThemeChange(theme => this._updateTheme(xterm, theme)));
|
||||
|
||||
this._commandTracker = new TerminalCommandTracker(this._xterm);
|
||||
this._disposables.add(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
|
||||
return xterm;
|
||||
}
|
||||
|
||||
private _isScreenReaderOptimized(): boolean {
|
||||
@@ -562,7 +552,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public _attachToElement(container: HTMLElement): void {
|
||||
this._xtermReadyPromise.then(() => {
|
||||
this._xtermReadyPromise.then(xterm => {
|
||||
if (this._wrapperElement) {
|
||||
throw new Error('The terminal instance has already been attached to a container');
|
||||
}
|
||||
@@ -573,11 +563,11 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._xtermElement = document.createElement('div');
|
||||
|
||||
// Attach the xterm object to the DOM, exposing it to the smoke tests
|
||||
(<any>this._wrapperElement).xterm = this._xterm;
|
||||
this._wrapperElement.xterm = this._xterm;
|
||||
|
||||
this._xterm.open(this._xtermElement);
|
||||
this._xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this));
|
||||
this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
|
||||
xterm.open(this._xtermElement);
|
||||
xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this));
|
||||
xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
|
||||
// Disable all input if the terminal is exiting
|
||||
if (this._isExiting) {
|
||||
return false;
|
||||
@@ -604,7 +594,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
return true;
|
||||
});
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'mousedown', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => {
|
||||
// We need to listen to the mouseup event on the document since the user may release
|
||||
// the mouse button anywhere outside of _xterm.element.
|
||||
const listener = dom.addDisposableListener(document, 'mouseup', () => {
|
||||
@@ -616,17 +606,17 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}));
|
||||
|
||||
// xterm.js currently drops selection on keyup as we need to handle this case.
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'keyup', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'keyup', () => {
|
||||
// Wait until keyup has propagated through the DOM before evaluating
|
||||
// the new selection state.
|
||||
setTimeout(() => this._refreshSelectionContextKey(), 0);
|
||||
}));
|
||||
|
||||
const xtermHelper: HTMLElement = <HTMLElement>this._xterm.element.querySelector('.xterm-helpers');
|
||||
const xtermHelper: HTMLElement = <HTMLElement>xterm.element.querySelector('.xterm-helpers');
|
||||
const focusTrap: HTMLElement = document.createElement('div');
|
||||
focusTrap.setAttribute('tabindex', '0');
|
||||
dom.addClass(focusTrap, 'focus-trap');
|
||||
this._disposables.add(dom.addDisposableListener(focusTrap, 'focus', () => {
|
||||
this._register(dom.addDisposableListener(focusTrap, 'focus', () => {
|
||||
let currentElement = focusTrap;
|
||||
while (!dom.hasClass(currentElement, 'part')) {
|
||||
currentElement = currentElement.parentElement!;
|
||||
@@ -634,20 +624,20 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
|
||||
hidePanelElement.focus();
|
||||
}));
|
||||
xtermHelper.insertBefore(focusTrap, this._xterm.textarea);
|
||||
xtermHelper.insertBefore(focusTrap, xterm.textarea);
|
||||
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'focus', () => {
|
||||
this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
|
||||
this._terminalFocusContextKey.set(true);
|
||||
this._onFocused.fire(this);
|
||||
}));
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'blur', () => {
|
||||
this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => {
|
||||
this._terminalFocusContextKey.reset();
|
||||
this._refreshSelectionContextKey();
|
||||
}));
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'focus', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'focus', () => {
|
||||
this._terminalFocusContextKey.set(true);
|
||||
}));
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'blur', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'blur', () => {
|
||||
this._terminalFocusContextKey.reset();
|
||||
this._refreshSelectionContextKey();
|
||||
}));
|
||||
@@ -655,13 +645,13 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._wrapperElement.appendChild(this._xtermElement);
|
||||
this._container.appendChild(this._wrapperElement);
|
||||
|
||||
if (this._processManager) {
|
||||
this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
|
||||
this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager));
|
||||
} else if (this._shellLaunchConfig.isRendererOnly) {
|
||||
this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
|
||||
this._linkHandler.setWidgetManager(this._widgetManager);
|
||||
}
|
||||
const widgetManager = new TerminalWidgetManager(this._wrapperElement);
|
||||
this._widgetManager = widgetManager;
|
||||
this._processManager.onProcessReady(() => {
|
||||
if (this._linkHandler) {
|
||||
this._linkHandler.setWidgetManager(widgetManager);
|
||||
}
|
||||
});
|
||||
|
||||
const computedStyle = window.getComputedStyle(this._container);
|
||||
const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
|
||||
@@ -672,8 +662,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
// If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal
|
||||
// panel was initialized.
|
||||
if (this._xterm.getOption('disableStdin')) {
|
||||
this._attachPressAnyKeyToCloseListener();
|
||||
if (xterm.getOption('disableStdin')) {
|
||||
this._attachPressAnyKeyToCloseListener(xterm);
|
||||
}
|
||||
|
||||
const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false);
|
||||
@@ -683,9 +673,10 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
});
|
||||
}
|
||||
|
||||
private _measureRenderTime(): void {
|
||||
private async _measureRenderTime(): Promise<void> {
|
||||
const xterm = await this._xtermReadyPromise;
|
||||
const frameTimes: number[] = [];
|
||||
const textRenderLayer = this._xterm._core._renderService._renderer._renderLayers[0];
|
||||
const textRenderLayer = xterm._core._renderService._renderer._renderLayers[0];
|
||||
const originalOnGridChanged = textRenderLayer.onGridChanged;
|
||||
|
||||
const evaluateCanvasRenderer = () => {
|
||||
@@ -730,34 +721,41 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number {
|
||||
return this._linkHandler.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback);
|
||||
return this._linkHandler!.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback);
|
||||
}
|
||||
|
||||
public deregisterLinkMatcher(linkMatcherId: number): void {
|
||||
this._xterm.deregisterLinkMatcher(linkMatcherId);
|
||||
this._xtermReadyPromise.then(xterm => xterm.deregisterLinkMatcher(linkMatcherId));
|
||||
}
|
||||
|
||||
public hasSelection(): boolean {
|
||||
return this._xterm && this._xterm.hasSelection();
|
||||
return this._xterm ? this._xterm.hasSelection() : false;
|
||||
}
|
||||
|
||||
public async copySelection(): Promise<void> {
|
||||
const xterm = await this._xtermReadyPromise;
|
||||
if (this.hasSelection()) {
|
||||
await this._clipboardService.writeText(this._xterm.getSelection());
|
||||
await this._clipboardService.writeText(xterm.getSelection());
|
||||
} else {
|
||||
this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
|
||||
}
|
||||
}
|
||||
|
||||
public get selection(): string | undefined {
|
||||
return this.hasSelection() ? this._xterm.getSelection() : undefined;
|
||||
return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined;
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
this._xterm.clearSelection();
|
||||
}
|
||||
|
||||
public selectAll(): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
// Focus here to ensure the terminal context key is set
|
||||
this._xterm.focus();
|
||||
this._xterm.selectAll();
|
||||
@@ -778,6 +776,9 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public notifyFindWidgetFocusChanged(isFocused: boolean): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element);
|
||||
this._terminalFocusContextKey.set(terminalFocused);
|
||||
}
|
||||
@@ -785,18 +786,18 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
public dispose(immediate?: boolean): void {
|
||||
this._logService.trace(`terminalInstance#dispose (id: ${this.id})`);
|
||||
|
||||
lifecycle.dispose(this._windowsShellHelper);
|
||||
dispose(this._windowsShellHelper);
|
||||
this._windowsShellHelper = undefined;
|
||||
this._linkHandler = lifecycle.dispose(this._linkHandler);
|
||||
this._commandTracker = lifecycle.dispose(this._commandTracker);
|
||||
this._widgetManager = lifecycle.dispose(this._widgetManager);
|
||||
this._linkHandler = dispose(this._linkHandler);
|
||||
this._commandTrackerAddon = dispose(this._commandTrackerAddon);
|
||||
this._widgetManager = dispose(this._widgetManager);
|
||||
|
||||
if (this._xterm && this._xterm.element) {
|
||||
this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus');
|
||||
}
|
||||
if (this._wrapperElement) {
|
||||
if ((<any>this._wrapperElement).xterm) {
|
||||
(<any>this._wrapperElement).xterm = null;
|
||||
if (this._wrapperElement.xterm) {
|
||||
this._wrapperElement.xterm = undefined;
|
||||
}
|
||||
if (this._wrapperElement.parentElement) {
|
||||
this._container.removeChild(this._wrapperElement);
|
||||
@@ -813,39 +814,29 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._pressAnyKeyToCloseListener = undefined;
|
||||
}
|
||||
|
||||
if (this._processManager) {
|
||||
this._processManager.dispose(immediate);
|
||||
} else {
|
||||
// In cases where there is no associated process (for example executing an extension callback task)
|
||||
// consumers still expect on onExit event to be fired. An example of this is terminating the extension callback
|
||||
// task.
|
||||
this._onExit.fire(0);
|
||||
}
|
||||
this._processManager.dispose(immediate);
|
||||
// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
|
||||
// hasn't happened yet
|
||||
this._onProcessExit(undefined);
|
||||
|
||||
if (!this._isDisposed) {
|
||||
this._isDisposed = true;
|
||||
this._onDisposed.fire(this);
|
||||
}
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
public rendererExit(exitCode: number): void {
|
||||
// The use of this API is for cases where there is no backing process behind a terminal
|
||||
// instance (e.g. a custom execution task).
|
||||
if (!this.shellLaunchConfig.isRendererOnly) {
|
||||
throw new Error('rendererExit is only expected to be called on a renderer only terminal');
|
||||
}
|
||||
|
||||
return this._onProcessExit(exitCode);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public forceRedraw(): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
if (this._configHelper.config.experimentalRefreshOnResume) {
|
||||
if (this._xterm.getOption('rendererType') !== 'dom') {
|
||||
this._xterm.setOption('rendererType', 'dom');
|
||||
// Do this asynchronously to clear our the texture atlas as all terminals will not
|
||||
// be using canvas
|
||||
setTimeout(() => this._xterm.setOption('rendererType', 'canvas'), 0);
|
||||
const xterm = this._xterm;
|
||||
setTimeout(() => xterm.setOption('rendererType', 'canvas'), 0);
|
||||
}
|
||||
}
|
||||
this._xterm.refresh(0, this._xterm.rows - 1);
|
||||
@@ -870,6 +861,9 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public async paste(): Promise<void> {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
this.focus();
|
||||
this._xterm._core._coreService.triggerDataEvent(await this._clipboardService.readText(), true);
|
||||
}
|
||||
@@ -880,10 +874,6 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
return;
|
||||
}
|
||||
this._xterm.write(text);
|
||||
if (this._shellLaunchConfig.isRendererOnly) {
|
||||
// Fire onData API in the extension host
|
||||
this._onData.fire(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -894,17 +884,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
text += '\r';
|
||||
}
|
||||
|
||||
if (this._shellLaunchConfig.isRendererOnly) {
|
||||
// If the terminal is a renderer only, fire the onInput ext API
|
||||
this._sendRendererInput(text);
|
||||
} else {
|
||||
// If the terminal has a process, send it to the process
|
||||
if (this._processManager) {
|
||||
this._processManager.ptyProcessReady.then(() => {
|
||||
this._processManager!.write(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Send it to the process
|
||||
this._processManager.ptyProcessReady.then(() => this._processManager.write(text));
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): void {
|
||||
@@ -936,31 +917,45 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public scrollDownLine(): void {
|
||||
this._xterm.scrollLines(1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollLines(1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollDownPage(): void {
|
||||
this._xterm.scrollPages(1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollPages(1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollToBottom(): void {
|
||||
this._xterm.scrollToBottom();
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
public scrollUpLine(): void {
|
||||
this._xterm.scrollLines(-1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollLines(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollUpPage(): void {
|
||||
this._xterm.scrollPages(-1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollPages(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollToTop(): void {
|
||||
this._xterm.scrollToTop();
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollToTop();
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._xterm.clear();
|
||||
if (this._xterm) {
|
||||
this._xterm.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshSelectionContextKey() {
|
||||
@@ -987,12 +982,12 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
if (platform.isWindows) {
|
||||
this._processManager.ptyProcessReady.then(() => {
|
||||
if (this._processManager!.remoteAuthority) {
|
||||
if (this._processManager.remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
this._xtermReadyPromise.then(() => {
|
||||
if (!this._isDisposed) {
|
||||
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager!.shellProcessId, this, this._xterm);
|
||||
this._xtermReadyPromise.then(xterm => {
|
||||
if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) {
|
||||
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, this, xterm);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1001,7 +996,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
// Create the process asynchronously to allow the terminal's container
|
||||
// to be created so dimensions are accurate
|
||||
setTimeout(() => {
|
||||
this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized());
|
||||
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized());
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -1021,13 +1016,13 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
* through user action.
|
||||
*/
|
||||
private _onProcessExit(exitCode?: number): void {
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
|
||||
|
||||
// Prevent dispose functions being triggered multiple times
|
||||
if (this._isExiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
|
||||
|
||||
this._isExiting = true;
|
||||
let exitCodeMessage: string | undefined;
|
||||
|
||||
@@ -1039,7 +1034,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
|
||||
} else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString());
|
||||
} else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
} else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
let args = '';
|
||||
if (typeof this._shellLaunchConfig.args === 'string') {
|
||||
args = ` ${this._shellLaunchConfig.args}`;
|
||||
@@ -1061,29 +1056,31 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id})${this._processManager ? ' state ' + this._processManager.processState : ''}`);
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager.processState}`);
|
||||
|
||||
// Only trigger wait on exit when the exit was *not* triggered by the
|
||||
// user (via the `workbench.action.terminal.kill` command).
|
||||
if (this._shellLaunchConfig.waitOnExit && (!this._processManager || this._processManager.processState !== ProcessState.KILLED_BY_USER)) {
|
||||
if (exitCodeMessage) {
|
||||
this._xterm.writeln(exitCodeMessage);
|
||||
}
|
||||
if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
|
||||
let message = this._shellLaunchConfig.waitOnExit;
|
||||
// Bold the message and add an extra new line to make it stand out from the rest of the output
|
||||
message = `\r\n\x1b[1m${message}\x1b[0m`;
|
||||
this._xterm.writeln(message);
|
||||
}
|
||||
// Disable all input if the terminal is exiting and listen for next keypress
|
||||
this._xterm.setOption('disableStdin', true);
|
||||
if (this._xterm.textarea) {
|
||||
this._attachPressAnyKeyToCloseListener();
|
||||
}
|
||||
if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KILLED_BY_USER) {
|
||||
this._xtermReadyPromise.then(xterm => {
|
||||
if (exitCodeMessage) {
|
||||
xterm.writeln(exitCodeMessage);
|
||||
}
|
||||
if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
|
||||
let message = this._shellLaunchConfig.waitOnExit;
|
||||
// Bold the message and add an extra new line to make it stand out from the rest of the output
|
||||
message = `\r\n\x1b[1m${message}\x1b[0m`;
|
||||
xterm.writeln(message);
|
||||
}
|
||||
// Disable all input if the terminal is exiting and listen for next keypress
|
||||
xterm.setOption('disableStdin', true);
|
||||
if (xterm.textarea) {
|
||||
this._attachPressAnyKeyToCloseListener(xterm);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.dispose();
|
||||
if (exitCodeMessage) {
|
||||
if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
this._notificationService.error(exitCodeMessage);
|
||||
} else {
|
||||
if (this._configHelper.config.showExitAlert) {
|
||||
@@ -1098,9 +1095,9 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._onExit.fire(exitCode || 0);
|
||||
}
|
||||
|
||||
private _attachPressAnyKeyToCloseListener() {
|
||||
private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
|
||||
if (!this._pressAnyKeyToCloseListener) {
|
||||
this._pressAnyKeyToCloseListener = dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => {
|
||||
this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => {
|
||||
if (this._pressAnyKeyToCloseListener) {
|
||||
this._pressAnyKeyToCloseListener.dispose();
|
||||
this._pressAnyKeyToCloseListener = undefined;
|
||||
@@ -1119,59 +1116,47 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
// Kill and clear up the process, making the process manager ready for a new process
|
||||
if (this._processManager) {
|
||||
this._processManager.dispose();
|
||||
this._processManager = undefined;
|
||||
this._processManager.dispose();
|
||||
|
||||
if (this._xterm) {
|
||||
// Ensure new processes' output starts at start of new line
|
||||
this._xterm.write('\n\x1b[G');
|
||||
|
||||
// Print initialText if specified
|
||||
if (shell.initialText) {
|
||||
this._xterm.writeln(shell.initialText);
|
||||
}
|
||||
|
||||
// Clean up waitOnExit state
|
||||
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
|
||||
this._xterm.setOption('disableStdin', false);
|
||||
this._isExiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure new processes' output starts at start of new line
|
||||
this._xterm.write('\n\x1b[G');
|
||||
|
||||
// Print initialText if specified
|
||||
if (shell.initialText) {
|
||||
this._xterm.writeln(shell.initialText);
|
||||
}
|
||||
|
||||
const oldTitle = this._title;
|
||||
// Clean up waitOnExit state
|
||||
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
|
||||
this._xterm.setOption('disableStdin', false);
|
||||
this._isExiting = false;
|
||||
}
|
||||
// HACK: Force initialText to be non-falsy for reused terminals such that the
|
||||
// conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to hang
|
||||
// in Windows 10 1903 so we only want to use it when something is definitely written to the
|
||||
// terminal.
|
||||
shell.initialText = ' ';
|
||||
|
||||
// Set the new shell launch config
|
||||
this._shellLaunchConfig = shell; // Must be done before calling _createProcess()
|
||||
|
||||
// Launch the process unless this is only a renderer.
|
||||
// In the renderer only cases, we still need to set the title correctly.
|
||||
if (!this._shellLaunchConfig.isRendererOnly) {
|
||||
this._createProcess();
|
||||
} else if (this._shellLaunchConfig.name) {
|
||||
this.setTitle(this._shellLaunchConfig.name, false);
|
||||
}
|
||||
const oldTitle = this._title;
|
||||
this._createProcess();
|
||||
|
||||
if (oldTitle !== this._title) {
|
||||
this.setTitle(this._title, true);
|
||||
}
|
||||
|
||||
if (this._processManager) {
|
||||
// The "!" operator is required here because _processManager is set to undefiend earlier
|
||||
// and TS does not know that createProcess sets it.
|
||||
this._processManager!.onProcessData(data => this._onProcessData(data));
|
||||
}
|
||||
}
|
||||
|
||||
private _sendRendererInput(input: string): void {
|
||||
if (this._processManager) {
|
||||
throw new Error('onRendererInput attempted to be used on a regular terminal');
|
||||
}
|
||||
|
||||
// For terminal renderers onData fires on keystrokes and when sendText is called.
|
||||
this._onRendererInput.fire(input);
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
}
|
||||
|
||||
private _onLineFeed(): void {
|
||||
const buffer = this._xterm.buffer;
|
||||
const buffer = this._xterm!.buffer;
|
||||
const newLine = buffer.getLine(buffer.baseY + buffer.cursorY);
|
||||
if (newLine && !newLine.isWrapped) {
|
||||
this._sendLineData(buffer, buffer.baseY + buffer.cursorY - 1);
|
||||
@@ -1179,7 +1164,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
private _onCursorMove(): void {
|
||||
const buffer = this._xterm.buffer;
|
||||
const buffer = this._xterm!.buffer;
|
||||
this._sendLineData(buffer, buffer.baseY + buffer.cursorY);
|
||||
}
|
||||
|
||||
@@ -1207,11 +1192,19 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
private async _onSelectionChange(): Promise<void> {
|
||||
if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
|
||||
if (this.hasSelection()) {
|
||||
await this.copySelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@debounce(2000)
|
||||
private async _updateProcessCwd(): Promise<string> {
|
||||
// reset cwd if it has changed, so file based url paths can be resolved
|
||||
const cwd = await this.getCwd();
|
||||
if (cwd) {
|
||||
if (cwd && this._linkHandler) {
|
||||
this._linkHandler.processCwd = cwd;
|
||||
}
|
||||
return cwd;
|
||||
@@ -1234,14 +1227,14 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const isEnabled = this._isScreenReaderOptimized();
|
||||
if (isEnabled) {
|
||||
this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey);
|
||||
this._xterm.loadAddon(this._navigationModeAddon);
|
||||
this._xterm!.loadAddon(this._navigationModeAddon);
|
||||
} else {
|
||||
if (this._navigationModeAddon) {
|
||||
this._navigationModeAddon.dispose();
|
||||
this._navigationModeAddon = undefined;
|
||||
}
|
||||
}
|
||||
this._xterm.setOption('screenReaderMode', isEnabled);
|
||||
this._xterm!.setOption('screenReaderMode', isEnabled);
|
||||
}
|
||||
|
||||
private _setCursorBlink(blink: boolean): void {
|
||||
@@ -1352,9 +1345,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._processManager) {
|
||||
this._processManager.ptyProcessReady.then(() => this._processManager!.setDimensions(cols, rows));
|
||||
}
|
||||
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows));
|
||||
}
|
||||
|
||||
public setTitle(title: string | undefined, eventFromProcess: boolean): void {
|
||||
@@ -1370,19 +1361,17 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
} else {
|
||||
// If the title has not been set by the API or the rename command, unregister the handler that
|
||||
// automatically updates the terminal name
|
||||
if (this._messageTitleDisposable) {
|
||||
lifecycle.dispose(this._messageTitleDisposable);
|
||||
lifecycle.dispose(this._windowsShellHelper);
|
||||
this._messageTitleDisposable = undefined;
|
||||
this._windowsShellHelper = undefined;
|
||||
}
|
||||
dispose(this._messageTitleDisposable);
|
||||
this._messageTitleDisposable = undefined;
|
||||
dispose(this._windowsShellHelper);
|
||||
this._windowsShellHelper = undefined;
|
||||
}
|
||||
const didTitleChange = title !== this._title;
|
||||
const oldTitle = this._title;
|
||||
this._title = title;
|
||||
if (didTitleChange) {
|
||||
if (!oldTitle) {
|
||||
if (this._titleReadyComplete) {
|
||||
this._titleReadyComplete(title);
|
||||
this._titleReadyComplete = undefined;
|
||||
}
|
||||
this._onTitleChanged.fire(this);
|
||||
}
|
||||
@@ -1440,25 +1429,21 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
};
|
||||
}
|
||||
|
||||
private _updateTheme(theme?: ITheme): void {
|
||||
this._xterm.setOption('theme', this._getXtermTheme(theme));
|
||||
private _updateTheme(xterm: XTermTerminal, theme?: ITheme): void {
|
||||
xterm.setOption('theme', this._getXtermTheme(theme));
|
||||
}
|
||||
|
||||
public toggleEscapeSequenceLogging(): void {
|
||||
this._xterm.setOption('logLevel', 'debug');
|
||||
public async toggleEscapeSequenceLogging(): Promise<void> {
|
||||
const xterm = await this._xtermReadyPromise;
|
||||
const isDebug = xterm.getOption('logLevel') === 'debug';
|
||||
xterm.setOption('logLevel', isDebug ? 'info' : 'debug');
|
||||
}
|
||||
|
||||
public getInitialCwd(): Promise<string> {
|
||||
if (!this._processManager) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
return this._processManager.getInitialCwd();
|
||||
}
|
||||
|
||||
public getCwd(): Promise<string> {
|
||||
if (!this._processManager) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
return this._processManager.getCwd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
|
||||
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
let Terminal: typeof XTermTerminal;
|
||||
let WebLinksAddon: typeof XTermWebLinksAddon;
|
||||
@@ -52,11 +53,16 @@ export class TerminalInstanceService implements ITerminalInstanceService {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> {
|
||||
return new Promise(r => this._onRequestDefaultShellAndArgs.fire((shell, args) => r({ shell, args })));
|
||||
public getDefaultShellAndArgs(useAutomationShell: boolean, ): Promise<{ shell: string, args: string[] | string | undefined }> {
|
||||
return new Promise(r => this._onRequestDefaultShellAndArgs.fire({
|
||||
useAutomationShell,
|
||||
callback: (shell, args) => r({ shell, args })
|
||||
}));
|
||||
}
|
||||
|
||||
public async getMainProcessParentEnv(): Promise<IProcessEnvironment> {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ITerminalInstanceService, TerminalInstanceService, true);
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalProcessManager, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
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 { ILinkMatcherOptions } from 'xterm';
|
||||
import { Terminal, ILinkMatcherOptions } from 'xterm';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { posix, win32 } from 'vs/base/common/path';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
@@ -67,21 +67,20 @@ interface IPath {
|
||||
|
||||
export class TerminalLinkHandler {
|
||||
private readonly _hoverDisposables = new DisposableStore();
|
||||
private _mouseMoveDisposable: IDisposable;
|
||||
private _widgetManager: TerminalWidgetManager;
|
||||
private _processCwd: string;
|
||||
private _widgetManager: TerminalWidgetManager | undefined;
|
||||
private _processCwd: string | undefined;
|
||||
private _gitDiffPreImagePattern: RegExp;
|
||||
private _gitDiffPostImagePattern: RegExp;
|
||||
private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void;
|
||||
private readonly _leaveCallback: () => void;
|
||||
|
||||
constructor(
|
||||
private _xterm: any,
|
||||
private _xterm: Terminal,
|
||||
private readonly _processManager: ITerminalProcessManager | undefined,
|
||||
private readonly _configHelper: ITerminalConfigHelper,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
|
||||
@IFileService private readonly _fileService: IFileService
|
||||
) {
|
||||
@@ -94,7 +93,7 @@ export class TerminalLinkHandler {
|
||||
if (!this._widgetManager) {
|
||||
return;
|
||||
}
|
||||
if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') {
|
||||
if (this._configHelper.config.rendererType === 'dom') {
|
||||
const target = (e.target as HTMLElement);
|
||||
this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString());
|
||||
} else {
|
||||
@@ -183,10 +182,7 @@ export class TerminalLinkHandler {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._xterm = null;
|
||||
|
||||
this._hoverDisposables.dispose();
|
||||
this._mouseMoveDisposable = dispose(this._mouseMoveDisposable);
|
||||
}
|
||||
|
||||
private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler {
|
||||
@@ -195,9 +191,6 @@ export class TerminalLinkHandler {
|
||||
event.preventDefault();
|
||||
// Require correct modifier on click
|
||||
if (!this._isLinkActivationModifierDown(event)) {
|
||||
// If the modifier is not pressed, the terminal should be
|
||||
// focused if it's not already
|
||||
this._terminalService.getActiveInstance()!.focus(true);
|
||||
return false;
|
||||
}
|
||||
return handler(uri);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
|
||||
import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
export class TerminalNativeService implements ITerminalNativeService {
|
||||
public _serviceBrand: any;
|
||||
@@ -30,4 +31,6 @@ export class TerminalNativeService implements ITerminalNativeService {
|
||||
public getWindowsBuildNumber(): number {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ITerminalNativeService, TerminalNativeService, true);
|
||||
|
||||
@@ -29,14 +29,14 @@ const FIND_FOCUS_CLASS = 'find-focused';
|
||||
|
||||
export class TerminalPanel extends Panel {
|
||||
|
||||
private _actions: IAction[];
|
||||
private _copyContextMenuAction: IAction;
|
||||
private _contextMenuActions: IAction[];
|
||||
private _actions: IAction[] | undefined;
|
||||
private _copyContextMenuAction: IAction | undefined;
|
||||
private _contextMenuActions: IAction[] | undefined;
|
||||
private _cancelContextMenu: boolean = false;
|
||||
private _fontStyleElement: HTMLElement;
|
||||
private _parentDomElement: HTMLElement;
|
||||
private _terminalContainer: HTMLElement;
|
||||
private _findWidget: TerminalFindWidget;
|
||||
private _fontStyleElement: HTMLElement | undefined;
|
||||
private _parentDomElement: HTMLElement | undefined;
|
||||
private _terminalContainer: HTMLElement | undefined;
|
||||
private _findWidget: TerminalFindWidget | undefined;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@@ -61,14 +61,13 @@ export class TerminalPanel extends Panel {
|
||||
dom.addClass(this._terminalContainer, 'terminal-outer-container');
|
||||
|
||||
this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState());
|
||||
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer.classList.add(FIND_FOCUS_CLASS));
|
||||
this._findWidget.focusTracker.onDidBlur(() => this._terminalContainer.classList.remove(FIND_FOCUS_CLASS));
|
||||
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS));
|
||||
|
||||
this._parentDomElement.appendChild(this._fontStyleElement);
|
||||
this._parentDomElement.appendChild(this._terminalContainer);
|
||||
this._parentDomElement.appendChild(this._findWidget.getDomNode());
|
||||
|
||||
this._attachEventListeners();
|
||||
this._attachEventListeners(this._parentDomElement, this._terminalContainer);
|
||||
|
||||
this._terminalService.setContainers(this.getContainer(), this._terminalContainer);
|
||||
|
||||
@@ -137,7 +136,7 @@ export class TerminalPanel extends Panel {
|
||||
}
|
||||
|
||||
private _getContextMenuActions(): IAction[] {
|
||||
if (!this._contextMenuActions) {
|
||||
if (!this._contextMenuActions || !this._copyContextMenuAction) {
|
||||
this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.SHORT_LABEL);
|
||||
this._contextMenuActions = [
|
||||
this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL),
|
||||
@@ -179,31 +178,31 @@ export class TerminalPanel extends Panel {
|
||||
public focusFindWidget() {
|
||||
const activeInstance = this._terminalService.getActiveInstance();
|
||||
if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) {
|
||||
this._findWidget.reveal(activeInstance.selection);
|
||||
this._findWidget!.reveal(activeInstance.selection);
|
||||
} else {
|
||||
this._findWidget.reveal();
|
||||
this._findWidget!.reveal();
|
||||
}
|
||||
}
|
||||
|
||||
public hideFindWidget() {
|
||||
this._findWidget.hide();
|
||||
this._findWidget!.hide();
|
||||
}
|
||||
|
||||
public showFindWidget() {
|
||||
const activeInstance = this._terminalService.getActiveInstance();
|
||||
if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) {
|
||||
this._findWidget.show(activeInstance.selection);
|
||||
this._findWidget!.show(activeInstance.selection);
|
||||
} else {
|
||||
this._findWidget.show();
|
||||
this._findWidget!.show();
|
||||
}
|
||||
}
|
||||
|
||||
public getFindWidget(): TerminalFindWidget {
|
||||
return this._findWidget;
|
||||
return this._findWidget!;
|
||||
}
|
||||
|
||||
private _attachEventListeners(): void {
|
||||
this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', async (event: MouseEvent) => {
|
||||
private _attachEventListeners(parentDomElement: HTMLElement, terminalContainer: HTMLElement): void {
|
||||
this._register(dom.addDisposableListener(parentDomElement, 'mousedown', async (event: MouseEvent) => {
|
||||
if (this._terminalService.terminalInstances.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -240,21 +239,7 @@ export class TerminalPanel extends Panel {
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', async (event: MouseEvent) => {
|
||||
if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
|
||||
if (this._terminalService.terminalInstances.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.which === 1) {
|
||||
const terminal = this._terminalService.getActiveInstance();
|
||||
if (terminal && terminal.hasSelection()) {
|
||||
await terminal.copySelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => {
|
||||
this._register(dom.addDisposableListener(parentDomElement, 'contextmenu', (event: MouseEvent) => {
|
||||
if (!this._cancelContextMenu) {
|
||||
const standardEvent = new StandardMouseEvent(event);
|
||||
const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
|
||||
@@ -269,19 +254,19 @@ export class TerminalPanel extends Panel {
|
||||
this._cancelContextMenu = false;
|
||||
}));
|
||||
this._register(dom.addDisposableListener(document, 'keydown', (event: KeyboardEvent) => {
|
||||
this._terminalContainer.classList.toggle('alt-active', !!event.altKey);
|
||||
terminalContainer.classList.toggle('alt-active', !!event.altKey);
|
||||
}));
|
||||
this._register(dom.addDisposableListener(document, 'keyup', (event: KeyboardEvent) => {
|
||||
this._terminalContainer.classList.toggle('alt-active', !!event.altKey);
|
||||
terminalContainer.classList.toggle('alt-active', !!event.altKey);
|
||||
}));
|
||||
this._register(dom.addDisposableListener(this._parentDomElement, 'keyup', (event: KeyboardEvent) => {
|
||||
this._register(dom.addDisposableListener(parentDomElement, 'keyup', (event: KeyboardEvent) => {
|
||||
if (event.keyCode === 27) {
|
||||
// Keep terminal open on escape
|
||||
event.stopPropagation();
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(this._parentDomElement, dom.EventType.DROP, async (e: DragEvent) => {
|
||||
if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, this._parentDomElement)) {
|
||||
this._register(dom.addDisposableListener(parentDomElement, dom.EventType.DROP, async (e: DragEvent) => {
|
||||
if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, parentDomElement)) {
|
||||
if (!e.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
@@ -315,11 +300,13 @@ export class TerminalPanel extends Panel {
|
||||
theme = this.themeService.getTheme();
|
||||
}
|
||||
|
||||
this._findWidget.updateTheme(theme);
|
||||
if (this._findWidget) {
|
||||
this._findWidget.updateTheme(theme);
|
||||
}
|
||||
}
|
||||
|
||||
private _updateFont(): void {
|
||||
if (this._terminalService.terminalInstances.length === 0) {
|
||||
if (this._terminalService.terminalInstances.length === 0 || !this._parentDomElement) {
|
||||
return;
|
||||
}
|
||||
// TODO: Can we support ligatures?
|
||||
|
||||
@@ -20,6 +20,7 @@ import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
/** The amount of time to consider terminal errors to be related to the launch */
|
||||
const LAUNCHING_DURATION = 500;
|
||||
@@ -42,10 +43,10 @@ enum ProcessType {
|
||||
* - Pty Process: The pseudoterminal master process (or the winpty agent process)
|
||||
* - Shell Process: The pseudoterminal slave process (ie. the shell)
|
||||
*/
|
||||
export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
export class TerminalProcessManager extends Disposable implements ITerminalProcessManager {
|
||||
public processState: ProcessState = ProcessState.UNINITIALIZED;
|
||||
public ptyProcessReady: Promise<void>;
|
||||
public shellProcessId: number;
|
||||
public shellProcessId: number | undefined;
|
||||
public remoteAuthority: string | undefined;
|
||||
public os: platform.OperatingSystem | undefined;
|
||||
public userHome: string | undefined;
|
||||
@@ -54,23 +55,22 @@ export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
private _processType: ProcessType = ProcessType.Process;
|
||||
private _preLaunchInputQueue: string[] = [];
|
||||
private _latency: number = -1;
|
||||
private _latencyRequest: Promise<number>;
|
||||
private _latencyLastMeasured: number = 0;
|
||||
private _initialCwd: string;
|
||||
private _initialCwd: string | undefined;
|
||||
|
||||
private readonly _onProcessReady = new Emitter<void>();
|
||||
private readonly _onProcessReady = this._register(new Emitter<void>());
|
||||
public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
|
||||
private readonly _onBeforeProcessData = new Emitter<IBeforeProcessDataEvent>();
|
||||
private readonly _onBeforeProcessData = this._register(new Emitter<IBeforeProcessDataEvent>());
|
||||
public get onBeforeProcessData(): Event<IBeforeProcessDataEvent> { return this._onBeforeProcessData.event; }
|
||||
private readonly _onProcessData = new Emitter<string>();
|
||||
private readonly _onProcessData = this._register(new Emitter<string>());
|
||||
public get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
private readonly _onProcessTitle = new Emitter<string>();
|
||||
private readonly _onProcessTitle = this._register(new Emitter<string>());
|
||||
public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
|
||||
private readonly _onProcessExit = new Emitter<number>();
|
||||
private readonly _onProcessExit = this._register(new Emitter<number>());
|
||||
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensions | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessOverrideShellLaunchConfig = new Emitter<IShellLaunchConfig>();
|
||||
private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessOverrideShellLaunchConfig.event; }
|
||||
|
||||
constructor(
|
||||
@@ -87,6 +87,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService
|
||||
) {
|
||||
super();
|
||||
this.ptyProcessReady = new Promise<void>(c => {
|
||||
this.onProcessReady(() => {
|
||||
this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`);
|
||||
@@ -105,6 +106,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
this._process.shutdown(immediate);
|
||||
this._process = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public async createProcess(
|
||||
@@ -193,7 +195,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
|
||||
const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
|
||||
if (!shellLaunchConfig.executable) {
|
||||
const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs();
|
||||
const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs(false);
|
||||
shellLaunchConfig.executable = defaultConfig.shell;
|
||||
shellLaunchConfig.args = defaultConfig.args;
|
||||
} else {
|
||||
@@ -259,7 +261,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
}
|
||||
|
||||
public getInitialCwd(): Promise<string> {
|
||||
return Promise.resolve(this._initialCwd);
|
||||
return Promise.resolve(this._initialCwd ? this._initialCwd : '');
|
||||
}
|
||||
|
||||
public getCwd(): Promise<string> {
|
||||
@@ -275,8 +277,8 @@ export class TerminalProcessManager implements ITerminalProcessManager {
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
if (this._latencyLastMeasured === 0 || this._latencyLastMeasured + LATENCY_MEASURING_INTERVAL < Date.now()) {
|
||||
this._latencyRequest = this._process.getLatency();
|
||||
this._latency = await this._latencyRequest;
|
||||
const latencyRequest = this._process.getLatency();
|
||||
this._latency = await latencyRequest;
|
||||
this._latencyLastMeasured = Date.now();
|
||||
}
|
||||
return Promise.resolve(this._latency);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
@@ -49,18 +49,15 @@ export class TerminalService extends CommonTerminalService implements ITerminalS
|
||||
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro);
|
||||
}
|
||||
|
||||
public createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance {
|
||||
const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig);
|
||||
public createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance {
|
||||
const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, container, shellLaunchConfig);
|
||||
this._onInstanceCreated.fire(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance {
|
||||
if (shell.hideFromUser) {
|
||||
const instance = this.createInstance(this._terminalFocusContextKey,
|
||||
this.configHelper,
|
||||
undefined,
|
||||
shell);
|
||||
const instance = this.createInstance(undefined, shell);
|
||||
this._backgroundedTerminalInstances.push(instance);
|
||||
this._initInstanceListeners(instance);
|
||||
return instance;
|
||||
@@ -137,7 +134,7 @@ export class TerminalService extends CommonTerminalService implements ITerminalS
|
||||
public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void {
|
||||
this._configHelper.panelContainer = panelContainer;
|
||||
this._terminalContainer = terminalContainer;
|
||||
this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer));
|
||||
this._terminalTabs.forEach(tab => tab.attachToElement(terminalContainer));
|
||||
}
|
||||
|
||||
public hidePanel(): void {
|
||||
|
||||
@@ -18,7 +18,7 @@ const TERMINAL_MIN_USEFUL_SIZE = 250;
|
||||
class SplitPaneContainer extends Disposable {
|
||||
private _height: number;
|
||||
private _width: number;
|
||||
private _splitView: SplitView;
|
||||
private _splitView!: SplitView;
|
||||
private readonly _splitViewDisposables = this._register(new DisposableStore());
|
||||
private _children: SplitPane[] = [];
|
||||
|
||||
@@ -177,7 +177,6 @@ class SplitPane implements IView {
|
||||
public maximumSize: number = Number.MAX_VALUE;
|
||||
|
||||
public orientation: Orientation | undefined;
|
||||
protected _size: number;
|
||||
|
||||
private _onDidChange: Event<number | undefined> = Event.None;
|
||||
public get onDidChange(): Event<number | undefined> { return this._onDidChange; }
|
||||
@@ -195,15 +194,14 @@ class SplitPane implements IView {
|
||||
|
||||
public layout(size: number): void {
|
||||
// Only layout when both sizes are known
|
||||
this._size = size;
|
||||
if (!this._size || !this.orthogonalSize) {
|
||||
if (!size || !this.orthogonalSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
this.instance.layout({ width: this.orthogonalSize, height: this._size });
|
||||
this.instance.layout({ width: this.orthogonalSize, height: size });
|
||||
} else {
|
||||
this.instance.layout({ width: this._size, height: this.orthogonalSize });
|
||||
this.instance.layout({ width: size, height: this.orthogonalSize });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,22 +213,22 @@ class SplitPane implements IView {
|
||||
export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
private _terminalInstances: ITerminalInstance[] = [];
|
||||
private _splitPaneContainer: SplitPaneContainer | undefined;
|
||||
private _tabElement: HTMLElement | null;
|
||||
private _tabElement: HTMLElement | undefined;
|
||||
private _panelPosition: Position = Position.BOTTOM;
|
||||
|
||||
private _activeInstanceIndex: number;
|
||||
|
||||
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
|
||||
|
||||
private readonly _onDisposed: Emitter<ITerminalTab> = new Emitter<ITerminalTab>();
|
||||
private readonly _onDisposed: Emitter<ITerminalTab> = this._register(new Emitter<ITerminalTab>());
|
||||
public readonly onDisposed: Event<ITerminalTab> = this._onDisposed.event;
|
||||
private readonly _onInstancesChanged: Emitter<void> = new Emitter<void>();
|
||||
private readonly _onInstancesChanged: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onInstancesChanged: Event<void> = this._onInstancesChanged.event;
|
||||
|
||||
constructor(
|
||||
terminalFocusContextKey: IContextKey<boolean>,
|
||||
configHelper: ITerminalConfigHelper,
|
||||
private _container: HTMLElement,
|
||||
private _container: HTMLElement | undefined,
|
||||
shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
|
||||
@@ -242,11 +240,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
if ('id' in shellLaunchConfigOrInstance) {
|
||||
instance = shellLaunchConfigOrInstance;
|
||||
} else {
|
||||
instance = this._terminalService.createInstance(
|
||||
terminalFocusContextKey,
|
||||
configHelper,
|
||||
undefined,
|
||||
shellLaunchConfigOrInstance);
|
||||
instance = this._terminalService.createInstance(undefined, shellLaunchConfigOrInstance);
|
||||
}
|
||||
this._terminalInstances.push(instance);
|
||||
this._initInstanceListeners(instance);
|
||||
@@ -259,9 +253,9 @@ export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
if (this._tabElement) {
|
||||
if (this._container && this._tabElement) {
|
||||
this._container.removeChild(this._tabElement);
|
||||
this._tabElement = null;
|
||||
this._tabElement = undefined;
|
||||
}
|
||||
this._terminalInstances = [];
|
||||
this._onInstancesChanged.fire();
|
||||
@@ -380,15 +374,14 @@ export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
configHelper: ITerminalConfigHelper,
|
||||
shellLaunchConfig: IShellLaunchConfig
|
||||
): ITerminalInstance | undefined {
|
||||
if (!this._container) {
|
||||
throw new Error('Cannot split terminal that has not been attached');
|
||||
}
|
||||
const newTerminalSize = ((this._panelPosition === Position.BOTTOM ? this._container.clientWidth : this._container.clientHeight) / (this._terminalInstances.length + 1));
|
||||
if (newTerminalSize < TERMINAL_MIN_USEFUL_SIZE) {
|
||||
return undefined;
|
||||
}
|
||||
const instance = this._terminalService.createInstance(
|
||||
terminalFocusContextKey,
|
||||
configHelper,
|
||||
undefined,
|
||||
shellLaunchConfig);
|
||||
const instance = this._terminalService.createInstance(undefined, shellLaunchConfig);
|
||||
this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance);
|
||||
this._initInstanceListeners(instance);
|
||||
this._setActiveInstance(instance);
|
||||
|
||||
@@ -8,10 +8,10 @@ import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'
|
||||
const WIDGET_HEIGHT = 29;
|
||||
|
||||
export class TerminalWidgetManager implements IDisposable {
|
||||
private _container: HTMLElement | null;
|
||||
private _xtermViewport: HTMLElement | null;
|
||||
private _container: HTMLElement | undefined;
|
||||
private _xtermViewport: HTMLElement | undefined;
|
||||
|
||||
private _messageWidget: MessageWidget;
|
||||
private _messageWidget: MessageWidget | undefined;
|
||||
private readonly _messageListeners = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@@ -27,9 +27,9 @@ export class TerminalWidgetManager implements IDisposable {
|
||||
public dispose(): void {
|
||||
if (this._container && this._container.parentElement) {
|
||||
this._container.parentElement.removeChild(this._container);
|
||||
this._container = null;
|
||||
this._container = undefined;
|
||||
}
|
||||
this._xtermViewport = null;
|
||||
this._xtermViewport = undefined;
|
||||
this._messageListeners.dispose();
|
||||
}
|
||||
|
||||
@@ -108,4 +108,4 @@ class MessageWidget {
|
||||
this._container.removeChild(this.domNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,11 @@ export interface ITerminalConfiguration {
|
||||
osx: string | null;
|
||||
windows: string | null;
|
||||
};
|
||||
automationShell: {
|
||||
linux: string | null;
|
||||
osx: string | null;
|
||||
windows: string | null;
|
||||
};
|
||||
shellArgs: {
|
||||
linux: string[];
|
||||
osx: string[];
|
||||
@@ -187,11 +192,6 @@ export interface IShellLaunchConfig {
|
||||
*/
|
||||
initialText?: string;
|
||||
|
||||
/**
|
||||
* @deprecated use `isExtensionTerminal`
|
||||
*/
|
||||
isRendererOnly?: boolean;
|
||||
|
||||
/**
|
||||
* Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`.
|
||||
*/
|
||||
@@ -244,16 +244,10 @@ export interface ITerminalService {
|
||||
*/
|
||||
createTerminal(shell?: IShellLaunchConfig): ITerminalInstance;
|
||||
|
||||
/**
|
||||
* Creates a terminal renderer.
|
||||
* @param name The name of the terminal.
|
||||
*/
|
||||
createTerminalRenderer(name: string): ITerminalInstance;
|
||||
|
||||
/**
|
||||
* Creates a raw terminal instance, this should not be used outside of the terminal part.
|
||||
*/
|
||||
createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
|
||||
createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
|
||||
getInstanceFromId(terminalId: number): ITerminalInstance | undefined;
|
||||
getInstanceFromIndex(terminalIndex: number): ITerminalInstance;
|
||||
getTabLabels(): string[];
|
||||
@@ -422,13 +416,6 @@ export interface ITerminalInstance {
|
||||
*/
|
||||
onData: Event<string>;
|
||||
|
||||
/**
|
||||
* Attach a listener to the "renderer" input event, this event fires for terminal renderers on
|
||||
* keystrokes and when the Terminal.sendText extension API is used.
|
||||
* @param listener The listener function.
|
||||
*/
|
||||
onRendererInput: Event<string>;
|
||||
|
||||
/**
|
||||
* Attach a listener to listen for new lines added to this terminal instance.
|
||||
*
|
||||
@@ -483,7 +470,7 @@ export interface ITerminalInstance {
|
||||
* An object that tracks when commands are run and enables navigating and selecting between
|
||||
* them.
|
||||
*/
|
||||
readonly commandTracker: ITerminalCommandTracker;
|
||||
readonly commandTracker: ICommandTracker | undefined;
|
||||
|
||||
readonly navigationMode: INavigationMode | undefined;
|
||||
|
||||
@@ -497,14 +484,6 @@ export interface ITerminalInstance {
|
||||
*/
|
||||
dispose(immediate?: boolean): void;
|
||||
|
||||
/**
|
||||
* Indicates that a consumer of a renderer only terminal is finished with it.
|
||||
*
|
||||
* @param exitCode The exit code of the terminal. Zero indicates success, non-zero indicates
|
||||
* failure.
|
||||
*/
|
||||
rendererExit(exitCode: number): void;
|
||||
|
||||
/**
|
||||
* Forces the terminal to redraw its viewport.
|
||||
*/
|
||||
@@ -671,7 +650,7 @@ export interface ITerminalInstance {
|
||||
getCwd(): Promise<string>;
|
||||
}
|
||||
|
||||
export interface ITerminalCommandTracker {
|
||||
export interface ICommandTracker {
|
||||
scrollToPreviousCommand(): void;
|
||||
scrollToNextCommand(): void;
|
||||
selectToPreviousCommand(): void;
|
||||
@@ -697,7 +676,7 @@ export interface IBeforeProcessDataEvent {
|
||||
export interface ITerminalProcessManager extends IDisposable {
|
||||
readonly processState: ProcessState;
|
||||
readonly ptyProcessReady: Promise<void>;
|
||||
readonly shellProcessId: number;
|
||||
readonly shellProcessId: number | undefined;
|
||||
readonly remoteAuthority: string | undefined;
|
||||
readonly os: OperatingSystem | undefined;
|
||||
readonly userHome: string | undefined;
|
||||
@@ -780,7 +759,8 @@ export interface IAvailableShellsRequest {
|
||||
}
|
||||
|
||||
export interface IDefaultShellAndArgsRequest {
|
||||
(shell: string, args: string[] | string | undefined): void;
|
||||
useAutomationShell: boolean;
|
||||
callback: (shell: string, args: string[] | string | undefined) => void;
|
||||
}
|
||||
|
||||
export enum LinuxDistro {
|
||||
|
||||
@@ -197,47 +197,61 @@ export function getDefaultShell(
|
||||
lastActiveWorkspace: IWorkspaceFolder | undefined,
|
||||
configurationResolverService: IConfigurationResolverService | undefined,
|
||||
logService: ILogService,
|
||||
useAutomationShell: boolean,
|
||||
platformOverride: platform.Platform = platform.platform
|
||||
): string {
|
||||
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
|
||||
const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`);
|
||||
let executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.user) || (<string | null>shellConfigValue.default || defaultShell);
|
||||
let maybeExecutable: string | null = null;
|
||||
if (useAutomationShell) {
|
||||
// If automationShell is specified, this should override the normal setting
|
||||
maybeExecutable = getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'automationShell', platformOverride);
|
||||
}
|
||||
if (!maybeExecutable) {
|
||||
maybeExecutable = getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'shell', platformOverride);
|
||||
}
|
||||
maybeExecutable = maybeExecutable || defaultShell;
|
||||
|
||||
// Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's
|
||||
// safe to assume that this was used by accident as Sysnative does not
|
||||
// exist and will break the terminal in non-WoW64 environments.
|
||||
if ((platformOverride === platform.Platform.Windows) && !isWoW64 && windir) {
|
||||
const sysnativePath = path.join(windir, 'Sysnative').toLowerCase();
|
||||
if (executable && executable.toLowerCase().indexOf(sysnativePath) === 0) {
|
||||
executable = path.join(windir, 'System32', executable.substr(sysnativePath.length));
|
||||
const sysnativePath = path.join(windir, 'Sysnative').replace(/\//g, '\\').toLowerCase();
|
||||
if (maybeExecutable && maybeExecutable.toLowerCase().indexOf(sysnativePath) === 0) {
|
||||
maybeExecutable = path.join(windir, 'System32', maybeExecutable.substr(sysnativePath.length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert / to \ on Windows for convenience
|
||||
if (executable && platformOverride === platform.Platform.Windows) {
|
||||
executable = executable.replace(/\//g, '\\');
|
||||
if (maybeExecutable && platformOverride === platform.Platform.Windows) {
|
||||
maybeExecutable = maybeExecutable.replace(/\//g, '\\');
|
||||
}
|
||||
|
||||
if (configurationResolverService) {
|
||||
try {
|
||||
executable = configurationResolverService.resolve(lastActiveWorkspace, executable);
|
||||
maybeExecutable = configurationResolverService.resolve(lastActiveWorkspace, maybeExecutable);
|
||||
} catch (e) {
|
||||
logService.error(`Could not resolve terminal.integrated.shell.${platformKey}`, e);
|
||||
executable = executable;
|
||||
logService.error(`Could not resolve shell`, e);
|
||||
maybeExecutable = maybeExecutable;
|
||||
}
|
||||
}
|
||||
|
||||
return executable;
|
||||
return maybeExecutable;
|
||||
}
|
||||
|
||||
export function getDefaultShellArgs(
|
||||
fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
|
||||
isWorkspaceShellAllowed: boolean,
|
||||
useAutomationShell: boolean,
|
||||
lastActiveWorkspace: IWorkspaceFolder | undefined,
|
||||
configurationResolverService: IConfigurationResolverService | undefined,
|
||||
logService: ILogService,
|
||||
platformOverride: platform.Platform = platform.platform,
|
||||
): string | string[] {
|
||||
if (useAutomationShell) {
|
||||
if (!!getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'automationShell', platformOverride)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
|
||||
const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`);
|
||||
let args = <string[] | string>((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default);
|
||||
@@ -259,6 +273,18 @@ export function getDefaultShellArgs(
|
||||
return args;
|
||||
}
|
||||
|
||||
function getShellSetting(
|
||||
fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
|
||||
isWorkspaceShellAllowed: boolean,
|
||||
type: 'automationShell' | 'shell',
|
||||
platformOverride: platform.Platform = platform.platform,
|
||||
): string | null {
|
||||
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
|
||||
const shellConfigValue = fetchSetting(`terminal.integrated.${type}.${platformKey}`);
|
||||
const executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.user) || (<string | null>shellConfigValue.default);
|
||||
return executable;
|
||||
}
|
||||
|
||||
export function createTerminalEnvironment(
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
lastActiveWorkspace: IWorkspaceFolder | null,
|
||||
|
||||
@@ -18,13 +18,13 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
public readonly onProcessData: Event<string> = this._onProcessData.event;
|
||||
private readonly _onProcessExit = this._register(new Emitter<number>());
|
||||
public readonly onProcessExit: Event<number> = this._onProcessExit.event;
|
||||
private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>();
|
||||
private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>());
|
||||
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
|
||||
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
|
||||
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
|
||||
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensions | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessResolvedShellLaunchConfig = new Emitter<IShellLaunchConfig>();
|
||||
private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessResolvedShellLaunchConfig.event; }
|
||||
|
||||
private readonly _onInput = this._register(new Emitter<string>());
|
||||
|
||||
@@ -35,7 +35,7 @@ export abstract class TerminalService implements ITerminalService {
|
||||
protected _isShuttingDown: boolean;
|
||||
protected _terminalFocusContextKey: IContextKey<boolean>;
|
||||
protected _findWidgetVisible: IContextKey<boolean>;
|
||||
protected _terminalContainer: HTMLElement;
|
||||
protected _terminalContainer: HTMLElement | undefined;
|
||||
protected _terminalTabs: ITerminalTab[] = [];
|
||||
protected _backgroundedTerminalInstances: ITerminalInstance[] = [];
|
||||
protected get _terminalInstances(): ITerminalInstance[] {
|
||||
@@ -123,13 +123,9 @@ export abstract class TerminalService implements ITerminalService {
|
||||
protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void;
|
||||
|
||||
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
|
||||
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
|
||||
public abstract createInstance(container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
|
||||
public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
|
||||
|
||||
public createTerminalRenderer(name: string): ITerminalInstance {
|
||||
return this.createTerminal({ name, isRendererOnly: true });
|
||||
}
|
||||
|
||||
public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance {
|
||||
const activeInstance = this.getActiveInstance();
|
||||
return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction);
|
||||
|
||||
@@ -73,7 +73,7 @@ export class TerminalInstanceService implements ITerminalInstanceService {
|
||||
return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false);
|
||||
}
|
||||
|
||||
public getDefaultShellAndArgs(platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> {
|
||||
public getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> {
|
||||
const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed();
|
||||
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
|
||||
let lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : undefined;
|
||||
@@ -87,11 +87,13 @@ export class TerminalInstanceService implements ITerminalInstanceService {
|
||||
lastActiveWorkspace,
|
||||
this._configurationResolverService,
|
||||
this._logService,
|
||||
useAutomationShell,
|
||||
platformOverride
|
||||
);
|
||||
const args = getDefaultShellArgs(
|
||||
(key) => this._configurationService.inspect(key),
|
||||
isWorkspaceShellAllowed,
|
||||
useAutomationShell,
|
||||
lastActiveWorkspace,
|
||||
this._configurationResolverService,
|
||||
this._logService,
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as pty from 'node-pty';
|
||||
import * as fs from 'fs';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { exec } from 'child_process';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -18,23 +18,23 @@ import { stat } from 'vs/base/node/pfs';
|
||||
import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
private _exitCode: number;
|
||||
export class TerminalProcess extends Disposable implements ITerminalChildProcess {
|
||||
private _exitCode: number | undefined;
|
||||
private _closeTimeout: any;
|
||||
private _ptyProcess: pty.IPty | undefined;
|
||||
private _currentTitle: string = '';
|
||||
private _processStartupComplete: Promise<void>;
|
||||
private _processStartupComplete: Promise<void> | undefined;
|
||||
private _isDisposed: boolean = false;
|
||||
private _titleInterval: NodeJS.Timer | null = null;
|
||||
private _initialCwd: string;
|
||||
|
||||
private readonly _onProcessData = new Emitter<string>();
|
||||
private readonly _onProcessData = this._register(new Emitter<string>());
|
||||
public get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
private readonly _onProcessExit = new Emitter<number>();
|
||||
private readonly _onProcessExit = this._register(new Emitter<number>());
|
||||
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>();
|
||||
private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>());
|
||||
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
|
||||
private readonly _onProcessTitleChanged = new Emitter<string>();
|
||||
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
|
||||
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
|
||||
|
||||
constructor(
|
||||
@@ -46,6 +46,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
windowsEnableConpty: boolean,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) {
|
||||
super();
|
||||
let shellName: string;
|
||||
if (os.platform() === 'win32') {
|
||||
shellName = path.basename(shellLaunchConfig.executable || '');
|
||||
@@ -65,7 +66,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
cols,
|
||||
rows,
|
||||
experimentalUseConpty: useConpty,
|
||||
conptyInheritCursor: true
|
||||
// This option will force conpty to not redraw the whole viewport on launch
|
||||
conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText
|
||||
};
|
||||
|
||||
const cwdVerification = stat(cwd).then(async stat => {
|
||||
@@ -87,7 +89,9 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
}, async (err) => {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!;
|
||||
const executable = await findExecutable(shellLaunchConfig.executable!, cwd);
|
||||
// Try to get path
|
||||
const envPaths: string[] | undefined = (shellLaunchConfig.env && shellLaunchConfig.env.PATH) ? shellLaunchConfig.env.PATH.split(path.delimiter) : undefined;
|
||||
const executable = await findExecutable(shellLaunchConfig.executable!, cwd, envPaths);
|
||||
if (!executable) {
|
||||
return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE);
|
||||
}
|
||||
@@ -143,6 +147,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
this._onProcessExit.dispose();
|
||||
this._onProcessReady.dispose();
|
||||
this._onProcessTitleChanged.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _setupTitlePolling(ptyProcess: pty.IPty) {
|
||||
@@ -172,7 +177,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
private _kill(): void {
|
||||
// Wait to kill to process until the start up code has run. This prevents us from firing a process exit before a
|
||||
// process start.
|
||||
this._processStartupComplete.then(() => {
|
||||
this._processStartupComplete!.then(() => {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
@@ -186,7 +191,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
} catch (ex) {
|
||||
// Swallow, the pty has already been killed
|
||||
}
|
||||
this._onProcessExit.fire(this._exitCode);
|
||||
this._onProcessExit.fire(this._exitCode || 0);
|
||||
this.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ITerminalInstance, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { Terminal as XTermTerminal } from 'xterm';
|
||||
import WindowsProcessTreeType = require('windows-process-tree');
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
const SHELL_EXECUTABLES = [
|
||||
'cmd.exe',
|
||||
@@ -24,17 +25,19 @@ const SHELL_EXECUTABLES = [
|
||||
|
||||
let windowsProcessTree: typeof WindowsProcessTreeType;
|
||||
|
||||
export class WindowsShellHelper implements IWindowsShellHelper {
|
||||
private _onCheckShell: Emitter<Promise<string> | undefined>;
|
||||
export class WindowsShellHelper extends Disposable implements IWindowsShellHelper {
|
||||
private _onCheckShell: Emitter<Promise<string> | undefined> = this._register(new Emitter<Promise<string> | undefined>());
|
||||
private _isDisposed: boolean;
|
||||
private _currentRequest: Promise<string> | null;
|
||||
private _newLineFeed: boolean;
|
||||
private _currentRequest: Promise<string> | undefined;
|
||||
private _newLineFeed: boolean = false;
|
||||
|
||||
public constructor(
|
||||
private _rootProcessId: number,
|
||||
private _terminalInstance: ITerminalInstance,
|
||||
private _xterm: XTermTerminal
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!platform.isWindows) {
|
||||
throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`);
|
||||
}
|
||||
@@ -47,7 +50,6 @@ export class WindowsShellHelper implements IWindowsShellHelper {
|
||||
}
|
||||
|
||||
windowsProcessTree = mod;
|
||||
this._onCheckShell = new Emitter<Promise<string>>();
|
||||
// The debounce is necessary to prevent multiple processes from spawning when
|
||||
// the enter key or output is spammed
|
||||
Event.debounce(this._onCheckShell.event, (l, e) => e, 150, true)(() => {
|
||||
@@ -65,6 +67,7 @@ export class WindowsShellHelper implements IWindowsShellHelper {
|
||||
this._xterm.onCursorMove(() => {
|
||||
if (this._newLineFeed) {
|
||||
this._onCheckShell.fire(undefined);
|
||||
this._newLineFeed = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -111,6 +114,7 @@ export class WindowsShellHelper implements IWindowsShellHelper {
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +131,7 @@ export class WindowsShellHelper implements IWindowsShellHelper {
|
||||
this._currentRequest = new Promise<string>(resolve => {
|
||||
windowsProcessTree.getProcessTree(this._rootProcessId, (tree) => {
|
||||
const name = this.traverseTree(tree);
|
||||
this._currentRequest = null;
|
||||
this._currentRequest = undefined;
|
||||
resolve(name);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Terminal, TerminalCore } from 'xterm';
|
||||
import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker';
|
||||
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
interface TestTerminalCore extends TerminalCore {
|
||||
|
||||
@@ -65,7 +65,7 @@ interface LinkFormatInfo {
|
||||
suite('Workbench - TerminalLinkHandler', () => {
|
||||
suite('localLinkRegex', () => {
|
||||
test('Windows', () => {
|
||||
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Windows,
|
||||
userHome: ''
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
@@ -141,7 +141,7 @@ suite('Workbench - TerminalLinkHandler', () => {
|
||||
});
|
||||
|
||||
test('Linux', () => {
|
||||
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: ''
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
@@ -209,7 +209,7 @@ suite('Workbench - TerminalLinkHandler', () => {
|
||||
|
||||
suite('preprocessPath', () => {
|
||||
test('Windows', () => {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Windows,
|
||||
userHome: 'C:\\Users\\Me'
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
@@ -222,7 +222,7 @@ suite('Workbench - TerminalLinkHandler', () => {
|
||||
assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file5'), 'C:\\absolute\\path\\file5');
|
||||
});
|
||||
test('Windows - spaces', () => {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Windows,
|
||||
userHome: 'C:\\Users\\M e'
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
@@ -236,7 +236,7 @@ suite('Workbench - TerminalLinkHandler', () => {
|
||||
});
|
||||
|
||||
test('Linux', () => {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: '/home/me'
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
@@ -249,7 +249,7 @@ suite('Workbench - TerminalLinkHandler', () => {
|
||||
});
|
||||
|
||||
test('No Workspace', () => {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: '/home/me'
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
@@ -263,7 +263,7 @@ suite('Workbench - TerminalLinkHandler', () => {
|
||||
|
||||
test('gitDiffLinkRegex', () => {
|
||||
// The platform is irrelevant because the links generated by Git are the same format regardless of platform
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm(), {
|
||||
const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, {
|
||||
os: OperatingSystem.Linux,
|
||||
userHome: ''
|
||||
} as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!);
|
||||
|
||||
@@ -128,4 +128,48 @@ suite('Workbench - TerminalEnvironment', () => {
|
||||
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, undefined, Uri.file('/bar'), '/foo'), '/bar');
|
||||
});
|
||||
});
|
||||
|
||||
suite('getDefaultShell', () => {
|
||||
test('should change Sysnative to System32 in non-WoW64 systems', () => {
|
||||
const shell = terminalEnvironment.getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
|
||||
assert.equal(shell, 'C:\\Windows\\System32\\cmd.exe');
|
||||
});
|
||||
|
||||
test('should not change Sysnative to System32 in WoW64 systems', () => {
|
||||
const shell = terminalEnvironment.getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', true, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
|
||||
assert.equal(shell, 'C:\\Windows\\Sysnative\\cmd.exe');
|
||||
});
|
||||
|
||||
test('should use automationShell when specified', () => {
|
||||
const shell1 = terminalEnvironment.getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined },
|
||||
'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
|
||||
assert.equal(shell1, 'shell', 'automationShell was false');
|
||||
const shell2 = terminalEnvironment.getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined },
|
||||
'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows);
|
||||
assert.equal(shell2, 'shell', 'automationShell was true');
|
||||
const shell3 = terminalEnvironment.getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined },
|
||||
'terminal.integrated.automationShell.windows': { user: 'automationShell', value: undefined, default: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows);
|
||||
assert.equal(shell3, 'automationShell', 'automationShell was true and specified in settings');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user