Archived
Merge from vscode 4d91d96e5e121b38d33508cdef17868bab255eae
This commit is contained in:
committed by
AzureDataStudio
parent
a971aee5bd
commit
5e7071e466
@@ -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'],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>`;
|
||||
|
||||
@@ -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}}.
|
||||
|
||||
@@ -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!);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Vendored
+1
-3
@@ -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[];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+135
-46
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
+130
-99
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
font-family: "Segoe UI", "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif;
|
||||
background-color: #373277;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ElectronService, IElectronService } from 'vs/platform/electron/electron
|
||||
import { ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import * as os from 'os';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { $, windowOpenNoOpener } from 'vs/base/browser/dom';
|
||||
import { $, windowOpenNoOpener, addClass } from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
|
||||
@@ -55,6 +55,9 @@ export interface IssueReporterConfiguration extends INativeWindowConfiguration {
|
||||
}
|
||||
|
||||
export function startup(configuration: IssueReporterConfiguration) {
|
||||
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
|
||||
addClass(document.body, platformClass); // used by our fonts
|
||||
|
||||
document.body.innerHTML = BaseHtml();
|
||||
const issueReporter = new IssueReporter(configuration);
|
||||
issueReporter.render();
|
||||
|
||||
@@ -95,26 +95,30 @@ textarea, input, select {
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
color: #CCCCCC;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html:lang(zh-Hans) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
}
|
||||
/* Font Families (with CJK support) */
|
||||
|
||||
html:lang(zh-Hant) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
}
|
||||
.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
||||
.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
|
||||
.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
|
||||
.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
|
||||
.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
|
||||
|
||||
html:lang(ja) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
}
|
||||
.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
|
||||
.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
|
||||
.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
|
||||
.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
|
||||
.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
|
||||
|
||||
html:lang(ko) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
}
|
||||
/* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */
|
||||
.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
|
||||
.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -4,25 +4,29 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
html {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
html:lang(zh-Hans) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
}
|
||||
/* Font Families (with CJK support) */
|
||||
|
||||
html:lang(zh-Hant) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
}
|
||||
.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
||||
.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; }
|
||||
.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; }
|
||||
.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; }
|
||||
.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; }
|
||||
|
||||
html:lang(ja) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
}
|
||||
.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; }
|
||||
.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; }
|
||||
.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; }
|
||||
.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
|
||||
.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
|
||||
|
||||
html:lang(ko) {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
}
|
||||
/* Linux: add `system-ui` as first font and not `Ubuntu` to allow other distribution pick their standard OS font */
|
||||
.linux { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
|
||||
.linux:lang(zh-Hans) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(zh-Hant) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ja) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
|
||||
.linux:lang(ko) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as platform from 'vs/base/common/platform';
|
||||
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
|
||||
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
|
||||
import { ProcessItem } from 'vs/base/common/processes';
|
||||
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { addDisposableListener, addClass } from 'vs/base/browser/dom';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
|
||||
@@ -389,6 +389,9 @@ function createCloseListener(): void {
|
||||
}
|
||||
|
||||
export function startup(data: ProcessExplorerData): void {
|
||||
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
|
||||
addClass(document.body, platformClass); // used by our fonts
|
||||
|
||||
applyStyles(data.styles);
|
||||
applyZoom(data.zoomLevel);
|
||||
createCloseListener();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 10pt;
|
||||
background-color: #F3F3F3;
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -49,10 +49,10 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
|
||||
@@ -63,9 +63,8 @@ import { NativeStorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService';
|
||||
import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/electron-browser/authenticationIpc';
|
||||
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
||||
import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
|
||||
@@ -95,7 +94,7 @@ class MainProcessService implements IMainProcessService {
|
||||
private mainRouter: StaticRouter
|
||||
) { }
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
getChannel(channelName: string): IChannel {
|
||||
return this.server.getChannel(channelName, this.mainRouter);
|
||||
@@ -199,14 +198,14 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService));
|
||||
|
||||
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
|
||||
services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService));
|
||||
services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService));
|
||||
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
|
||||
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
|
||||
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
|
||||
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
|
||||
services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService));
|
||||
services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService));
|
||||
services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService));
|
||||
services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService));
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
registerConfiguration();
|
||||
|
||||
@@ -234,9 +233,9 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
|
||||
server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
|
||||
|
||||
const authTokenService = accessor.get(IAuthenticationTokenService);
|
||||
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
|
||||
server.registerChannel('authToken', authTokenChannel);
|
||||
const authTokenService = accessor.get(IUserDataSyncAccountService);
|
||||
const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService);
|
||||
server.registerChannel('userDataSyncAccount', authTokenChannel);
|
||||
|
||||
const userDataSyncService = accessor.get(IUserDataSyncService);
|
||||
const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService, logService);
|
||||
|
||||
@@ -6,7 +6,29 @@
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
const perf = require('../../../base/common/performance');
|
||||
const perf = (function () {
|
||||
let sharedObj;
|
||||
if (typeof global === 'object') {
|
||||
// nodejs
|
||||
sharedObj = global;
|
||||
} else if (typeof self === 'object') {
|
||||
// browser
|
||||
sharedObj = self;
|
||||
} else {
|
||||
sharedObj = {};
|
||||
}
|
||||
// @ts-ignore
|
||||
sharedObj._performanceEntries = sharedObj._performanceEntries || [];
|
||||
return {
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
mark(name) {
|
||||
sharedObj._performanceEntries.push(name, Date.now());
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
perf.mark('renderer/started');
|
||||
|
||||
const bootstrapWindow = require('../../../../bootstrap-window');
|
||||
@@ -150,7 +172,7 @@ function showPartsSplash(configuration) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function getLazyEnv() {
|
||||
// @ts-ignore
|
||||
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow, dialog, session } from 'electron';
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, IpcMainEvent, BrowserWindow, dialog, session } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
@@ -26,7 +26,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc';
|
||||
import { ITelemetryService, machineIdKey, trueMachineIdKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
@@ -261,13 +261,6 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('vscode:exit', (event: Event, code: number) => {
|
||||
this.logService.trace('IPC#vscode:exit', code);
|
||||
|
||||
this.dispose();
|
||||
this.lifecycleMainService.kill(code);
|
||||
});
|
||||
|
||||
ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => {
|
||||
const webContents = event.sender;
|
||||
|
||||
@@ -294,13 +287,6 @@ export class CodeApplication extends Disposable {
|
||||
(async () => {
|
||||
await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
|
||||
|
||||
// After waking up from sleep (after window opened)
|
||||
powerMonitor.on('resume', () => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:osResume', undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard layout changes (after window opened)
|
||||
const nativeKeymap = await import('native-keymap');
|
||||
nativeKeymap.onDidChangeKeyboardLayout(() => {
|
||||
@@ -365,8 +351,8 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Resolve unique machine ID
|
||||
this.logService.trace('Resolving machine identifier...');
|
||||
const { machineId, trueMachineId } = await this.resolveMachineId();
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);
|
||||
const machineId = await this.resolveMachineId();
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process after the first window has opened and 3s have passed
|
||||
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
|
||||
@@ -387,7 +373,7 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
|
||||
// Services
|
||||
const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessReady);
|
||||
const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady);
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
@@ -412,32 +398,21 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveMachineId(): Promise<{ machineId: string, trueMachineId?: string }> {
|
||||
private async resolveMachineId(): Promise<string> {
|
||||
|
||||
// We cache the machineId for faster lookups on startup
|
||||
// and resolve it only once initially if not cached
|
||||
// and resolve it only once initially if not cached or we need to replace the macOS iBridge device
|
||||
let machineId = this.stateService.getItem<string>(machineIdKey);
|
||||
if (!machineId) {
|
||||
if (!machineId || (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead')) {
|
||||
machineId = await getMachineId();
|
||||
|
||||
this.stateService.setItem(machineIdKey, machineId);
|
||||
}
|
||||
|
||||
// Check if machineId is hashed iBridge Device
|
||||
let trueMachineId: string | undefined;
|
||||
if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') {
|
||||
trueMachineId = this.stateService.getItem<string>(trueMachineIdKey);
|
||||
if (!trueMachineId) {
|
||||
trueMachineId = await getMachineId();
|
||||
|
||||
this.stateService.setItem(trueMachineIdKey, trueMachineId);
|
||||
}
|
||||
}
|
||||
|
||||
return { machineId, trueMachineId };
|
||||
return machineId;
|
||||
}
|
||||
|
||||
private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
|
||||
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<Client<string>>): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
switch (process.platform) {
|
||||
@@ -489,7 +464,7 @@ export class CodeApplication extends Disposable {
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
|
||||
const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId, sendErrorTelemetry: true };
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ type Credentials = {
|
||||
|
||||
export class ProxyAuthHandler extends Disposable {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private retryCount = 0;
|
||||
|
||||
@@ -60,7 +60,8 @@ export class ProxyAuthHandler extends Disposable {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true,
|
||||
enableWebSQL: false
|
||||
enableWebSQL: false,
|
||||
nativeWindowOpen: true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
|
||||
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
class ExpectedError extends Error {
|
||||
readonly isExpected = true;
|
||||
@@ -162,6 +165,8 @@ class CodeMain {
|
||||
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
|
||||
services.set(ISignService, new SyncDescriptor(SignService));
|
||||
services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService));
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
services.set(ITunnelService, new SyncDescriptor(TunnelService));
|
||||
|
||||
return [new InstantiationService(services, true), instanceEnvironment, environmentService];
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
nodeIntegration: true,
|
||||
webgl: false,
|
||||
enableWebSQL: false,
|
||||
nativeWindowOpen: true,
|
||||
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron';
|
||||
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event } from 'electron';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -91,15 +91,18 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
|
||||
|
||||
private readonly _onLoad = this._register(new Emitter<void>());
|
||||
readonly onLoad = this._onLoad.event;
|
||||
|
||||
private readonly _onReady = this._register(new Emitter<void>());
|
||||
readonly onReady = this._onReady.event;
|
||||
|
||||
private readonly _onClose = this._register(new Emitter<void>());
|
||||
readonly onClose = this._onClose.event;
|
||||
|
||||
private readonly _onDestroy = this._register(new Emitter<void>());
|
||||
readonly onDestroy = this._onDestroy.event;
|
||||
|
||||
private readonly _onLoad = this._register(new Emitter<void>());
|
||||
readonly onLoad = this._onLoad.event;
|
||||
|
||||
private hiddenTitleBarStyle: boolean | undefined;
|
||||
private showTimeoutHandle: NodeJS.Timeout | undefined;
|
||||
private _lastFocusTime: number;
|
||||
@@ -167,7 +170,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER,
|
||||
webviewTag: true,
|
||||
enableWebSQL: false
|
||||
enableWebSQL: false,
|
||||
nativeWindowOpen: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -346,6 +350,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
while (this.whenReadyCallbacks.length) {
|
||||
this.whenReadyCallbacks.pop()!(this);
|
||||
}
|
||||
|
||||
// Events
|
||||
this._onReady.fire();
|
||||
}
|
||||
|
||||
ready(): Promise<ICodeWindow> {
|
||||
@@ -431,24 +438,34 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._lastFocusTime = Date.now();
|
||||
});
|
||||
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
|
||||
// we need to detect when display metrics change or displays are added/removed and toggle the
|
||||
// fullscreen manually.
|
||||
if (isMacintosh) {
|
||||
const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
|
||||
const displayChangedScheduler = this._register(new RunOnceScheduler(() => {
|
||||
if (!this._win) {
|
||||
return; // disposed
|
||||
}
|
||||
|
||||
// Notify renderers about displays changed
|
||||
this.sendWhenReady('vscode:displayChanged');
|
||||
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
|
||||
// we need to detect when display metrics change or displays are added/removed and toggle the
|
||||
// fullscreen manually.
|
||||
if (!this.useNativeFullScreen() && this.isFullScreen) {
|
||||
this.setFullScreen(false);
|
||||
this.setFullScreen(true);
|
||||
}
|
||||
|
||||
this.sendWhenReady('vscode:displayChanged');
|
||||
}, 100));
|
||||
|
||||
const displayChangedListener = () => simpleFullScreenScheduler.schedule();
|
||||
const displayChangedListener = (event: Event, display: Display, changedMetrics?: string[]) => {
|
||||
if (Array.isArray(changedMetrics) && changedMetrics.length === 1 && changedMetrics[0] === 'workArea') {
|
||||
// Electron will emit 'display-metrics-changed' events even when actually
|
||||
// going fullscreen, because the dock hides. However, we do not want to
|
||||
// react on this event as there is no change in display bounds.
|
||||
return;
|
||||
}
|
||||
|
||||
displayChangedScheduler.schedule();
|
||||
};
|
||||
|
||||
screen.on('display-metrics-changed', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));
|
||||
|
||||
@@ -86,7 +86,7 @@ export class Main {
|
||||
} else if (argv['list-extensions']) {
|
||||
await this.listExtensions(!!argv['show-versions'], argv['category']);
|
||||
} else if (argv['install-extension']) {
|
||||
await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['donot-sync']);
|
||||
await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['do-not-sync']);
|
||||
} else if (argv['uninstall-extension']) {
|
||||
await this.uninstallExtension(argv['uninstall-extension']);
|
||||
} else if (argv['locate-extension']) {
|
||||
@@ -126,7 +126,7 @@ export class Main {
|
||||
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
|
||||
}
|
||||
|
||||
private async installExtensions(extensions: string[], force: boolean, donotSync: boolean): Promise<void> {
|
||||
private async installExtensions(extensions: string[], force: boolean, doNotSync: boolean): Promise<void> {
|
||||
const failed: string[] = [];
|
||||
const installedExtensionsManifests: IExtensionManifest[] = [];
|
||||
if (extensions.length) {
|
||||
@@ -135,7 +135,7 @@ export class Main {
|
||||
|
||||
for (const extension of extensions) {
|
||||
try {
|
||||
const manifest = await this.installExtension(extension, force, donotSync);
|
||||
const manifest = await this.installExtension(extension, force, doNotSync);
|
||||
if (manifest) {
|
||||
installedExtensionsManifests.push(manifest);
|
||||
}
|
||||
@@ -150,7 +150,7 @@ export class Main {
|
||||
return failed.length ? Promise.reject(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))) : Promise.resolve();
|
||||
}
|
||||
|
||||
private async installExtension(extension: string, force: boolean, donotSync: boolean): Promise<IExtensionManifest | null> {
|
||||
private async installExtension(extension: string, force: boolean, doNotSync: boolean): Promise<IExtensionManifest | null> {
|
||||
if (/\.vsix$/i.test(extension)) {
|
||||
extension = path.isAbsolute(extension) ? extension : path.join(process.cwd(), extension);
|
||||
|
||||
@@ -158,7 +158,7 @@ export class Main {
|
||||
const valid = await this.validate(manifest, force);
|
||||
|
||||
if (valid) {
|
||||
return this.extensionManagementService.install(URI.file(extension), donotSync).then(id => {
|
||||
return this.extensionManagementService.install(URI.file(extension), doNotSync).then(id => {
|
||||
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(extension)));
|
||||
return manifest;
|
||||
}, error => {
|
||||
@@ -205,7 +205,7 @@ export class Main {
|
||||
}
|
||||
console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, extension.version));
|
||||
}
|
||||
await this.installFromGallery(id, extension, donotSync);
|
||||
await this.installFromGallery(id, extension, doNotSync);
|
||||
return manifest;
|
||||
}));
|
||||
}
|
||||
@@ -227,11 +227,11 @@ export class Main {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async installFromGallery(id: string, extension: IGalleryExtension, donotSync: boolean): Promise<void> {
|
||||
private async installFromGallery(id: string, extension: IGalleryExtension, doNotSync: boolean): Promise<void> {
|
||||
console.log(localize('installing', "Installing extension '{0}' v{1}...", id, extension.version));
|
||||
|
||||
try {
|
||||
await this.extensionManagementService.installFromGallery(extension, donotSync);
|
||||
await this.extensionManagementService.installFromGallery(extension, doNotSync);
|
||||
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, extension.version));
|
||||
} catch (error) {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
|
||||
@@ -958,7 +958,7 @@ export namespace CoreNavigationCommands {
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.moveToEndOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
|
||||
CursorMoveCommands.moveToEndOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode, args.sticky || false)
|
||||
);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
@@ -969,10 +969,27 @@ export namespace CoreNavigationCommands {
|
||||
id: 'cursorEnd',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
args: { sticky: false },
|
||||
weight: CORE_WEIGHT,
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyCode.End,
|
||||
mac: { primary: KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow] }
|
||||
},
|
||||
description: {
|
||||
description: `Go to End`,
|
||||
args: [{
|
||||
name: 'args',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'sticky': {
|
||||
description: nls.localize('stickydesc', "Stick to the end even when going to longer lines"),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -981,10 +998,27 @@ export namespace CoreNavigationCommands {
|
||||
id: 'cursorEndSelect',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
args: { sticky: false },
|
||||
weight: CORE_WEIGHT,
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyMod.Shift | KeyCode.End,
|
||||
mac: { primary: KeyMod.Shift | KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow] }
|
||||
},
|
||||
description: {
|
||||
description: `Select to End`,
|
||||
args: [{
|
||||
name: 'args',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'sticky': {
|
||||
description: nls.localize('stickydesc', "Stick to the end even when going to longer lines"),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ export class TextAreaInput extends Disposable {
|
||||
}
|
||||
|
||||
private _installSelectionChangeListener(): IDisposable {
|
||||
// See https://github.com/Microsoft/vscode/issues/27216
|
||||
// See https://github.com/Microsoft/vscode/issues/27216 and https://github.com/microsoft/vscode/issues/98256
|
||||
// When using a Braille display, it is possible for users to reposition the
|
||||
// system caret. This is reflected in Chrome as a `selectionchange` event.
|
||||
//
|
||||
@@ -406,8 +406,8 @@ export class TextAreaInput extends Disposable {
|
||||
if (this._isDoingComposition) {
|
||||
return;
|
||||
}
|
||||
if (!browser.isChrome || !platform.isWindows) {
|
||||
// Support only for Chrome on Windows until testing happens on other browsers + OS configurations
|
||||
if (!browser.isChrome) {
|
||||
// Support only for Chrome until testing happens on other browsers
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
const IEditorCancellationTokens = createDecorator<IEditorCancellationTokens>('IEditorCancelService');
|
||||
|
||||
interface IEditorCancellationTokens {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
add(editor: ICodeEditor, cts: CancellationTokenSource): () => void;
|
||||
cancel(editor: ICodeEditor): void;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const ctxCancellableOperation = new RawContextKey('cancellableOperation', false)
|
||||
|
||||
registerSingleton(IEditorCancellationTokens, class implements IEditorCancellationTokens {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _tokens = new WeakMap<ICodeEditor, { key: IContextKey<boolean>, tokens: LinkedList<CancellationTokenSource> }>();
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ export interface IDiffEditorContributionDescription {
|
||||
export interface ICommandKeybindingsOptions extends IKeybindings {
|
||||
kbExpr?: ContextKeyExpression | null;
|
||||
weight: number;
|
||||
/**
|
||||
* the default keybinding arguments
|
||||
*/
|
||||
args?: any;
|
||||
}
|
||||
export interface ICommandMenuOptions {
|
||||
menuId: MenuId;
|
||||
@@ -96,6 +100,7 @@ export abstract class Command {
|
||||
id: this.id,
|
||||
handler: (accessor, args) => this.runCommand(accessor, args),
|
||||
weight: this._kbOpts.weight,
|
||||
args: this._kbOpts.args,
|
||||
when: kbWhen,
|
||||
primary: this._kbOpts.primary,
|
||||
secondary: this._kbOpts.secondary,
|
||||
|
||||
@@ -10,10 +10,11 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
|
||||
import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
|
||||
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export abstract class AbstractCodeEditorService extends Disposable implements ICodeEditorService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onCodeEditorAdd: Emitter<ICodeEditor> = this._register(new Emitter<ICodeEditor>());
|
||||
public readonly onCodeEditorAdd: Event<ICodeEditor> = this._onCodeEditorAdd.event;
|
||||
@@ -94,6 +95,29 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
|
||||
abstract resolveDecorationOptions(decorationTypeKey: string | undefined, writable: boolean): IModelDecorationOptions;
|
||||
|
||||
private readonly _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {};
|
||||
private readonly _modelProperties = new Map<string, Map<string, any>>();
|
||||
|
||||
public setModelProperty(resource: URI, key: string, value: any): void {
|
||||
const key1 = resource.toString();
|
||||
let dest: Map<string, any>;
|
||||
if (this._modelProperties.has(key1)) {
|
||||
dest = this._modelProperties.get(key1)!;
|
||||
} else {
|
||||
dest = new Map<string, any>();
|
||||
this._modelProperties.set(key1, dest);
|
||||
}
|
||||
|
||||
dest.set(key, value);
|
||||
}
|
||||
|
||||
public getModelProperty(resource: URI, key: string): any {
|
||||
const key1 = resource.toString();
|
||||
if (this._modelProperties.has(key1)) {
|
||||
const innerMap = this._modelProperties.get(key1)!;
|
||||
return innerMap.get(key);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public setTransientModelProperty(model: ITextModel, key: string, value: any): void {
|
||||
const uri = model.uri.toString();
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface IBulkEditResult {
|
||||
export type IBulkEditPreviewHandler = (edit: WorkspaceEdit, options?: IBulkEditOptions) => Promise<WorkspaceEdit>;
|
||||
|
||||
export interface IBulkEditService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
hasPreviewHandler(): boolean;
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
|
||||
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');
|
||||
|
||||
export interface ICodeEditorService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly onCodeEditorAdd: Event<ICodeEditor>;
|
||||
readonly onCodeEditorRemove: Event<ICodeEditor>;
|
||||
@@ -41,6 +42,9 @@ export interface ICodeEditorService {
|
||||
removeDecorationType(key: string): void;
|
||||
resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions;
|
||||
|
||||
setModelProperty(resource: URI, key: string, value: any): void;
|
||||
getModelProperty(resource: URI, key: string): any;
|
||||
|
||||
setTransientModelProperty(model: ITextModel, key: string, value: any): void;
|
||||
getTransientModelProperty(model: ITextModel, key: string): any;
|
||||
getTransientModelProperties(model: ITextModel): [string, any][] | undefined;
|
||||
|
||||
@@ -85,7 +85,7 @@ class EditorOpener implements IOpener {
|
||||
|
||||
export class OpenerService implements IOpenerService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _openers = new LinkedList<IOpener>();
|
||||
private readonly _validators = new LinkedList<IValidator>();
|
||||
|
||||
@@ -752,7 +752,7 @@ export class DiffReview extends Disposable {
|
||||
switch (type) {
|
||||
case DiffEntryType.Equal:
|
||||
if (originalLine === modifiedLine) {
|
||||
ariaLabel = nls.localize('unchangedLine', "{0} unchanged line {1}", lineContent, originalLine);
|
||||
ariaLabel = nls.localize({ key: 'unchangedLine', comment: ['The placholders are contents of the line and should not be translated.'] }, "{0} unchanged line {1}", lineContent, originalLine);
|
||||
} else {
|
||||
ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine);
|
||||
}
|
||||
|
||||
@@ -94,10 +94,10 @@ export interface IEditorOptions {
|
||||
*/
|
||||
renderFinalNewline?: boolean;
|
||||
/**
|
||||
* Remove unusual line terminators like LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL).
|
||||
* Defaults to true.
|
||||
* Remove unusual line terminators like LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS).
|
||||
* Defaults to 'prompt'.
|
||||
*/
|
||||
removeUnusualLineTerminators?: boolean;
|
||||
unusualLineTerminators?: 'off' | 'prompt' | 'auto';
|
||||
/**
|
||||
* Should the corresponding line be selected when clicking on the line number?
|
||||
* Defaults to true.
|
||||
@@ -3560,7 +3560,6 @@ export const enum EditorOption {
|
||||
quickSuggestions,
|
||||
quickSuggestionsDelay,
|
||||
readOnly,
|
||||
removeUnusualLineTerminators,
|
||||
renameOnType,
|
||||
renderControlCharacters,
|
||||
renderIndentGuides,
|
||||
@@ -3590,6 +3589,7 @@ export const enum EditorOption {
|
||||
suggestOnTriggerCharacters,
|
||||
suggestSelection,
|
||||
tabCompletion,
|
||||
unusualLineTerminators,
|
||||
useTabStops,
|
||||
wordSeparators,
|
||||
wordWrap,
|
||||
@@ -3979,10 +3979,6 @@ export const EditorOptions = {
|
||||
readOnly: register(new EditorBooleanOption(
|
||||
EditorOption.readOnly, 'readOnly', false,
|
||||
)),
|
||||
removeUnusualLineTerminators: register(new EditorBooleanOption(
|
||||
EditorOption.removeUnusualLineTerminators, 'removeUnusualLineTerminators', true,
|
||||
{ description: nls.localize('removeUnusualLineTerminators', "Remove unusual line terminators that might cause problems.") }
|
||||
)),
|
||||
renameOnType: register(new EditorBooleanOption(
|
||||
EditorOption.renameOnType, 'renameOnType', false,
|
||||
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") }
|
||||
@@ -4152,6 +4148,19 @@ export const EditorOptions = {
|
||||
description: nls.localize('tabCompletion', "Enables tab completions.")
|
||||
}
|
||||
)),
|
||||
unusualLineTerminators: register(new EditorStringEnumOption(
|
||||
EditorOption.unusualLineTerminators, 'unusualLineTerminators',
|
||||
'prompt' as 'off' | 'prompt' | 'auto',
|
||||
['off', 'prompt', 'auto'] as const,
|
||||
{
|
||||
enumDescriptions: [
|
||||
nls.localize('unusualLineTerminators.off', "Unusual line terminators are ignored."),
|
||||
nls.localize('unusualLineTerminators.prompt', "Unusual line terminators prompt to be removed."),
|
||||
nls.localize('unusualLineTerminators.auto', "Unusual line terminators are automatically removed."),
|
||||
],
|
||||
description: nls.localize('unusualLineTerminators', "Remove unusual line terminators that might cause problems.")
|
||||
}
|
||||
)),
|
||||
useTabStops: register(new EditorBooleanOption(
|
||||
EditorOption.useTabStops, 'useTabStops', true,
|
||||
{ description: nls.localize('useTabStops', "Inserting and deleting whitespace follows tab stops.") }
|
||||
|
||||
@@ -80,17 +80,17 @@ export class CursorMoveCommands {
|
||||
);
|
||||
}
|
||||
|
||||
public static moveToEndOfLine(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean): PartialCursorState[] {
|
||||
public static moveToEndOfLine(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean, sticky: boolean): PartialCursorState[] {
|
||||
let result: PartialCursorState[] = [];
|
||||
for (let i = 0, len = cursors.length; i < len; i++) {
|
||||
const cursor = cursors[i];
|
||||
result[i] = this._moveToLineEnd(viewModel, cursor, inSelectionMode);
|
||||
result[i] = this._moveToLineEnd(viewModel, cursor, inSelectionMode, sticky);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _moveToLineEnd(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean): PartialCursorState {
|
||||
private static _moveToLineEnd(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean, sticky: boolean): PartialCursorState {
|
||||
const viewStatePosition = cursor.viewState.position;
|
||||
const viewModelMaxColumn = viewModel.getLineMaxColumn(viewStatePosition.lineNumber);
|
||||
const isEndOfViewLine = viewStatePosition.column === viewModelMaxColumn;
|
||||
@@ -100,21 +100,21 @@ export class CursorMoveCommands {
|
||||
const isEndLineOfWrappedLine = viewModelMaxColumn - viewStatePosition.column === modelMaxColumn - modelStatePosition.column;
|
||||
|
||||
if (isEndOfViewLine || isEndLineOfWrappedLine) {
|
||||
return this._moveToLineEndByModel(viewModel, cursor, inSelectionMode);
|
||||
return this._moveToLineEndByModel(viewModel, cursor, inSelectionMode, sticky);
|
||||
} else {
|
||||
return this._moveToLineEndByView(viewModel, cursor, inSelectionMode);
|
||||
return this._moveToLineEndByView(viewModel, cursor, inSelectionMode, sticky);
|
||||
}
|
||||
}
|
||||
|
||||
private static _moveToLineEndByView(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean): PartialCursorState {
|
||||
private static _moveToLineEndByView(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean, sticky: boolean): PartialCursorState {
|
||||
return CursorState.fromViewState(
|
||||
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode)
|
||||
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, sticky)
|
||||
);
|
||||
}
|
||||
|
||||
private static _moveToLineEndByModel(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean): PartialCursorState {
|
||||
private static _moveToLineEndByModel(viewModel: IViewModel, cursor: CursorState, inSelectionMode: boolean, sticky: boolean): PartialCursorState {
|
||||
return CursorState.fromModelState(
|
||||
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel.model, cursor.modelState, inSelectionMode)
|
||||
MoveOperations.moveToEndOfLine(viewModel.cursorConfig, viewModel.model, cursor.modelState, inSelectionMode, sticky)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -404,13 +404,14 @@ export class CursorMoveCommands {
|
||||
}
|
||||
|
||||
private static _moveLeft(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean, noOfColumns: number): PartialCursorState[] {
|
||||
const hasMultipleCursors = (cursors.length > 1);
|
||||
let result: PartialCursorState[] = [];
|
||||
for (let i = 0, len = cursors.length; i < len; i++) {
|
||||
const cursor = cursors[i];
|
||||
|
||||
const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection();
|
||||
let newViewState = MoveOperations.moveLeft(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns);
|
||||
|
||||
if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
|
||||
if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
|
||||
// moved over to the previous view line
|
||||
const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position);
|
||||
if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) {
|
||||
@@ -436,12 +437,14 @@ export class CursorMoveCommands {
|
||||
}
|
||||
|
||||
private static _moveRight(viewModel: IViewModel, cursors: CursorState[], inSelectionMode: boolean, noOfColumns: number): PartialCursorState[] {
|
||||
const hasMultipleCursors = (cursors.length > 1);
|
||||
let result: PartialCursorState[] = [];
|
||||
for (let i = 0, len = cursors.length; i < len; i++) {
|
||||
const cursor = cursors[i];
|
||||
const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection();
|
||||
let newViewState = MoveOperations.moveRight(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns);
|
||||
|
||||
if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
|
||||
if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
|
||||
// moved over to the next view line
|
||||
const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position);
|
||||
if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) {
|
||||
|
||||
@@ -222,10 +222,10 @@ export class MoveOperations {
|
||||
return cursor.move(inSelectionMode, lineNumber, column, 0);
|
||||
}
|
||||
|
||||
public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
|
||||
public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, sticky: boolean): SingleCursorState {
|
||||
let lineNumber = cursor.position.lineNumber;
|
||||
let maxColumn = model.getLineMaxColumn(lineNumber);
|
||||
return cursor.move(inSelectionMode, lineNumber, maxColumn, Constants.MAX_SAFE_SMALL_INTEGER - maxColumn);
|
||||
return cursor.move(inSelectionMode, lineNumber, maxColumn, sticky ? Constants.MAX_SAFE_SMALL_INTEGER - maxColumn : 0);
|
||||
}
|
||||
|
||||
public static moveToBeginningOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
|
||||
|
||||
@@ -36,6 +36,8 @@ export namespace EditorContextKeys {
|
||||
export const canUndo = new RawContextKey<boolean>('canUndo', false);
|
||||
export const canRedo = new RawContextKey<boolean>('canRedo', false);
|
||||
|
||||
export const hoverVisible = new RawContextKey<boolean>('editorHoverVisible', false);
|
||||
|
||||
/**
|
||||
* A context key that is set when an editor is part of a larger editor, like notebooks or
|
||||
* (future) a diff editor
|
||||
|
||||
@@ -552,7 +552,7 @@ export interface ITextModel {
|
||||
mightContainRTL(): boolean;
|
||||
|
||||
/**
|
||||
* If true, the text model might contain LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL).
|
||||
* If true, the text model might contain LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS).
|
||||
* If false, the text model definitely does not contain these.
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -168,6 +168,16 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
|
||||
this._data = SingleModelEditStackData.create(model, beforeCursorState);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
const data = (this._data instanceof SingleModelEditStackData ? this._data : SingleModelEditStackData.deserialize(this._data));
|
||||
return data.changes.map(change => change.toString()).join(', ');
|
||||
}
|
||||
|
||||
public matchesResource(resource: URI): boolean {
|
||||
const uri = (URI.isUri(this.model) ? this.model : this.model.uri);
|
||||
return (uri.toString() === resource.toString());
|
||||
}
|
||||
|
||||
public setModel(model: ITextModel | URI): void {
|
||||
this.model = model;
|
||||
}
|
||||
@@ -270,6 +280,11 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
|
||||
return result;
|
||||
}
|
||||
|
||||
public matchesResource(resource: URI): boolean {
|
||||
const key = uriGetComparisonKey(resource);
|
||||
return (this._editStackElementsMap.has(key));
|
||||
}
|
||||
|
||||
public setModel(model: ITextModel | URI): void {
|
||||
const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri);
|
||||
if (this._editStackElementsMap.has(key)) {
|
||||
@@ -338,7 +353,7 @@ function getModelEOL(model: ITextModel): EndOfLineSequence {
|
||||
}
|
||||
}
|
||||
|
||||
function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement {
|
||||
export function isEditStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
@@ -357,7 +372,7 @@ export class EditStack {
|
||||
|
||||
public pushStackElement(): void {
|
||||
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
|
||||
if (isKnownStackElement(lastElement)) {
|
||||
if (isEditStackElement(lastElement)) {
|
||||
lastElement.close();
|
||||
}
|
||||
}
|
||||
@@ -368,7 +383,7 @@ export class EditStack {
|
||||
|
||||
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement {
|
||||
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
|
||||
if (isKnownStackElement(lastElement) && lastElement.canAppend(this._model)) {
|
||||
if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) {
|
||||
return lastElement;
|
||||
}
|
||||
const newElement = new SingleModelEditStackElement(this._model, beforeCursorState);
|
||||
|
||||
@@ -510,6 +510,32 @@ export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
|
||||
public getPieceTree(): PieceTreeBase {
|
||||
return this._pieceTree;
|
||||
}
|
||||
|
||||
public static _getInverseEditRange(range: Range, text: string) {
|
||||
let startLineNumber = range.startLineNumber;
|
||||
let startColumn = range.startColumn;
|
||||
const [eolCount, firstLineLength, lastLineLength] = countEOL(text);
|
||||
let resultRange: Range;
|
||||
|
||||
if (text.length > 0) {
|
||||
// the operation inserts something
|
||||
const lineCount = eolCount + 1;
|
||||
|
||||
if (lineCount === 1) {
|
||||
// single line insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLineLength);
|
||||
} else {
|
||||
// multi line insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLineLength + 1);
|
||||
}
|
||||
} else {
|
||||
// There is nothing to insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn);
|
||||
}
|
||||
|
||||
return resultRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes `operations` are validated and sorted ascending
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,16 @@ export class TextChange {
|
||||
public readonly newText: string
|
||||
) { }
|
||||
|
||||
public toString(): string {
|
||||
if (this.oldText.length === 0) {
|
||||
return `(insert@${this.oldPosition} "${this.newText}")`;
|
||||
}
|
||||
if (this.newText.length === 0) {
|
||||
return `(delete@${this.oldPosition} "${this.oldText}")`;
|
||||
}
|
||||
return `(replace@${this.oldPosition} "${this.oldText}" with "${this.newText}")`;
|
||||
}
|
||||
|
||||
private static _writeStringSize(str: string): number {
|
||||
return (
|
||||
4 + 2 * str.length
|
||||
|
||||
@@ -706,9 +706,8 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
public removeUnusualLineTerminators(selections: Selection[] | null = null): void {
|
||||
const matches = this.findMatches(strings.UNUSUAL_LINE_TERMINATORS.source, false, true, false, null, false, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
const eol = this.getEOL();
|
||||
this._buffer.resetMightContainUnusualLineTerminators();
|
||||
this.pushEditOperations(selections, matches.map(m => ({ range: m.range, text: eol })), () => null);
|
||||
this.pushEditOperations(selections, matches.map(m => ({ range: m.range, text: null })), () => null);
|
||||
}
|
||||
|
||||
public mightContainNonBasicASCII(): boolean {
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IDiffComputationResult {
|
||||
}
|
||||
|
||||
export interface IEditorWorkerService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
canComputeDiff(original: URI, modified: URI): boolean;
|
||||
computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null>;
|
||||
|
||||
@@ -46,7 +46,7 @@ function canSyncModel(modelService: IModelService, resource: URI): boolean {
|
||||
|
||||
export class EditorWorkerServiceImpl extends Disposable implements IEditorWorkerService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _modelService: IModelService;
|
||||
private readonly _workerManager: WorkerManager;
|
||||
|
||||
@@ -70,7 +70,7 @@ class MarkerDecorations extends Disposable {
|
||||
|
||||
export class MarkerDecorationsService extends Disposable implements IMarkerDecorationsService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeMarker = this._register(new Emitter<ITextModel>());
|
||||
readonly onDidChangeMarker: Event<ITextModel> = this._onDidChangeMarker.event;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
export const IMarkerDecorationsService = createDecorator<IMarkerDecorationsService>('markerDecorationsService');
|
||||
|
||||
export interface IMarkerDecorationsService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
onDidChangeMarker: Event<ITextModel>;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface ILanguageSelection extends IDisposable {
|
||||
}
|
||||
|
||||
export interface IModeService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
onDidCreateMode: Event<IMode>;
|
||||
onLanguagesMaybeChanged: Event<void>;
|
||||
|
||||
@@ -16,7 +16,7 @@ export const IModelService = createDecorator<IModelService>('modelService');
|
||||
export type DocumentTokensProvider = DocumentSemanticTokensProvider | DocumentRangeSemanticTokensProvider;
|
||||
|
||||
export interface IModelService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource?: URI, isForSimpleWidget?: boolean): ITextModel;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { StringSHA1 } from 'vs/base/common/hash';
|
||||
import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack';
|
||||
import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement, isEditStackElement } from 'vs/editor/common/model/editStack';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling';
|
||||
|
||||
@@ -139,6 +139,7 @@ class DisposedModelInfo {
|
||||
constructor(
|
||||
public readonly uri: URI,
|
||||
public readonly time: number,
|
||||
public readonly sharesUndoRedoStack: boolean,
|
||||
public readonly heapSize: number,
|
||||
public readonly sha1: string,
|
||||
public readonly versionId: number,
|
||||
@@ -352,7 +353,11 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
if (this._disposedModelsHeapSize > maxModelsHeapSize) {
|
||||
// we must remove some old undo stack elements to free up some memory
|
||||
const disposedModels: DisposedModelInfo[] = [];
|
||||
this._disposedModels.forEach(entry => disposedModels.push(entry));
|
||||
this._disposedModels.forEach(entry => {
|
||||
if (!entry.sharesUndoRedoStack) {
|
||||
disposedModels.push(entry);
|
||||
}
|
||||
});
|
||||
disposedModels.sort((a, b) => a.time - b.time);
|
||||
while (disposedModels.length > 0 && this._disposedModelsHeapSize > maxModelsHeapSize) {
|
||||
const disposedModel = disposedModels.shift()!;
|
||||
@@ -369,16 +374,23 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
if (resource && this._disposedModels.has(MODEL_ID(resource))) {
|
||||
const disposedModelData = this._removeDisposedModel(resource)!;
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) {
|
||||
const sha1IsEqual = (computeModelSha1(model) === disposedModelData.sha1);
|
||||
if (sha1IsEqual || disposedModelData.sharesUndoRedoStack) {
|
||||
for (const element of elements.past) {
|
||||
element.setModel(model);
|
||||
if (isEditStackElement(element) && element.matchesResource(resource)) {
|
||||
element.setModel(model);
|
||||
}
|
||||
}
|
||||
for (const element of elements.future) {
|
||||
element.setModel(model);
|
||||
if (isEditStackElement(element) && element.matchesResource(resource)) {
|
||||
element.setModel(model);
|
||||
}
|
||||
}
|
||||
this._undoRedoService.setElementsValidFlag(resource, true, (element) => (isEditStackElement(element) && element.matchesResource(resource)));
|
||||
if (sha1IsEqual) {
|
||||
model._overwriteVersionId(disposedModelData.versionId);
|
||||
model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId);
|
||||
}
|
||||
this._undoRedoService.setElementsIsValid(resource, true);
|
||||
model._overwriteVersionId(disposedModelData.versionId);
|
||||
model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId);
|
||||
} else {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
@@ -504,31 +516,39 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
return;
|
||||
}
|
||||
const model = modelData.model;
|
||||
const sharesUndoRedoStack = (this._undoRedoService.getUriComparisonKey(model.uri) !== model.uri.toString());
|
||||
let maintainUndoRedoStack = false;
|
||||
let heapSize = 0;
|
||||
if (this._shouldRestoreUndoStack() && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) {
|
||||
if (sharesUndoRedoStack || (this._shouldRestoreUndoStack() && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData))) {
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements)) {
|
||||
maintainUndoRedoStack = true;
|
||||
if (elements.past.length > 0 || elements.future.length > 0) {
|
||||
for (const element of elements.past) {
|
||||
heapSize += element.heapSize(resource);
|
||||
element.setModel(resource); // remove reference from text buffer instance
|
||||
if (isEditStackElement(element) && element.matchesResource(resource)) {
|
||||
maintainUndoRedoStack = true;
|
||||
heapSize += element.heapSize(resource);
|
||||
element.setModel(resource); // remove reference from text buffer instance
|
||||
}
|
||||
}
|
||||
for (const element of elements.future) {
|
||||
heapSize += element.heapSize(resource);
|
||||
element.setModel(resource); // remove reference from text buffer instance
|
||||
if (isEditStackElement(element) && element.matchesResource(resource)) {
|
||||
maintainUndoRedoStack = true;
|
||||
heapSize += element.heapSize(resource);
|
||||
element.setModel(resource); // remove reference from text buffer instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!maintainUndoRedoStack) {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
if (!sharesUndoRedoStack) {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
modelData.model.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMemory = ModelServiceImpl.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK;
|
||||
if (heapSize > maxMemory) {
|
||||
if (!sharesUndoRedoStack && heapSize > maxMemory) {
|
||||
// the undo stack for this file would never fit in the configured memory, so don't bother with it.
|
||||
this._undoRedoService.removeElements(resource);
|
||||
modelData.model.dispose();
|
||||
@@ -538,8 +558,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
this._ensureDisposedModelsHeapSize(maxMemory - heapSize);
|
||||
|
||||
// We only invalidate the elements, but they remain in the undo-redo service.
|
||||
this._undoRedoService.setElementsIsValid(resource, false);
|
||||
this._insertDisposedModel(new DisposedModelInfo(resource, Date.now(), heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
this._undoRedoService.setElementsValidFlag(resource, false, (element) => (isEditStackElement(element) && element.matchesResource(resource)));
|
||||
this._insertDisposedModel(new DisposedModelInfo(resource, Date.now(), sharesUndoRedoStack, heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
|
||||
modelData.model.dispose();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
export const ITextModelService = createDecorator<ITextModelService>('textModelService');
|
||||
|
||||
export interface ITextModelService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Provided a resource URI, it will return a model reference
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface ITextResourceConfigurationChangeEvent {
|
||||
|
||||
export interface ITextResourceConfigurationService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Event that fires when the configuration changes.
|
||||
@@ -70,7 +70,7 @@ export const ITextResourcePropertiesService = createDecorator<ITextResourcePrope
|
||||
|
||||
export interface ITextResourcePropertiesService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Returns the End of Line characters for the given resource
|
||||
|
||||
@@ -28,9 +28,9 @@ export class TextResourceConfigurationService extends Disposable implements ITex
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this._onDidChangeConfiguration.fire(this.toResourceConfigurationChangeEvent(e))));
|
||||
}
|
||||
|
||||
getValue<T>(resource: URI, section?: string): T;
|
||||
getValue<T>(resource: URI, at?: IPosition, section?: string): T;
|
||||
getValue<T>(resource: URI, arg2?: any, arg3?: any): T {
|
||||
getValue<T>(resource: URI | undefined, section?: string): T;
|
||||
getValue<T>(resource: URI | undefined, at?: IPosition, section?: string): T;
|
||||
getValue<T>(resource: URI | undefined, arg2?: any, arg3?: any): T {
|
||||
if (typeof arg3 === 'string') {
|
||||
return this._getValue(resource, Position.isIPosition(arg2) ? arg2 : null, arg3);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export class TextResourceConfigurationService extends Disposable implements ITex
|
||||
return ConfigurationTarget.USER_LOCAL;
|
||||
}
|
||||
|
||||
private _getValue<T>(resource: URI, position: IPosition | null, section: string | undefined): T {
|
||||
private _getValue<T>(resource: URI | undefined, position: IPosition | null, section: string | undefined): T {
|
||||
const language = resource ? this.getLanguage(resource, position) : undefined;
|
||||
if (typeof section === 'undefined') {
|
||||
return this.configurationService.getValue<T>({ resource, overrideIdentifier: language });
|
||||
|
||||
@@ -240,36 +240,36 @@ export enum EditorOption {
|
||||
quickSuggestions = 70,
|
||||
quickSuggestionsDelay = 71,
|
||||
readOnly = 72,
|
||||
removeUnusualLineTerminators = 73,
|
||||
renameOnType = 74,
|
||||
renderControlCharacters = 75,
|
||||
renderIndentGuides = 76,
|
||||
renderFinalNewline = 77,
|
||||
renderLineHighlight = 78,
|
||||
renderLineHighlightOnlyWhenFocus = 79,
|
||||
renderValidationDecorations = 80,
|
||||
renderWhitespace = 81,
|
||||
revealHorizontalRightPadding = 82,
|
||||
roundedSelection = 83,
|
||||
rulers = 84,
|
||||
scrollbar = 85,
|
||||
scrollBeyondLastColumn = 86,
|
||||
scrollBeyondLastLine = 87,
|
||||
scrollPredominantAxis = 88,
|
||||
selectionClipboard = 89,
|
||||
selectionHighlight = 90,
|
||||
selectOnLineNumbers = 91,
|
||||
showFoldingControls = 92,
|
||||
showUnused = 93,
|
||||
snippetSuggestions = 94,
|
||||
smoothScrolling = 95,
|
||||
stopRenderingLineAfter = 96,
|
||||
suggest = 97,
|
||||
suggestFontSize = 98,
|
||||
suggestLineHeight = 99,
|
||||
suggestOnTriggerCharacters = 100,
|
||||
suggestSelection = 101,
|
||||
tabCompletion = 102,
|
||||
renameOnType = 73,
|
||||
renderControlCharacters = 74,
|
||||
renderIndentGuides = 75,
|
||||
renderFinalNewline = 76,
|
||||
renderLineHighlight = 77,
|
||||
renderLineHighlightOnlyWhenFocus = 78,
|
||||
renderValidationDecorations = 79,
|
||||
renderWhitespace = 80,
|
||||
revealHorizontalRightPadding = 81,
|
||||
roundedSelection = 82,
|
||||
rulers = 83,
|
||||
scrollbar = 84,
|
||||
scrollBeyondLastColumn = 85,
|
||||
scrollBeyondLastLine = 86,
|
||||
scrollPredominantAxis = 87,
|
||||
selectionClipboard = 88,
|
||||
selectionHighlight = 89,
|
||||
selectOnLineNumbers = 90,
|
||||
showFoldingControls = 91,
|
||||
showUnused = 92,
|
||||
snippetSuggestions = 93,
|
||||
smoothScrolling = 94,
|
||||
stopRenderingLineAfter = 95,
|
||||
suggest = 96,
|
||||
suggestFontSize = 97,
|
||||
suggestLineHeight = 98,
|
||||
suggestOnTriggerCharacters = 99,
|
||||
suggestSelection = 100,
|
||||
tabCompletion = 101,
|
||||
unusualLineTerminators = 102,
|
||||
useTabStops = 103,
|
||||
wordSeparators = 104,
|
||||
wordWrap = 105,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { once } from 'vs/base/common/functional';
|
||||
export const ICodeLensCache = createDecorator<ICodeLensCache>('ICodeLensCache');
|
||||
|
||||
export interface ICodeLensCache {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
put(model: ITextModel, data: CodeLensModel): void;
|
||||
get(model: ITextModel): CodeLensModel | undefined;
|
||||
delete(model: ITextModel): void;
|
||||
@@ -38,7 +38,7 @@ class CacheItem {
|
||||
|
||||
export class CodeLensCache implements ICodeLensCache {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _fakeProvider = new class implements CodeLensProvider {
|
||||
provideCodeLenses(): CodeLensList {
|
||||
@@ -96,7 +96,7 @@ export class CodeLensCache implements ICodeLensCache {
|
||||
|
||||
private _serialize(): string {
|
||||
const data: Record<string, ISerializedCacheData> = Object.create(null);
|
||||
this._cache.forEach((value, key) => {
|
||||
for (const [key, value] of this._cache) {
|
||||
const lines = new Set<number>();
|
||||
for (const d of value.data.lenses) {
|
||||
lines.add(d.symbol.range.startLineNumber);
|
||||
@@ -105,7 +105,7 @@ export class CodeLensCache implements ICodeLensCache {
|
||||
lineCount: value.lineCount,
|
||||
lines: [...lines.values()]
|
||||
};
|
||||
});
|
||||
}
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ReplacePattern, parseReplaceString } from 'vs/editor/contrib/find/repla
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { findFirstInSorted } from 'vs/base/common/arrays';
|
||||
|
||||
export const CONTEXT_FIND_WIDGET_VISIBLE = new RawContextKey<boolean>('findWidgetVisible', false);
|
||||
export const CONTEXT_FIND_WIDGET_NOT_VISIBLE = CONTEXT_FIND_WIDGET_VISIBLE.toNegated();
|
||||
@@ -189,8 +190,17 @@ export class FindModelBoundToEditorModel {
|
||||
let findMatches = this._findMatches(findScope, false, MATCHES_LIMIT);
|
||||
this._decorations.set(findMatches, findScope);
|
||||
|
||||
const editorSelection = this._editor.getSelection();
|
||||
let currentMatchesPosition = this._decorations.getCurrentMatchesPosition(editorSelection);
|
||||
if (currentMatchesPosition === 0 && findMatches.length > 0) {
|
||||
// current selection is not on top of a match
|
||||
// try to find its nearest result from the top of the document
|
||||
const matchAfterSelection = findFirstInSorted(findMatches.map(match => match.range), range => Range.compareRangesUsingStarts(range, editorSelection) >= 0);
|
||||
currentMatchesPosition = matchAfterSelection > 0 ? matchAfterSelection - 1 + 1 /** match position is one based */ : currentMatchesPosition;
|
||||
}
|
||||
|
||||
this._state.changeMatchInfo(
|
||||
this._decorations.getCurrentMatchesPosition(this._editor.getSelection()),
|
||||
currentMatchesPosition,
|
||||
this._decorations.getCount(),
|
||||
undefined
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPosit
|
||||
import { FIND_IDS } from 'vs/editor/contrib/find/findModel';
|
||||
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
@@ -47,12 +47,14 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
this._domNode.setAttribute('aria-hidden', 'true');
|
||||
|
||||
const inputActiveOptionBorderColor = themeService.getColorTheme().getColor(inputActiveOptionBorder);
|
||||
const inputActiveOptionForegroundColor = themeService.getColorTheme().getColor(inputActiveOptionForeground);
|
||||
const inputActiveOptionBackgroundColor = themeService.getColorTheme().getColor(inputActiveOptionBackground);
|
||||
|
||||
this.caseSensitive = this._register(new CaseSensitiveCheckbox({
|
||||
appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
|
||||
isChecked: this._state.matchCase,
|
||||
inputActiveOptionBorder: inputActiveOptionBorderColor,
|
||||
inputActiveOptionForeground: inputActiveOptionForegroundColor,
|
||||
inputActiveOptionBackground: inputActiveOptionBackgroundColor
|
||||
}));
|
||||
this._domNode.appendChild(this.caseSensitive.domNode);
|
||||
@@ -66,6 +68,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
|
||||
isChecked: this._state.wholeWord,
|
||||
inputActiveOptionBorder: inputActiveOptionBorderColor,
|
||||
inputActiveOptionForeground: inputActiveOptionForegroundColor,
|
||||
inputActiveOptionBackground: inputActiveOptionBackgroundColor
|
||||
}));
|
||||
this._domNode.appendChild(this.wholeWords.domNode);
|
||||
@@ -79,6 +82,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
|
||||
isChecked: this._state.isRegex,
|
||||
inputActiveOptionBorder: inputActiveOptionBorderColor,
|
||||
inputActiveOptionForeground: inputActiveOptionForegroundColor,
|
||||
inputActiveOptionBackground: inputActiveOptionBackgroundColor
|
||||
}));
|
||||
this._domNode.appendChild(this.regex.domNode);
|
||||
@@ -185,6 +189,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
private _applyTheme(theme: IColorTheme) {
|
||||
let inputStyles = {
|
||||
inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
|
||||
inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground),
|
||||
inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground)
|
||||
};
|
||||
this.caseSensitive.style(inputStyles);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user