chore(vscode): update to 1.55.2

This commit is contained in:
Akash Satheesan
2021-04-09 11:32:27 +05:30
1102 changed files with 39988 additions and 23544 deletions

View File

@@ -383,6 +383,7 @@ export class SelectActionViewItem extends BaseActionViewItem {
super(ctx, action);
this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions);
this.selectBox.setFocusable(false);
this._register(this.selectBox);
this.registerListeners();
@@ -406,6 +407,10 @@ export class SelectActionViewItem extends BaseActionViewItem {
return option;
}
setFocusable(focusable: boolean): void {
this.selectBox.setFocusable(focusable);
}
focus(): void {
if (this.selectBox) {
this.selectBox.focus();

View File

@@ -20,10 +20,6 @@
display: inline-block;
}
.monaco-action-bar.reverse .actions-container {
flex-direction: row-reverse;
}
.monaco-action-bar .action-item {
cursor: pointer;
display: inline-block;

View File

@@ -28,9 +28,7 @@ export interface IActionViewItemProvider {
export const enum ActionsOrientation {
HORIZONTAL,
HORIZONTAL_REVERSE,
VERTICAL,
VERTICAL_REVERSE,
}
export interface ActionTrigger {
@@ -135,21 +133,11 @@ export class ActionBar extends Disposable implements IActionRunner {
previousKeys = [KeyCode.LeftArrow];
nextKeys = [KeyCode.RightArrow];
break;
case ActionsOrientation.HORIZONTAL_REVERSE:
previousKeys = [KeyCode.RightArrow];
nextKeys = [KeyCode.LeftArrow];
this.domNode.className += ' reverse';
break;
case ActionsOrientation.VERTICAL:
previousKeys = [KeyCode.UpArrow];
nextKeys = [KeyCode.DownArrow];
this.domNode.className += ' vertical';
break;
case ActionsOrientation.VERTICAL_REVERSE:
previousKeys = [KeyCode.DownArrow];
nextKeys = [KeyCode.UpArrow];
this.domNode.className += ' vertical reverse';
break;
}
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => {
@@ -163,8 +151,12 @@ export class ActionBar extends Disposable implements IActionRunner {
eventHandled = this.focusNext();
} else if (event.equals(KeyCode.Escape) && this.cancelHasListener) {
this._onDidCancel.fire();
} else if (event.equals(KeyCode.Home)) {
eventHandled = this.focusFirst();
} else if (event.equals(KeyCode.End)) {
eventHandled = this.focusLast();
} else if (event.equals(KeyCode.Tab) && focusedItem instanceof BaseActionViewItem && focusedItem.trapsArrowNavigation) {
this.focusNext();
eventHandled = this.focusNext();
} else if (this.isTriggerKeyEvent(event)) {
// Staying out of the else branch even if not triggered
if (this._triggerKeys.keyDown) {
@@ -425,9 +417,21 @@ export class ActionBar extends Disposable implements IActionRunner {
}
}
private focusFirst(): boolean {
this.focusedItem = this.length() > 1 ? 1 : 0;
return this.focusPrevious();
}
private focusLast(): boolean {
this.focusedItem = this.length() < 2 ? 0 : this.length() - 2;
return this.focusNext();
}
protected focusNext(): boolean {
if (typeof this.focusedItem === 'undefined') {
this.focusedItem = this.viewItems.length - 1;
} else if (this.viewItems.length <= 1) {
return false;
}
const startIndex = this.focusedItem;
@@ -450,6 +454,8 @@ export class ActionBar extends Disposable implements IActionRunner {
protected focusPrevious(): boolean {
if (typeof this.focusedItem === 'undefined') {
this.focusedItem = 0;
} else if (this.viewItems.length <= 1) {
return false;
}
const startIndex = this.focusedItem;

View File

@@ -14,6 +14,10 @@
align-items: center;
}
.monaco-text-button:focus {
outline-offset: 2px !important;
}
.monaco-text-button:hover {
text-decoration: none !important;
}

View File

@@ -20,4 +20,5 @@ export interface IHoverDelegateOptions {
export interface IHoverDelegate {
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
delay: number;
}

View File

@@ -10,7 +10,6 @@ import { IMatch } from 'vs/base/common/filters';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/base/common/range';
import { equals } from 'vs/base/common/objects';
import { isMacintosh } from 'vs/base/common/platform';
import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
import { IMarkdownString } from 'vs/base/common/htmlContent';
@@ -218,9 +217,6 @@ export class IconLabel extends Disposable {
htmlElement.removeAttribute('title');
let tooltip = this.getTooltipForCustom(markdownTooltip);
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
// On Mac, the delay is 1500.
const hoverDelay = isMacintosh ? 1500 : 500;
let hoverOptions: IHoverDelegateOptions | undefined;
let mouseX: number | undefined;
let isHovering = false;
@@ -232,9 +228,12 @@ export class IconLabel extends Disposable {
}
tokenSource = new CancellationTokenSource();
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any {
if ((e.type === dom.EventType.MOUSE_DOWN) || (<any>e).fromElement === htmlElement) {
const isMouseDown = e.type === dom.EventType.MOUSE_DOWN;
if (isMouseDown) {
hoverDisposable?.dispose();
hoverDisposable = undefined;
}
if (isMouseDown || (<any>e).fromElement === htmlElement) {
isHovering = false;
hoverOptions = undefined;
tokenSource.dispose(true);
@@ -282,7 +281,7 @@ export class IconLabel extends Disposable {
}
mouseMoveDisposable.dispose();
}, hoverDelay);
}, hoverDelegate.delay);
}
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
this.customHovers.set(htmlElement, mouseOverDisposable);

View File

@@ -27,6 +27,7 @@ const $ = dom.$;
export interface IInputOptions extends IInputBoxStyles {
readonly placeholder?: string;
readonly tooltip?: string;
readonly ariaLabel?: string;
readonly type?: string;
readonly validationOptions?: IInputValidationOptions;
@@ -95,6 +96,7 @@ export class InputBox extends Widget {
private options: IInputOptions;
private message: IMessage | null;
private placeholder: string;
private tooltip: string;
private ariaLabel: string;
private validation?: IInputValidator;
private state: 'idle' | 'open' | 'closed' = 'idle';
@@ -133,6 +135,7 @@ export class InputBox extends Widget {
mixin(this.options, defaultOpts, false);
this.message = null;
this.placeholder = this.options.placeholder || '';
this.tooltip = this.options.tooltip ?? (this.placeholder || '');
this.ariaLabel = this.options.ariaLabel || '';
this.inputBackground = this.options.inputBackground;
@@ -207,6 +210,10 @@ export class InputBox extends Widget {
this.setPlaceHolder(this.placeholder);
}
if (this.tooltip) {
this.setTooltip(this.tooltip);
}
this.oninput(this.input, () => this.onValueChange());
this.onblur(this.input, () => this.onBlur());
this.onfocus(this.input, () => this.onFocus());
@@ -235,7 +242,11 @@ export class InputBox extends Widget {
public setPlaceHolder(placeHolder: string): void {
this.placeholder = placeHolder;
this.input.setAttribute('placeholder', placeHolder);
this.input.title = placeHolder;
}
public setTooltip(tooltip: string): void {
this.tooltip = tooltip;
this.input.title = tooltip;
}
public setAriaLabel(label: string): void {

View File

@@ -226,6 +226,14 @@ export class PagedList<T> implements IThemable, IDisposable {
this.list.scrollLeft = scrollLeft;
}
setAnchor(index: number | undefined): void {
this.list.setAnchor(index);
}
getAnchor(): number | undefined {
return this.list.getAnchor();
}
setFocus(indexes: number[]): void {
this.list.setFocus(indexes);
}
@@ -238,12 +246,20 @@ export class PagedList<T> implements IThemable, IDisposable {
this.list.focusPrevious(n, loop);
}
focusNextPage(): void {
this.list.focusNextPage();
focusNextPage(): Promise<void> {
return this.list.focusNextPage();
}
focusPreviousPage(): void {
this.list.focusPreviousPage();
focusPreviousPage(): Promise<void> {
return this.list.focusPreviousPage();
}
focusLast(): void {
this.list.focusLast();
}
focusFirst(): void {
this.list.focusFirst();
}
getFocus(): number[] {

View File

@@ -6,7 +6,7 @@
import 'vs/css!./list';
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { isNumber } from 'vs/base/common/types';
import { range, binarySearch } from 'vs/base/common/arrays';
import { range, binarySearch, firstOrDefault } from 'vs/base/common/arrays';
import { memoize } from 'vs/base/common/decorators';
import * as platform from 'vs/base/common/platform';
import { Gesture } from 'vs/base/browser/touch';
@@ -27,6 +27,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { IThemable } from 'vs/base/common/styler';
import { createStyleSheet } from 'vs/base/browser/dom';
import { timeout } from 'vs/base/common/async';
interface ITraitChangeEvent {
indexes: number[];
@@ -617,27 +618,25 @@ export class MouseController<T> implements IDisposable {
return;
}
let reference = this.list.getFocus()[0];
const selection = this.list.getSelection();
reference = reference === undefined ? selection[0] : reference;
const focus = e.index;
if (typeof focus === 'undefined') {
this.list.setFocus([], e.browserEvent);
this.list.setSelection([], e.browserEvent);
this.list.setAnchor(undefined);
return;
}
if (this.multipleSelectionSupport && this.isSelectionRangeChangeEvent(e)) {
return this.changeSelection(e, reference);
return this.changeSelection(e);
}
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
return this.changeSelection(e, reference);
return this.changeSelection(e);
}
this.list.setFocus([focus], e.browserEvent);
this.list.setAnchor(focus);
if (!isMouseRightClick(e.browserEvent)) {
this.list.setSelection([focus], e.browserEvent);
@@ -659,15 +658,16 @@ export class MouseController<T> implements IDisposable {
this.list.setSelection(focus, e.browserEvent);
}
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
const focus = e.index!;
const anchor = this.list.getAnchor();
if (this.isSelectionRangeChangeEvent(e) && reference !== undefined) {
const min = Math.min(reference, focus);
const max = Math.max(reference, focus);
if (this.isSelectionRangeChangeEvent(e) && typeof anchor === 'number') {
const min = Math.min(anchor, focus);
const max = Math.max(anchor, focus);
const rangeSelection = range(min, max + 1);
const selection = this.list.getSelection();
const contiguousRange = getContiguousRangeContaining(disjunction(selection, [reference]), reference);
const contiguousRange = getContiguousRangeContaining(disjunction(selection, [anchor]), anchor);
if (contiguousRange.length === 0) {
return;
@@ -675,12 +675,14 @@ export class MouseController<T> implements IDisposable {
const newSelection = disjunction(rangeSelection, relativeComplement(selection, contiguousRange));
this.list.setSelection(newSelection, e.browserEvent);
this.list.setFocus([focus], e.browserEvent);
} else if (this.isSelectionSingleChangeEvent(e)) {
const selection = this.list.getSelection();
const newSelection = selection.filter(i => i !== focus);
this.list.setFocus([focus]);
this.list.setAnchor(focus);
if (selection.length === newSelection.length) {
this.list.setSelection([...newSelection, focus], e.browserEvent);
@@ -1130,8 +1132,9 @@ export interface IListOptionsUpdate extends IListViewOptionsUpdate {
export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
private focus: Trait<T>;
private focus = new Trait<T>('focused');
private selection: Trait<T>;
private anchor = new Trait<T>('anchor');
private eventBufferer = new EventBufferer();
protected view: ListView<T>;
private spliceable: ISpliceable<T>;
@@ -1223,7 +1226,6 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
) {
const role = this._options.accessibilityProvider && this._options.accessibilityProvider.getWidgetRole ? this._options.accessibilityProvider?.getWidgetRole() : 'list';
this.selection = new SelectionTrait(role !== 'listbox');
this.focus = new Trait('focused');
mixin(_options, defaultStyles, false);
@@ -1259,11 +1261,13 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.spliceable = new CombinedSpliceable([
new TraitSpliceable(this.focus, this.view, _options.identityProvider),
new TraitSpliceable(this.selection, this.view, _options.identityProvider),
new TraitSpliceable(this.anchor, this.view, _options.identityProvider),
this.view
]);
this.disposables.add(this.focus);
this.disposables.add(this.selection);
this.disposables.add(this.anchor);
this.disposables.add(this.view);
this.disposables.add(this._onDidDispose);
@@ -1436,6 +1440,23 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
return this.getSelection().map(i => this.view.element(i));
}
setAnchor(index: number | undefined): void {
if (typeof index === 'undefined') {
this.anchor.set([]);
return;
}
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
this.anchor.set([index]);
}
getAnchor(): number | undefined {
return firstOrDefault(this.anchor.get(), undefined);
}
setFocus(indexes: number[], browserEvent?: UIEvent): void {
for (const index of indexes) {
if (index < 0 || index >= this.length) {
@@ -1468,7 +1489,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
}
}
focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
async focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise<void> {
let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight);
lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
const lastPageElement = this.view.element(lastPageIndex);
@@ -1490,12 +1511,13 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.setFocus([]);
// Let the scroll event listener run
setTimeout(() => this.focusNextPage(browserEvent, filter), 0);
await timeout(0);
await this.focusNextPage(browserEvent, filter);
}
}
}
focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
async focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise<void> {
let firstPageIndex: number;
const scrollTop = this.view.getScrollTop();
@@ -1524,7 +1546,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.setFocus([]);
// Let the scroll event listener run
setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0);
await timeout(0);
await this.focusPreviousPage(browserEvent, filter);
}
}
}

View File

@@ -85,3 +85,11 @@
.menubar.compact .toolbar-toggle-more::before {
content: "\eb94" !important;
}
/* Match behavior of outline for activity bar icons */
.menubar.compact > .menubar-menu-button.open,
.menubar.compact > .menubar-menu-button:focus,
.menubar.compact > .menubar-menu-button:hover {
outline-width: 1px !important;
outline-offset: -8px !important;
}

View File

@@ -976,7 +976,7 @@ export class MenuBar extends Disposable {
menuHolder.style.right = `${this.container.clientWidth}px`;
menuHolder.style.left = 'auto';
} else {
menuHolder.style.top = `${this.container.clientHeight}px`;
menuHolder.style.top = `${buttonBoundingRect.bottom}px`;
menuHolder.style.left = `${buttonBoundingRect.left}px`;
}

View File

@@ -85,27 +85,42 @@
}
.monaco-sash.vertical > .orthogonal-drag-handle.start {
left: calc(var(--sash-size) / -2);
left: calc(var(--sash-size) * -0.5);
top: calc(var(--sash-size) * -1);
}
.monaco-sash.vertical > .orthogonal-drag-handle.end {
left: calc(var(--sash-size) / -2);
left: calc(var(--sash-size) * -0.5);
bottom: calc(var(--sash-size) * -1);
}
.monaco-sash.horizontal > .orthogonal-drag-handle.start {
top: calc(var(--sash-size) / -2);
top: calc(var(--sash-size) * -0.5);
left: calc(var(--sash-size) * -1);
}
.monaco-sash.horizontal > .orthogonal-drag-handle.end {
top: calc(var(--sash-size) / -2);
top: calc(var(--sash-size) * -0.5);
right: calc(var(--sash-size) * -1);
}
.monaco-sash {
.monaco-sash:before {
content: '';
pointer-events: none;
position: absolute;
width: 100%;
height: 100%;
transition: background-color 0.1s ease-out;
background: transparent;
}
.monaco-sash.vertical:before {
width: var(--sash-hover-size);
left: calc(50% - (var(--sash-hover-size) / 2));
}
.monaco-sash.horizontal:before {
height: var(--sash-hover-size);
top: calc(50% - (var(--sash-hover-size) / 2));
}
/** Debug **/
.monaco-sash.debug {

View File

@@ -81,6 +81,13 @@ export function setGlobalSashSize(size: number): void {
onDidChangeGlobalSize.fire(size);
}
let globalHoverDelay = 300;
const onDidChangeHoverDelay = new Emitter<number>();
export function setGlobalHoverDelay(size: number): void {
globalHoverDelay = size;
onDidChangeHoverDelay.fire(size);
}
export class Sash extends Disposable {
private el: HTMLElement;
@@ -88,7 +95,8 @@ export class Sash extends Disposable {
private hidden: boolean;
private orientation!: Orientation;
private size: number;
private hoverDelayer = this._register(new Delayer(300));
private hoverDelay = globalHoverDelay;
private hoverDelayer = this._register(new Delayer(this.hoverDelay));
private _state: SashState = SashState.Enabled;
get state(): SashState { return this._state; }
@@ -221,6 +229,8 @@ export class Sash extends Disposable {
}));
}
this._register(onDidChangeHoverDelay.event(delay => this.hoverDelay = delay));
this.hidden = false;
this.layoutProvider = layoutProvider;
@@ -403,7 +413,7 @@ export class Sash extends Disposable {
sash.hoverDelayer.cancel();
sash.el.classList.add('hover');
} else {
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'));
sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'), sash.hoverDelay).then(undefined, () => { });
}
if (!fromLinkedSash && sash.linkedSash) {

View File

@@ -29,6 +29,7 @@ export interface ISelectBoxDelegate extends IDisposable {
setAriaLabel(label: string): void;
focus(): void;
blur(): void;
setFocusable(focus: boolean): void;
// Delegated Widget interface
render(container: HTMLElement): void;
@@ -93,41 +94,43 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
// Public SelectBox Methods - routed through delegate interface
public get onDidSelect(): Event<ISelectData> {
get onDidSelect(): Event<ISelectData> {
return this.selectBoxDelegate.onDidSelect;
}
public setOptions(options: ISelectOptionItem[], selected?: number): void {
setOptions(options: ISelectOptionItem[], selected?: number): void {
this.selectBoxDelegate.setOptions(options, selected);
}
public select(index: number): void {
select(index: number): void {
this.selectBoxDelegate.select(index);
}
public setAriaLabel(label: string): void {
setAriaLabel(label: string): void {
this.selectBoxDelegate.setAriaLabel(label);
}
public focus(): void {
focus(): void {
this.selectBoxDelegate.focus();
}
public blur(): void {
blur(): void {
this.selectBoxDelegate.blur();
}
// Public Widget Methods - routed through delegate interface
setFocusable(focusable: boolean): void {
this.selectBoxDelegate.setFocusable(focusable);
}
public render(container: HTMLElement): void {
render(container: HTMLElement): void {
this.selectBoxDelegate.render(container);
}
public style(styles: ISelectBoxStyles): void {
style(styles: ISelectBoxStyles): void {
this.selectBoxDelegate.style(styles);
}
public applyStyles(): void {
applyStyles(): void {
this.selectBoxDelegate.applyStyles();
}
}

View File

@@ -293,16 +293,22 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
public focus(): void {
if (this.selectElement) {
this.selectElement.tabIndex = 0;
this.selectElement.focus();
}
}
public blur(): void {
if (this.selectElement) {
this.selectElement.tabIndex = -1;
this.selectElement.blur();
}
}
public setFocusable(focusable: boolean): void {
this.selectElement.tabIndex = focusable ? 0 : -1;
}
public render(container: HTMLElement): void {
this.container = container;
container.classList.add('select-container');

View File

@@ -132,16 +132,22 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
public focus(): void {
if (this.selectElement) {
this.selectElement.tabIndex = 0;
this.selectElement.focus();
}
}
public blur(): void {
if (this.selectElement) {
this.selectElement.tabIndex = -1;
this.selectElement.blur();
}
}
public setFocusable(focusable: boolean): void {
this.selectElement.tabIndex = focusable ? 0 : -1;
}
public render(container: HTMLElement): void {
container.classList.add('select-container');
container.appendChild(this.selectElement);

View File

@@ -73,6 +73,7 @@
align-items: center;
justify-content: center;
color: inherit;
outline-offset: -2px;
}
.monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container {

View File

@@ -22,6 +22,7 @@
.monaco-table-tr {
display: flex;
height: 100%;
}
.monaco-table-th {
@@ -35,18 +36,12 @@
.monaco-table-th,
.monaco-table-td {
box-sizing: border-box;
padding-left: 10px;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.monaco-table-th[data-col-index="0"],
.monaco-table-td[data-col-index="0"] {
padding-left: 20px;
}
.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before {
content: "";
position: absolute;

View File

@@ -250,7 +250,10 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
this.cachedHeight = height;
this.splitview.layout(width);
this.list.layout(height - this.virtualDelegate.headerRowHeight, width);
const listHeight = height - this.virtualDelegate.headerRowHeight;
this.list.getHTMLElement().style.height = `${listHeight}px`;
this.list.layout(listHeight, width);
}
toggleKeyboardNavigation(): void {
@@ -273,6 +276,14 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
this.list.domFocus();
}
setAnchor(index: number | undefined): void {
this.list.setAnchor(index);
}
getAnchor(): number | undefined {
return this.list.getAnchor();
}
getSelectedElements(): TRow[] {
return this.list.getSelectedElements();
}
@@ -297,12 +308,12 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
this.list.focusPrevious(n, loop, browserEvent);
}
focusNextPage(browserEvent?: UIEvent): void {
this.list.focusNextPage(browserEvent);
focusNextPage(browserEvent?: UIEvent): Promise<void> {
return this.list.focusNextPage(browserEvent);
}
focusPreviousPage(browserEvent?: UIEvent): void {
this.list.focusPreviousPage(browserEvent);
focusPreviousPage(browserEvent?: UIEvent): Promise<void> {
return this.list.focusPreviousPage(browserEvent);
}
focusFirst(browserEvent?: UIEvent): void {
@@ -317,6 +328,10 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
return this.list.getFocus();
}
getFocusedElements(): TRow[] {
return this.list.getFocusedElements();
}
reveal(index: number, relativeTop?: number): void {
this.list.reveal(index, relativeTop);
}

View File

@@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd';
import { range, equals, distinctES6 } from 'vs/base/common/arrays';
import { range, equals, distinctES6, firstOrDefault } from 'vs/base/common/arrays';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { domEvent } from 'vs/base/browser/event';
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
@@ -683,6 +683,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
if (typeof options.filterOnType !== 'undefined') {
this._filterOnType = !!options.filterOnType;
this.filterOnTypeDomNode.checked = this._filterOnType;
this.updateFilterOnTypeTitleAndIcon();
}
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
@@ -1168,6 +1169,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
renderers: IListRenderer<any /* TODO@joao */, any>[],
private focusTrait: Trait<T>,
private selectionTrait: Trait<T>,
private anchorTrait: Trait<T>,
options: ITreeNodeListOptions<T, TFilterData, TRef>
) {
super(user, container, virtualDelegate, renderers, options);
@@ -1186,6 +1188,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
const additionalFocus: number[] = [];
const additionalSelection: number[] = [];
let anchor: number | undefined;
elements.forEach((node, index) => {
if (this.focusTrait.has(node)) {
@@ -1195,6 +1198,10 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
if (this.selectionTrait.has(node)) {
additionalSelection.push(start + index);
}
if (this.anchorTrait.has(node)) {
anchor = start + index;
}
});
if (additionalFocus.length > 0) {
@@ -1204,6 +1211,10 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
if (additionalSelection.length > 0) {
super.setSelection(distinctES6([...super.getSelection(), ...additionalSelection]));
}
if (typeof anchor === 'number') {
super.setAnchor(anchor);
}
}
setFocus(indexes: number[], browserEvent?: UIEvent, fromAPI = false): void {
@@ -1221,6 +1232,18 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>>
this.selectionTrait.set(indexes.map(i => this.element(i)), browserEvent);
}
}
setAnchor(index: number | undefined, fromAPI = false): void {
super.setAnchor(index);
if (!fromAPI) {
if (typeof index === 'undefined') {
this.anchorTrait.set([]);
} else {
this.anchorTrait.set([this.element(index)]);
}
}
}
}
export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable {
@@ -1230,6 +1253,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
protected model: ITreeModel<T, TFilterData, TRef>;
private focus: Trait<T>;
private selection: Trait<T>;
private anchor: Trait<T>;
private eventBufferer = new EventBufferer();
private typeFilterController?: TypeFilterController<T, TFilterData>;
private focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;
@@ -1298,7 +1322,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.focus = new Trait(_options.identityProvider);
this.selection = new Trait(_options.identityProvider);
this.view = new TreeNodeList(user, container, treeDelegate, this.renderers, this.focus, this.selection, { ...asListOptions(() => this.model, _options), tree: this });
this.anchor = new Trait(_options.identityProvider);
this.view = new TreeNodeList(user, container, treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this });
this.model = this.createModel(user, this.view, _options);
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
@@ -1552,6 +1577,25 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.model.refilter();
}
setAnchor(element: TRef | undefined): void {
if (typeof element === 'undefined') {
return this.view.setAnchor(undefined);
}
const node = this.model.getNode(element);
this.anchor.set([node]);
const index = this.model.getListIndex(element);
if (index > -1) {
this.view.setAnchor(index, true);
}
}
getAnchor(): T | undefined {
return firstOrDefault(this.anchor.get(), undefined);
}
setSelection(elements: TRef[], browserEvent?: UIEvent): void {
const nodes = elements.map(e => this.model.getNode(e));
this.selection.set(nodes, browserEvent);
@@ -1580,12 +1624,12 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.view.focusPrevious(n, loop, browserEvent, filter);
}
focusNextPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void {
this.view.focusNextPage(browserEvent, filter);
focusNextPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): Promise<void> {
return this.view.focusNextPage(browserEvent, filter);
}
focusPreviousPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void {
this.view.focusPreviousPage(browserEvent, filter);
focusPreviousPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): Promise<void> {
return this.view.focusPreviousPage(browserEvent, filter);
}
focusLast(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void {

View File

@@ -616,7 +616,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return this.tree.isCollapsible(this.getDataNode(element));
}
isCollapsed(element: T): boolean {
isCollapsed(element: TInput | T): boolean {
return this.tree.isCollapsed(this.getDataNode(element));
}
@@ -628,6 +628,15 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.tree.refilter();
}
setAnchor(element: T | undefined): void {
this.tree.setAnchor(typeof element === 'undefined' ? undefined : this.getDataNode(element));
}
getAnchor(): T | undefined {
const node = this.tree.getAnchor();
return node?.element as T;
}
setSelection(elements: T[], browserEvent?: UIEvent): void {
const nodes = elements.map(e => this.getDataNode(e));
this.tree.setSelection(nodes, browserEvent);
@@ -651,12 +660,12 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.tree.focusPrevious(n, loop, browserEvent);
}
focusNextPage(browserEvent?: UIEvent): void {
this.tree.focusNextPage(browserEvent);
focusNextPage(browserEvent?: UIEvent): Promise<void> {
return this.tree.focusNextPage(browserEvent);
}
focusPreviousPage(browserEvent?: UIEvent): void {
this.tree.focusPreviousPage(browserEvent);
focusPreviousPage(browserEvent?: UIEvent): Promise<void> {
return this.tree.focusPreviousPage(browserEvent);
}
focusLast(browserEvent?: UIEvent): void {

View File

@@ -8,7 +8,6 @@ import { IndexTreeModel, IIndexTreeModelOptions, IList, IIndexTreeModelSpliceOpt
import { Event } from 'vs/base/common/event';
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree';
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { mergeSort } from 'vs/base/common/arrays';
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
@@ -130,7 +129,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
private preserveCollapseState(elements: Iterable<ITreeElement<T>> = Iterable.empty()): Iterable<ITreeElement<T>> {
if (this.sorter) {
elements = mergeSort([...elements], this.sorter.compare.bind(this.sorter));
elements = [...elements].sort(this.sorter.compare.bind(this.sorter));
}
return Iterable.map(elements, treeElement => {
@@ -185,7 +184,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
let childrenNodes = [...node.children] as ITreeNode<T, TFilterData>[];
if (recursive || first) {
childrenNodes = mergeSort(childrenNodes, this.sorter!.compare.bind(this.sorter));
childrenNodes = childrenNodes.sort(this.sorter!.compare.bind(this.sorter));
}
return Iterable.map<ITreeNode<T | null, TFilterData>, ITreeElement<T>>(childrenNodes, node => ({

View File

@@ -3,23 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { mergeSort } from 'vs/base/common/arrays';
import { URI } from 'vs/base/common/uri';
/**
* @deprecated use `FileAccess.asFileUri(relativePath, requireFn).fsPath`
*/
export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string {
return getUriFromAmdModule(requirefn, relativePath).fsPath;
}
/**
* @deprecated use `FileAccess.asFileUri()` for node.js contexts or `FileAccess.asBrowserUri` for browser contexts.
*/
export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI {
return URI.parse(requirefn.toUrl(relativePath));
}
export abstract class LoaderStats {
abstract get amdLoad(): [string, number][];
abstract get amdInvoke(): [string, number][];
@@ -27,10 +10,7 @@ export abstract class LoaderStats {
abstract get nodeEval(): [string, number][];
abstract get nodeRequireTotal(): number;
static get(): LoaderStats {
const amdLoadScript = new Map<string, number>();
const amdInvokeFactory = new Map<string, number>();
const nodeRequire = new Map<string, number>();
@@ -60,7 +40,7 @@ export abstract class LoaderStats {
map.set(stat.detail, duration + stat.timestamp);
}
const stats = mergeSort(require.getStats().slice(0), (a, b) => a.timestamp - b.timestamp);
const stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp);
for (const stat of stats) {
switch (stat.type) {

View File

@@ -121,58 +121,10 @@ export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {
}
}
/**
* Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort`
* so only use this when actually needing stable sort.
*/
export function mergeSort<T>(data: T[], compare: Compare<T>): T[] {
_sort(data, compare, 0, data.length - 1, []);
return data;
}
function _merge<T>(a: T[], compare: Compare<T>, lo: number, mid: number, hi: number, aux: T[]): void {
let leftIdx = lo, rightIdx = mid + 1;
for (let i = lo; i <= hi; i++) {
aux[i] = a[i];
}
for (let i = lo; i <= hi; i++) {
if (leftIdx > mid) {
// left side consumed
a[i] = aux[rightIdx++];
} else if (rightIdx > hi) {
// right side consumed
a[i] = aux[leftIdx++];
} else if (compare(aux[rightIdx], aux[leftIdx]) < 0) {
// right element is less -> comes first
a[i] = aux[rightIdx++];
} else {
// left element comes first (less or equal)
a[i] = aux[leftIdx++];
}
}
}
function _sort<T>(a: T[], compare: Compare<T>, lo: number, hi: number, aux: T[]) {
if (hi <= lo) {
return;
}
const mid = lo + ((hi - lo) / 2) | 0;
_sort(a, compare, lo, mid, aux);
_sort(a, compare, mid + 1, hi, aux);
if (compare(a[mid], a[mid + 1]) <= 0) {
// left and right are sorted and if the last-left element is less
// or equals than the first-right element there is nothing else
// to do
return;
}
_merge(a, compare, lo, mid, hi, aux);
}
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
const result: T[][] = [];
let currentGroup: T[] | undefined = undefined;
for (const element of mergeSort(data.slice(0), compare)) {
for (const element of data.slice(0).sort(compare)) {
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
currentGroup = [element];
result.push(currentGroup);

View File

@@ -8,6 +8,7 @@ import { canceled, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event, Listener } from 'vs/base/common/event';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
export function isThenable<T>(obj: unknown): obj is Promise<T> {
@@ -592,19 +593,21 @@ export class ResourceQueue implements IDisposable {
private readonly queues = new Map<string, Queue<void>>();
queueFor(resource: URI): Queue<void> {
const key = resource.toString();
if (!this.queues.has(key)) {
const queue = new Queue<void>();
queue.onFinished(() => {
queue.dispose();
queueFor(resource: URI, extUri: IExtUri = defaultExtUri): Queue<void> {
const key = extUri.getComparisonKey(resource);
let queue = this.queues.get(key);
if (!queue) {
queue = new Queue<void>();
Event.once(queue.onFinished)(() => {
queue?.dispose();
this.queues.delete(key);
});
this.queues.set(key, queue);
}
return this.queues.get(key)!;
return queue;
}
dispose(): void {

View File

@@ -548,6 +548,9 @@ export namespace Codicon {
export const runBelow = new Codicon('run-below', { fontCharacter: '\\ebbe' });
export const notebookTemplate = new Codicon('notebook-template', { fontCharacter: '\\ebbf' });
export const debugRerun = new Codicon('debug-rerun', { fontCharacter: '\\ebc0' });
export const workspaceTrusted = new Codicon('workspace-trusted', { fontCharacter: '\\ebc1' });
export const workspaceUntrusted = new Codicon('workspace-untrusted', { fontCharacter: '\\ebc2' });
export const workspaceUnknown = new Codicon('workspace-unknown', { fontCharacter: '\\ebc3' });
export const dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition);
}

View File

@@ -838,12 +838,8 @@ export class LcsDiff {
let modifiedStop = 0;
if (i > 0) {
const prevChange = changes[i - 1];
if (prevChange.originalLength > 0) {
originalStop = prevChange.originalStart + prevChange.originalLength;
}
if (prevChange.modifiedLength > 0) {
modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;
}
originalStop = prevChange.originalStart + prevChange.originalLength;
modifiedStop = prevChange.modifiedStart + prevChange.modifiedLength;
}
const checkOriginal = change.originalLength > 0;
@@ -868,7 +864,11 @@ export class LcsDiff {
break;
}
const score = this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength);
const touchingPreviousChange = (originalStart === originalStop && modifiedStart === modifiedStop);
const score = (
(touchingPreviousChange ? 5 : 0)
+ this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength)
);
if (score > bestScore) {
bestScore = score;
@@ -878,6 +878,14 @@ export class LcsDiff {
change.originalStart -= bestDelta;
change.modifiedStart -= bestDelta;
const mergedChangeArr: Array<DiffChange | null> = [null];
if (i > 0 && this.ChangesOverlap(changes[i - 1], changes[i], mergedChangeArr)) {
changes[i - 1] = mergedChangeArr[0]!;
changes.splice(i, 1);
i++;
continue;
}
}
// There could be multiple longest common substrings.

View File

@@ -365,28 +365,15 @@ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
return score ? createMatches(score) : null;
}
export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore {
const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
if (result) {
return result;
}
let matches: number[] = [];
let score = 0;
let idx = _wordPos;
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
if (wordPos >= 0) {
score += 1;
matches.unshift(wordPos);
idx = wordPos + 1;
} else if (matches.length > 0) {
// once we have started matching things
// we need to match the remaining pattern
// characters
break;
export function anyScore(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number): FuzzyScore {
const max = Math.min(13, pattern.length);
for (; patternPos < max; patternPos++) {
const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, false);
if (result) {
return result;
}
}
return [score, _wordPos, ...matches];
return [0, wordPos];
}
//#region --- fuzzyScore ---

View File

@@ -5,7 +5,6 @@
import { ParseError, Node, JSONPath, Segment, parseTree, findNodeAtLocation } from './json';
import { Edit, format, isEOL, FormattingOptions } from './jsonFormatter';
import { mergeSort } from 'vs/base/common/arrays';
export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] {
@@ -156,7 +155,7 @@ export function applyEdit(text: string, edit: Edit): string {
}
export function applyEdits(text: string, edits: Edit[]): string {
let sortedEdits = mergeSort(edits, (a, b) => {
let sortedEdits = edits.slice(0).sort((a, b) => {
const diff = a.offset - b.offset;
if (diff === 0) {
return a.length - b.length;

View File

@@ -7,113 +7,117 @@
//@ts-check
/**
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
*/
function _definePolyfillMarks(timeOrigin) {
(function () {
const _data = [];
if (typeof timeOrigin === 'number') {
_data.push('code/timeOrigin', timeOrigin);
}
/**
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
*/
function _definePolyfillMarks(timeOrigin) {
function mark(name) {
_data.push(name, Date.now());
}
function getMarks() {
const result = [];
for (let i = 0; i < _data.length; i += 2) {
result.push({
name: _data[i],
startTime: _data[i + 1],
});
const _data = [];
if (typeof timeOrigin === 'number') {
_data.push('code/timeOrigin', timeOrigin);
}
return result;
function mark(name) {
_data.push(name, Date.now());
}
function getMarks() {
const result = [];
for (let i = 0; i < _data.length; i += 2) {
result.push({
name: _data[i],
startTime: _data[i + 1],
});
}
return result;
}
return { mark, getMarks };
}
return { mark, getMarks };
}
/**
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
*/
function _define() {
/**
* @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}}
*/
function _define() {
if (typeof performance === 'object' && typeof performance.mark === 'function') {
// in a browser context, reuse performance-util
if (typeof performance === 'object' && typeof performance.mark === 'function') {
// in a browser context, reuse performance-util
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {
// safari & webworker: because there is no timeOrigin and no workaround
// we use the `Date.now`-based polyfill.
return _definePolyfillMarks();
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {
// safari & webworker: because there is no timeOrigin and no workaround
// we use the `Date.now`-based polyfill.
return _definePolyfillMarks();
} else {
// use "native" performance for mark and getMarks
return {
mark(name) {
performance.mark(name);
},
getMarks() {
let timeOrigin = performance.timeOrigin;
if (typeof timeOrigin !== 'number') {
// safari: there is no timerOrigin but in renderers there is the timing-property
// see https://bugs.webkit.org/show_bug.cgi?id=174862
timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart;
}
const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }];
for (const entry of performance.getEntriesByType('mark')) {
result.push({
name: entry.name,
startTime: Math.round(timeOrigin + entry.startTime)
});
}
return result;
}
};
}
} else if (typeof process === 'object') {
// node.js: use the normal polyfill but add the timeOrigin
// from the node perf_hooks API as very first mark
const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin);
return _definePolyfillMarks(timeOrigin);
} else {
// use "native" performance for mark and getMarks
return {
mark(name) {
performance.mark(name);
},
getMarks() {
let timeOrigin = performance.timeOrigin;
if (typeof timeOrigin !== 'number') {
// safari: there is no timerOrigin but in renderers there is the timing-property
// see https://bugs.webkit.org/show_bug.cgi?id=174862
timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart;
}
const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }];
for (const entry of performance.getEntriesByType('mark')) {
result.push({
name: entry.name,
startTime: Math.round(timeOrigin + entry.startTime)
});
}
return result;
}
};
// unknown environment
console.trace('perf-util loaded in UNKNOWN environment');
return _definePolyfillMarks();
}
}
} else if (typeof process === 'object') {
// node.js: use the normal polyfill but add the timeOrigin
// from the node perf_hooks API as very first mark
const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin);
return _definePolyfillMarks(timeOrigin);
function _factory(sharedObj) {
if (!sharedObj.MonacoPerformanceMarks) {
sharedObj.MonacoPerformanceMarks = _define();
}
return sharedObj.MonacoPerformanceMarks;
}
// This module can be loaded in an amd and commonjs-context.
// Because we want both instances to use the same perf-data
// we store them globally
// eslint-disable-next-line no-var
var sharedObj;
if (typeof global === 'object') {
// nodejs
sharedObj = global;
} else if (typeof self === 'object') {
// browser
sharedObj = self;
} else {
// unknown environment
console.trace('perf-util loaded in UNKNOWN environment');
return _definePolyfillMarks();
sharedObj = {};
}
}
function _factory(sharedObj) {
if (!sharedObj.MonacoPerformanceMarks) {
sharedObj.MonacoPerformanceMarks = _define();
if (typeof define === 'function') {
// amd
define([], function () { return _factory(sharedObj); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
// commonjs
module.exports = _factory(sharedObj);
} else {
console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)');
sharedObj.perf = _factory(sharedObj);
}
return sharedObj.MonacoPerformanceMarks;
}
// This module can be loaded in an amd and commonjs-context.
// Because we want both instances to use the same perf-data
// we store them globally
// eslint-disable-next-line no-var
var sharedObj;
if (typeof global === 'object') {
// nodejs
sharedObj = global;
} else if (typeof self === 'object') {
// browser
sharedObj = self;
} else {
sharedObj = {};
}
if (typeof define === 'function') {
// amd
define([], function () { return _factory(sharedObj); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
// commonjs
module.exports = _factory(sharedObj);
} else {
console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)');
sharedObj.perf = _factory(sharedObj);
}
})();

View File

@@ -27,37 +27,38 @@ export interface IProcessEnvironment {
[key: string]: string;
}
/**
* This interface is intentionally not identical to node.js
* process because it also works in sandboxed environments
* where the process object is implemented differently. We
* define the properties here that we need for `platform`
* to work and nothing else.
*/
export interface INodeProcess {
platform: 'win32' | 'linux' | 'darwin';
platform: string;
env: IProcessEnvironment;
nextTick: Function;
nextTick?: (callback: (...args: any[]) => void) => void;
versions?: {
electron?: string;
};
sandboxed?: boolean; // Electron
sandboxed?: boolean;
type?: string;
cwd(): string;
cwd: () => string;
}
declare const process: INodeProcess;
declare const global: any;
declare const global: unknown;
declare const self: unknown;
interface INavigator {
userAgent: string;
language: string;
maxTouchPoints?: number;
}
declare const navigator: INavigator;
declare const self: any;
const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any);
export const globals: any = (typeof self === 'object' ? self : typeof global === 'object' ? global : {});
let nodeProcess: INodeProcess | undefined = undefined;
if (typeof process !== 'undefined') {
// Native environment (non-sandboxed)
nodeProcess = process;
} else if (typeof _globals.vscode !== 'undefined') {
} else if (typeof globals.vscode !== 'undefined') {
// Native environment (sandboxed)
nodeProcess = _globals.vscode.process;
nodeProcess = globals.vscode.process;
}
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
@@ -83,6 +84,13 @@ export const browserCodeLoadingCacheStrategy: 'none' | 'code' | 'bypassHeatCheck
})();
export const isPreferringBrowserCodeLoad = typeof browserCodeLoadingCacheStrategy === 'string';
interface INavigator {
userAgent: string;
language: string;
maxTouchPoints?: number;
}
declare const navigator: INavigator;
// Web environment
if (typeof navigator === 'object' && !isElectronRenderer) {
_userAgent = navigator.userAgent;
@@ -209,10 +217,8 @@ export const locale = _locale;
*/
export const translationsConfigFile = _translationsConfigFile;
export const globals: any = _globals;
interface ISetImmediate {
(callback: (...args: any[]) => void): void;
(callback: (...args: unknown[]) => void): void;
}
export const setImmediate: ISetImmediate = (function defineSetImmediate() {
@@ -247,11 +253,11 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
};
}
if (nodeProcess && typeof nodeProcess.nextTick === 'function') {
if (typeof nodeProcess?.nextTick === 'function') {
return nodeProcess.nextTick.bind(nodeProcess);
}
const _promise = Promise.resolve();
return (callback: (...args: any[]) => void) => _promise.then(callback);
return (callback: (...args: unknown[]) => void) => _promise.then(callback);
})();
export const enum OperatingSystem {

View File

@@ -5,26 +5,27 @@
import { isWindows, isMacintosh, setImmediate, globals, INodeProcess } from 'vs/base/common/platform';
declare const process: INodeProcess;
let safeProcess: INodeProcess;
let safeProcess: INodeProcess & { nextTick: (callback: (...args: any[]) => void) => void; };
// Native node.js environment
declare const process: INodeProcess;
if (typeof process !== 'undefined') {
safeProcess = process;
safeProcess = {
get platform() { return process.platform; },
get env() { return process.env; },
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); },
nextTick(callback: (...args: any[]) => void): void { return process.nextTick!(callback); }
};
}
// Native sandbox environment
else if (typeof globals.vscode !== 'undefined') {
const sandboxProcess: INodeProcess = globals.vscode.process;
safeProcess = {
// Supported
get platform(): 'win32' | 'linux' | 'darwin' { return globals.vscode.process.platform; },
get env() { return globals.vscode.process.env; },
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
// Unsupported
cwd(): string { return globals.vscode.process.env['VSCODE_CWD'] || globals.vscode.process.execPath.substr(0, globals.vscode.process.execPath.lastIndexOf(globals.vscode.process.platform === 'win32' ? '\\' : '/')); }
get platform() { return sandboxProcess.platform; },
get env() { return sandboxProcess.env; },
cwd() { return sandboxProcess.cwd(); },
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }
};
}
@@ -33,16 +34,39 @@ else {
safeProcess = {
// Supported
get platform(): 'win32' | 'linux' | 'darwin' { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
// Unsupported
get env() { return Object.create(null); },
cwd(): string { return '/'; }
cwd() { return '/'; }
};
}
/**
* Provides safe access to the `cwd` property in node.js, sandboxed or web
* environments.
*
* Note: in web, this property is hardcoded to be `/`.
*/
export const cwd = safeProcess.cwd;
/**
* Provides safe access to the `env` property in node.js, sandboxed or web
* environments.
*
* Note: in web, this property is hardcoded to be `{}`.
*/
export const env = safeProcess.env;
/**
* Provides safe access to the `platform` property in node.js, sandboxed or web
* environments.
*/
export const platform = safeProcess.platform;
/**
* Provides safe access to the `nextTick` method in node.js, sandboxed or web
* environments.
*/
export const nextTick = safeProcess.nextTick;

View File

@@ -20,6 +20,8 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
return pattern.toLowerCase();
} else if (strings.containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) {
return pattern[0].toUpperCase() + pattern.substr(1);
} else if (matches[0][0].toUpperCase() !== matches[0][0] && pattern.length > 0) {
return pattern[0].toLowerCase() + pattern.substr(1);
} else {
// we don't understand its pattern yet.
return pattern;

View File

@@ -101,19 +101,19 @@ export interface WriteableStream<T> extends ReadableStream<T> {
/**
* Signals an error to the consumer of the stream via the
* on('error') handler if the stream is flowing.
*
* NOTE: call `end` to signal that the stream has ended,
* this DOES NOT happen automatically from `error`.
*/
error(error: Error): void;
/**
* Signals the end of the stream to the consumer. If the
* result is not an error, will trigger the on('data') event
* result is provided, will trigger the on('data') event
* listener if the stream is flowing and buffer the data
* otherwise until the stream is flowing.
*
* In case of an error, the on('error') event will be used
* if the stream is flowing.
*/
end(result?: T | Error): void;
end(result?: T): void;
}
/**
@@ -267,15 +267,13 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
}
}
end(result?: T | Error): void {
end(result?: T): void {
if (this.state.destroyed) {
return;
}
// end with data or error if provided
if (result instanceof Error) {
this.error(result);
} else if (typeof result !== 'undefined') {
// end with data if provided
if (typeof result !== 'undefined') {
this.write(result);
}

View File

@@ -50,6 +50,13 @@ export function isNumber(obj: unknown): obj is number {
return (typeof obj === 'number' && !isNaN(obj));
}
/**
* @returns whether the provided parameter is an Iterable, casting to the given generic
*/
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
return !!obj && typeof (obj as any)[Symbol.iterator] === 'function';
}
/**
* @returns whether the provided parameter is a JavaScript Boolean or not.
*/

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { promisify } from 'util';
import { rtrim } from 'vs/base/common/strings';
import { sep, join, normalize, dirname, basename } from 'vs/base/common/path';
import { readdirSync } from 'vs/base/node/pfs';
@@ -52,7 +53,11 @@ export function realcaseSync(path: string): string | null {
export async function realpath(path: string): Promise<string> {
try {
return await fs.promises.realpath(path);
// DO NOT USE `fs.promises.realpath` here as it internally
// calls `fs.native.realpath` which will result in subst
// drives to be resolved to their target on Windows
// https://github.com/microsoft/vscode/issues/118562
return await promisify(fs.realpath)(path);
} catch (error) {
// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization

View File

@@ -6,27 +6,13 @@
import * as fs from 'fs';
import { tmpdir } from 'os';
import { join } from 'vs/base/common/path';
import { Queue } from 'vs/base/common/async';
import { ResourceQueue } from 'vs/base/common/async';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { Event } from 'vs/base/common/event';
import { isEqualOrParent, isRootOrDriveLetter } from 'vs/base/common/extpath';
import { generateUuid } from 'vs/base/common/uuid';
import { normalizeNFC } from 'vs/base/common/normalization';
//#region Constants
// See https://github.com/microsoft/vscode/issues/30180
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB
// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149
const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB
const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB
export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;
export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE;
//#endregion
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
//#region rimraf
@@ -361,6 +347,11 @@ export namespace SymlinkSupport {
//#region Write File
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
const writeQueues = new ResourceQueue();
/**
* Same as `fs.writeFile` but with an additional call to
* `fs.fdatasync` after writing to ensure changes are
@@ -373,47 +364,13 @@ export function writeFile(path: string, data: Buffer, options?: IWriteFileOption
export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void> {
const queueKey = toQueueKey(path);
return ensureWriteFileQueue(queueKey).queue(() => {
return writeQueues.queueFor(URI.file(path), extUriBiasedIgnorePathCase).queue(() => {
const ensuredOptions = ensureWriteOptions(options);
return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()));
});
}
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
const writeFilePathQueues: Map<string, Queue<void>> = new Map();
function toQueueKey(path: string): string {
let queueKey = path;
if (isWindows || isMacintosh) {
queueKey = queueKey.toLowerCase(); // accommodate for case insensitive file systems
}
return queueKey;
}
function ensureWriteFileQueue(queueKey: string): Queue<void> {
const existingWriteFileQueue = writeFilePathQueues.get(queueKey);
if (existingWriteFileQueue) {
return existingWriteFileQueue;
}
const writeFileQueue = new Queue<void>();
writeFilePathQueues.set(queueKey, writeFileQueue);
const onFinish = Event.once(writeFileQueue.onFinished);
onFinish(() => {
writeFilePathQueues.delete(queueKey);
writeFileQueue.dispose();
});
return writeFileQueue;
}
export interface IWriteFileOptions {
mode?: number;
flag?: string;
@@ -607,9 +564,6 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr
// Symlink
if (symbolicLink) {
if (symbolicLink.dangling) {
return; // do not copy dangling symbolic links (https://github.com/microsoft/vscode/issues/111621)
}
// Try to re-create the symlink unless `preserveSymlinks: false`
if (payload.options.preserveSymlinks) {
@@ -620,6 +574,10 @@ async function doCopy(source: string, target: string, payload: ICopyPayload): Pr
console.warn('[node.js fs] copy of symlink failed: ', error);
}
}
if (symbolicLink.dangling) {
return; // skip dangling symbolic links from here on (https://github.com/microsoft/vscode/issues/111621)
}
}
// Folder

View File

@@ -6,7 +6,6 @@
import * as pfs from 'vs/base/node/pfs';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { env } from 'vs/base/common/process';
// This is required, since parseInt("7-preview") will return 7.
const IntRegex: RegExp = /^\d+$/;
@@ -103,17 +102,17 @@ function getProgramFilesPath(
if (!useAlternateBitness) {
// Just use the native system bitness
return env.ProgramFiles || null;
return process.env.ProgramFiles || null;
}
// We might be a 64-bit process looking for 32-bit program files
if (processArch === Arch.x64) {
return env['ProgramFiles(x86)'] || null;
return process.env['ProgramFiles(x86)'] || null;
}
// We might be a 32-bit process looking for 64-bit program files
if (osArch === Arch.x64) {
return env.ProgramW6432 || null;
return process.env.ProgramW6432 || null;
}
// We're a 32-bit process on 32-bit Windows, there is no other Program Files dir
@@ -194,12 +193,12 @@ async function findPSCoreWindowsInstallation(
async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise<IPossiblePowerShellExe | null> {
// We can't proceed if there's no LOCALAPPDATA path
if (!env.LOCALAPPDATA) {
if (!process.env.LOCALAPPDATA) {
return null;
}
// Find the base directory for MSIX application exe shortcuts
const msixAppDir = path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
const msixAppDir = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WindowsApps');
if (!await pfs.SymlinkSupport.existsDirectory(msixAppDir)) {
return null;
@@ -211,15 +210,11 @@ async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}):
: { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' };
// We should find only one such application, so return on the first one
try {
for (const subdir of await pfs.readdir(msixAppDir)) {
if (pwshMsixDirRegex.test(subdir)) {
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
}
for (const subdir of await pfs.readdir(msixAppDir)) {
if (pwshMsixDirRegex.test(subdir)) {
const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe');
return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName);
}
} catch (err) {
console.warn(`Unable to read MSIX directory (${msixAppDir}) because of the following error: ${err}`);
}
// If we find nothing, return null
@@ -234,7 +229,7 @@ function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe {
function findWinPS(): IPossiblePowerShellExe | null {
const winPSPath = path.join(
env.windir!,
process.env.windir!,
processArch === Arch.x86 && osArch !== Arch.x86 ? 'SysNative' : 'System32',
'WindowsPowerShell', 'v1.0', 'powershell.exe');

View File

@@ -8,6 +8,7 @@ import * as fs from 'fs';
import * as pfs from 'vs/base/node/pfs';
import * as cp from 'child_process';
import * as nls from 'vs/nls';
import * as process from 'vs/base/common/process';
import * as Types from 'vs/base/common/types';
import { IStringDictionary } from 'vs/base/common/collections';
import * as Objects from 'vs/base/common/objects';

View File

@@ -13,7 +13,7 @@ import * as processes from 'vs/base/node/processes';
* shell that the terminal uses by default.
* @param p The platform to detect the shell of.
*/
export async function getSystemShell(p: platform.Platform, env = process.env as platform.IProcessEnvironment): Promise<string> {
export async function getSystemShell(p: platform.Platform, env: platform.IProcessEnvironment): Promise<string> {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindows();
@@ -25,7 +25,7 @@ export async function getSystemShell(p: platform.Platform, env = process.env as
return getSystemShellUnixLike(p, env);
}
export function getSystemShellSync(p: platform.Platform, env = process.env as platform.IProcessEnvironment): string {
export function getSystemShellSync(p: platform.Platform, env: platform.IProcessEnvironment): string {
if (p === platform.Platform.Windows) {
if (platform.isWindows) {
return getSystemShellWindowsSync(env);
@@ -45,7 +45,7 @@ function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvi
}
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
let unixLikeTerminal: string;
let unixLikeTerminal: string | undefined;
if (platform.isWindows) {
unixLikeTerminal = '/bin/bash'; // for WSL
} else {

View File

@@ -1,9 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Returns the user data path to use.
*/
export function getDefaultUserDataPath(): string;

View File

@@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="../../../typings/require.d.ts" />
//@ts-check
(function () {
'use strict';
/**
* @param {typeof import('path')} path
* @param {typeof import('os')} os
* @param {string} productName
*/
function factory(path, os, productName) {
function getDefaultUserDataPath() {
// Support global VSCODE_APPDATA environment variable
let appDataPath = process.env['VSCODE_APPDATA'];
// Otherwise check per platform
if (!appDataPath) {
switch (process.platform) {
case 'win32':
appDataPath = process.env['APPDATA'];
if (!appDataPath) {
const userProfile = process.env['USERPROFILE'];
if (typeof userProfile !== 'string') {
throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable');
}
appDataPath = path.join(userProfile, 'AppData', 'Roaming');
}
break;
case 'darwin':
appDataPath = path.join(os.homedir(), 'Library', 'Application Support');
break;
case 'linux':
appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
break;
default:
throw new Error('Platform not supported');
}
}
return path.join(appDataPath, productName);
}
return {
getDefaultUserDataPath
};
}
if (typeof define === 'function') {
define(['require', 'path', 'os', 'vs/base/common/network', 'vs/base/common/resources'], function (require, /** @type {typeof import('path')} */ path, /** @type {typeof import('os')} */ os, /** @type {typeof import('../common/network')} */ network, /** @type {typeof import("../common/resources")} */ resources) {
const rootPath = resources.dirname(network.FileAccess.asFileUri('', require));
const pkg = require.__$__nodeRequire(resources.joinPath(rootPath, 'package.json').fsPath);
return factory(path, os, pkg.name);
}); // amd
} else if (typeof module === 'object' && typeof module.exports === 'object') {
const pkg = require('../../../../package.json');
const path = require('path');
const os = require('os');
module.exports = factory(path, os, pkg.name); // commonjs
} else {
throw new Error('Unknown context');
}
}());

View File

@@ -18,13 +18,31 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Platform, platform } from 'vs/base/common/platform';
export class NodeSocket implements ISocket {
public readonly socket: Socket;
private readonly _errorListener: (err: any) => void;
constructor(socket: Socket) {
this.socket = socket;
this._errorListener = (err: any) => {
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
// so ignore the error since the socket will fire the close event soon anyways:
// > https://nodejs.org/api/errors.html#errors_common_system_errors
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
// > process to read the data. Commonly encountered at the net and http layers,
// > indicative that the remote side of the stream being written to has been closed.
return;
}
onUnexpectedError(err);
}
};
this.socket.on('error', this._errorListener);
}
public dispose(): void {
this.socket.off('error', this._errorListener);
this.socket.destroy();
}
@@ -62,7 +80,20 @@ export class NodeSocket implements ISocket {
// > However, the false return value is only advisory and the writable stream will unconditionally
// > accept and buffer chunk even if it has not been allowed to drain.
try {
this.socket.write(<Buffer>buffer.buffer);
this.socket.write(<Buffer>buffer.buffer, (err: any) => {
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
// so ignore the error since the socket will fire the close event soon anyways:
// > https://nodejs.org/api/errors.html#errors_common_system_errors
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
// > process to read the data. Commonly encountered at the net and http layers,
// > indicative that the remote side of the stream being written to has been closed.
return;
}
onUnexpectedError(err);
}
});
} catch (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { TestServiceClient } from './testService';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
function createClient(): Client {
return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), {

View File

@@ -7,7 +7,7 @@
position: absolute;
width: 600px;
z-index: 2000;
padding-bottom: 6px;
padding: 0 1px 6px 1px;
left: 50%;
margin-left: -300px;
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/quickInput';
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS, ItemActivation } from 'vs/base/parts/quickinput/common/quickInput';
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS, ItemActivation, QuickInputHideReason, IQuickInputHideEvent } from 'vs/base/parts/quickinput/common/quickInput';
import * as dom from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { QuickInputList, QuickInputListFocus } from './quickInputList';
@@ -31,6 +31,7 @@ import { registerCodicon, Codicon } from 'vs/base/common/codicons';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { escape } from 'vs/base/common/strings';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { isString } from 'vs/base/common/types';
export interface IQuickInputOptions {
idPrefix: string;
@@ -133,6 +134,7 @@ type Visibilities = {
};
class QuickInput extends Disposable implements IQuickInput {
protected static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
private _title: string | undefined;
private _description: string | undefined;
@@ -144,9 +146,14 @@ class QuickInput extends Disposable implements IQuickInput {
private _busy = false;
private _ignoreFocusOut = false;
private _buttons: IQuickInputButton[] = [];
protected noValidationMessage = QuickInput.noPromptMessage;
private _validationMessage: string | undefined;
private _lastValidationMessage: string | undefined;
private _severity: Severity = Severity.Ignore;
private _lastSeverity: Severity | undefined;
private buttonsUpdated = false;
private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
private readonly onDidHideEmitter = this._register(new Emitter<void>());
private readonly onDidHideEmitter = this._register(new Emitter<IQuickInputHideEvent>());
private readonly onDisposeEmitter = this._register(new Emitter<void>());
protected readonly visibleDisposables = this._register(new DisposableStore());
@@ -241,6 +248,24 @@ class QuickInput extends Disposable implements IQuickInput {
this.update();
}
get validationMessage() {
return this._validationMessage;
}
set validationMessage(validationMessage: string | undefined) {
this._validationMessage = validationMessage;
this.update();
}
get severity() {
return this._severity;
}
set severity(severity: Severity) {
this._severity = severity;
this.update();
}
readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event;
show(): void {
@@ -266,10 +291,10 @@ class QuickInput extends Disposable implements IQuickInput {
this.ui.hide();
}
didHide(): void {
didHide(reason = QuickInputHideReason.Other): void {
this.visible = false;
this.visibleDisposables.clear();
this.onDidHideEmitter.fire();
this.onDidHideEmitter.fire({ reason });
}
readonly onDidHide = this.onDidHideEmitter.event;
@@ -328,6 +353,16 @@ class QuickInput extends Disposable implements IQuickInput {
this.ui.ignoreFocusOut = this.ignoreFocusOut;
this.ui.setEnabled(this.enabled);
this.ui.setContextKey(this.contextKey);
const validationMessage = this.validationMessage || this.noValidationMessage;
if (this._lastValidationMessage !== validationMessage) {
this._lastValidationMessage = validationMessage;
dom.reset(this.ui.message, ...renderLabelWithIcons(escape(validationMessage)));
}
if (this._lastSeverity !== this.severity) {
this._lastSeverity = this.severity;
this.showMessageDecoration(this.severity);
}
}
private getTitle() {
@@ -359,7 +394,7 @@ class QuickInput extends Disposable implements IQuickInput {
protected showMessageDecoration(severity: Severity) {
this.ui.inputBox.showDecoration(severity);
if (severity === Severity.Error) {
if (severity !== Severity.Ignore) {
const styles = this.ui.inputBox.stylesForType(severity);
this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : '';
this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';
@@ -414,8 +449,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter<IQuickPickItemButtonEvent<T>>());
private _valueSelection: Readonly<[number, number]> | undefined;
private valueSelectionUpdated = true;
private _validationMessage: string | undefined;
private _lastValidationMessage: string | undefined;
private _ok: boolean | 'default' = 'default';
private _customButton = false;
private _customButtonLabel: string | undefined;
@@ -587,15 +620,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update();
}
get validationMessage() {
return this._validationMessage;
}
set validationMessage(validationMessage: string | undefined) {
this._validationMessage = validationMessage;
this.update();
}
get customButton() {
return this._customButton;
}
@@ -964,12 +988,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.selectedItemsToConfirm = null;
}
}
const validationMessage = this.validationMessage || '';
if (this._lastValidationMessage !== validationMessage) {
this._lastValidationMessage = validationMessage;
dom.reset(this.ui.message, ...renderLabelWithIcons(escape(validationMessage)));
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
}
this.ui.customButton.label = this.customLabel || '';
this.ui.customButton.element.title = this.customHover || '';
this.ui.setComboboxAccessibility(true);
@@ -987,18 +1005,12 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
}
class InputBox extends QuickInput implements IInputBox {
private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
private _value = '';
private _valueSelection: Readonly<[number, number]> | undefined;
private valueSelectionUpdated = true;
private _placeholder: string | undefined;
private _password = false;
private _prompt: string | undefined;
private noValidationMessage = InputBox.noPromptMessage;
private _validationMessage: string | undefined;
private _lastValidationMessage: string | undefined;
private readonly onDidValueChangeEmitter = this._register(new Emitter<string>());
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
@@ -1043,16 +1055,7 @@ class InputBox extends QuickInput implements IInputBox {
this._prompt = prompt;
this.noValidationMessage = prompt
? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt)
: InputBox.noPromptMessage;
this.update();
}
get validationMessage() {
return this._validationMessage;
}
set validationMessage(validationMessage: string | undefined) {
this._validationMessage = validationMessage;
: QuickInput.noPromptMessage;
this.update();
}
@@ -1100,12 +1103,7 @@ class InputBox extends QuickInput implements IInputBox {
if (this.ui.inputBox.password !== this.password) {
this.ui.inputBox.password = this.password;
}
const validationMessage = this.validationMessage || this.noValidationMessage;
if (this._lastValidationMessage !== validationMessage) {
this._lastValidationMessage = validationMessage;
dom.reset(this.ui.message, ...renderLabelWithIcons(validationMessage));
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
}
}
}
@@ -1222,9 +1220,6 @@ export class QuickInputController extends Disposable {
const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`));
const progressBar = new ProgressBar(container);
progressBar.getContainer().classList.add('quick-input-progress');
const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options));
this._register(list.onChangedAllVisibleChecked(checked => {
checkAll.checked = checked;
@@ -1250,6 +1245,9 @@ export class QuickInputController extends Disposable {
}
}));
const progressBar = new ProgressBar(container);
progressBar.getContainer().classList.add('quick-input-progress');
const focusTracker = dom.trackFocus(container);
this._register(focusTracker);
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => {
@@ -1257,7 +1255,7 @@ export class QuickInputController extends Disposable {
}, true));
this._register(focusTracker.onDidBlur(() => {
if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) {
this.hide();
this.hide(QuickInputHideReason.Blur);
}
this.previousFocusElement = undefined;
}));
@@ -1273,7 +1271,7 @@ export class QuickInputController extends Disposable {
break;
case KeyCode.Escape:
dom.EventHelper.stop(e, true);
this.hide();
this.hide(QuickInputHideReason.Gesture);
break;
case KeyCode.Tab:
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
@@ -1320,8 +1318,8 @@ export class QuickInputController extends Disposable {
message,
customButtonContainer,
customButton,
progressBar,
list,
progressBar,
onDidAccept: this.onDidAcceptEmitter.event,
onDidCustom: this.onDidCustomEmitter.event,
onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
@@ -1408,6 +1406,7 @@ export class QuickInputController extends Disposable {
resolve(undefined);
}),
];
input.title = options.title;
input.canSelectMany = !!options.canPickMany;
input.placeholder = options.placeHolder;
input.ignoreFocusOut = !!options.ignoreFocusLost;
@@ -1438,6 +1437,22 @@ export class QuickInputController extends Disposable {
});
}
private setValidationOnInput(input: IInputBox, validationResult: string | {
content: string;
severity: Severity;
} | null | undefined) {
if (validationResult && isString(validationResult)) {
input.severity = Severity.Error;
input.validationMessage = validationResult;
} else if (validationResult && !isString(validationResult)) {
input.severity = validationResult.severity;
input.validationMessage = validationResult.content;
} else {
input.severity = Severity.Ignore;
input.validationMessage = undefined;
}
}
input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise<string | undefined> {
return new Promise<string | undefined>((resolve) => {
if (token.isCancellationRequested) {
@@ -1458,7 +1473,7 @@ export class QuickInputController extends Disposable {
}
validation.then(result => {
if (value === validationValue) {
input.validationMessage = result || undefined;
this.setValidationOnInput(input, result);
}
});
}),
@@ -1469,11 +1484,11 @@ export class QuickInputController extends Disposable {
validationValue = value;
}
validation.then(result => {
if (!result) {
if (!result || (!isString(result) && result.severity !== Severity.Error)) {
resolve(value);
input.hide();
} else if (value === validationValue) {
input.validationMessage = result;
this.setValidationOnInput(input, result);
}
});
}),
@@ -1485,6 +1500,8 @@ export class QuickInputController extends Disposable {
resolve(undefined);
}),
];
input.title = options.title;
input.value = options.value || '';
input.valueSelection = options.valueSelection;
input.prompt = options.prompt;
@@ -1600,7 +1617,7 @@ export class QuickInputController extends Disposable {
}
}
hide() {
hide(reason?: QuickInputHideReason) {
const controller = this.controller;
if (controller) {
const focusChanged = !this.ui?.container.contains(document.activeElement);
@@ -1615,7 +1632,7 @@ export class QuickInputController extends Disposable {
this.options.returnFocus();
}
}
controller.didHide();
controller.didHide(reason);
}
}

View File

@@ -10,6 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { IMatch } from 'vs/base/common/filters';
import { IItemAccessor } from 'vs/base/common/fuzzyScorer';
import { Schemas } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
export interface IQuickPickItemHighlights {
label?: IMatch[];
@@ -58,6 +59,11 @@ export interface IQuickNavigateConfiguration {
export interface IPickOptions<T extends IQuickPickItem> {
/**
* an optional string to show as the title of the quick input
*/
title?: string;
/**
* an optional string to show as placeholder in the input box to guide the user what she picks on
*/
@@ -115,6 +121,11 @@ export interface IPickOptions<T extends IQuickPickItem> {
export interface IInputOptions {
/**
* an optional string to show as the title of the quick input
*/
title?: string;
/**
* the value to prefill in the input box
*/
@@ -145,12 +156,34 @@ export interface IInputOptions {
/**
* an optional function that is used to validate user input.
*/
validateInput?: (input: string) => Promise<string | null | undefined>;
validateInput?: (input: string) => Promise<string | null | undefined | { content: string, severity: Severity }>;
}
export enum QuickInputHideReason {
/**
* Focus moved away from the quick input.
*/
Blur = 1,
/**
* An explicit user gesture, e.g. pressing Escape key.
*/
Gesture,
/**
* Anything else.
*/
Other
}
export interface IQuickInputHideEvent {
reason: QuickInputHideReason;
}
export interface IQuickInput extends IDisposable {
readonly onDidHide: Event<void>;
readonly onDidHide: Event<IQuickInputHideEvent>;
readonly onDispose: Event<void>;
title: string | undefined;
@@ -301,6 +334,8 @@ export interface IInputBox extends IQuickInput {
prompt: string | undefined;
validationMessage: string | undefined;
severity: Severity;
}
export interface IQuickInputButton {

View File

@@ -22,6 +22,8 @@
/**
* A minimal set of methods exposed from Electron's `ipcRenderer`
* to support communication to main process.
*
* @type {import('../electron-sandbox/electronTypes').IpcRenderer}
*/
ipcRenderer: {
@@ -49,34 +51,46 @@
/**
* @param {string} channel
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
* @returns {import('../electron-sandbox/electronTypes').IpcRenderer}
*/
on(channel, listener) {
if (validateIPC(channel)) {
ipcRenderer.on(channel, listener);
return this;
}
},
/**
* @param {string} channel
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
* @returns {import('../electron-sandbox/electronTypes').IpcRenderer}
*/
once(channel, listener) {
if (validateIPC(channel)) {
ipcRenderer.once(channel, listener);
return this;
}
},
/**
* @param {string} channel
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
* @returns {import('../electron-sandbox/electronTypes').IpcRenderer}
*/
removeListener(channel, listener) {
if (validateIPC(channel)) {
ipcRenderer.removeListener(channel, listener);
return this;
}
}
},
/**
* @type {import('../electron-sandbox/globals').IpcMessagePort}
*/
ipcMessagePort: {
/**
@@ -106,6 +120,8 @@
/**
* Support for subset of methods of Electron's `webFrame` type.
*
* @type {import('../electron-sandbox/electronTypes').WebFrame}
*/
webFrame: {
@@ -121,6 +137,8 @@
/**
* Support for subset of methods of Electron's `crashReporter` type.
*
* @type {import('../electron-sandbox/electronTypes').CrashReporter}
*/
crashReporter: {
@@ -138,6 +156,8 @@
*
* Note: when `sandbox` is enabled, the only properties available
* are https://github.com/electron/electron/blob/master/docs/api/process.md#sandbox
*
* @type {import('../electron-sandbox/globals').ISandboxNodeProcess}
*/
process: {
get platform() { return process.platform; },
@@ -146,6 +166,21 @@
get versions() { return process.versions; },
get type() { return 'renderer'; },
get execPath() { return process.execPath; },
get sandboxed() { return process.sandboxed; },
/**
* @returns {string}
*/
cwd() {
return process.env['VSCODE_CWD'] || process.execPath.substr(0, process.execPath.lastIndexOf(process.platform === 'win32' ? '\\' : '/'));
},
/**
* @returns {Promise<typeof process.env>}
*/
getShellEnv() {
return shellEnv;
},
/**
* @param {{[key: string]: string}} userEnv
@@ -164,20 +199,17 @@
/**
* @param {string} type
* @param {() => void} callback
* @param {Function} callback
* @returns {import('../electron-sandbox/globals').ISandboxNodeProcess}
*/
on(type, callback) {
if (validateProcessEventType(type)) {
// @ts-ignore
process.on(type, callback);
return this;
}
}
},
/**
* Some information about the context we are running in.
*/
context: {
get sandbox() { return process.sandboxed; }
}
};
@@ -226,8 +258,8 @@
return true;
}
/** @type {Promise<void> | undefined} */
let resolvedEnv = undefined;
/** @type {Promise<typeof process.env> | undefined} */
let shellEnv = undefined;
/**
* If VSCode is not run from a terminal, we should resolve additional
@@ -238,28 +270,29 @@
* @param {{[key: string]: string}} userEnv
* @returns {Promise<void>}
*/
function resolveEnv(userEnv) {
if (!resolvedEnv) {
async function resolveEnv(userEnv) {
if (!shellEnv) {
// Apply `userEnv` directly
Object.assign(process.env, userEnv);
// Resolve `shellEnv` from the main side
resolvedEnv = new Promise(function (resolve) {
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnv) {
shellEnv = new Promise(function (resolve) {
ipcRenderer.once('vscode:acceptShellEnv', function (event, shellEnvResult) {
if (!process.env['VSCODE_SKIP_PROCESS_ENV_PATCHING'] /* TODO@bpasero for https://github.com/microsoft/vscode/issues/108804 */) {
// Assign all keys of the shell environment to our process environment
// But make sure that the user environment wins in the end over shell environment
Object.assign(process.env, shellEnvResult, userEnv);
}
// Assign all keys of the shell environment to our process environment
// But make sure that the user environment wins in the end
Object.assign(process.env, shellEnv, userEnv);
resolve();
resolve({ ...process.env, ...shellEnvResult, ...userEnv });
});
ipcRenderer.send('vscode:fetchShellEnv');
});
}
return resolvedEnv;
await shellEnv;
}
//#endregion

View File

@@ -3,19 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { globals, IProcessEnvironment } from 'vs/base/common/platform';
import { globals, INodeProcess, IProcessEnvironment } from 'vs/base/common/platform';
import { ProcessMemoryInfo, CrashReporter, IpcRenderer, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
/**
* In sandboxed renderers we cannot expose all of the `process` global of node.js
*/
export interface IPartialNodeProcess {
export interface ISandboxNodeProcess extends INodeProcess {
/**
* The process.platform property returns a string identifying the operating system platform
* on which the Node.js process is running.
*/
readonly platform: 'win32' | 'linux' | 'darwin';
readonly platform: string;
/**
* The process.arch property returns a string identifying the CPU architecture
@@ -24,9 +24,14 @@ export interface IPartialNodeProcess {
readonly arch: string;
/**
* The type will always be Electron renderer.
* The type will always be `renderer`.
*/
readonly type: 'renderer';
readonly type: string;
/**
* Whether the process is sandboxed or not.
*/
readonly sandboxed: boolean;
/**
* A list of versions for the current node.js/electron configuration.
@@ -48,6 +53,11 @@ export interface IPartialNodeProcess {
*/
on: (type: string, callback: Function) => void;
/**
* The current working directory of the process.
*/
cwd: () => string;
/**
* Resolves with a ProcessMemoryInfo
*
@@ -62,9 +72,6 @@ export interface IPartialNodeProcess {
* process on macOS.
*/
getProcessMemoryInfo: () => Promise<ProcessMemoryInfo>;
}
export interface ISandboxNodeProcess extends IPartialNodeProcess {
/**
* A custom method we add to `process`: Resolve the true process environment to use and
@@ -84,14 +91,12 @@ export interface ISandboxNodeProcess extends IPartialNodeProcess {
* set of environment in `process.env`.
*/
resolveEnv(userEnv: IProcessEnvironment): Promise<void>;
}
export interface ISandboxContext {
/**
* Whether the renderer runs with `sandbox` enabled or not.
* Returns a process environment that includes any shell environment even if the application
* was not started from a shell / terminal / console.
*/
sandbox: boolean;
getShellEnv(): Promise<IProcessEnvironment>;
}
export interface IpcMessagePort {
@@ -114,4 +119,3 @@ export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort;
export const webFrame: WebFrame = globals.vscode.webFrame;
export const crashReporter: CrashReporter = globals.vscode.crashReporter;
export const process: ISandboxNodeProcess = globals.vscode.process;
export const context: ISandboxContext = globals.vscode.context;

View File

@@ -8,7 +8,7 @@ import { ipcRenderer, crashReporter, webFrame, process } from 'vs/base/parts/san
suite('Sandbox', () => {
test('globals', () => {
assert.ok(typeof ipcRenderer.invoke === 'function');
assert.ok(typeof ipcRenderer.send === 'function');
assert.ok(typeof crashReporter.addExtraParameter === 'function');
assert.ok(typeof webFrame.setZoomLevel === 'function');
assert.ok(typeof process.platform === 'string');

View File

@@ -49,80 +49,6 @@ suite('Arrays', () => {
assertMedian(13, [13, 4, 8], 2);
});
test('stableSort', () => {
function fill<T>(num: number, valueFn: () => T, arr: T[] = []): T[] {
for (let i = 0; i < num; i++) {
arr[i] = valueFn();
}
return arr;
}
let counter = 0;
let data = fill(10000, () => ({ n: 1, m: counter++ }));
arrays.mergeSort(data, (a, b) => a.n - b.n);
let lastM = -1;
for (const element of data) {
assert.ok(lastM < element.m);
lastM = element.m;
}
});
test('mergeSort', () => {
let data = arrays.mergeSort([6, 5, 3, 1, 8, 7, 2, 4], (a, b) => a - b);
assert.deepStrictEqual(data, [1, 2, 3, 4, 5, 6, 7, 8]);
});
test('mergeSort, sorted array', function () {
let data = arrays.mergeSort([1, 2, 3, 4, 5, 6], (a, b) => a - b);
assert.deepStrictEqual(data, [1, 2, 3, 4, 5, 6]);
});
test('mergeSort, is stable', function () {
let numbers = arrays.mergeSort([33, 22, 11, 4, 99, 1], (a, b) => 0);
assert.deepStrictEqual(numbers, [33, 22, 11, 4, 99, 1]);
});
test('mergeSort, many random numbers', function () {
function compare(a: number, b: number) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}
function assertSorted(array: number[]) {
let last = array[0];
for (let i = 1; i < array.length; i++) {
let n = array[i];
if (last > n) {
assert.fail(JSON.stringify(array.slice(i - 10, i + 10)));
}
}
}
const MAX = 101;
const data: number[][] = [];
for (let i = 1; i < MAX; i++) {
let array: number[] = [];
for (let j = 0; j < 10 + i; j++) {
array.push(Math.random() * 10e8 | 0);
}
data.push(array);
}
for (const array of data) {
arrays.mergeSort(array, compare);
assertSorted(array);
}
});
test('sortedDiff', () => {
function compare(a: number, b: number): number {
return a - b;

View File

@@ -88,7 +88,8 @@ suite('Buffer', () => {
await timeout(0);
stream.write(VSBuffer.fromString('Hello'));
await timeout(0);
stream.end(new Error());
stream.error(new Error());
stream.end();
assert.strictEqual(chunks.length, 1);
assert.strictEqual(chunks[0].toString(), 'Hello');
@@ -329,7 +330,8 @@ suite('Buffer', () => {
await timeout(0);
stream.write(VSBuffer.fromString('Hello'));
await timeout(0);
stream.end(new Error());
stream.error(new Error());
stream.end();
assert.strictEqual(chunks.length, 0);
assert.strictEqual(ended, false);

View File

@@ -66,6 +66,10 @@ suite('Stream', () => {
stream.error(new Error());
assert.strictEqual(error, true);
error = false;
stream.error(new Error());
assert.strictEqual(error, true);
stream.end('Final Bit');
assert.strictEqual(chunks.length, 4);
assert.strictEqual(chunks[3], 'Final Bit');
@@ -86,6 +90,15 @@ suite('Stream', () => {
assert.strictEqual(result, '');
});
test('WriteableStream - end with error works', async () => {
const reducer = (errors: Error[]) => errors.length > 0 ? errors[0] : null as unknown as Error;
const stream = newWriteableStream<Error>(reducer);
stream.end(new Error('error'));
const result = await consumeStream(stream, reducer);
assert.ok(result instanceof Error);
});
test('WriteableStream - removeListener', () => {
const stream = newWriteableStream<string>(strings => strings.join());

View File

@@ -28,3 +28,14 @@ export function testRepeat(n: number, description: string, callback: (this: any)
test(`${description} (iteration ${i})`, callback);
}
}
export async function assertThrowsAsync(block: () => any, message: string | Error = 'Missing expected exception'): Promise<void> {
try {
await block();
} catch {
return;
}
const err = message instanceof Error ? message : new Error(message);
throw err;
}

View File

@@ -8,9 +8,9 @@ import { join } from 'vs/base/common/path';
import { tmpdir } from 'os';
import { promises } from 'fs';
import { rimraf, writeFile } from 'vs/base/node/pfs';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
suite('Crypto', () => {
flakySuite('Crypto', () => {
let testDir: string;

View File

@@ -10,10 +10,9 @@ import { join, sep } from 'vs/base/common/path';
import { generateUuid } from 'vs/base/common/uuid';
import { copy, exists, move, readdir, readDirsInDir, rimraf, RimRafMode, rimrafSync, SymlinkSupport, writeFile, writeFileSync } from 'vs/base/node/pfs';
import { timeout } from 'vs/base/common/async';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { canNormalize } from 'vs/base/common/normalization';
import { VSBuffer } from 'vs/base/common/buffer';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils';
import { isWindows } from 'vs/base/common/platform';
flakySuite('PFS', function () {
@@ -232,14 +231,19 @@ flakySuite('PFS', function () {
assert.ok(!symbolicLink2);
}
// Copy ignores dangling symlinks
// Copy does not fail over dangling symlinks
await rimraf(copyTarget);
await rimraf(symbolicLinkTarget);
await copy(symLink, copyTarget, { preserveSymlinks: true }); // this should not throw
assert.ok(!fs.existsSync(copyTarget));
if (!isWindows) {
const { symbolicLink } = await SymlinkSupport.stat(copyTarget);
assert.ok(symbolicLink?.dangling);
} else {
assert.ok(!fs.existsSync(copyTarget));
}
});
test('copy handles symbolic links when the reference is inside source', async () => {

View File

@@ -8,7 +8,7 @@ import * as cp from 'child_process';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import * as processes from 'vs/base/node/processes';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
function fork(id: string): cp.ChildProcess {
const opts: any = {

View File

@@ -5,12 +5,17 @@
import type { Suite } from 'mocha';
import { join } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
export function getRandomTestPath(tmpdir: string, ...segments: string[]): string {
return join(tmpdir, ...segments, generateUuid());
}
export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string {
return URI.parse(requirefn.toUrl(relativePath)).fsPath;
}
export function flakySuite(title: string, fn: (this: Suite) => void): Suite {
return suite(title, function () {

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { readFileSync } from 'fs';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
suite('URI - perf', function () {

View File

@@ -1,15 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { getDefaultUserDataPath } from 'vs/base/node/userDataPath';
suite('User data path', () => {
test('getDefaultUserDataPath', () => {
const path = getDefaultUserDataPath();
assert.ok(path.length > 0);
});
});

View File

@@ -9,9 +9,8 @@ import { tmpdir } from 'os';
import { promises } from 'fs';
import { extract } from 'vs/base/node/zip';
import { rimraf, exists } from 'vs/base/node/pfs';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { createCancelablePromise } from 'vs/base/common/async';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils';
suite('Zip', () => {

View File

@@ -31,23 +31,15 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
}
// ESM-comment-begin
export function getWorkerBootstrapUrl(scriptPath: string, label: string, forceDataUri: boolean = false): string {
if (forceDataUri || /^((http:)|(https:)|(file:))/.test(scriptPath)) {
const currentUrl = String(window.location);
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
if (forceDataUri || scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
if (forceDataUri) {
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
return url;
}
const blob = new Blob([js], { type: 'application/javascript' });
return URL.createObjectURL(blob);
}
export function getWorkerBootstrapUrl(scriptPath: string, label: string): string {
if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, self.origin.length) !== self.origin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
const blob = new Blob([js], { type: 'application/javascript' });
return URL.createObjectURL(blob);
}
return scriptPath + '#' + label;
}

View File

@@ -10,36 +10,77 @@
const trustedTypesPolicy = (
typeof self.trustedTypes?.createPolicy === 'function'
? self.trustedTypes?.createPolicy('amdLoader', { createScriptURL: value => value })
? self.trustedTypes?.createPolicy('amdLoader', {
createScriptURL: value => value,
createScript: (_, ...args: string[]) => {
// workaround a chrome issue not allowing to create new functions
// see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
const fnArgs = args.slice(0, -1).join(',');
const fnBody = args.pop()!.toString();
const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`;
return body;
}
})
: undefined
);
if (typeof (<any>self).define !== 'function' || !(<any>self).define.amd) {
let loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js';
if (trustedTypesPolicy) {
loaderSrc = trustedTypesPolicy.createScriptURL(loaderSrc);
}
importScripts(loaderSrc as string);
function loadAMDLoader() {
return new Promise<void>((resolve, reject) => {
if (typeof (<any>self).define === 'function' && (<any>self).define.amd) {
return resolve();
}
const loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js';
const isCrossOrigin = (/^((http:)|(https:)|(file:))/.test(loaderSrc) && loaderSrc.substring(0, self.origin.length) !== self.origin);
if (!isCrossOrigin) {
// use `fetch` if possible because `importScripts`
// is synchronous and can lead to deadlocks on Safari
fetch(loaderSrc).then((response) => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.text();
}).then((text) => {
text = `${text}\n//# sourceURL=${loaderSrc}`;
const func = (
trustedTypesPolicy
? self.eval(trustedTypesPolicy.createScript('', text) as unknown as string)
: new Function(text)
);
func.call(self);
resolve();
}).then(undefined, reject);
return;
}
if (trustedTypesPolicy) {
importScripts(trustedTypesPolicy.createScriptURL(loaderSrc) as unknown as string);
} else {
importScripts(loaderSrc as string);
}
resolve();
});
}
require.config({
baseUrl: monacoBaseUrl,
catchError: true,
trustedTypesPolicy,
});
const loadCode = function (moduleId: string) {
loadAMDLoader().then(() => {
require.config({
baseUrl: monacoBaseUrl,
catchError: true,
trustedTypesPolicy,
});
require([moduleId], function (ws) {
setTimeout(function () {
let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => {
(<any>self).postMessage(msg, transfer);
}, null);
let loadCode = function (moduleId: string) {
require([moduleId], function (ws) {
setTimeout(function () {
let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => {
(<any>self).postMessage(msg, transfer);
}, null);
self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data);
while (beforeReadyMessages.length > 0) {
self.onmessage(beforeReadyMessages.shift()!);
}
}, 0);
self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data);
while (beforeReadyMessages.length > 0) {
self.onmessage(beforeReadyMessages.shift()!);
}
}, 0);
});
});
};