Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568

This commit is contained in:
ADS Merger
2020-04-30 00:53:43 +00:00
parent 87e5239713
commit 93f35ca321
413 changed files with 7190 additions and 8756 deletions

View File

@@ -19,8 +19,37 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo {
}
getInfo(): string {
let info = localize('extensionEnvironmentContribution', "Extensions want to make the follow changes to the terminal's environment:");
info += `\n\n${this._summarizeDiff()}`;
const addsAndChanges: string[] = [];
const removals: string[] = [];
this._diff.added.forEach((mutators, variable) => {
mutators.forEach(mutator => addsAndChanges.push(mutatorTypeLabel(mutator.type, mutator.value, variable)));
});
this._diff.changed.forEach((mutators, variable) => {
mutators.forEach(mutator => addsAndChanges.push(mutatorTypeLabel(mutator.type, mutator.value, variable)));
});
this._diff.removed.forEach((mutators, variable) => {
mutators.forEach(mutator => removals.push(mutatorTypeLabel(mutator.type, mutator.value, variable)));
});
let info: string = '';
if (addsAndChanges.length > 0) {
info = localize('extensionEnvironmentContributionChanges', "Extensions want to make the following changes to the terminal's environment:");
info += '\n\n';
info += '```\n';
info += addsAndChanges.join('\n');
info += '\n```';
}
if (removals.length > 0) {
info += info.length > 0 ? '\n\n' : '';
info += localize('extensionEnvironmentContributionRemoval', "Extensions want to remove these existing changes from the terminal's environment:");
info += '\n\n';
info += '```\n';
info += removals.join('\n');
info += '\n```';
}
return info;
}
@@ -35,27 +64,6 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo {
commandId: TERMINAL_COMMAND_ID.RELAUNCH
}];
}
private _summarizeDiff(): string {
const summary: string[] = [];
this._diff.added.forEach((mutators, variable) => {
mutators.forEach(mutator => {
summary.push(`- ${mutatorTypeLabel(mutator.type, mutator.value, variable)}`);
});
});
this._diff.changed.forEach((mutators, variable) => {
mutators.forEach(mutator => {
summary.push(`- "${mutatorTypeLabel(mutator.type, mutator.value, variable)}"`);
});
});
this._diff.removed.forEach((mutators, variable) => {
mutators.forEach(mutator => {
const removePrefixText = localize('removeEnvironmentVariableChange', "Remove the change {0}", mutatorTypeLabel(mutator.type, mutator.value, variable));
summary.push(`- ${removePrefixText}`);
});
});
return summary.join('\n');
}
}
export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariableInfo {
@@ -67,13 +75,12 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl
}
getInfo(): string {
const info: string[] = ['Extensions have made changes to this terminal\'s environment:', ''];
const changes: string[] = [];
this._collection.map.forEach((mutators, variable) => {
mutators.forEach(mutator => {
info.push(`- ${mutatorTypeLabel(mutator.type, mutator.value, variable)}`);
});
mutators.forEach(mutator => changes.push(mutatorTypeLabel(mutator.type, mutator.value, variable)));
});
return info.join('\n');
const message = localize('extensionEnvironmentContributionInfo', "Extensions have made changes to this terminal's environment");
return message + '\n\n```\n' + changes.join('\n') + '\n```';
}
getIcon(): string {
@@ -83,8 +90,8 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl
function mutatorTypeLabel(type: EnvironmentVariableMutatorType, value: string, variable: string): string {
switch (type) {
case EnvironmentVariableMutatorType.Prepend: return localize('prependValueToEnvironmentVariableMarkdown', "Add `{0}` to the beginning of `{1}`", value, variable);
case EnvironmentVariableMutatorType.Append: return localize('appendValueToEnvironmentVariableMarkdown', "Add `{0}` to the end of `{1}`", value, variable);
default: return localize('replaceEnvironmentVariableWithValueMarkdown', "Replace `{1}`\'s value with `{0}`", value, variable);
case EnvironmentVariableMutatorType.Prepend: return `${variable}=${value}\${env:${variable}}`;
case EnvironmentVariableMutatorType.Append: return `${variable}=\${env:${variable}}${value}`;
default: return `${variable}=${value}`;
}
}

View File

@@ -37,6 +37,7 @@ const excludedPathCharactersClause = '[^\\0\\s!$`&*()\\[\\]+\'":;\\\\]';
/** A regex that matches paths in the form /foo, ~/foo, ./foo, ../foo, foo/bar */
const unixLocalLinkClause = '((' + pathPrefix + '|(' + excludedPathCharactersClause + ')+)?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + ')+)+)';
// Valid absolute formats: C:, \\?\C: and \\?\%VAR%
const winDrivePrefix = '(?:\\\\\\\\\\?\\\\)?[a-zA-Z]:';
const winPathPrefix = '(' + winDrivePrefix + '|\\.\\.?|\\~)';
const winPathSeparatorClause = '(\\\\|\\/)';
@@ -464,7 +465,7 @@ export class TerminalLinkManager extends DisposableStore {
} else if (link.charAt(0) !== '/' && link.charAt(0) !== '~') {
// Resolve workspace path . | .. | <relative_path> -> <path>/. | <path>/.. | <path>/<relative_path>
if (this._processManager.os === OperatingSystem.Windows) {
if (!link.match('^' + winDrivePrefix)) {
if (!link.match('^' + winDrivePrefix) && !link.startsWith('\\\\?\\')) {
if (!this._processCwd) {
// Abort if no workspace is open
return null;

View File

@@ -168,10 +168,10 @@ export class TerminalValidatedLocalLinkProvider implements ILinkProvider {
this._hostService.openWindow([{ folderUri: uri }], { forceNewWindow: true });
}
private async _isDirectoryInsideWorkspace(uri: URI) {
private _isDirectoryInsideWorkspace(uri: URI) {
const folders = this._workspaceContextService.getWorkspace().folders;
for (let i = 0; i < folders.length; i++) {
if (isEqualOrParent(uri, folders[0].uri)) {
if (isEqualOrParent(uri, folders[i].uri)) {
return true;
}
}

View File

@@ -37,21 +37,21 @@ export class TerminalWordLinkProvider implements ILinkProvider {
const end: IBufferCellPosition = { x: position.x, y: position.y };
// TODO: Support wrapping
// Expand to the left until a word separator is hit
const line = this._xterm.buffer.active.getLine(position.y - 1)!;
let text = '';
start.x++; // The hovered cell is considered first
for (let x = position.x; x > 0; x--) {
const char = line.getCell(x - 1)?.getChars();
if (!char) {
const cell = line.getCell(x - 1);
if (!cell) {
break;
}
const char = cell.getChars();
const config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
if (config.wordSeparators.indexOf(char) >= 0) {
if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) {
break;
}
start.x--;
start.x = x;
text = char + text;
}
@@ -62,17 +62,17 @@ export class TerminalWordLinkProvider implements ILinkProvider {
}
// Expand to the right until a word separator is hit
// end.x++; // The hovered cell is considered first
for (let x = position.x + 1; x <= line.length; x++) {
const char = line.getCell(x - 1)?.getChars();
if (!char) {
const cell = line.getCell(x - 1);
if (!cell) {
break;
}
const char = cell.getChars();
const config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
if (config.wordSeparators.indexOf(char) >= 0) {
if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) {
break;
}
end.x++;
end.x = x;
text += char;
}

View File

@@ -26,6 +26,15 @@
color: #3794ff;
}
.monaco-workbench .terminal-hover-widget.right-aligned .hover-row.status-bar .actions {
flex-direction: row-reverse;
}
.monaco-workbench .terminal-hover-widget.right-aligned .hover-row.status-bar .actions .action-container {
margin-right: 0;
margin-left: 16px;
}
.monaco-workbench .terminal-overlay-widget {
position: absolute;
left: 0;
@@ -45,7 +54,7 @@
width: 28px;
height: 28px;
text-align: center;
z-index: 25;
z-index: 10;
opacity: 0.5;
}

View File

@@ -68,6 +68,7 @@ if (platform.isWeb) {
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: TERMINAL_VIEW_ID,
name: nls.localize('terminal', "Terminal"),
icon: 'codicon-terminal',
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
storageId: TERMINAL_VIEW_ID,
focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS },

View File

@@ -35,7 +35,7 @@ export interface ITerminalInstanceService {
getXtermUnicode11Constructor(): Promise<typeof XTermUnicode11Addon>;
getXtermWebLinksConstructor(): Promise<typeof XTermWebLinksAddon>;
getXtermWebglConstructor(): Promise<typeof XTermWebglAddon>;
createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper;
createWindowsShellHelper(shellProcessId: number, xterm: XTermTerminal): IWindowsShellHelper;
createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess;
getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>;

View File

@@ -1219,7 +1219,7 @@ export function registerTerminalActions() {
constructor() {
super({
id: TERMINAL_COMMAND_ID.FIND_NEXT,
title: localize('workbench.action.terminal.findNext', "Find next"),
title: localize('workbench.action.terminal.findNext', "Find Next"),
f1: true,
category,
keybinding: [
@@ -1245,7 +1245,7 @@ export function registerTerminalActions() {
constructor() {
super({
id: TERMINAL_COMMAND_ID.FIND_PREVIOUS,
title: localize('workbench.action.terminal.findPrevious', "Find previous"),
title: localize('workbench.action.terminal.findPrevious', "Find Previous"),
f1: true,
category,
keybinding: [

View File

@@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, ITerminalBeforeHandleLinkEvent } 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';
@@ -902,7 +902,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
this._xtermReadyPromise.then(xterm => {
if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) {
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, this, xterm);
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, xterm);
this._windowsShellHelper.onShellNameChange(title => {
this.setShellType(this.getShellType(title));
if (this.isTitleSetByProcess) {
this.setTitle(title, TitleEventSource.Process);
}
});
}
});
});
@@ -915,6 +921,28 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}, 0);
}
private getShellType(executable: string): TerminalShellType {
switch (executable.toLowerCase()) {
case 'cmd.exe':
return WindowsShellType.CommandPrompt;
case 'powershell.exe':
case 'pwsh.exe':
return WindowsShellType.PowerShell;
case 'bash.exe':
return WindowsShellType.GitBash;
case 'wsl.exe':
case 'ubuntu.exe':
case 'ubuntu1804.exe':
case 'kali.exe':
case 'debian.exe':
case 'opensuse-42.exe':
case 'sles-12.exe':
return WindowsShellType.Wsl;
default:
return undefined;
}
}
private _onProcessData(data: string): void {
this._xterm?.write(data);
}
@@ -1049,6 +1077,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
// Dispose the environment info widget if it exists
this._environmentInfo?.disposable.dispose();
if (!reset) {
// 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

View File

@@ -84,10 +84,6 @@ export class TerminalViewPane extends ViewPane {
this._register(this.themeService.onDidColorThemeChange(theme => this._updateTheme(theme)));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) {
this._updateFont();
}
if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) {
const configHelper = this._terminalService.configHelper;
if (!configHelper.configFontIsMonospace()) {
@@ -99,7 +95,6 @@ export class TerminalViewPane extends ViewPane {
}
}
}));
this._updateFont();
this._updateTheme();
this._register(this.onDidChangeBodyVisibility(visible => {
@@ -108,7 +103,6 @@ export class TerminalViewPane extends ViewPane {
if (!hadTerminals) {
this._terminalService.createTerminal();
}
this._updateFont();
this._updateTheme();
if (hadTerminals) {
this._terminalService.getActiveTab()?.setVisible(visible);
@@ -310,6 +304,7 @@ export class TerminalViewPane extends ViewPane {
if (terminal) {
const preparedPath = await this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title, terminal.shellType);
terminal.sendText(preparedPath, false);
terminal.focus();
}
}
}));
@@ -334,15 +329,6 @@ export class TerminalViewPane extends ViewPane {
this._findWidget.updateTheme(theme);
}
}
private _updateFont(): void {
if (this._terminalService.terminalInstances.length === 0 || !this._parentDomElement) {
return;
}
// TODO: Can we support ligatures?
// dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures);
this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth);
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {

View File

@@ -5,11 +5,14 @@
import { Widget } from 'vs/base/browser/ui/widget';
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { getDomNodePagePosition } from 'vs/base/browser/dom';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { ITerminalWidget, IHoverTarget, IHoverAnchor, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { HoverWidget } from 'vs/workbench/contrib/terminal/browser/widgets/hoverWidget';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import * as dom from 'vs/base/browser/dom';
import { IDisposable } from 'vs/base/common/lifecycle';
export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWidget {
readonly id = 'env-var-info';
@@ -17,12 +20,14 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi
private _domNode: HTMLElement | undefined;
private _container: HTMLElement | undefined;
private _hoverWidget: HoverWidget | undefined;
private _mouseMoveListener: IDisposable | undefined;
get requiresAction() { return this._info.requiresAction; }
constructor(
private _info: IEnvironmentVariableInfo,
@IInstantiationService private readonly _instantiationService: IInstantiationService
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super();
}
@@ -32,12 +37,37 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi
this._domNode = document.createElement('div');
this._domNode.classList.add('terminal-env-var-info', 'codicon', `codicon-${this._info.getIcon()}`);
container.appendChild(this._domNode);
this.onmouseover(this._domNode, () => this._showHover());
const timeout = this._configurationService.getValue<number>('editor.hover.delay');
const scheduler: RunOnceScheduler = new RunOnceScheduler(() => this._showHover(), timeout);
this._register(scheduler);
let origin = { x: 0, y: 0 };
this.onmouseover(this._domNode, e => {
origin.x = e.browserEvent.pageX;
origin.y = e.browserEvent.pageY;
scheduler.schedule();
this._mouseMoveListener = dom.addDisposableListener(this._domNode!, dom.EventType.MOUSE_MOVE, e => {
// Reset the scheduler if the mouse moves too much
if (Math.abs(e.pageX - origin.x) > window.devicePixelRatio * 2 || Math.abs(e.pageY - origin.y) > window.devicePixelRatio * 2) {
origin.x = e.pageX;
origin.y = e.pageY;
scheduler.schedule();
}
});
});
this.onnonbubblingmouseout(this._domNode, () => {
scheduler.cancel();
this._mouseMoveListener?.dispose();
});
}
dispose() {
super.dispose();
this._domNode?.parentElement?.removeChild(this._domNode);
this._mouseMoveListener?.dispose();
}
focus() {
@@ -67,7 +97,7 @@ class ElementHoverTarget implements IHoverTarget {
}
get anchor(): IHoverAnchor {
const position = getDomNodePagePosition(this._element);
const position = dom.getDomNodePagePosition(this._element);
return {
x: position.left,
horizontalAnchorSide: HorizontalAnchorSide.Left,

View File

@@ -14,18 +14,23 @@ import * as dom from 'vs/base/browser/dom';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IHoverTarget, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
const $ = dom.$;
export class HoverWidget extends Widget {
private readonly _containerDomNode: HTMLElement;
private readonly _domNode: HTMLElement;
private readonly _messageListeners = new DisposableStore();
private readonly _mouseTracker: CompositeMouseTracker;
private readonly _scrollbar: DomScrollableElement;
private _isDisposed: boolean = false;
get isDisposed(): boolean { return this._isDisposed; }
get domNode(): HTMLElement { return this._domNode; }
get domNode(): HTMLElement { return this._containerDomNode; }
private readonly _onDispose = new Emitter<void>();
get onDispose(): Event<void> { return this._onDispose.event; }
@@ -36,20 +41,28 @@ export class HoverWidget extends Widget {
private _text: IMarkdownString,
private _linkHandler: (url: string) => void,
private _actions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }[] | undefined,
@IKeybindingService private readonly _keybindingService: IKeybindingService
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super();
this._containerDomNode = document.createElement('div');
this._containerDomNode.classList.add('terminal-hover-widget', 'fadeIn', 'monaco-editor-hover', 'xterm-hover');
this._containerDomNode.tabIndex = 0;
this._containerDomNode.setAttribute('role', 'tooltip');
this._domNode = document.createElement('div');
this._domNode.classList.add('terminal-hover-widget', 'fadeIn', 'monaco-editor-hover', 'xterm-hover');
this._domNode.tabIndex = 0;
this._domNode.setAttribute('role', 'tooltip');
this._domNode.className = 'monaco-editor-hover-content';
this._scrollbar = new DomScrollableElement(this._domNode, {});
this._register(this._scrollbar);
this._containerDomNode.appendChild(this._scrollbar.getDomNode());
// Don't allow mousedown out of the widget, otherwise preventDefault will call and text will
// not be selected.
this.onmousedown(this._domNode, e => e.stopPropagation());
this.onmousedown(this._containerDomNode, e => e.stopPropagation());
// Hide hover on escape
this.onkeydown(this._domNode, e => {
this.onkeydown(this._containerDomNode, e => {
if (e.equals(KeyCode.Escape)) {
this.dispose();
}
@@ -61,6 +74,14 @@ export class HoverWidget extends Widget {
actionHandler: {
callback: (content) => this._linkHandler(content),
disposeables: this._messageListeners
},
codeBlockRenderer: async (_, value) => {
const fontFamily = this._configurationService.getValue<IEditorOptions>('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
return `<span style="font-family: ${fontFamily}; white-space: nowrap">${value.replace(/\n/g, '<br>')}</span>`;
},
codeBlockRenderCallback: () => {
contentsElement.classList.add('code-hover-contents');
this.layout();
}
});
contentsElement.appendChild(markdownElement);
@@ -75,11 +96,11 @@ export class HoverWidget extends Widget {
this._domNode.appendChild(statusBarElement);
}
this._mouseTracker = new CompositeMouseTracker([this._domNode, ..._target.targetElements]);
this._mouseTracker = new CompositeMouseTracker([this._containerDomNode, ..._target.targetElements]);
this._register(this._mouseTracker.onMouseOut(() => this.dispose()));
this._register(this._mouseTracker);
this._container.appendChild(this._domNode);
this._container.appendChild(this._containerDomNode);
this.layout();
}
@@ -106,45 +127,55 @@ export class HoverWidget extends Widget {
public layout(): void {
const anchor = this._target.anchor;
this._containerDomNode.classList.remove('right-aligned');
this._domNode.style.maxHeight = '';
if (anchor.horizontalAnchorSide === HorizontalAnchorSide.Left) {
if (anchor.x + this._domNode.clientWidth * 0.75 > document.documentElement.clientWidth) {
// Shift the hover to the left when > 25% would be cut off
const width = Math.round(this._domNode.clientWidth * 0.75);
this._domNode.style.width = `${width - 1}px`;
this._domNode.style.maxWidth = '';
this._domNode.style.left = `${document.documentElement.clientWidth - width - 1}px`;
if (anchor.x + this._containerDomNode.clientWidth > document.documentElement.clientWidth) {
// Shift the hover to the left when part of it would get cut off
const width = Math.round(this._containerDomNode.clientWidth);
this._containerDomNode.style.width = `${width - 1}px`;
this._containerDomNode.style.maxWidth = '';
const left = document.documentElement.clientWidth - width - 1;
this._containerDomNode.style.left = `${left}px`;
// Right align if the right edge is closer to the anchor than the left edge
if (left + width / 2 < anchor.x) {
this._containerDomNode.classList.add('right-aligned');
}
} else {
this._domNode.style.width = '';
this._domNode.style.maxWidth = `${document.documentElement.clientWidth - anchor.x - 1}px`;
this._domNode.style.left = `${anchor.x}px`;
this._containerDomNode.style.width = '';
this._containerDomNode.style.maxWidth = `${document.documentElement.clientWidth - anchor.x - 1}px`;
this._containerDomNode.style.left = `${anchor.x}px`;
}
} else {
this._domNode.style.right = `${anchor.x}px`;
this._containerDomNode.style.right = `${anchor.x}px`;
}
// Use fallback y value if there is not enough vertical space
if (anchor.verticalAnchorSide === VerticalAnchorSide.Bottom) {
if (anchor.y + this._domNode.clientHeight > document.documentElement.clientHeight) {
this._domNode.style.top = `${anchor.fallbackY}px`;
if (anchor.y + this._containerDomNode.clientHeight > document.documentElement.clientHeight) {
this._containerDomNode.style.top = `${anchor.fallbackY}px`;
this._domNode.style.maxHeight = `${document.documentElement.clientHeight - anchor.fallbackY}px`;
} else {
this._domNode.style.bottom = `${anchor.y}px`;
this._containerDomNode.style.bottom = `${anchor.y}px`;
this._containerDomNode.style.maxHeight = '';
}
} else {
if (anchor.y + this._domNode.clientHeight > document.documentElement.clientHeight) {
this._domNode.style.bottom = `${anchor.fallbackY}px`;
if (anchor.y + this._containerDomNode.clientHeight > document.documentElement.clientHeight) {
this._containerDomNode.style.bottom = `${anchor.fallbackY}px`;
} else {
this._domNode.style.top = `${anchor.y}px`;
this._containerDomNode.style.top = `${anchor.y}px`;
}
}
this._scrollbar.scanDomNode();
}
public focus() {
this._domNode.focus();
this._containerDomNode.focus();
}
public dispose(): void {
if (!this._isDisposed) {
this._onDispose.fire();
this._domNode.parentElement?.removeChild(this.domNode);
this._containerDomNode.parentElement?.removeChild(this.domNode);
this._messageListeners.dispose();
this._target.dispose();
super.dispose();

View File

@@ -92,11 +92,7 @@ export interface IEnvironmentVariableService {
delete(extensionIdentifier: string): void;
}
/**
* First: Variable
* Second: Value
* Third: Type
*/
/** [variable, mutator] */
export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVariableMutator][];
export interface IEnvironmentVariableInfo {

View File

@@ -397,6 +397,8 @@ export enum TitleEventSource {
}
export interface IWindowsShellHelper extends IDisposable {
readonly onShellNameChange: Event<string>;
getShellName(): Promise<string>;
}

View File

@@ -264,7 +264,7 @@ export const terminalConfiguration: IConfigurationNode = {
localize('terminal.integrated.environmentChangesIndicator.on', "Enable the indicator."),
localize('terminal.integrated.environmentChangesIndicator.warnonly', "Only show the warning indicator when a terminal's environment is 'stale', not the information indicator that shows a terminal has had its environment modified by an extension."),
],
default: 'on'
default: 'warnonly'
},
'terminal.integrated.showExitAlert': {
description: localize('terminal.integrated.showExitAlert', "Controls whether to show the alert \"The terminal process terminated with exit code\" when exit code is non-zero."),

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal';
import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/electron-browser/windowsShellHelper';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -79,8 +79,8 @@ export class TerminalInstanceService implements ITerminalInstanceService {
return WebglAddon;
}
public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper {
return new WindowsShellHelper(shellProcessId, instance, xterm);
public createWindowsShellHelper(shellProcessId: number, xterm: XTermTerminal): IWindowsShellHelper {
return new WindowsShellHelper(shellProcessId, xterm);
}
public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess {

View File

@@ -5,11 +5,10 @@
import * as platform from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
import { IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { Terminal as XTermTerminal } from 'xterm';
import * as WindowsProcessTreeType from 'windows-process-tree';
import { Disposable } from 'vs/base/common/lifecycle';
import { ITerminalInstance, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal';
import { timeout } from 'vs/base/common/async';
const SHELL_EXECUTABLES = [
@@ -34,9 +33,11 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
private _currentRequest: Promise<string> | undefined;
private _newLineFeed: boolean = false;
private readonly _onShellNameChange = new Emitter<string>();
public get onShellNameChange(): Event<string> { return this._onShellNameChange.event; }
public constructor(
private _rootProcessId: number,
private _terminalInstance: ITerminalInstance,
private _xterm: XTermTerminal
) {
super();
@@ -84,13 +85,9 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
}
private checkShell(): void {
if (platform.isWindows && this._terminalInstance.isTitleSetByProcess) {
this.getShellName().then(title => {
if (!this._isDisposed) {
this._terminalInstance.setShellType(this.getShellType(title));
this._terminalInstance.setTitle(title, TitleEventSource.Process);
}
});
if (platform.isWindows) {
// TODO: Only fire when it's different
this.getShellName().then(title => this._onShellNameChange.fire(title));
}
}
@@ -145,26 +142,4 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
});
return this._currentRequest;
}
public getShellType(executable: string): TerminalShellType {
switch (executable.toLowerCase()) {
case 'cmd.exe':
return WindowsShellType.CommandPrompt;
case 'powershell.exe':
case 'pwsh.exe':
return WindowsShellType.PowerShell;
case 'bash.exe':
return WindowsShellType.GitBash;
case 'wsl.exe':
case 'ubuntu.exe':
case 'ubuntu1804.exe':
case 'kali.exe':
case 'debian.exe':
case 'opensuse-42.exe':
case 'sles-12.exe':
return WindowsShellType.Wsl;
default:
return undefined;
}
}
}

View File

@@ -113,10 +113,10 @@ suite('Workbench - TerminalLinkHandler', () => {
if (lineNo) {
const lineColumnInfo: LineColumnInfo = terminalLinkHandler.extractLineColumnInfo(link);
assert.equal(lineColumnInfo.lineNumber, lineNo);
assert.equal(lineColumnInfo.lineNumber, lineNo, `For link ${link}, expected line number ${lineNo}, actual ${lineColumnInfo.lineNumber}`);
if (columnNo) {
assert.equal(lineColumnInfo.columnNumber, columnNo);
assert.equal(lineColumnInfo.columnNumber, columnNo, `For link ${link}, expected column number ${columnNo}, actual ${lineColumnInfo.columnNumber}`);
}
}
}
@@ -190,10 +190,10 @@ suite('Workbench - TerminalLinkHandler', () => {
if (lineNo) {
const lineColumnInfo: LineColumnInfo = terminalLinkHandler.extractLineColumnInfo(link);
assert.equal(lineColumnInfo.lineNumber, lineNo);
assert.equal(lineColumnInfo.lineNumber, lineNo, `For link ${link}, expected line number ${lineNo}, actual ${lineColumnInfo.lineNumber}`);
if (columnNo) {
assert.equal(lineColumnInfo.columnNumber, columnNo);
assert.equal(lineColumnInfo.columnNumber, columnNo, `For link ${link}, expected column number ${columnNo}, actual ${lineColumnInfo.columnNumber}`);
}
}
}

View File

@@ -23,7 +23,7 @@ suite('Workbench - TerminalWordLinkProvider', () => {
async function assertLink(text: string, expected: { text: string, range: [number, number][] }) {
const xterm = new Terminal();
const provider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { }, () => { });
const provider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { });
// Write the text and wait for the parser to finish
await new Promise<void>(r => xterm.write(text, r));
@@ -75,4 +75,14 @@ suite('Workbench - TerminalWordLinkProvider', () => {
await assertLink('[foo]', { range: [[1, 1], [5, 1]], text: '[foo]' });
await assertLink('{foo}', { range: [[1, 1], [5, 1]], text: '{foo}' });
});
test('should support wide characters', async () => {
await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' []' } });
await assertLink('aabbccdd.txt ', { range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' });
await assertLink('我是学生.txt ', { range: [[1, 1], [12, 1]], text: '我是学生.txt' });
await assertLink(' aabbccdd.txt ', { range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' });
await assertLink(' 我是学生.txt ', { range: [[2, 1], [13, 1]], text: '我是学生.txt' });
await assertLink(' [aabbccdd.txt] ', { range: [[3, 1], [14, 1]], text: 'aabbccdd.txt' });
await assertLink(' [我是学生.txt] ', { range: [[3, 1], [14, 1]], text: '我是学生.txt' });
});
});