mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-04-01 09:30:31 -04:00
Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568
This commit is contained in:
@@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 }>;
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -397,6 +397,8 @@ export enum TitleEventSource {
|
||||
}
|
||||
|
||||
export interface IWindowsShellHelper extends IDisposable {
|
||||
readonly onShellNameChange: Event<string>;
|
||||
|
||||
getShellName(): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user