Merge from vscode 4d91d96e5e121b38d33508cdef17868bab255eae

This commit is contained in:
ADS Merger
2020-06-18 04:32:54 +00:00
committed by AzureDataStudio
parent a971aee5bd
commit 5e7071e466
1002 changed files with 24201 additions and 13193 deletions

View File

@@ -88,9 +88,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
href = _href(href, true);
if (options.baseUrl) {
href = resolvePath(options.baseUrl, href).toString();
}
try {
const hrefAsUri = URI.parse(href);
if (options.baseUrl && hrefAsUri.scheme === Schemas.file) { // absolute or relative local path, or file: uri
href = resolvePath(options.baseUrl, href).toString();
}
} catch (err) { }
attributes.push(`src="${href}"`);
}
if (text) {
@@ -225,9 +229,12 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
);
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
if (token.tag === 'span' && markdown.isTrusted) {
if (token.attrs['style'] && Object.keys(token.attrs).length === 1) {
if (token.tag === 'span' && markdown.isTrusted && (Object.keys(token.attrs).length === 1)) {
if (token.attrs['style']) {
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
} else if (token.attrs['class']) {
// The class should match codicon rendering in src\vs\base\common\codicons.ts
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
}
return false;
}
@@ -239,7 +246,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
// allowedTags should included everything that markdown renders to.
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'tr', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
// HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension-
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
allowedAttributes: {
'a': ['href', 'name', 'target', 'data-href'],
'img': ['src', 'title', 'alt', 'width', 'height'],

View File

@@ -479,27 +479,27 @@ export class ActionBar extends Disposable implements IActionRunner {
DOM.addClass(this.domNode, 'animated');
}
let previousKeys: KeyCode[];
let nextKeys: KeyCode[];
let previousKey: KeyCode;
let nextKey: KeyCode;
switch (this.options.orientation) {
case ActionsOrientation.HORIZONTAL:
previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
previousKey = KeyCode.LeftArrow;
nextKey = KeyCode.RightArrow;
break;
case ActionsOrientation.HORIZONTAL_REVERSE:
previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
previousKey = KeyCode.RightArrow;
nextKey = KeyCode.LeftArrow;
this.domNode.className += ' reverse';
break;
case ActionsOrientation.VERTICAL:
previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
previousKey = KeyCode.UpArrow;
nextKey = KeyCode.DownArrow;
this.domNode.className += ' vertical';
break;
case ActionsOrientation.VERTICAL_REVERSE:
previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
previousKey = KeyCode.DownArrow;
nextKey = KeyCode.UpArrow;
this.domNode.className += ' vertical reverse';
break;
}
@@ -508,9 +508,9 @@ export class ActionBar extends Disposable implements IActionRunner {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) {
if (event.equals(previousKey)) {
this.focusPrevious();
} else if (nextKeys && (event.equals(nextKeys[0]) || event.equals(nextKeys[1]))) {
} else if (event.equals(nextKey)) {
this.focusNext();
} else if (event.equals(KeyCode.Escape)) {
this._onDidCancel.fire();
@@ -696,7 +696,8 @@ export class ActionBar extends Disposable implements IActionRunner {
}
clear(): void {
this.viewItems = dispose(this.viewItems);
dispose(this.viewItems);
this.viewItems = [];
DOM.clearNode(this.actionsList);
}

View File

@@ -11,50 +11,78 @@ import * as dom from 'vs/base/browser/dom';
const MAX_MESSAGE_LENGTH = 20000;
let ariaContainer: HTMLElement;
let alertContainer: HTMLElement;
let alertContainer2: HTMLElement;
let statusContainer: HTMLElement;
let statusContainer2: HTMLElement;
export function setARIAContainer(parent: HTMLElement) {
ariaContainer = document.createElement('div');
ariaContainer.className = 'monaco-aria-container';
alertContainer = document.createElement('div');
alertContainer.className = 'monaco-alert';
alertContainer.setAttribute('role', 'alert');
alertContainer.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(alertContainer);
const createAlertContainer = () => {
const element = document.createElement('div');
element.className = 'monaco-alert';
element.setAttribute('role', 'alert');
element.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(element);
return element;
};
alertContainer = createAlertContainer();
alertContainer2 = createAlertContainer();
statusContainer = document.createElement('div');
statusContainer.className = 'monaco-status';
statusContainer.setAttribute('role', 'complementary');
statusContainer.setAttribute('aria-live', 'polite');
statusContainer.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(statusContainer);
const createStatusContainer = () => {
const element = document.createElement('div');
element.className = 'monaco-status';
element.setAttribute('role', 'complementary');
element.setAttribute('aria-live', 'polite');
element.setAttribute('aria-atomic', 'true');
ariaContainer.appendChild(element);
return element;
};
statusContainer = createStatusContainer();
statusContainer2 = createStatusContainer();
parent.appendChild(ariaContainer);
}
/**
* Given the provided message, will make sure that it is read as alert to screen readers.
*/
export function alert(msg: string): void {
insertMessage(alertContainer, msg);
if (!ariaContainer) {
return;
}
// Use alternate containers such that duplicated messages get read out by screen readers #99466
if (alertContainer.textContent !== msg) {
dom.clearNode(alertContainer2);
insertMessage(alertContainer, msg);
} else {
dom.clearNode(alertContainer);
insertMessage(alertContainer2, msg);
}
}
/**
* Given the provided message, will make sure that it is read as status to screen readers.
*/
export function status(msg: string): void {
if (isMacintosh) {
alert(msg); // VoiceOver does not seem to support status role
} else {
insertMessage(statusContainer, msg);
}
}
function insertMessage(target: HTMLElement, msg: string): void {
if (!ariaContainer) {
return;
}
if (isMacintosh) {
alert(msg); // VoiceOver does not seem to support status role
} else {
if (statusContainer.textContent !== msg) {
dom.clearNode(statusContainer2);
insertMessage(statusContainer, msg);
} else {
dom.clearNode(statusContainer);
insertMessage(statusContainer2, msg);
}
}
}
function insertMessage(target: HTMLElement, msg: string): void {
dom.clearNode(target);
if (msg.length > MAX_MESSAGE_LENGTH) {
msg = msg.substr(0, MAX_MESSAGE_LENGTH);

View File

@@ -18,12 +18,16 @@ import { escape } from 'vs/base/common/strings';
export interface IButtonOptions extends IButtonStyles {
readonly title?: boolean | string;
readonly supportCodicons?: boolean;
readonly secondary?: boolean;
}
export interface IButtonStyles {
buttonBackground?: Color;
buttonHoverBackground?: Color;
buttonForeground?: Color;
buttonSecondaryBackground?: Color;
buttonSecondaryHoverBackground?: Color;
buttonSecondaryForeground?: Color;
buttonBorder?: Color;
}
@@ -41,6 +45,9 @@ export class Button extends Disposable {
private buttonBackground: Color | undefined;
private buttonHoverBackground: Color | undefined;
private buttonForeground: Color | undefined;
private buttonSecondaryBackground: Color | undefined;
private buttonSecondaryHoverBackground: Color | undefined;
private buttonSecondaryForeground: Color | undefined;
private buttonBorder: Color | undefined;
private _onDidClick = this._register(new Emitter<Event>());
@@ -54,9 +61,14 @@ export class Button extends Disposable {
this.options = options || Object.create(null);
mixin(this.options, defaultOptions, false);
this.buttonForeground = this.options.buttonForeground;
this.buttonBackground = this.options.buttonBackground;
this.buttonHoverBackground = this.options.buttonHoverBackground;
this.buttonForeground = this.options.buttonForeground;
this.buttonSecondaryForeground = this.options.buttonSecondaryForeground;
this.buttonSecondaryBackground = this.options.buttonSecondaryBackground;
this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground;
this.buttonBorder = this.options.buttonBorder;
this._element = document.createElement('a');
@@ -114,7 +126,12 @@ export class Button extends Disposable {
}
private setHoverBackground(): void {
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
let hoverBackground;
if (this.options.secondary) {
hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null;
} else {
hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
}
if (hoverBackground) {
this._element.style.backgroundColor = hoverBackground;
}
@@ -124,6 +141,9 @@ export class Button extends Disposable {
this.buttonForeground = styles.buttonForeground;
this.buttonBackground = styles.buttonBackground;
this.buttonHoverBackground = styles.buttonHoverBackground;
this.buttonSecondaryForeground = styles.buttonSecondaryForeground;
this.buttonSecondaryBackground = styles.buttonSecondaryBackground;
this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
this.buttonBorder = styles.buttonBorder;
this.applyStyles();
@@ -132,8 +152,15 @@ export class Button extends Disposable {
// {{SQL CARBON EDIT}} -- removed 'private' access modifier @todo anthonydresser 4/12/19 things needs investigation whether we need this
applyStyles(): void {
if (this._element) {
const background = this.buttonBackground ? this.buttonBackground.toString() : '';
const foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
let background, foreground;
if (this.options.secondary) {
foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : '';
background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : '';
} else {
foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
background = this.buttonBackground ? this.buttonBackground.toString() : '';
}
const border = this.buttonBorder ? this.buttonBorder.toString() : '';
this._element.style.color = foreground;

View File

@@ -23,6 +23,7 @@ export interface ICheckboxOpts extends ICheckboxStyles {
export interface ICheckboxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionForeground?: Color;
inputActiveOptionBackground?: Color;
}
@@ -34,6 +35,7 @@ export interface ISimpleCheckboxStyles {
const defaultOpts = {
inputActiveOptionBorder: Color.fromHex('#007ACC00'),
inputActiveOptionForeground: Color.fromHex('#FFFFFF'),
inputActiveOptionBackground: Color.fromHex('#0E639C50')
};
@@ -170,6 +172,9 @@ export class Checkbox extends Widget {
if (styles.inputActiveOptionBorder) {
this._opts.inputActiveOptionBorder = styles.inputActiveOptionBorder;
}
if (styles.inputActiveOptionForeground) {
this._opts.inputActiveOptionForeground = styles.inputActiveOptionForeground;
}
if (styles.inputActiveOptionBackground) {
this._opts.inputActiveOptionBackground = styles.inputActiveOptionBackground;
}
@@ -179,6 +184,7 @@ export class Checkbox extends Widget {
protected applyStyles(): void {
if (this.domNode) {
this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : 'transparent';
this.domNode.style.color = this._checked && this._opts.inputActiveOptionForeground ? this._opts.inputActiveOptionForeground.toString() : 'inherit';
this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : 'transparent';
}
}

View File

@@ -73,6 +73,7 @@ export class Dialog extends Disposable {
this.modal = this.container.appendChild($(`.monaco-dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
this.shadowElement = this.modal.appendChild($('.dialog-shadow'));
this.element = this.shadowElement.appendChild($('.monaco-dialog-box'));
this.element.setAttribute('role', 'dialog');
hide(this.element);
// If no button is provided, default to OK
@@ -109,6 +110,28 @@ export class Dialog extends Disposable {
this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar'));
}
private getAriaLabel(): string {
let typeLabel = nls.localize('dialogInfoMessage', 'Info');
switch (this.options.type) {
case 'error':
nls.localize('dialogErrorMessage', 'Error');
break;
case 'warning':
nls.localize('dialogWarningMessage', 'Warning');
break;
case 'pending':
nls.localize('dialogPendingMessage', 'In Progress');
break;
case 'none':
case 'info':
case 'question':
default:
break;
}
return `${typeLabel}: ${this.message} ${this.options.detail || ''}`;
}
updateMessage(message: string): void {
if (this.messageDetailElement) {
this.messageDetailElement.innerText = message;
@@ -242,7 +265,7 @@ export class Dialog extends Disposable {
this.applyStyles();
this.element.setAttribute('aria-label', this.message);
this.element.setAttribute('aria-label', this.getAriaLabel());
show(this.element);
// Focus first element

View File

@@ -214,6 +214,7 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions {
actions?: ReadonlyArray<IAction>;
actionProvider?: IActionProvider;
menuClassName?: string;
menuAsChild?: boolean; // scope down for #99448
}
export class DropdownMenu extends BaseDropdown {
@@ -222,6 +223,7 @@ export class DropdownMenu extends BaseDropdown {
private _actions: ReadonlyArray<IAction> = [];
private actionProvider?: IActionProvider;
private menuClassName: string;
private menuAsChild?: boolean;
constructor(container: HTMLElement, options: IDropdownMenuOptions) {
super(container, options);
@@ -230,6 +232,7 @@ export class DropdownMenu extends BaseDropdown {
this.actions = options.actions || [];
this.actionProvider = options.actionProvider;
this.menuClassName = options.menuClassName || '';
this.menuAsChild = !!options.menuAsChild;
}
set menuOptions(options: IMenuOptions | undefined) {
@@ -267,7 +270,7 @@ export class DropdownMenu extends BaseDropdown {
onHide: () => this.onHide(),
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT,
anchorAsContainer: true
anchorAsContainer: this.menuAsChild
});
}
@@ -289,10 +292,11 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
private keybindings?: (action: IAction) => ResolvedKeybinding | undefined;
private clazz: string | undefined;
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
private menuAsChild?: boolean;
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) {
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) {
super(null, action);
this.menuActionsOrProvider = menuActionsOrProvider;
@@ -302,6 +306,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
this.keybindings = keybindings;
this.clazz = clazz;
this.anchorAlignmentProvider = anchorAlignmentProvider;
this.menuAsChild = menuAsChild;
}
render(container: HTMLElement): void {
@@ -322,7 +327,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
const options: IDropdownMenuOptions = {
contextMenuProvider: this.contextMenuProvider,
labelRenderer: labelRenderer
labelRenderer: labelRenderer,
menuAsChild: this.menuAsChild
};
// Render the DropdownMenu around a simple action to toggle it

View File

@@ -35,6 +35,7 @@ export interface IFindInputOptions extends IFindInputStyles {
export interface IFindInputStyles extends IInputBoxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionForeground?: Color;
inputActiveOptionBackground?: Color;
}
@@ -51,6 +52,7 @@ export class FindInput extends Widget {
private fixFocusOnOptionClickEnabled = true;
private inputActiveOptionBorder?: Color;
private inputActiveOptionForeground?: Color;
private inputActiveOptionBackground?: Color;
private inputBackground?: Color;
private inputForeground?: Color;
@@ -101,6 +103,7 @@ export class FindInput extends Widget {
this.label = options.label || NLS_DEFAULT_LABEL;
this.inputActiveOptionBorder = options.inputActiveOptionBorder;
this.inputActiveOptionForeground = options.inputActiveOptionForeground;
this.inputActiveOptionBackground = options.inputActiveOptionBackground;
this.inputBackground = options.inputBackground;
this.inputForeground = options.inputForeground;
@@ -155,6 +158,7 @@ export class FindInput extends Widget {
appendTitle: appendRegexLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground
}));
this._register(this.regex.onChange(viaKeyboard => {
@@ -172,6 +176,7 @@ export class FindInput extends Widget {
appendTitle: appendWholeWordsLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground
}));
this._register(this.wholeWords.onChange(viaKeyboard => {
@@ -186,6 +191,7 @@ export class FindInput extends Widget {
appendTitle: appendCaseSensitiveLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground
}));
this._register(this.caseSensitive.onChange(viaKeyboard => {
@@ -301,6 +307,7 @@ export class FindInput extends Widget {
public style(styles: IFindInputStyles): void {
this.inputActiveOptionBorder = styles.inputActiveOptionBorder;
this.inputActiveOptionForeground = styles.inputActiveOptionForeground;
this.inputActiveOptionBackground = styles.inputActiveOptionBackground;
this.inputBackground = styles.inputBackground;
this.inputForeground = styles.inputForeground;
@@ -323,6 +330,7 @@ export class FindInput extends Widget {
if (this.domNode) {
const checkBoxStyles: ICheckboxStyles = {
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground,
};
this.regex.style(checkBoxStyles);

View File

@@ -12,6 +12,7 @@ export interface IFindInputCheckboxOpts {
readonly appendTitle: string;
readonly isChecked: boolean;
readonly inputActiveOptionBorder?: Color;
readonly inputActiveOptionForeground?: Color;
readonly inputActiveOptionBackground?: Color;
}
@@ -26,6 +27,7 @@ export class CaseSensitiveCheckbox extends Checkbox {
title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -38,6 +40,7 @@ export class WholeWordsCheckbox extends Checkbox {
title: NLS_WHOLE_WORD_CHECKBOX_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -50,6 +53,7 @@ export class RegexCheckbox extends Checkbox {
title: NLS_REGEX_CHECKBOX_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}

View File

@@ -33,6 +33,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles {
export interface IReplaceInputStyles extends IInputBoxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionForeground?: Color;
inputActiveOptionBackground?: Color;
}
@@ -47,6 +48,7 @@ export class PreserveCaseCheckbox extends Checkbox {
title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle,
isChecked: opts.isChecked,
inputActiveOptionBorder: opts.inputActiveOptionBorder,
inputActiveOptionForeground: opts.inputActiveOptionForeground,
inputActiveOptionBackground: opts.inputActiveOptionBackground
});
}
@@ -63,6 +65,7 @@ export class ReplaceInput extends Widget {
private fixFocusOnOptionClickEnabled = true;
private inputActiveOptionBorder?: Color;
private inputActiveOptionForeground?: Color;
private inputActiveOptionBackground?: Color;
private inputBackground?: Color;
private inputForeground?: Color;
@@ -109,6 +112,7 @@ export class ReplaceInput extends Widget {
this.label = options.label || NLS_DEFAULT_LABEL;
this.inputActiveOptionBorder = options.inputActiveOptionBorder;
this.inputActiveOptionForeground = options.inputActiveOptionForeground;
this.inputActiveOptionBackground = options.inputActiveOptionBackground;
this.inputBackground = options.inputBackground;
this.inputForeground = options.inputForeground;
@@ -160,6 +164,7 @@ export class ReplaceInput extends Widget {
appendTitle: '',
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground,
}));
this._register(this.preserveCase.onChange(viaKeyboard => {
@@ -271,6 +276,7 @@ export class ReplaceInput extends Widget {
public style(styles: IReplaceInputStyles): void {
this.inputActiveOptionBorder = styles.inputActiveOptionBorder;
this.inputActiveOptionForeground = styles.inputActiveOptionForeground;
this.inputActiveOptionBackground = styles.inputActiveOptionBackground;
this.inputBackground = styles.inputBackground;
this.inputForeground = styles.inputForeground;
@@ -293,6 +299,7 @@ export class ReplaceInput extends Widget {
if (this.domNode) {
const checkBoxStyles: ICheckboxStyles = {
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionForeground: this.inputActiveOptionForeground,
inputActiveOptionBackground: this.inputActiveOptionBackground,
};
this.preserveCase.style(checkBoxStyles);

View File

@@ -35,6 +35,7 @@
}
.monaco-hover p,
.monaco-hover .code,
.monaco-hover ul {
margin: 8px 0;
}
@@ -45,18 +46,20 @@
.monaco-hover hr {
margin-top: 4px;
margin-bottom: -6px;
margin-bottom: -4px;
margin-left: -10px;
margin-right: -10px;
height: 1px;
}
.monaco-hover p:first-child,
.monaco-hover .code:first-child,
.monaco-hover ul:first-child {
margin-top: 0;
}
.monaco-hover p:last-child,
.monaco-hover .code:last-child,
.monaco-hover ul:last-child {
margin-bottom: 0;
}

View File

@@ -55,6 +55,10 @@
white-space: pre; /* enable to show labels that include multiple whitespaces */
}
.vs .monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
opacity: .95;
}
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
font-style: italic;

View File

@@ -6,12 +6,13 @@
import 'vs/css!./list';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { range } from 'vs/base/common/arrays';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list';
import { List, IListStyles, IListOptions, IListAccessibilityProvider } from './listWidget';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent } from './list';
import { List, IListStyles, IListOptions, IListAccessibilityProvider, IListOptionsUpdate } from './listWidget';
import { IPagedModel } from 'vs/base/common/paging';
import { Event } from 'vs/base/common/event';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemable } from 'vs/base/common/styler';
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
renderPlaceholder(index: number, templateData: TTemplateData): void;
@@ -119,7 +120,7 @@ function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: I
};
}
export class PagedList<T> implements IDisposable {
export class PagedList<T> implements IThemable, IDisposable {
private list: List<number>;
private _model!: IPagedModel<T>;
@@ -136,6 +137,10 @@ export class PagedList<T> implements IDisposable {
this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options));
}
updateOptions(options: IListOptionsUpdate) {
this.list.updateOptions(options);
}
getHTMLElement(): HTMLElement {
return this.list.getHTMLElement();
}
@@ -164,22 +169,30 @@ export class PagedList<T> implements IDisposable {
return this.list.onDidDispose;
}
get onMouseClick(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onMouseDblClick(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onTap(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onPointer(): Event<IListMouseEvent<T>> {
return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
}
get onDidChangeFocus(): Event<IListEvent<T>> {
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onDidOpen(): Event<IListEvent<T>> {
return Event.map(this.list.onDidOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onDidChangeSelection(): Event<IListEvent<T>> {
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onPin(): Event<IListEvent<T>> {
return Event.map(this.list.onDidPin, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onContextMenu(): Event<IListContextMenuEvent<T>> {
return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));
}
@@ -213,10 +226,6 @@ export class PagedList<T> implements IDisposable {
this.list.scrollLeft = scrollLeft;
}
open(indexes: number[], browserEvent?: UIEvent): void {
this.list.open(indexes, browserEvent);
}
setFocus(indexes: number[]): void {
this.list.setFocus(indexes);
}

View File

@@ -51,6 +51,7 @@ export interface IListViewAccessibilityProvider<T> {
export interface IListViewOptionsUpdate {
readonly additionalScrollHeight?: number;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
}
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
@@ -61,7 +62,6 @@ export interface IListViewOptions<T> extends IListViewOptionsUpdate {
readonly setRowHeight?: boolean;
readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean;
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
readonly transformOptimization?: boolean;
}
@@ -86,7 +86,14 @@ const DefaultOptions = {
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
readonly elements: T[];
context: TContext | undefined;
private _context: TContext | undefined;
public get context(): TContext | undefined {
return this._context;
}
public set context(value: TContext | undefined) {
this._context = value;
}
constructor(elements: T[]) {
this.elements = elements;
@@ -220,7 +227,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private setRowLineHeight: boolean;
private setRowHeight: boolean;
private supportDynamicHeights: boolean;
private horizontalScrolling: boolean;
private additionalScrollHeight: number;
private accessibilityProvider: ListViewAccessibilityProvider<T>;
private scrollWidth: number | undefined;
@@ -242,6 +248,35 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; }
get containerDomNode(): HTMLElement { return this.rowsContainer; }
private _horizontalScrolling: boolean = false;
private get horizontalScrolling(): boolean { return this._horizontalScrolling; }
private set horizontalScrolling(value: boolean) {
if (value === this._horizontalScrolling) {
return;
}
if (value && this.supportDynamicHeights) {
throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously');
}
this._horizontalScrolling = value;
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this._horizontalScrolling);
if (this._horizontalScrolling) {
for (const item of this.items) {
this.measureItemWidth(item);
}
this.updateScrollWidth();
this.scrollableElement.setScrollDimensions({ width: DOM.getContentWidth(this.domNode) });
this.rowsContainer.style.width = `${Math.max(this.scrollWidth || 0, this.renderWidth)}px`;
} else {
this.scrollableElementWidthDelayer.cancel();
this.scrollableElement.setScrollDimensions({ width: this.renderWidth, scrollWidth: this.renderWidth });
this.rowsContainer.style.width = '';
}
}
constructor(
container: HTMLElement,
private virtualDelegate: IListVirtualDelegate<T>,
@@ -273,8 +308,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling);
this._horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this._horizontalScrolling);
this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;
@@ -293,7 +328,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => DOM.scheduleAtNextAnimationFrame(cb));
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: true,
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
horizontal: ScrollbarVisibility.Auto,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
}, this.scrollable));
@@ -322,7 +357,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.layout();
}
updateOptions(options: IListViewOptions<T>) {
updateOptions(options: IListViewOptionsUpdate) {
if (options.additionalScrollHeight !== undefined) {
this.additionalScrollHeight = options.additionalScrollHeight;
}
@@ -330,6 +365,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (options.smoothScrolling !== undefined) {
this.scrollable.setSmoothScrollDuration(options.smoothScrolling ? 125 : 0);
}
if (options.horizontalScrolling !== undefined) {
this.horizontalScrolling = options.horizontalScrolling;
}
}
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
@@ -478,6 +517,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private eventuallyUpdateScrollWidth(): void {
if (!this.horizontalScrolling) {
this.scrollableElementWidthDelayer.cancel();
return;
}
@@ -489,10 +529,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return;
}
if (this.items.length === 0) {
this.scrollableElement.setScrollDimensions({ scrollWidth: 0 });
}
let scrollWidth = 0;
for (const item of this.items) {
@@ -502,7 +538,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
this.scrollWidth = scrollWidth;
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth + 10 });
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth === 0 ? 0 : (scrollWidth + 10) });
}
updateWidth(index: number): void {

View File

@@ -26,6 +26,7 @@ import { clamp } from 'vs/base/common/numbers';
import { matchesPrefix } from 'vs/base/common/filters';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { IThemable } from 'vs/base/common/styler';
interface ITraitChangeEvent {
indexes: number[];
@@ -231,7 +232,6 @@ function isInputElement(e: HTMLElement): boolean {
class KeyboardController<T> implements IDisposable {
private readonly disposables = new DisposableStore();
private openController: IOpenController;
constructor(
private list: List<T>,
@@ -240,8 +240,6 @@ class KeyboardController<T> implements IDisposable {
) {
const multipleSelectionSupport = options.multipleSelectionSupport !== false;
this.openController = options.openController || DefaultOpenController;
const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
@@ -262,10 +260,6 @@ class KeyboardController<T> implements IDisposable {
e.preventDefault();
e.stopPropagation();
this.list.setSelection(this.list.getFocus(), e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open(this.list.getFocus(), e.browserEvent);
}
}
private onUpArrow(e: StandardKeyboardEvent): void {
@@ -527,24 +521,16 @@ const DefaultMultipleSelectionController = {
isSelectionRangeChangeEvent
};
const DefaultOpenController: IOpenController = {
shouldOpen: (event: UIEvent) => {
if (event instanceof MouseEvent) {
return !isMouseRightClick(event);
}
return true;
}
};
export class MouseController<T> implements IDisposable {
private multipleSelectionSupport: boolean;
readonly multipleSelectionController: IMultipleSelectionController<T> | undefined;
private openController: IOpenController;
private mouseSupport: boolean;
private readonly disposables = new DisposableStore();
private _onPointer = new Emitter<IListMouseEvent<T>>();
readonly onPointer: Event<IListMouseEvent<T>> = this._onPointer.event;
constructor(protected list: List<T>) {
this.multipleSelectionSupport = !(list.options.multipleSelectionSupport === false);
@@ -552,7 +538,6 @@ export class MouseController<T> implements IDisposable {
this.multipleSelectionController = list.options.multipleSelectionController || DefaultMultipleSelectionController;
}
this.openController = list.options.openController || DefaultOpenController;
this.mouseSupport = typeof list.options.mouseSupport === 'undefined' || !!list.options.mouseSupport;
if (this.mouseSupport) {
@@ -563,9 +548,7 @@ export class MouseController<T> implements IDisposable {
this.disposables.add(Gesture.addTarget(list.getHTMLElement()));
}
list.onMouseClick(this.onPointer, this, this.disposables);
list.onMouseMiddleClick(this.onPointer, this, this.disposables);
list.onTap(this.onPointer, this, this.disposables);
Event.any(list.onMouseClick, list.onMouseMiddleClick, list.onTap)(this.onViewPointer, this, this.disposables);
}
protected isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
@@ -599,7 +582,7 @@ export class MouseController<T> implements IDisposable {
this.list.setFocus(focus, e.browserEvent);
}
protected onPointer(e: IListMouseEvent<T>): void {
protected onViewPointer(e: IListMouseEvent<T>): void {
if (!this.mouseSupport) {
return;
}
@@ -632,11 +615,9 @@ export class MouseController<T> implements IDisposable {
if (!isMouseRightClick(e.browserEvent)) {
this.list.setSelection([focus], e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open([focus], e.browserEvent);
}
}
this._onPointer.fire(e);
}
protected onDoubleClick(e: IListMouseEvent<T>): void {
@@ -650,7 +631,6 @@ export class MouseController<T> implements IDisposable {
const focus = this.list.getFocus();
this.list.setSelection(focus, e.browserEvent);
this.list.pin(focus);
}
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
@@ -694,10 +674,6 @@ export interface IMultipleSelectionController<T> {
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
}
export interface IOpenController {
shouldOpen(event: UIEvent): boolean;
}
export interface IStyleController {
style(styles: IListStyles): void;
}
@@ -841,7 +817,6 @@ export interface IListOptions<T> {
readonly keyboardSupport?: boolean;
readonly multipleSelectionSupport?: boolean;
readonly multipleSelectionController?: IMultipleSelectionController<T>;
readonly openController?: IOpenController;
readonly styleController?: (suffix: string) => IStyleController;
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
@@ -1112,7 +1087,7 @@ export interface IListOptionsUpdate extends IListViewOptionsUpdate {
readonly automaticKeyboardNavigation?: boolean;
}
export class List<T> implements ISpliceable<T>, IDisposable {
export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
private focus: Trait<T>;
private selection: Trait<T>;
@@ -1122,6 +1097,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
private styleController: IStyleController;
private typeLabelController?: TypeLabelController<T>;
private accessibilityProvider?: IListAccessibilityProvider<T>;
private mouseController: MouseController<T>;
private _ariaLabel: string = '';
protected readonly disposables = new DisposableStore();
@@ -1134,17 +1110,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
}
private readonly _onDidOpen = new Emitter<IListEvent<T>>();
readonly onDidOpen: Event<IListEvent<T>> = this._onDidOpen.event;
private readonly _onDidPin = new Emitter<IListEvent<T>>();
readonly onDidPin: Event<IListEvent<T>> = this._onDidPin.event;
get domId(): string { return this.view.domId; }
get onDidScroll(): Event<ScrollEvent> { return this.view.onDidScroll; }
get onMouseClick(): Event<IListMouseEvent<T>> { return this.view.onMouseClick; }
get onMouseDblClick(): Event<IListMouseEvent<T>> { return this.view.onMouseDblClick; }
get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return this.view.onMouseMiddleClick; }
get onPointer(): Event<IListMouseEvent<T>> { return this.mouseController.onPointer; }
get onMouseUp(): Event<IListMouseEvent<T>> { return this.view.onMouseUp; }
get onMouseDown(): Event<IListMouseEvent<T>> { return this.view.onMouseDown; }
get onMouseOver(): Event<IListMouseEvent<T>> { return this.view.onMouseOver; }
@@ -1263,7 +1234,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.disposables.add(this.typeLabelController);
}
this.disposables.add(this.createMouseController(_options));
this.mouseController = this.createMouseController(_options);
this.disposables.add(this.mouseController);
this.onDidChangeFocus(this._onFocusChange, this, this.disposables);
this.onDidChangeSelection(this._onSelectionChange, this, this.disposables);
@@ -1625,26 +1597,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.domNode;
}
open(indexes: number[], browserEvent?: UIEvent): void {
for (const index of indexes) {
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
}
this._onDidOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
}
pin(indexes: number[], browserEvent?: UIEvent): void {
for (const index of indexes) {
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
}
this._onDidPin.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
}
style(styles: IListStyles): void {
this.styleController.style(styles);
}
@@ -1687,8 +1639,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this._onDidDispose.fire();
this.disposables.dispose();
this._onDidOpen.dispose();
this._onDidPin.dispose();
this._onDidDispose.dispose();
}
}

View File

@@ -446,6 +446,16 @@ export class MenuBar extends Disposable {
return this.container.clientHeight;
}
toggleFocus(): void {
if (!this.isFocused && this.options.visibility !== 'hidden') {
this.mnemonicsInUse = true;
this.focusedMenu = { index: this.numMenusShown > 0 ? 0 : MenuBar.OVERFLOW_INDEX };
this.focusState = MenubarState.FOCUSED;
} else if (!this.isOpen) {
this.setUnfocusedState();
}
}
private updateOverflowAction(): void {
if (!this.menuCache || !this.menuCache.length) {
return;

View File

@@ -343,7 +343,13 @@ export abstract class AbstractScrollableElement extends Widget {
const classifier = MouseWheelClassifier.INSTANCE;
if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) {
classifier.accept(Date.now(), e.deltaX, e.deltaY);
if (platform.isWindows) {
// On Windows, the incoming delta events are multiplied with the device pixel ratio,
// so to get a better classification, simply undo that.
classifier.accept(Date.now(), e.deltaX / window.devicePixelRatio, e.deltaY / window.devicePixelRatio);
} else {
classifier.accept(Date.now(), e.deltaX, e.deltaY);
}
}
// console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`);

View File

@@ -30,7 +30,7 @@
.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown code {
line-height: 15px; /** For some reason, this is needed, otherwise <code> will take up 20px height */
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
font-family: var(--monaco-monospace-font);
}

View File

@@ -475,6 +475,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
// Track initial selection the case user escape, blur
this._currentSelection = this.selected;
this._isVisible = true;
this.selectElement.setAttribute('aria-expanded', 'true');
}
private hideSelectDropDown(focusSelect: boolean) {
@@ -483,6 +484,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
}
this._isVisible = false;
this.selectElement.setAttribute('aria-expanded', 'false');
if (focusSelect) {
this.selectElement.focus();

View File

@@ -29,6 +29,7 @@
display: flex;
cursor: pointer;
align-items: center;
box-sizing: border-box;
}
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header {

View File

@@ -69,7 +69,8 @@ export class ToolBar extends Disposable {
this.actionRunner,
this.options.getKeyBinding,
toolBarMoreIcon.classNames,
this.options.anchorAlignmentProvider
this.options.anchorAlignmentProvider,
true
);
this.toggleMenuActionViewItem.value.setActionContext(this.actionBar.context);

View File

@@ -6,7 +6,7 @@
import 'vs/css!./media/tree';
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -22,7 +22,6 @@ import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTr
import { localize } from 'vs/nls';
import { disposableTimeout } from 'vs/base/common/async';
import { isMacintosh } from 'vs/base/common/platform';
import { values } from 'vs/base/common/map';
import { clamp } from 'vs/base/common/numbers';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { SetMap } from 'vs/base/common/collections';
@@ -922,13 +921,6 @@ function isInputElement(e: HTMLElement): boolean {
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
}
function asTreeEvent<T>(event: IListEvent<ITreeNode<T, any>>): ITreeEvent<T> {
return {
elements: event.elements.map(node => node.element),
browserEvent: event.browserEvent
};
}
function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMouseEvent<T> {
let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown;
@@ -961,8 +953,8 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly automaticKeyboardNavigation?: boolean;
readonly simpleKeyboardNavigation?: boolean;
readonly filterOnType?: boolean;
readonly openOnSingleClick?: boolean;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
}
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
@@ -1042,7 +1034,7 @@ class Trait<T> {
const set = this.createNodeSet();
const visit = (node: ITreeNode<T, any>) => set.delete(node);
deletedNodes.forEach(node => dfs(node, visit));
this.set(values(set));
this.set([...set.values()]);
return;
}
@@ -1091,7 +1083,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
super(list);
}
protected onPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
protected onViewPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
if (isInputElement(e.browserEvent.target as HTMLElement)) {
return;
}
@@ -1099,19 +1091,14 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
const node = e.element;
if (!node) {
return super.onPointer(e);
return super.onViewPointer(e);
}
if (this.isSelectionRangeChangeEvent(e) || this.isSelectionSingleChangeEvent(e)) {
return super.onPointer(e);
return super.onViewPointer(e);
}
const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie');
if (!this.tree.openOnSingleClick && e.browserEvent.detail !== 2 && !onTwistie) {
return super.onPointer(e);
}
let expandOnlyOnTwistieClick = false;
if (typeof this.tree.expandOnlyOnTwistieClick === 'function') {
@@ -1121,7 +1108,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
}
if (expandOnlyOnTwistieClick && !onTwistie) {
return super.onPointer(e);
return super.onViewPointer(e);
}
if (node.collapsible) {
@@ -1135,7 +1122,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
}
}
super.onPointer(e);
super.onViewPointer(e);
}
protected onDoubleClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
@@ -1238,12 +1225,12 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get onDidChangeFocus(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.focus.onDidChange); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); }
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.view.onDidOpen, asTreeEvent); }
get onDidPin(): Event<ITreeEvent<T>> { return Event.map(this.view.onDidPin, asTreeEvent); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); }
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onTap, asTreeMouseEvent); }
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onPointer, asTreeMouseEvent); }
get onKeyDown(): Event<KeyboardEvent> { return this.view.onKeyDown; }
get onKeyUp(): Event<KeyboardEvent> { return this.view.onKeyUp; }
@@ -1261,7 +1248,6 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get filterOnType(): boolean { return !!this._options.filterOnType; }
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; }
private readonly _onDidUpdateOptions = new Emitter<IAbstractTreeOptions<T, TFilterData>>();
@@ -1328,7 +1314,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
set.add(node);
}
return values(set);
return [...set.values()];
}).event;
if (_options.keyboardSupport !== false) {
@@ -1362,7 +1348,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.view.updateOptions({
enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
automaticKeyboardNavigation: this._options.automaticKeyboardNavigation,
smoothScrolling: this._options.smoothScrolling
smoothScrolling: this._options.smoothScrolling,
horizontalScrolling: this._options.horizontalScrolling
});
if (this.typeFilterController) {
@@ -1601,11 +1588,6 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.focus.get();
}
open(elements: TRef[], browserEvent?: UIEvent): void {
const indexes = elements.map(e => this.model.getListIndex(e)).filter(i => i >= 0);
this.view.open(indexes, browserEvent);
}
reveal(location: TRef, relativeTop?: number): void {
this.model.expandTo(location);

View File

@@ -16,7 +16,6 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { removeClasses, addClasses } from 'vs/base/browser/dom';
import { values } from 'vs/base/common/map';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { IThemable } from 'vs/base/common/styler';
@@ -331,12 +330,13 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidChangeFocus(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); }
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidOpen, asTreeEvent); }
get onKeyDown(): Event<KeyboardEvent> { return this.tree.onKeyDown; }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.tree.onContextMenu, asTreeContextMenuEvent); }
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onTap, asTreeMouseEvent); }
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onPointer, asTreeMouseEvent); }
get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
@@ -345,7 +345,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
get filterOnType(): boolean { return this.tree.filterOnType; }
get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
return this.tree.expandOnlyOnTwistieClick;
@@ -408,6 +407,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.tree.updateOptions(options);
}
get options(): IAsyncDataTreeOptions<T, TFilterData> {
return this.tree.options as unknown as IAsyncDataTreeOptions<T, TFilterData>; // {{SQL CARBON EDIT}} strict-null-check
}
// Widget
getHTMLElement(): HTMLElement {
@@ -668,11 +671,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return nodes.map(n => n!.element as T);
}
open(elements: T[], browserEvent?: UIEvent): void {
const nodes = elements.map(e => this.getDataNode(e));
this.tree.open(nodes, browserEvent);
}
reveal(element: T, relativeTop?: number): void {
this.tree.reveal(this.getDataNode(element), relativeTop);
}
@@ -904,7 +902,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return childAsyncDataTreeNode;
});
for (const node of values(nodesToForget)) {
for (const node of nodesToForget.values()) {
dfs(node, node => this.nodes.delete(node.element as T));
}

View File

@@ -489,12 +489,21 @@ export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string,
export function insert<T>(array: T[], element: T): () => void {
array.push(element);
return () => {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
}
};
return () => remove(array, element);
}
/**
* Removes an element from an array if it can be found.
*/
export function remove<T>(array: T[], element: T): T | undefined {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
return element;
}
return undefined;
}
/**

View File

@@ -194,6 +194,8 @@ export interface VSBufferReadableStream extends streams.ReadableStream<VSBuffer>
export interface VSBufferWriteableStream extends streams.WriteableStream<VSBuffer> { }
export interface VSBufferReadableBufferedStream extends streams.ReadableBufferedStream<VSBuffer> { }
export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
return streams.consumeReadable<VSBuffer>(readable, chunks => VSBuffer.concat(chunks));
}
@@ -206,6 +208,21 @@ export function streamToBuffer(stream: streams.ReadableStream<VSBuffer>): Promis
return streams.consumeStream<VSBuffer>(stream, chunks => VSBuffer.concat(chunks));
}
export async function bufferedStreamToBuffer(bufferedStream: streams.ReadableBufferedStream<VSBuffer>): Promise<VSBuffer> {
if (bufferedStream.ended) {
return VSBuffer.concat(bufferedStream.buffer);
}
return VSBuffer.concat([
// Include already read chunks...
...bufferedStream.buffer,
// ...and all additional chunks
await streamToBuffer(bufferedStream.stream)
]);
}
export function bufferToStream(buffer: VSBuffer): streams.ReadableStream<VSBuffer> {
return streams.toStream<VSBuffer>(buffer, chunks => VSBuffer.concat(chunks));
}
@@ -214,6 +231,6 @@ export function streamToBufferReadableStream(stream: streams.ReadableStreamEvent
return streams.transform<Uint8Array | string, VSBuffer>(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
}
export function newWriteableBufferStream(): streams.WriteableStream<VSBuffer> {
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks));
export function newWriteableBufferStream(options?: streams.WriteableStreamOptions): streams.WriteableStream<VSBuffer> {
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks), options);
}

View File

@@ -495,6 +495,7 @@ export function markdownUnescapeCodicons(text: string): string {
const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi;
export function renderCodicons(text: string): string {
return text.replace(renderCodiconsRegex, (_, escaped, codicon, name, animation) => {
// If the class for codicons is changed, it should also be updated in src\vs\base\browser\markdownRenderer.ts
return escaped
? `$(${codicon})`
: `<span class="codicon codicon-${name}${animation ? ` codicon-animation-${animation}` : ''}"></span>`;

View File

@@ -32,25 +32,6 @@ export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[
return result;
}
export function size<T>(from: IStringDictionary<T> | INumberDictionary<T>): number {
let count = 0;
for (let key in from) {
if (hasOwnProperty.call(from, key)) {
count += 1;
}
}
return count;
}
export function first<T>(from: IStringDictionary<T> | INumberDictionary<T>): T | undefined {
for (const key in from) {
if (hasOwnProperty.call(from, key)) {
return (from as any)[key];
}
}
return undefined;
}
/**
* Iterates over each entry in the provided dictionary. The iterator allows
* to remove elements and will stop when the callback returns {{false}}.

View File

@@ -440,14 +440,14 @@ class LeakageMonitor {
this._warnCountdown = threshold * 0.5;
// find most frequent listener and print warning
let topStack: string;
let topStack: string | undefined;
let topCount: number = 0;
this._stacks.forEach((count, stack) => {
for (const [stack, count] of this._stacks) {
if (!topStack || topCount < count) {
topStack = stack;
topCount = count;
}
});
}
console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
console.warn(topStack!);

View File

@@ -285,7 +285,7 @@ export function isRootOrDriveLetter(path: string): boolean {
return pathNormalized === posix.sep;
}
export function indexOfPath(path: string, candidate: string, ignoreCase: boolean): number {
export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
if (candidate.length > path.length) {
return -1;
}

View File

@@ -182,7 +182,9 @@ function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabe
}
// the actual key
result.push(key);
if (key !== '') {
result.push(key);
}
return result.join(labels.separator);
}

View File

@@ -66,7 +66,7 @@ export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | un
d.dispose();
}
}
return arg;
return Array.isArray(arg) ? [] : arg;
} else if (arg) {
markTracked(arg);
arg.dispose();
@@ -219,11 +219,11 @@ export abstract class ReferenceCollection<T> {
private readonly references: Map<string, { readonly object: T; counter: number; }> = new Map();
acquire(key: string): IReference<T> {
acquire(key: string, ...args: any[]): IReference<T> {
let reference = this.references.get(key);
if (!reference) {
reference = { counter: 0, object: this.createReferencedObject(key) };
reference = { counter: 0, object: this.createReferencedObject(key, ...args) };
this.references.set(key, reference);
}
@@ -240,7 +240,7 @@ export abstract class ReferenceCollection<T> {
return { object, dispose };
}
protected abstract createReferencedObject(key: string): T;
protected abstract createReferencedObject(key: string, ...args: any[]): T;
protected abstract destroyReferencedObject(key: string, object: T): void;
}

View File

@@ -481,14 +481,40 @@ export class TernarySearchTree<K, V> {
}
}
interface ResourceMapKeyFn {
(resource: URI): string;
}
export class ResourceMap<T> implements Map<URI, T> {
private static readonly defaultToKey = (resource: URI) => resource.toString();
readonly [Symbol.toStringTag] = 'ResourceMap';
protected readonly map: Map<string, T>;
private readonly map: Map<string, T>;
private readonly toKey: ResourceMapKeyFn;
constructor(other?: ResourceMap<T>) {
this.map = other ? new Map(other.map) : new Map();
/**
*
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(toKey?: ResourceMapKeyFn);
/**
*
* @param other Another resource which this maps is created from
* @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util
*/
constructor(other?: ResourceMap<T>, toKey?: ResourceMapKeyFn);
constructor(mapOrKeyFn?: ResourceMap<T> | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) {
if (mapOrKeyFn instanceof ResourceMap) {
this.map = new Map(mapOrKeyFn.map);
this.toKey = toKey ?? ResourceMap.defaultToKey;
} else {
this.map = new Map();
this.toKey = mapOrKeyFn ?? ResourceMap.defaultToKey;
}
}
set(resource: URI, value: T): this {
@@ -546,10 +572,6 @@ export class ResourceMap<T> implements Map<URI, T> {
yield [URI.parse(item[0]), item[1]];
}
}
private toKey(resource: URI): string {
return resource.toString();
}
}
interface Item<K, V> {

View File

@@ -56,11 +56,18 @@ export namespace Schemas {
export const vscodeCustomEditor = 'vscode-custom-editor';
export const vscodeNotebook = 'vscode-notebook';
export const vscodeSettings = 'vscode-settings';
export const webviewPanel = 'webview-panel';
export const vscodeWebviewResource = 'vscode-webview-resource';
/**
* Scheme used for extension pages
*/
export const extension = 'extension';
}
class RemoteAuthoritiesImpl {

View File

@@ -5,7 +5,7 @@
export interface PerformanceEntry {
readonly name: string;
readonly timestamp: number;
readonly startTime: number;
}
export function mark(name: string): void;
@@ -15,8 +15,6 @@ export function mark(name: string): void;
*/
export function getEntries(): PerformanceEntry[];
export function getEntry(name: string): PerformanceEntry;
export function getDuration(from: string, to: string): number;
type ExportData = any[];

View File

@@ -28,24 +28,12 @@ function _factory(sharedObj) {
for (let i = 0; i < entries.length; i += _dataLen) {
result.push({
name: entries[i],
timestamp: entries[i + 1],
startTime: entries[i + 1],
});
}
return result;
}
function getEntry(name) {
const entries = sharedObj._performanceEntries;
for (let i = 0; i < entries.length; i += _dataLen) {
if (entries[i] === name) {
return {
name: entries[i],
timestamp: entries[i + 1],
};
}
}
}
function getDuration(from, to) {
const entries = sharedObj._performanceEntries;
let target = to;
@@ -73,7 +61,6 @@ function _factory(sharedObj) {
const exports = {
mark: mark,
getEntries: getEntries,
getEntry: getEntry,
getDuration: getDuration,
importEntries: importEntries,
exportEntries: exportEntries

View File

@@ -7,7 +7,7 @@ import { memoize } from 'vs/base/common/decorators';
import * as paths from 'vs/base/common/path';
import { relativePath, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { PathIterator, values } from 'vs/base/common/map';
import { PathIterator } from 'vs/base/common/map';
export interface IResourceNode<T, C = void> {
readonly uri: URI;
@@ -30,7 +30,7 @@ class Node<T, C> implements IResourceNode<T, C> {
}
get children(): Iterable<Node<T, C>> {
return [...values(this._children)];
return this._children.values();
}
@memoize

View File

@@ -6,9 +6,9 @@
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { URI, uriToFsPath } from 'vs/base/common/uri';
import { equalsIgnoreCase, compare as strCompare, compareIgnoreCase } from 'vs/base/common/strings';
import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { TernarySearchTree } from 'vs/base/common/map';
@@ -138,32 +138,10 @@ export class ExtUri implements IExtUri {
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
// scheme
let ret = strCompare(uri1.scheme, uri2.scheme);
if (ret === 0) {
// authority
ret = compareIgnoreCase(uri1.authority, uri2.authority);
if (ret === 0) {
// path
ret = this._ignorePathCasing(uri1) ? compareIgnoreCase(uri1.path, uri2.path) : strCompare(uri1.path, uri2.path);
// query
if (ret === 0) {
ret = strCompare(uri1.query, uri2.query);
// fragment
if (ret === 0 && !ignoreFragment) {
ret = strCompare(uri1.fragment, uri2.fragment);
}
}
}
if (uri1 === uri2) {
return 0;
}
return ret;
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
}
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
@@ -173,11 +151,14 @@ export class ExtUri implements IExtUri {
if (!uri1 || !uri2) {
return false;
}
if (uri1.scheme !== uri2.scheme || !isEqualAuthority(uri1.authority, uri2.authority)) {
return false;
}
const p1 = uri1.path, p2 = uri2.path;
return (p1 === p2 || this._ignorePathCasing(uri1) && equalsIgnoreCase(p1, p2)) && uri1.query === uri2.query && (ignoreFragment || uri1.fragment === uri2.fragment);
return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
}
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
@@ -332,6 +313,7 @@ export class ExtUri implements IExtUri {
}
}
/**
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
* uri#toString() usages. The following is true
@@ -342,36 +324,52 @@ export class ExtUri implements IExtUri {
export const extUri = new ExtUri(() => false);
/**
* BIASED utility that always ignores the casing of uris path. ONLY use these util if you
* BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you
* understand what you are doing.
*
* Note that `IUriIdentityService#extUri` is a better replacement for this because that utility
* knows when path casing matters and when not.
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
*
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
* because those uris come from a "trustworthy source". When creating unknown uris it's always
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
* casing matters.
*/
export const extUriBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri.scheme === Schemas.file ? !isLinux : true;
});
/**
* BIASED utility that always ignores the casing of uris paths. ONLY use this util if you
* understand what you are doing.
*
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
*
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
* because those uris come from a "trustworthy source". When creating unknown uris it's always
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
* casing matters.
*/
export const extUriIgnorePathCase = new ExtUri(_ => true);
const exturiBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri && uri.scheme === Schemas.file ? !isLinux : true;
});
export const isEqual = exturiBiasedIgnorePathCase.isEqual.bind(exturiBiasedIgnorePathCase);
export const isEqualOrParent = exturiBiasedIgnorePathCase.isEqualOrParent.bind(exturiBiasedIgnorePathCase);
export const getComparisonKey = exturiBiasedIgnorePathCase.getComparisonKey.bind(exturiBiasedIgnorePathCase);
export const basenameOrAuthority = exturiBiasedIgnorePathCase.basenameOrAuthority.bind(exturiBiasedIgnorePathCase);
export const basename = exturiBiasedIgnorePathCase.basename.bind(exturiBiasedIgnorePathCase);
export const extname = exturiBiasedIgnorePathCase.extname.bind(exturiBiasedIgnorePathCase);
export const dirname = exturiBiasedIgnorePathCase.dirname.bind(exturiBiasedIgnorePathCase);
export const isEqual = extUri.isEqual.bind(extUri);
export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);
export const getComparisonKey = extUri.getComparisonKey.bind(extUri);
export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
export const basename = extUri.basename.bind(extUri);
export const extname = extUri.extname.bind(extUri);
export const dirname = extUri.dirname.bind(extUri);
export const joinPath = extUri.joinPath.bind(extUri);
export const normalizePath = exturiBiasedIgnorePathCase.normalizePath.bind(exturiBiasedIgnorePathCase);
export const relativePath = exturiBiasedIgnorePathCase.relativePath.bind(exturiBiasedIgnorePathCase);
export const resolvePath = exturiBiasedIgnorePathCase.resolvePath.bind(exturiBiasedIgnorePathCase);
export const isAbsolutePath = exturiBiasedIgnorePathCase.isAbsolutePath.bind(exturiBiasedIgnorePathCase);
export const isEqualAuthority = exturiBiasedIgnorePathCase.isEqualAuthority.bind(exturiBiasedIgnorePathCase);
export const hasTrailingPathSeparator = exturiBiasedIgnorePathCase.hasTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const removeTrailingPathSeparator = exturiBiasedIgnorePathCase.removeTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const addTrailingPathSeparator = exturiBiasedIgnorePathCase.addTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const normalizePath = extUri.normalizePath.bind(extUri);
export const relativePath = extUri.relativePath.bind(extUri);
export const resolvePath = extUri.resolvePath.bind(extUri);
export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);
export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);
export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);
//#endregion

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
enum Severity {
@@ -20,11 +19,6 @@ namespace Severity {
const _warn = 'warn';
const _info = 'info';
const _displayStrings: { [value: number]: string; } = Object.create(null);
_displayStrings[Severity.Error] = nls.localize('sev.error', "Error");
_displayStrings[Severity.Warning] = nls.localize('sev.warning', "Warning");
_displayStrings[Severity.Info] = nls.localize('sev.info', "Info");
/**
* Parses 'error', 'warning', 'warn', 'info' in call casings
* and falls back to ignore.

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
/**
* The payload that flows in readable stream events.
*/
@@ -49,6 +51,11 @@ export interface ReadableStream<T> extends ReadableStreamEvents<T> {
* Destroys the stream and stops emitting any event.
*/
destroy(): void;
/**
* Allows to remove a listener that was previously added.
*/
removeListener(event: string, callback: Function): void;
}
/**
@@ -74,8 +81,14 @@ export interface WriteableStream<T> extends ReadableStream<T> {
* Writing data to the stream will trigger the on('data')
* event listener if the stream is flowing and buffer the
* data otherwise until the stream is flowing.
*
* If a `highWaterMark` is configured and writing to the
* stream reaches this mark, a promise will be returned
* that should be awaited on before writing more data.
* Otherwise there is a risk of buffering a large number
* of data chunks without consumer.
*/
write(data: T): void;
write(data: T): void | Promise<void>;
/**
* Signals an error to the consumer of the stream via the
@@ -95,12 +108,43 @@ export interface WriteableStream<T> extends ReadableStream<T> {
end(result?: T | Error): void;
}
/**
* A stream that has a buffer already read. Returns the original stream
* that was read as well as the chunks that got read.
*
* The `ended` flag indicates if the stream has been fully consumed.
*/
export interface ReadableBufferedStream<T> {
/**
* The original stream that is being read.
*/
stream: ReadableStream<T>;
/**
* An array of chunks already read from this stream.
*/
buffer: T[];
/**
* Signals if the stream has ended or not. If not, consumers
* should continue to read from the stream until consumed.
*/
ended: boolean;
}
export function isReadableStream<T>(obj: unknown): obj is ReadableStream<T> {
const candidate = obj as ReadableStream<T>;
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
}
export function isReadableBufferedStream<T>(obj: unknown): obj is ReadableBufferedStream<T> {
const candidate = obj as ReadableBufferedStream<T>;
return candidate && isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean';
}
export interface IReducer<T> {
(data: T[]): T;
}
@@ -118,8 +162,18 @@ export interface ITransformer<Original, Transformed> {
error?: IErrorTransformer;
}
export function newWriteableStream<T>(reducer: IReducer<T>): WriteableStream<T> {
return new WriteableStreamImpl<T>(reducer);
export function newWriteableStream<T>(reducer: IReducer<T>, options?: WriteableStreamOptions): WriteableStream<T> {
return new WriteableStreamImpl<T>(reducer, options);
}
export interface WriteableStreamOptions {
/**
* The number of objects to buffer before WriteableStream#write()
* signals back that the buffer is full. Can be used to reduce
* the memory pressure when the stream is not flowing.
*/
highWaterMark?: number;
}
class WriteableStreamImpl<T> implements WriteableStream<T> {
@@ -141,7 +195,9 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
end: [] as { (): void }[]
};
constructor(private reducer: IReducer<T>) { }
private readonly pendingWritePromises: Function[] = [];
constructor(private reducer: IReducer<T>, private options?: WriteableStreamOptions) { }
pause(): void {
if (this.state.destroyed) {
@@ -166,7 +222,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
}
}
write(data: T): void {
write(data: T): void | Promise<void> {
if (this.state.destroyed) {
return;
}
@@ -179,6 +235,11 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
// not yet flowing: buffer data until flowing
else {
this.buffer.data.push(data);
// highWaterMark: if configured, signal back when buffer reached limits
if (typeof this.options?.highWaterMark === 'number' && this.buffer.data.length > this.options.highWaterMark) {
return new Promise(resolve => this.pendingWritePromises.push(resolve));
}
}
}
@@ -267,6 +328,35 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
}
}
removeListener(event: string, callback: Function): void {
if (this.state.destroyed) {
return;
}
let listeners: unknown[] | undefined = undefined;
switch (event) {
case 'data':
listeners = this.listeners.data;
break;
case 'end':
listeners = this.listeners.end;
break;
case 'error':
listeners = this.listeners.error;
break;
}
if (listeners) {
const index = listeners.indexOf(callback);
if (index >= 0) {
listeners.splice(index, 1);
}
}
}
private flowData(): void {
if (this.buffer.data.length > 0) {
const fullDataBuffer = this.reducer(this.buffer.data);
@@ -274,6 +364,11 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
this.listeners.data.forEach(listener => listener(fullDataBuffer));
this.buffer.data.length = 0;
// When the buffer is empty, resolve all pending writers
const pendingWritePromises = [...this.pendingWritePromises];
this.pendingWritePromises.length = 0;
pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise());
}
}
@@ -308,6 +403,8 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
this.listeners.data.length = 0;
this.listeners.error.length = 0;
this.listeners.end.length = 0;
this.pendingWritePromises.length = 0;
}
}
}
@@ -331,7 +428,7 @@ export function consumeReadable<T>(readable: Readable<T>, reducer: IReducer<T>):
* reached, will return a readable instead to ensure all data can still
* be read.
*/
export function consumeReadableWithLimit<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
export function peekReadable<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
const chunks: T[] = [];
let chunk: T | null | undefined = undefined;
@@ -388,58 +485,50 @@ export function consumeStream<T>(stream: ReadableStream<T>, reducer: IReducer<T>
}
/**
* Helper to read a T stream up to a maximum of chunks. If the limit is
* reached, will return a stream instead to ensure all data can still
* be read.
* Helper to peek up to `maxChunks` into a stream. The return type signals if
* the stream has ended or not. If not, caller needs to add a `data` listener
* to continue reading.
*/
export function consumeStreamWithLimit<T>(stream: ReadableStream<T>, reducer: IReducer<T>, maxChunks: number): Promise<T | ReadableStream<T>> {
export function peekStream<T>(stream: ReadableStream<T>, maxChunks: number): Promise<ReadableBufferedStream<T>> {
return new Promise((resolve, reject) => {
const chunks: T[] = [];
const streamListeners = new DisposableStore();
let wrapperStream: WriteableStream<T> | undefined = undefined;
// Data Listener
const buffer: T[] = [];
const dataListener = (chunk: T) => {
stream.on('data', data => {
// Add to buffer
buffer.push(chunk);
// If we reach maxChunks, we start to return a stream
// and make sure that any data we have already read
// is in it as well
if (!wrapperStream && chunks.length === maxChunks) {
wrapperStream = newWriteableStream(reducer);
while (chunks.length) {
wrapperStream.write(chunks.shift()!);
}
// We reached maxChunks and thus need to return
if (buffer.length > maxChunks) {
wrapperStream.write(data);
// Dispose any listeners and ensure to pause the
// stream so that it can be consumed again by caller
streamListeners.dispose();
stream.pause();
return resolve(wrapperStream);
return resolve({ stream, buffer, ended: false });
}
};
if (wrapperStream) {
wrapperStream.write(data);
} else {
chunks.push(data);
}
});
streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener)));
stream.on('data', dataListener);
stream.on('error', error => {
if (wrapperStream) {
wrapperStream.error(error);
} else {
return reject(error);
}
});
// Error Listener
const errorListener = (error: Error) => {
return reject(error);
};
stream.on('end', () => {
if (wrapperStream) {
while (chunks.length) {
wrapperStream.write(chunks.shift()!);
}
streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener)));
stream.on('error', errorListener);
wrapperStream.end();
} else {
return resolve(reducer(chunks));
}
});
const endListener = () => {
return resolve({ stream, buffer, ended: true });
};
streamListeners.add(toDisposable(() => stream.removeListener('end', endListener)));
stream.on('end', endListener);
});
}

View File

@@ -728,9 +728,9 @@ export function isBasicASCII(str: string): boolean {
return IS_BASIC_ASCII.test(str);
}
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029\u0085]/; // LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL)
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029]/; // LINE SEPARATOR (LS) or PARAGRAPH SEPARATOR (PS)
/**
* Returns true if `str` contains unusual line terminators, like LS, PS or NEL
* Returns true if `str` contains unusual line terminators, like LS or PS
*/
export function containsUnusualLineTerminators(str: string): boolean {
return UNUSUAL_LINE_TERMINATORS.test(str);

View File

@@ -64,6 +64,13 @@ export function isUndefined(obj: any): obj is undefined {
return (typeof obj === 'undefined');
}
/**
* @returns whether the provided parameter is defined.
*/
export function isDefined<T>(arg: T | null | undefined): arg is T {
return !isUndefinedOrNull(arg);
}
/**
* @returns whether the provided parameter is undefined or null.
*/

View File

@@ -413,7 +413,7 @@ interface UriState extends UriComponents {
const _pathSepMarker = isWindows ? 1 : undefined;
// eslint-disable-next-line @typescript-eslint/class-name-casing
// eslint-disable-next-line @typescript-eslint/naming-convention
class _URI extends URI {
_formatted: string | null = null;

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as iconv from 'iconv-lite';
import { Readable, Writable } from 'stream';
import { VSBuffer } from 'vs/base/common/buffer';
import { DecoderStream } from 'iconv-lite';
import { Readable, ReadableStream, newWriteableStream } from 'vs/base/common/stream';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
export const UTF8 = 'utf8';
export const UTF8_with_bom = 'utf8bom';
@@ -31,125 +31,156 @@ export interface IDecodeStreamOptions {
guessEncoding: boolean;
minBytesRequiredForDetection?: number;
overwriteEncoding(detectedEncoding: string | null): string;
overwriteEncoding(detectedEncoding: string | null): Promise<string>;
}
export interface IDecodeStreamResult {
stream: NodeJS.ReadableStream;
stream: ReadableStream<string>;
detected: IDetectedEncodingResult;
}
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
if (!options.minBytesRequiredForDetection) {
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
}
export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
return new Promise<IDecodeStreamResult>((resolve, reject) => {
const writer = new class extends Writable {
private decodeStream: NodeJS.ReadWriteStream | undefined;
private decodeStreamPromise: Promise<void> | undefined;
const target = newWriteableStream<string>(strings => strings.join(''));
private bufferedChunks: Buffer[] = [];
private bytesBuffered = 0;
const bufferedChunks: VSBuffer[] = [];
let bytesBuffered = 0;
_write(chunk: Buffer, encoding: string, callback: (error: Error | null | undefined) => void): void {
if (!Buffer.isBuffer(chunk)) {
return callback(new Error('toDecodeStream(): data must be a buffer'));
}
let decoder: DecoderStream | undefined = undefined;
// if the decode stream is ready, we just write directly
if (this.decodeStream) {
this.decodeStream.write(chunk, callback);
return;
}
// otherwise we need to buffer the data until the stream is ready
this.bufferedChunks.push(chunk);
this.bytesBuffered += chunk.byteLength;
// waiting for the decoder to be ready
if (this.decodeStreamPromise) {
this.decodeStreamPromise.then(() => callback(null), error => callback(error));
}
// buffered enough data for encoding detection, create stream and forward data
else if (typeof options.minBytesRequiredForDetection === 'number' && this.bytesBuffered >= options.minBytesRequiredForDetection) {
this._startDecodeStream(callback);
}
// only buffering until enough data for encoding detection is there
else {
callback(null);
}
}
_startDecodeStream(callback: (error: Error | null | undefined) => void): void {
const createDecoder = async () => {
try {
// detect encoding from buffer
this.decodeStreamPromise = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this.bufferedChunks),
bytesRead: this.bytesBuffered
}, options.guessEncoding)).then(detected => {
const detected = await detectEncodingFromBuffer({
buffer: VSBuffer.concat(bufferedChunks),
bytesRead: bytesBuffered
}, options.guessEncoding);
// ensure to respect overwrite of encoding
detected.encoding = options.overwriteEncoding(detected.encoding);
// ensure to respect overwrite of encoding
detected.encoding = await options.overwriteEncoding(detected.encoding);
// decode and write buffer
this.decodeStream = decodeStream(detected.encoding);
this.decodeStream.write(Buffer.concat(this.bufferedChunks), callback);
this.bufferedChunks.length = 0;
// decode and write buffered content
const iconv = await import('iconv-lite');
decoder = iconv.getDecoder(toNodeEncoding(detected.encoding));
const decoded = decoder.write(Buffer.from(VSBuffer.concat(bufferedChunks).buffer));
target.write(decoded);
// signal to the outside our detected encoding
// and final decoder stream
resolve({ detected, stream: this.decodeStream });
}, error => {
this.emit('error', error);
bufferedChunks.length = 0;
bytesBuffered = 0;
callback(error);
// signal to the outside our detected encoding and final decoder stream
resolve({
stream: target,
detected
});
}
_final(callback: () => void) {
// normal finish
if (this.decodeStream) {
this.decodeStream.end(callback);
}
// we were still waiting for data to do the encoding
// detection. thus, wrap up starting the stream even
// without all the data to get things going
else {
this._startDecodeStream(() => {
if (this.decodeStream) {
this.decodeStream.end(callback);
}
});
}
} catch (error) {
reject(error);
}
};
// errors
readable.on('error', reject);
// Stream error: forward to target
source.on('error', error => target.error(error));
// pipe through
readable.pipe(writer);
// Stream data
source.on('data', async chunk => {
// if the decoder is ready, we just write directly
if (decoder) {
target.write(decoder.write(Buffer.from(chunk.buffer)));
}
// otherwise we need to buffer the data until the stream is ready
else {
bufferedChunks.push(chunk);
bytesBuffered += chunk.byteLength;
// buffered enough data for encoding detection, create stream
if (bytesBuffered >= minBytesRequiredForDetection) {
// pause stream here until the decoder is ready
source.pause();
await createDecoder();
// resume stream now that decoder is ready but
// outside of this stack to reduce recursion
setTimeout(() => source.resume());
}
}
});
// Stream end
source.on('end', async () => {
// we were still waiting for data to do the encoding
// detection. thus, wrap up starting the stream even
// without all the data to get things going
if (!decoder) {
await createDecoder();
}
// end the target with the remainders of the decoder
target.end(decoder?.end());
});
});
}
export function encodingExists(encoding: string): boolean {
export async function toEncodeReadable(readable: Readable<string>, encoding: string, options?: { addBOM?: boolean }): Promise<VSBufferReadable> {
const iconv = await import('iconv-lite');
const encoder = iconv.getEncoder(toNodeEncoding(encoding), options);
let bytesRead = 0;
let done = false;
return {
read() {
if (done) {
return null;
}
const chunk = readable.read();
if (typeof chunk !== 'string') {
done = true;
// If we are instructed to add a BOM but we detect that no
// bytes have been read, we must ensure to return the BOM
// ourselves so that we comply with the contract.
if (bytesRead === 0 && options?.addBOM) {
switch (encoding) {
case UTF8:
case UTF8_with_bom:
return VSBuffer.wrap(Uint8Array.from(UTF8_BOM));
case UTF16be:
return VSBuffer.wrap(Uint8Array.from(UTF16be_BOM));
case UTF16le:
return VSBuffer.wrap(Uint8Array.from(UTF16le_BOM));
}
}
const leftovers = encoder.end();
if (leftovers && leftovers.length > 0) {
return VSBuffer.wrap(leftovers);
}
return null;
}
bytesRead += chunk.length;
return VSBuffer.wrap(encoder.write(chunk));
}
};
}
export async function encodingExists(encoding: string): Promise<boolean> {
const iconv = await import('iconv-lite');
return iconv.encodingExists(toNodeEncoding(encoding));
}
function decodeStream(encoding: string | null): NodeJS.ReadWriteStream {
return iconv.decodeStream(toNodeEncoding(encoding));
}
export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream {
return iconv.encodeStream(toNodeEncoding(encoding), options);
}
export function toNodeEncoding(enc: string | null): string {
if (enc === UTF8_with_bom || enc === null) {
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
@@ -158,7 +189,7 @@ export function toNodeEncoding(enc: string | null): string {
return enc;
}
export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
export function detectEncodingByBOMFromBuffer(buffer: VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
if (!buffer || bytesRead < UTF16be_BOM.length) {
return null;
}
@@ -200,10 +231,10 @@ const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32'];
/**
* Guesses the encoding from buffer.
*/
async function guessEncodingByBuffer(buffer: Buffer): Promise<string | null> {
async function guessEncodingByBuffer(buffer: VSBuffer): Promise<string | null> {
const jschardet = await import('jschardet');
const guessed = jschardet.detect(buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES)); // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
const guessed = jschardet.detect(Buffer.from(buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES).buffer)); // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
if (!guessed || !guessed.encoding) {
return null;
}
@@ -271,7 +302,7 @@ export interface IDetectedEncodingResult {
}
export interface IReadResult {
buffer: Buffer | null;
buffer: VSBuffer | null;
bytesRead: number;
}
@@ -298,7 +329,7 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, aut
// that is using 4 bytes to encode a character).
for (let i = 0; i < bytesRead && i < ZERO_BYTE_DETECTION_BUFFER_MAX_LEN; i++) {
const isEndian = (i % 2 === 1); // assume 2-byte sequences typical for UTF-16
const isZeroByte = (buffer.readInt8(i) === 0);
const isZeroByte = (buffer.readUInt8(i) === 0);
if (isZeroByte) {
containsZeroByte = true;

View File

@@ -1,112 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer';
import { Readable } from 'stream';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding';
export function streamToNodeReadable(stream: VSBufferReadableStream): Readable {
return new class extends Readable {
private listening = false;
_read(size?: number): void {
if (!this.listening) {
this.listening = true;
// Data
stream.on('data', data => {
try {
if (!this.push(data.buffer)) {
stream.pause(); // pause the stream if we should not push anymore
}
} catch (error) {
this.emit(error);
}
});
// End
stream.on('end', () => {
try {
this.push(null); // signal EOS
} catch (error) {
this.emit(error);
}
});
// Error
stream.on('error', error => this.emit('error', error));
}
// ensure the stream is flowing
stream.resume();
}
_destroy(error: Error | null, callback: (error: Error | null) => void): void {
stream.destroy();
callback(null);
}
};
}
export function nodeReadableToString(stream: NodeJS.ReadableStream): Promise<string> {
return new Promise((resolve, reject) => {
let result = '';
stream.on('data', chunk => result += chunk);
stream.on('error', reject);
stream.on('end', () => resolve(result));
});
}
export function nodeStreamToVSBufferReadable(stream: NodeJS.ReadWriteStream, addBOM?: { encoding: UTF_ENCODING }): VSBufferReadable {
let bytesRead = 0;
let done = false;
return {
read(): VSBuffer | null {
if (done) {
return null;
}
const res = stream.read();
if (isUndefinedOrNull(res)) {
done = true;
// If we are instructed to add a BOM but we detect that no
// bytes have been read, we must ensure to return the BOM
// ourselves so that we comply with the contract.
if (bytesRead === 0 && addBOM) {
switch (addBOM.encoding) {
case UTF8:
case UTF8_with_bom:
return VSBuffer.wrap(Buffer.from(UTF8_BOM));
case UTF16be:
return VSBuffer.wrap(Buffer.from(UTF16be_BOM));
case UTF16le:
return VSBuffer.wrap(Buffer.from(UTF16le_BOM));
}
}
return null;
}
// Handle String
if (typeof res === 'string') {
bytesRead += res.length;
return VSBuffer.fromString(res);
}
// Handle Buffer
else {
bytesRead += res.byteLength;
return VSBuffer.wrap(res);
}
}
};
}

View File

@@ -719,13 +719,13 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
break;
case KeyCode.Home:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
this.ui.list.focus(QuickInputListFocus.First);
dom.EventHelper.stop(event, true);
}
break;
case KeyCode.End:
if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
this.ui.list.focus(QuickInputListFocus.Last);
dom.EventHelper.stop(event, true);
}

View File

@@ -283,7 +283,6 @@ export class QuickInputList {
const accessibilityProvider = new QuickInputAccessibilityProvider();
this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], {
identityProvider: { getId: element => element.saneLabel },
openController: { shouldOpen: () => false }, // Workaround #58124
setRowLineHeight: false,
multipleSelectionSupport: false,
horizontalScrolling: false,

View File

@@ -342,5 +342,14 @@ suite('Arrays', () => {
arrays.coalesceInPlace(sparse);
assert.equal(sparse.length, 5);
});
test('insert, remove', function () {
const array: string[] = [];
const remove = arrays.insert(array, 'foo');
assert.equal(array[0], 'foo');
remove();
assert.equal(array.length, 0);
});
});

View File

@@ -48,6 +48,21 @@ suite('Lifecycle', () => {
assert(disposable.isDisposed);
assert(disposable2.isDisposed);
});
test('Action bar has broken accessibility #100273', function () {
let array = [{ dispose() { } }, { dispose() { } }];
let array2 = dispose(array);
assert.equal(array.length, 2);
assert.equal(array2.length, 0);
assert.ok(array !== array2);
let set = new Set<IDisposable>([{ dispose() { } }, { dispose() { } }]);
let setValues = set.values();
let setValues2 = dispose(setValues);
assert.ok(setValues === setValues2);
});
});
suite('Reference Collection', () => {

View File

@@ -6,6 +6,7 @@
import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator } from 'vs/base/common/map';
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { extUriIgnorePathCase } from 'vs/base/common/resources';
suite('Map', () => {
@@ -811,32 +812,32 @@ suite('Map', () => {
assert.equal(map.get(uncFile), 'true');
});
// test('ResourceMap - files (ignorecase)', function () {
// const map = new ResourceMap<any>(true);
test('ResourceMap - files (ignorecase)', function () {
const map = new ResourceMap<any>(uri => extUriIgnorePathCase.getComparisonKey(uri));
// const fileA = URI.parse('file://some/filea');
// const fileB = URI.parse('some://some/other/fileb');
// const fileAUpper = URI.parse('file://SOME/FILEA');
const fileA = URI.parse('file://some/filea');
const fileB = URI.parse('some://some/other/fileb');
const fileAUpper = URI.parse('file://SOME/FILEA');
// map.set(fileA, 'true');
// assert.equal(map.get(fileA), 'true');
map.set(fileA, 'true');
assert.equal(map.get(fileA), 'true');
// assert.equal(map.get(fileAUpper), 'true');
assert.equal(map.get(fileAUpper), 'true');
// assert.ok(!map.get(fileB));
assert.ok(!map.get(fileB));
// map.set(fileAUpper, 'false');
// assert.equal(map.get(fileAUpper), 'false');
map.set(fileAUpper, 'false');
assert.equal(map.get(fileAUpper), 'false');
// assert.equal(map.get(fileA), 'false');
assert.equal(map.get(fileA), 'false');
// const windowsFile = URI.file('c:\\test with %25\\c#code');
// const uncFile = URI.file('\\\\shäres\\path\\c#\\plugin.json');
const windowsFile = URI.file('c:\\test with %25\\c#code');
const uncFile = URI.file('\\\\shäres\\path\\c#\\plugin.json');
// map.set(windowsFile, 'true');
// map.set(uncFile, 'true');
map.set(windowsFile, 'true');
map.set(uncFile, 'true');
// assert.equal(map.get(windowsFile), 'true');
// assert.equal(map.get(uncFile), 'true');
// });
assert.equal(map.get(windowsFile), 'true');
assert.equal(map.get(uncFile), 'true');
});
});

View File

@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isReadableStream, newWriteableStream, Readable, consumeReadable, consumeReadableWithLimit, consumeStream, ReadableStream, toStream, toReadable, transform, consumeStreamWithLimit } from 'vs/base/common/stream';
import { isReadableStream, newWriteableStream, Readable, consumeReadable, peekReadable, consumeStream, ReadableStream, toStream, toReadable, transform, peekStream, isReadableBufferedStream } from 'vs/base/common/stream';
import { timeout } from 'vs/base/common/async';
suite('Stream', () => {
@@ -13,7 +14,16 @@ suite('Stream', () => {
assert.ok(isReadableStream(newWriteableStream(d => d)));
});
test('WriteableStream', () => {
test('isReadableBufferedStream', async () => {
assert.ok(!isReadableBufferedStream(Object.create(null)));
const stream = newWriteableStream(d => d);
stream.end();
const bufferedStream = await peekStream(stream, 1);
assert.ok(isReadableBufferedStream(bufferedStream));
});
test('WriteableStream - basics', () => {
const stream = newWriteableStream<string>(strings => strings.join());
let error = false;
@@ -66,17 +76,92 @@ suite('Stream', () => {
assert.equal(chunks.length, 4);
});
test('WriteableStream - removeListener', () => {
const stream = newWriteableStream<string>(strings => strings.join());
let error = false;
const errorListener = (e: Error) => {
error = true;
};
stream.on('error', errorListener);
let data = false;
const dataListener = () => {
data = true;
};
stream.on('data', dataListener);
stream.write('Hello');
assert.equal(data, true);
data = false;
stream.removeListener('data', dataListener);
stream.write('World');
assert.equal(data, false);
stream.error(new Error());
assert.equal(error, true);
error = false;
stream.removeListener('error', errorListener);
stream.error(new Error());
assert.equal(error, false);
});
test('WriteableStream - highWaterMark', async () => {
const stream = newWriteableStream<string>(strings => strings.join(), { highWaterMark: 3 });
let res = stream.write('1');
assert.ok(!res);
res = stream.write('2');
assert.ok(!res);
res = stream.write('3');
assert.ok(!res);
let promise1 = stream.write('4');
assert.ok(promise1 instanceof Promise);
let promise2 = stream.write('5');
assert.ok(promise2 instanceof Promise);
let drained1 = false;
(async () => {
await promise1;
drained1 = true;
})();
let drained2 = false;
(async () => {
await promise2;
drained2 = true;
})();
let data: string | undefined = undefined;
stream.on('data', chunk => {
data = chunk;
});
assert.ok(data);
await timeout(0);
assert.equal(drained1, true);
assert.equal(drained2, true);
});
test('consumeReadable', () => {
const readable = arrayToReadable(['1', '2', '3', '4', '5']);
const consumed = consumeReadable(readable, strings => strings.join());
assert.equal(consumed, '1,2,3,4,5');
});
test('consumeReadableWithLimit', () => {
test('peekReadable', () => {
for (let i = 0; i < 5; i++) {
const readable = arrayToReadable(['1', '2', '3', '4', '5']);
const consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), i);
const consumedOrReadable = peekReadable(readable, strings => strings.join(), i);
if (typeof consumedOrReadable === 'string') {
assert.fail('Unexpected result');
} else {
@@ -86,14 +171,75 @@ suite('Stream', () => {
}
let readable = arrayToReadable(['1', '2', '3', '4', '5']);
let consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 5);
let consumedOrReadable = peekReadable(readable, strings => strings.join(), 5);
assert.equal(consumedOrReadable, '1,2,3,4,5');
readable = arrayToReadable(['1', '2', '3', '4', '5']);
consumedOrReadable = consumeReadableWithLimit(readable, strings => strings.join(), 6);
consumedOrReadable = peekReadable(readable, strings => strings.join(), 6);
assert.equal(consumedOrReadable, '1,2,3,4,5');
});
test('peekReadable - error handling', async () => {
// 0 Chunks
let stream = newWriteableStream(data => data);
let error: Error | undefined = undefined;
let promise = (async () => {
try {
await peekStream(stream, 1);
} catch (err) {
error = err;
}
})();
stream.error(new Error());
await promise;
assert.ok(error);
// 1 Chunk
stream = newWriteableStream(data => data);
error = undefined;
promise = (async () => {
try {
await peekStream(stream, 1);
} catch (err) {
error = err;
}
})();
stream.write('foo');
stream.error(new Error());
await promise;
assert.ok(error);
// 2 Chunks
stream = newWriteableStream(data => data);
error = undefined;
promise = (async () => {
try {
await peekStream(stream, 1);
} catch (err) {
error = err;
}
})();
stream.write('foo');
stream.write('bar');
stream.error(new Error());
await promise;
assert.ok(!error);
stream.on('error', err => error = err);
stream.on('data', chunk => { });
assert.ok(error);
});
function arrayToReadable<T>(array: T[]): Readable<T> {
return {
read: () => array.shift() || null
@@ -122,26 +268,39 @@ suite('Stream', () => {
assert.equal(consumed, '1,2,3,4,5');
});
test('consumeStreamWithLimit', async () => {
test('peekStream', async () => {
for (let i = 0; i < 5; i++) {
const readable = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
const stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
const consumedOrStream = await consumeStreamWithLimit(readable, strings => strings.join(), i);
if (typeof consumedOrStream === 'string') {
assert.fail('Unexpected result');
const result = await peekStream(stream, i);
assert.equal(stream, result.stream);
if (result.ended) {
assert.fail('Unexpected result, stream should not have ended yet');
} else {
const consumed = await consumeStream(consumedOrStream, strings => strings.join());
assert.equal(consumed, '1,2,3,4,5');
assert.equal(result.buffer.length, i + 1, `maxChunks: ${i}`);
const additionalResult: string[] = [];
await consumeStream(stream, strings => {
additionalResult.push(...strings);
return strings.join();
});
assert.equal([...result.buffer, ...additionalResult].join(), '1,2,3,4,5');
}
}
let stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
let consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 5);
assert.equal(consumedOrStream, '1,2,3,4,5');
let result = await peekStream(stream, 5);
assert.equal(stream, result.stream);
assert.equal(result.buffer.join(), '1,2,3,4,5');
assert.equal(result.ended, true);
stream = readableToStream(arrayToReadable(['1', '2', '3', '4', '5']));
consumedOrStream = await consumeStreamWithLimit(stream, strings => strings.join(), 6);
assert.equal(consumedOrStream, '1,2,3,4,5');
result = await peekStream(stream, 6);
assert.equal(stream, result.stream);
assert.equal(result.buffer.join(), '1,2,3,4,5');
assert.equal(result.ended, true);
});
test('toStream', async () => {

View File

@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer';
import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, newWriteableBufferStream, bufferedStreamToBuffer } from 'vs/base/common/buffer';
import { timeout } from 'vs/base/common/async';
import { peekStream } from 'vs/base/common/stream';
suite('Buffer', () => {
@@ -29,6 +30,13 @@ suite('Buffer', () => {
assert.equal((await streamToBuffer(stream)).toString(), content);
});
test('bufferedStreamToBuffer', async () => {
const content = 'Hello World';
const stream = await peekStream(bufferToStream(VSBuffer.fromString(content)), 1);
assert.equal((await bufferedStreamToBuffer(stream)).toString(), content);
});
test('bufferWriteableStream - basics (no error)', async () => {
const stream = newWriteableBufferStream();

View File

@@ -7,9 +7,10 @@ import * as assert from 'assert';
import * as fs from 'fs';
import * as encoding from 'vs/base/node/encoding';
import * as terminalEncoding from 'vs/base/node/terminalEncoding';
import { Readable } from 'stream';
import * as streams from 'vs/base/common/stream';
import * as iconv from 'iconv-lite';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer';
export async function detectEncodingByBOM(file: string): Promise<typeof encoding.UTF16be | typeof encoding.UTF16le | typeof encoding.UTF8_with_bom | null> {
try {
@@ -22,7 +23,7 @@ export async function detectEncodingByBOM(file: string): Promise<typeof encoding
}
interface ReadResult {
buffer: Buffer | null;
buffer: VSBuffer | null;
bytesRead: number;
}
@@ -43,7 +44,7 @@ function readExactlyByFile(file: string, totalBytes: number): Promise<ReadResult
return reject(err); // we want to bubble this error up (file is actually a folder)
}
return resolve({ buffer: resultBuffer, bytesRead });
return resolve({ buffer: resultBuffer ? VSBuffer.wrap(resultBuffer) : null, bytesRead });
});
}
@@ -128,7 +129,7 @@ suite('Encoding', () => {
process.env['VSCODE_CLI_ENCODING'] = 'utf16le';
const enc = await terminalEncoding.resolveTerminalEncoding();
assert.ok(encoding.encodingExists(enc));
assert.ok(await encoding.encodingExists(enc));
assert.equal(enc, 'utf16le');
});
@@ -231,32 +232,33 @@ suite('Encoding', () => {
});
}
async function readAllAsString(stream: NodeJS.ReadableStream) {
return new Promise<string>((resolve, reject) => {
let all = '';
stream.on('data', chunk => {
all += chunk;
assert.equal(typeof chunk, 'string');
function newTestReadableStream(buffers: Buffer[]): VSBufferReadableStream {
const stream = newWriteableBufferStream();
buffers
.map(VSBuffer.wrap)
.forEach(buffer => {
setTimeout(() => {
stream.write(buffer);
});
});
stream.on('end', () => {
resolve(all);
});
stream.on('error', reject);
setTimeout(() => {
stream.end();
});
return stream;
}
async function readAllAsString(stream: streams.ReadableStream<string>) {
return streams.consumeStream(stream, strings => strings.join(''));
}
test('toDecodeStream - some stream', async function () {
const source = newTestReadableStream([
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
]);
let source = new Readable({
read(size) {
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(null);
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -266,17 +268,13 @@ suite('Encoding', () => {
});
test('toDecodeStream - some stream, expect too much data', async function () {
const source = newTestReadableStream([
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
Buffer.from([65, 66, 67]),
]);
let source = new Readable({
read(size) {
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(Buffer.from([65, 66, 67]));
this.push(null);
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -286,14 +284,10 @@ suite('Encoding', () => {
});
test('toDecodeStream - some stream, no data', async function () {
const source = newWriteableBufferStream();
source.end();
let source = new Readable({
read(size) {
this.push(null); // empty
}
});
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.ok(detected);
assert.ok(stream);
@@ -304,29 +298,105 @@ suite('Encoding', () => {
test('toDecodeStream - encoding, utf16be', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
const source = streamToBufferReadableStream(fs.createReadStream(path));
let path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
let source = fs.createReadStream(path);
let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
assert.equal(detected.encoding, 'utf16be');
assert.equal(detected.seemsBinary, false);
let expected = await readAndDecodeFromDisk(path, detected.encoding);
let actual = await readAllAsString(stream);
const expected = await readAndDecodeFromDisk(path, detected.encoding);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toDecodeStream - empty file', async function () {
const path = getPathFromAmdModule(require, './fixtures/empty.txt');
const source = streamToBufferReadableStream(fs.createReadStream(path));
const { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
let path = getPathFromAmdModule(require, './fixtures/empty.txt');
let source = fs.createReadStream(path);
let { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 });
let expected = await readAndDecodeFromDisk(path, detected.encoding);
let actual = await readAllAsString(stream);
const expected = await readAndDecodeFromDisk(path, detected.encoding);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toDecodeStream - decodes buffer entirely', async function () {
const emojis = Buffer.from('🖥️💻💾');
const incompleteEmojis = emojis.slice(0, emojis.length - 1);
const buffers: Buffer[] = [];
for (let i = 0; i < incompleteEmojis.length; i++) {
buffers.push(incompleteEmojis.slice(i, i + 1));
}
const source = newTestReadableStream(buffers);
const { stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
const expected = incompleteEmojis.toString(encoding.UTF8);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toEncodeReadable - encoding, utf16be', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
const source = await readAndDecodeFromDisk(path, encoding.UTF16be);
const expected = VSBuffer.wrap(
iconv.encode(source, encoding.toNodeEncoding(encoding.UTF16be))
).toString();
const actual = streams.consumeReadable(
await encoding.toEncodeReadable(streams.toReadable(source), encoding.UTF16be),
VSBuffer.concat
).toString();
assert.equal(actual, expected);
});
test('toEncodeReadable - empty readable to utf8', async function () {
const source: streams.Readable<string> = {
read() {
return null;
}
};
const actual = streams.consumeReadable(
await encoding.toEncodeReadable(source, encoding.UTF8),
VSBuffer.concat
).toString();
assert.equal(actual, '');
});
[{
utfEncoding: encoding.UTF8,
relatedBom: encoding.UTF8_BOM
}, {
utfEncoding: encoding.UTF8_with_bom,
relatedBom: encoding.UTF8_BOM
}, {
utfEncoding: encoding.UTF16be,
relatedBom: encoding.UTF16be_BOM,
}, {
utfEncoding: encoding.UTF16le,
relatedBom: encoding.UTF16le_BOM
}].forEach(({ utfEncoding, relatedBom }) => {
test(`toEncodeReadable - empty readable to ${utfEncoding} with BOM`, async function () {
const source: streams.Readable<string> = {
read() {
return null;
}
};
const encodedReadable = encoding.toEncodeReadable(source, utfEncoding, { addBOM: true });
const expected = VSBuffer.wrap(Buffer.from(relatedBom)).toString();
const actual = streams.consumeReadable(await encodedReadable, VSBuffer.concat).toString();
assert.equal(actual, expected);
});
});
});

View File

@@ -164,8 +164,7 @@ suite('Paths (Node Implementation)', () => {
os = 'posix';
}
const message =
`path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
`path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected && actualAlt !== expected) {
failures.push(`\n${message}`);
}
@@ -319,8 +318,7 @@ suite('Paths (Node Implementation)', () => {
os = 'posix';
}
const actual = extname(input);
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) {
failures.push(`\n${message}`);
}
@@ -328,8 +326,7 @@ suite('Paths (Node Implementation)', () => {
{
const input = `C:${test[0].replace(slashRE, '\\')}`;
const actual = path.win32.extname(input);
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) {
failures.push(`\n${message}`);
}
@@ -416,8 +413,7 @@ suite('Paths (Node Implementation)', () => {
const expected = test[1];
const message =
`path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
`path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected && actualAlt !== expected) {
failures.push(`\n${message}`);
}
@@ -585,9 +581,7 @@ suite('Paths (Node Implementation)', () => {
const actual = relative(test[0], test[1]);
const expected = test[2];
const os = relative === path.win32.relative ? 'win32' : 'posix';
const message = `path.${os}.relative(${
test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${
JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
const message = `path.${os}.relative(${test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
if (actual !== expected) {
failures.push(`\n${message}`);
}