chore(vscode): update to 1.55.2
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.monaco-text-button:focus {
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
.monaco-text-button:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -20,4 +20,5 @@ export interface IHoverDelegateOptions {
|
||||
|
||||
export interface IHoverDelegate {
|
||||
showHover(options: IHoverDelegateOptions): IDisposable | undefined;
|
||||
delay: number;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 => ({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
}
|
||||
}());
|
||||
@@ -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
|
||||
|
||||
@@ -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'), {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
z-index: 2000;
|
||||
padding-bottom: 6px;
|
||||
padding: 0 1px 6px 1px;
|
||||
left: 50%;
|
||||
margin-left: -300px;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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', () => {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user