mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-08 17:24:01 -05:00
Merge from vscode 2cd495805cf99b31b6926f08ff4348124b2cf73d
This commit is contained in:
committed by
AzureDataStudio
parent
a8a7559229
commit
1388493cc1
@@ -820,6 +820,7 @@ export function isHTMLElement(o: any): o is HTMLElement {
|
||||
export const EventType = {
|
||||
// Mouse
|
||||
CLICK: 'click',
|
||||
AUXCLICK: 'auxclick',
|
||||
DBLCLICK: 'dblclick',
|
||||
MOUSE_UP: 'mouseup',
|
||||
MOUSE_DOWN: 'mousedown',
|
||||
|
||||
@@ -19,4 +19,6 @@
|
||||
.monaco-count-badge.long {
|
||||
padding: 2px 3px;
|
||||
border-radius: 2px;
|
||||
min-height: auto;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.monaco-dialog-box .dialog-message-row > .codicon {
|
||||
.monaco-dialog-box .dialog-message-row > .dialog-icon.codicon {
|
||||
flex: 0 0 48px;
|
||||
height: 48px;
|
||||
align-self: baseline;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextViewProvider, IAnchor, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
|
||||
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses } from 'vs/base/browser/dom';
|
||||
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses, DOMEvent } from 'vs/base/browser/dom';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
@@ -122,7 +122,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
return !!this.visible;
|
||||
}
|
||||
|
||||
protected onEvent(e: Event, activeElement: HTMLElement): void {
|
||||
protected onEvent(e: DOMEvent, activeElement: HTMLElement): void {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
@@ -294,6 +294,9 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
|
||||
private menuAsChild?: boolean;
|
||||
|
||||
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
|
||||
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) {
|
||||
@@ -339,7 +342,10 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
}
|
||||
|
||||
this.dropdownMenu = this._register(new DropdownMenu(container, options));
|
||||
this._register(this.dropdownMenu.onDidChangeVisibility(visible => this.element?.setAttribute('aria-expanded', `${visible}`)));
|
||||
this._register(this.dropdownMenu.onDidChangeVisibility(visible => {
|
||||
this.element?.setAttribute('aria-expanded', `${visible}`);
|
||||
this._onDidChangeVisibility.fire(visible);
|
||||
}));
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionViewItemProvider: this.actionViewItemProvider,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { tail2 as tail, equals } from 'vs/base/common/arrays';
|
||||
import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, IGridViewOptions, IBoundarySashes } from './gridview';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export { Orientation, Sizing as GridViewSizing, IViewSize, orthogonal, LayoutPriority } from './gridview';
|
||||
export { Orientation, IViewSize, orthogonal, LayoutPriority } from './gridview';
|
||||
|
||||
export const enum Direction {
|
||||
Up,
|
||||
|
||||
@@ -376,6 +376,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
updateElementHeight(index: number, size: number, anchorIndex: number | null): void {
|
||||
if (index < 0 || index >= this.items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.items[index].size === size) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,10 +225,26 @@ class TraitSpliceable<T> implements ISpliceable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
export function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
export function isMonacoEditor(e: HTMLElement): boolean {
|
||||
if (DOM.hasClass(e, 'monaco-editor')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DOM.hasClass(e, 'monaco-list')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!e.parentElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isMonacoEditor(e.parentElement);
|
||||
}
|
||||
|
||||
class KeyboardController<T> implements IDisposable {
|
||||
|
||||
private readonly disposables = new DisposableStore();
|
||||
@@ -572,12 +588,20 @@ export class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
if (isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.activeElement !== e.browserEvent.target) {
|
||||
this.list.domFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(e: IListContextMenuEvent<T>): void {
|
||||
if (isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = typeof e.index === 'undefined' ? [] : [e.index];
|
||||
this.list.setFocus(focus, e.browserEvent);
|
||||
}
|
||||
@@ -587,7 +611,7 @@ export class MouseController<T> implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -621,7 +645,7 @@ export class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
protected onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -942,7 +942,6 @@ export class MenuBar extends Disposable {
|
||||
menuHolder.style.top = `0px`;
|
||||
menuHolder.style.right = `${this.container.clientWidth}px`;
|
||||
menuHolder.style.left = 'auto';
|
||||
console.log(customMenu.buttonElement.getBoundingClientRect().right - this.container.clientWidth);
|
||||
} else {
|
||||
menuHolder.style.top = `${this.container.clientHeight}px`;
|
||||
menuHolder.style.left = `${customMenu.buttonElement.getBoundingClientRect().left}px`;
|
||||
|
||||
@@ -12,3 +12,16 @@
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/** Actions */
|
||||
|
||||
.monaco-workbench .monaco-action-bar .action-item.select-container {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-action-bar .action-item .monaco-select-box {
|
||||
cursor: pointer;
|
||||
min-width: 110px;
|
||||
min-height: 18px;
|
||||
padding: 2px 23px 2px 8px;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuProvider, DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export const CONTEXT = 'context.toolbar';
|
||||
|
||||
@@ -35,17 +36,21 @@ export class ToolBar extends Disposable {
|
||||
private options: IToolBarOptions;
|
||||
private actionBar: ActionBar;
|
||||
private toggleMenuAction: ToggleMenuAction;
|
||||
private toggleMenuActionViewItem = this._register(new MutableDisposable<DropdownMenuActionViewItem>());
|
||||
private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined;
|
||||
private toggleMenuActionViewItemDisposable: IDisposable = Disposable.None;
|
||||
private hasSecondaryActions: boolean = false;
|
||||
private lookupKeybindings: boolean;
|
||||
|
||||
private _onDidChangeDropdownVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event;
|
||||
|
||||
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
|
||||
super();
|
||||
|
||||
this.options = options;
|
||||
this.lookupKeybindings = typeof this.options.getKeyBinding === 'function';
|
||||
|
||||
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem.value && this.toggleMenuActionViewItem.value.show(), options.toggleMenuTitle));
|
||||
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle));
|
||||
|
||||
let element = document.createElement('div');
|
||||
element.className = 'monaco-toolbar';
|
||||
@@ -60,8 +65,10 @@ export class ToolBar extends Disposable {
|
||||
// Return special action item for the toggle menu action
|
||||
if (action.id === ToggleMenuAction.ID) {
|
||||
|
||||
this.toggleMenuActionViewItemDisposable.dispose();
|
||||
|
||||
// Create new
|
||||
this.toggleMenuActionViewItem.value = new DropdownMenuActionViewItem(
|
||||
this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(
|
||||
action,
|
||||
(<ToggleMenuAction>action).menuActions,
|
||||
contextMenuProvider,
|
||||
@@ -72,9 +79,14 @@ export class ToolBar extends Disposable {
|
||||
this.options.anchorAlignmentProvider,
|
||||
true
|
||||
);
|
||||
this.toggleMenuActionViewItem.value.setActionContext(this.actionBar.context);
|
||||
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
|
||||
|
||||
return this.toggleMenuActionViewItem.value;
|
||||
this.toggleMenuActionViewItemDisposable = combinedDisposable(
|
||||
this.toggleMenuActionViewItem,
|
||||
this.toggleMenuActionViewItem.onDidChangeVisibility(e => this._onDidChangeDropdownVisibility.fire(e))
|
||||
);
|
||||
|
||||
return this.toggleMenuActionViewItem;
|
||||
}
|
||||
|
||||
return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined;
|
||||
@@ -92,8 +104,8 @@ export class ToolBar extends Disposable {
|
||||
|
||||
set context(context: unknown) {
|
||||
this.actionBar.context = context;
|
||||
if (this.toggleMenuActionViewItem.value) {
|
||||
this.toggleMenuActionViewItem.value.setActionContext(context);
|
||||
if (this.toggleMenuActionViewItem) {
|
||||
this.toggleMenuActionViewItem.setActionContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,23 +125,21 @@ export class ToolBar extends Disposable {
|
||||
this.actionBar.setAriaLabel(label);
|
||||
}
|
||||
|
||||
setActions(primaryActions: ReadonlyArray<IAction>, secondaryActions?: ReadonlyArray<IAction>): () => void {
|
||||
return () => {
|
||||
let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
|
||||
setActions(primaryActions: ReadonlyArray<IAction>, secondaryActions?: ReadonlyArray<IAction>): void {
|
||||
let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
|
||||
|
||||
// Inject additional action to open secondary actions if present
|
||||
this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0);
|
||||
if (this.hasSecondaryActions && secondaryActions) {
|
||||
this.toggleMenuAction.menuActions = secondaryActions.slice(0);
|
||||
primaryActionsToSet.push(this.toggleMenuAction);
|
||||
}
|
||||
// Inject additional action to open secondary actions if present
|
||||
this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0);
|
||||
if (this.hasSecondaryActions && secondaryActions) {
|
||||
this.toggleMenuAction.menuActions = secondaryActions.slice(0);
|
||||
primaryActionsToSet.push(this.toggleMenuAction);
|
||||
}
|
||||
|
||||
this.actionBar.clear();
|
||||
this.actionBar.clear();
|
||||
|
||||
primaryActionsToSet.forEach(action => {
|
||||
this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) });
|
||||
});
|
||||
};
|
||||
primaryActionsToSet.forEach(action => {
|
||||
this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) });
|
||||
});
|
||||
}
|
||||
|
||||
private getKeybindingLabel(action: IAction): string | undefined {
|
||||
@@ -153,6 +163,11 @@ export class ToolBar extends Disposable {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this.toggleMenuActionViewItemDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleMenuAction extends Action {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./media/tree';
|
||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate, isInputElement, isMonacoEditor } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
@@ -917,10 +917,6 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMouseEvent<T> {
|
||||
let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown;
|
||||
|
||||
@@ -1084,7 +1080,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
||||
}
|
||||
|
||||
protected onViewPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
@@ -126,7 +126,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
list: IList<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, list, options);
|
||||
@@ -290,6 +290,16 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
this.model.rerender(compressedNode);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
const compressedNode = this.getCompressedNode(element);
|
||||
|
||||
if (!compressedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.updateElementHeight(compressedNode, height);
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
this.model.refilter();
|
||||
}
|
||||
@@ -340,10 +350,13 @@ class CompressedTreeNodeWrapper<T, TFilterData> implements ITreeNode<T | null, T
|
||||
) { }
|
||||
}
|
||||
|
||||
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: ISpliceable<ITreeNode<T, TFilterData>>): ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>> {
|
||||
function mapList<T, TFilterData>(nodeMapper: CompressedNodeWeakMapper<T, TFilterData>, list: IList<ITreeNode<T, TFilterData>>): IList<ITreeNode<ICompressedTreeNode<T>, TFilterData>> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, toInsert: ITreeNode<ICompressedTreeNode<T>, TFilterData>[]): void {
|
||||
list.splice(start, deleteCount, toInsert.map(node => nodeMapper.map(node)) as ITreeNode<T, TFilterData>[]);
|
||||
},
|
||||
updateElementHeight(index: number, height: number): void {
|
||||
list.updateElementHeight(index, height);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -402,7 +415,7 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
list: IList<ITreeNode<T, TFilterData>>,
|
||||
options: ICompressibleObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.elementMapper = options.elementMapper || DefaultElementMapper;
|
||||
@@ -492,6 +505,10 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
||||
return this.model.rerender(location);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
this.model.updateElementHeight(element, height);
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
return this.model.refilter();
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource, TreeError } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
@@ -171,7 +171,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
return { elements, size: children.length };
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
import 'vs/css!./media/tree';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ITreeElement, ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
@@ -41,7 +40,11 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
|
||||
this.model.rerender(location);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
updateElementHeight(location: number[], height: number): void {
|
||||
this.model.updateElementHeight(location, height);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
return new IndexTreeModel(user, view, this.rootElement, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ function isCollapsibleStateUpdate(update: CollapseStateUpdate): update is Collap
|
||||
return typeof (update as any).collapsible === 'boolean';
|
||||
}
|
||||
|
||||
export interface IList<T> extends ISpliceable<T> {
|
||||
updateElementHeight(index: number, height: number): void;
|
||||
}
|
||||
|
||||
export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
|
||||
|
||||
readonly rootRef = [];
|
||||
@@ -78,7 +82,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
private list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
private list: IList<ITreeNode<T, TFilterData>>,
|
||||
rootElement: T,
|
||||
options: IIndexTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
@@ -212,6 +216,15 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
}
|
||||
|
||||
updateElementHeight(location: number[], height: number): void {
|
||||
if (location.length === 0) {
|
||||
throw new TreeError(this.user, 'Invalid tree location');
|
||||
}
|
||||
|
||||
const { listIndex } = this.getTreeNodeWithListIndex(location);
|
||||
this.list.updateElementHeight(listIndex, height);
|
||||
}
|
||||
|
||||
has(location: number[]): boolean {
|
||||
return this.hasTreeNode(location);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
@@ -46,6 +46,10 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
this.model.rerender(element);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
this.model.updateElementHeight(element, height);
|
||||
}
|
||||
|
||||
resort(element: T, recursive = true): void {
|
||||
this.model.resort(element, recursive);
|
||||
}
|
||||
@@ -54,7 +58,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
return this.model.has(element);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(user, view, options);
|
||||
}
|
||||
}
|
||||
@@ -188,7 +192,7 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
protected createModel(user: string, view: IList<ITreeNode<T, TFilterData>>, options: ICompressibleObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new CompressibleObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
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';
|
||||
@@ -16,6 +15,7 @@ export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>
|
||||
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
|
||||
setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined): void;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
updateElementHeight(element: T, height: number): void;
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
@@ -41,7 +41,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
list: ISpliceable<ITreeNode<T, TFilterData>>,
|
||||
list: IList<ITreeNode<T, TFilterData>>,
|
||||
options: IObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new IndexTreeModel(user, list, null, options);
|
||||
@@ -169,6 +169,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
this.model.rerender(location);
|
||||
}
|
||||
|
||||
updateElementHeight(element: T, height: number): void {
|
||||
const location = this.getElementLocation(element);
|
||||
this.model.updateElementHeight(location, height);
|
||||
}
|
||||
|
||||
resort(element: T | null = null, recursive = true): void {
|
||||
if (!this.sorter) {
|
||||
return;
|
||||
|
||||
@@ -62,6 +62,14 @@ export namespace Schemas {
|
||||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
|
||||
/**
|
||||
* Scheme used for loading the wrapper html and script in webviews.
|
||||
*/
|
||||
export const vscodeWebview = 'vscode-webview';
|
||||
|
||||
/**
|
||||
* Scheme used for loading resources inside of webviews.
|
||||
*/
|
||||
export const vscodeWebviewResource = 'vscode-webview-resource';
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,7 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_userAgent = navigator.userAgent;
|
||||
_isWindows = _userAgent.indexOf('Windows') >= 0;
|
||||
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
|
||||
_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||
_isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||
_isLinux = _userAgent.indexOf('Linux') >= 0;
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
@@ -208,7 +208,7 @@ export const enum OperatingSystem {
|
||||
Macintosh = 2,
|
||||
Linux = 3
|
||||
}
|
||||
export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
|
||||
export const OS = (_isMacintosh || _isIOS ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
|
||||
|
||||
let _isLittleEndian = true;
|
||||
let _isLittleEndianComputed = false;
|
||||
|
||||
@@ -18,7 +18,7 @@ export function buildReplaceStringWithCasePreserved(matches: string[] | null, pa
|
||||
return pattern.toUpperCase();
|
||||
} else if (matches[0].toLowerCase() === matches[0]) {
|
||||
return pattern.toLowerCase();
|
||||
} else if (strings.containsUppercaseCharacter(matches[0][0])) {
|
||||
} else if (strings.containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) {
|
||||
return pattern[0].toUpperCase() + pattern.substr(1);
|
||||
} else {
|
||||
// we don't understand its pattern yet.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface ISplice<T> {
|
||||
readonly start: number;
|
||||
@@ -32,3 +33,26 @@ export class Sequence<T> implements ISequence<T>, ISpliceable<T> {
|
||||
this._onDidSplice.fire({ start, deleteCount, toInsert });
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleSequence<T> implements ISequence<T> {
|
||||
|
||||
private _elements: T[];
|
||||
get elements(): T[] { return this._elements; }
|
||||
|
||||
readonly onDidSplice: Event<ISplice<T>>;
|
||||
private disposable: IDisposable;
|
||||
|
||||
constructor(elements: T[], onDidAdd: Event<T>, onDidRemove: Event<T>) {
|
||||
this._elements = [...elements];
|
||||
this.onDidSplice = Event.any(
|
||||
Event.map(onDidAdd, e => ({ start: this.elements.length, deleteCount: 0, toInsert: [e] })),
|
||||
Event.map(Event.filter(Event.map(onDidRemove, e => this.elements.indexOf(e)), i => i > -1), i => ({ start: i, deleteCount: 1, toInsert: [] }))
|
||||
);
|
||||
|
||||
this.disposable = this.onDidSplice(({ start, deleteCount, toInsert }) => this._elements.splice(start, deleteCount, ...toInsert));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DecoderStream } from 'iconv-lite';
|
||||
import { Readable, ReadableStream, newWriteableStream } from 'vs/base/common/stream';
|
||||
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
|
||||
|
||||
export const UTF8 = 'utf8';
|
||||
export const UTF8_with_bom = 'utf8bom';
|
||||
export const UTF16be = 'utf16be';
|
||||
export const UTF16le = 'utf16le';
|
||||
|
||||
export type UTF_ENCODING = typeof UTF8 | typeof UTF8_with_bom | typeof UTF16be | typeof UTF16le;
|
||||
|
||||
export function isUTFEncoding(encoding: string): encoding is UTF_ENCODING {
|
||||
return [UTF8, UTF8_with_bom, UTF16be, UTF16le].some(utfEncoding => utfEncoding === encoding);
|
||||
}
|
||||
|
||||
export const UTF16be_BOM = [0xFE, 0xFF];
|
||||
export const UTF16le_BOM = [0xFF, 0xFE];
|
||||
export const UTF8_BOM = [0xEF, 0xBB, 0xBF];
|
||||
|
||||
const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not
|
||||
const NO_ENCODING_GUESS_MIN_BYTES = 512; // when not auto guessing the encoding, small number of bytes are enough
|
||||
const AUTO_ENCODING_GUESS_MIN_BYTES = 512 * 8; // with auto guessing we want a lot more content to be read for guessing
|
||||
const AUTO_ENCODING_GUESS_MAX_BYTES = 512 * 128; // set an upper limit for the number of bytes we pass on to jschardet
|
||||
|
||||
export interface IDecodeStreamOptions {
|
||||
guessEncoding: boolean;
|
||||
minBytesRequiredForDetection?: number;
|
||||
|
||||
overwriteEncoding(detectedEncoding: string | null): Promise<string>;
|
||||
}
|
||||
|
||||
export interface IDecodeStreamResult {
|
||||
stream: ReadableStream<string>;
|
||||
detected: IDetectedEncodingResult;
|
||||
}
|
||||
|
||||
export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
|
||||
const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
|
||||
|
||||
return new Promise<IDecodeStreamResult>((resolve, reject) => {
|
||||
const target = newWriteableStream<string>(strings => strings.join(''));
|
||||
|
||||
const bufferedChunks: VSBuffer[] = [];
|
||||
let bytesBuffered = 0;
|
||||
|
||||
let decoder: DecoderStream | undefined = undefined;
|
||||
|
||||
const createDecoder = async () => {
|
||||
try {
|
||||
|
||||
// detect encoding from buffer
|
||||
const detected = await detectEncodingFromBuffer({
|
||||
buffer: VSBuffer.concat(bufferedChunks),
|
||||
bytesRead: bytesBuffered
|
||||
}, options.guessEncoding);
|
||||
|
||||
// ensure to respect overwrite of encoding
|
||||
detected.encoding = await options.overwriteEncoding(detected.encoding);
|
||||
|
||||
// decode and write buffered content
|
||||
const iconv = await import('iconv-lite');
|
||||
decoder = iconv.getDecoder(toNodeEncoding(detected.encoding));
|
||||
const decoded = decoder.write(Buffer.from(VSBuffer.concat(bufferedChunks).buffer));
|
||||
target.write(decoded);
|
||||
|
||||
bufferedChunks.length = 0;
|
||||
bytesBuffered = 0;
|
||||
|
||||
// signal to the outside our detected encoding and final decoder stream
|
||||
resolve({
|
||||
stream: target,
|
||||
detected
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Stream error: forward to target
|
||||
source.on('error', error => target.error(error));
|
||||
|
||||
// Stream data
|
||||
source.on('data', async chunk => {
|
||||
|
||||
// if the decoder is ready, we just write directly
|
||||
if (decoder) {
|
||||
target.write(decoder.write(Buffer.from(chunk.buffer)));
|
||||
}
|
||||
|
||||
// otherwise we need to buffer the data until the stream is ready
|
||||
else {
|
||||
bufferedChunks.push(chunk);
|
||||
bytesBuffered += chunk.byteLength;
|
||||
|
||||
// buffered enough data for encoding detection, create stream
|
||||
if (bytesBuffered >= minBytesRequiredForDetection) {
|
||||
|
||||
// pause stream here until the decoder is ready
|
||||
source.pause();
|
||||
|
||||
await createDecoder();
|
||||
|
||||
// resume stream now that decoder is ready but
|
||||
// outside of this stack to reduce recursion
|
||||
setTimeout(() => source.resume());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Stream end
|
||||
source.on('end', async () => {
|
||||
|
||||
// we were still waiting for data to do the encoding
|
||||
// detection. thus, wrap up starting the stream even
|
||||
// without all the data to get things going
|
||||
if (!decoder) {
|
||||
await createDecoder();
|
||||
}
|
||||
|
||||
// end the target with the remainders of the decoder
|
||||
target.end(decoder?.end());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function toEncodeReadable(readable: Readable<string>, encoding: string, options?: { addBOM?: boolean }): Promise<VSBufferReadable> {
|
||||
const iconv = await import('iconv-lite');
|
||||
const encoder = iconv.getEncoder(toNodeEncoding(encoding), options);
|
||||
|
||||
let bytesRead = 0;
|
||||
let done = false;
|
||||
|
||||
return {
|
||||
read() {
|
||||
if (done) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chunk = readable.read();
|
||||
if (typeof chunk !== 'string') {
|
||||
done = true;
|
||||
|
||||
// If we are instructed to add a BOM but we detect that no
|
||||
// bytes have been read, we must ensure to return the BOM
|
||||
// ourselves so that we comply with the contract.
|
||||
if (bytesRead === 0 && options?.addBOM) {
|
||||
switch (encoding) {
|
||||
case UTF8:
|
||||
case UTF8_with_bom:
|
||||
return VSBuffer.wrap(Uint8Array.from(UTF8_BOM));
|
||||
case UTF16be:
|
||||
return VSBuffer.wrap(Uint8Array.from(UTF16be_BOM));
|
||||
case UTF16le:
|
||||
return VSBuffer.wrap(Uint8Array.from(UTF16le_BOM));
|
||||
}
|
||||
}
|
||||
|
||||
const leftovers = encoder.end();
|
||||
if (leftovers && leftovers.length > 0) {
|
||||
return VSBuffer.wrap(leftovers);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bytesRead += chunk.length;
|
||||
|
||||
return VSBuffer.wrap(encoder.write(chunk));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function encodingExists(encoding: string): Promise<boolean> {
|
||||
const iconv = await import('iconv-lite');
|
||||
|
||||
return iconv.encodingExists(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function toNodeEncoding(enc: string | null): string {
|
||||
if (enc === UTF8_with_bom || enc === null) {
|
||||
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
|
||||
}
|
||||
|
||||
return enc;
|
||||
}
|
||||
|
||||
export function detectEncodingByBOMFromBuffer(buffer: VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
|
||||
if (!buffer || bytesRead < UTF16be_BOM.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const b0 = buffer.readUInt8(0);
|
||||
const b1 = buffer.readUInt8(1);
|
||||
|
||||
// UTF-16 BE
|
||||
if (b0 === UTF16be_BOM[0] && b1 === UTF16be_BOM[1]) {
|
||||
return UTF16be;
|
||||
}
|
||||
|
||||
// UTF-16 LE
|
||||
if (b0 === UTF16le_BOM[0] && b1 === UTF16le_BOM[1]) {
|
||||
return UTF16le;
|
||||
}
|
||||
|
||||
if (bytesRead < UTF8_BOM.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const b2 = buffer.readUInt8(2);
|
||||
|
||||
// UTF-8
|
||||
if (b0 === UTF8_BOM[0] && b1 === UTF8_BOM[1] && b2 === UTF8_BOM[2]) {
|
||||
return UTF8_with_bom;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// we explicitly ignore a specific set of encodings from auto guessing
|
||||
// - ASCII: we never want this encoding (most UTF-8 files would happily detect as
|
||||
// ASCII files and then you could not type non-ASCII characters anymore)
|
||||
// - UTF-16: we have our own detection logic for UTF-16
|
||||
// - UTF-32: we do not support this encoding in VSCode
|
||||
const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32'];
|
||||
|
||||
/**
|
||||
* Guesses the encoding from buffer.
|
||||
*/
|
||||
async function guessEncodingByBuffer(buffer: VSBuffer): Promise<string | null> {
|
||||
const jschardet = await import('jschardet');
|
||||
|
||||
const guessed = jschardet.detect(Buffer.from(buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES).buffer)); // ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
|
||||
if (!guessed || !guessed.encoding) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enc = guessed.encoding.toLowerCase();
|
||||
if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
|
||||
return null; // see comment above why we ignore some encodings
|
||||
}
|
||||
|
||||
return toIconvLiteEncoding(guessed.encoding);
|
||||
}
|
||||
|
||||
const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
|
||||
'ibm866': 'cp866',
|
||||
'big5': 'cp950'
|
||||
};
|
||||
|
||||
function toIconvLiteEncoding(encodingName: string): string {
|
||||
const normalizedEncodingName = encodingName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
||||
const mapped = JSCHARDET_TO_ICONV_ENCODINGS[normalizedEncodingName];
|
||||
|
||||
return mapped || normalizedEncodingName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The encodings that are allowed in a settings file don't match the canonical encoding labels specified by WHATWG.
|
||||
* See https://encoding.spec.whatwg.org/#names-and-labels
|
||||
* Iconv-lite strips all non-alphanumeric characters, but ripgrep doesn't. For backcompat, allow these labels.
|
||||
*/
|
||||
export function toCanonicalName(enc: string): string {
|
||||
switch (enc) {
|
||||
case 'shiftjis':
|
||||
return 'shift-jis';
|
||||
case 'utf16le':
|
||||
return 'utf-16le';
|
||||
case 'utf16be':
|
||||
return 'utf-16be';
|
||||
case 'big5hkscs':
|
||||
return 'big5-hkscs';
|
||||
case 'eucjp':
|
||||
return 'euc-jp';
|
||||
case 'euckr':
|
||||
return 'euc-kr';
|
||||
case 'koi8r':
|
||||
return 'koi8-r';
|
||||
case 'koi8u':
|
||||
return 'koi8-u';
|
||||
case 'macroman':
|
||||
return 'x-mac-roman';
|
||||
case 'utf8bom':
|
||||
return 'utf8';
|
||||
default:
|
||||
const m = enc.match(/windows(\d+)/);
|
||||
if (m) {
|
||||
return 'windows-' + m[1];
|
||||
}
|
||||
|
||||
return enc;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDetectedEncodingResult {
|
||||
encoding: string | null;
|
||||
seemsBinary: boolean;
|
||||
}
|
||||
|
||||
export interface IReadResult {
|
||||
buffer: VSBuffer | null;
|
||||
bytesRead: number;
|
||||
}
|
||||
|
||||
export function detectEncodingFromBuffer(readResult: IReadResult, autoGuessEncoding?: false): IDetectedEncodingResult;
|
||||
export function detectEncodingFromBuffer(readResult: IReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult>;
|
||||
export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult> | IDetectedEncodingResult {
|
||||
|
||||
// Always first check for BOM to find out about encoding
|
||||
let encoding = detectEncodingByBOMFromBuffer(buffer, bytesRead);
|
||||
|
||||
// Detect 0 bytes to see if file is binary or UTF-16 LE/BE
|
||||
// unless we already know that this file has a UTF-16 encoding
|
||||
let seemsBinary = false;
|
||||
if (encoding !== UTF16be && encoding !== UTF16le && buffer) {
|
||||
let couldBeUTF16LE = true; // e.g. 0xAA 0x00
|
||||
let couldBeUTF16BE = true; // e.g. 0x00 0xAA
|
||||
let containsZeroByte = false;
|
||||
|
||||
// This is a simplified guess to detect UTF-16 BE or LE by just checking if
|
||||
// the first 512 bytes have the 0-byte at a specific location. For UTF-16 LE
|
||||
// this would be the odd byte index and for UTF-16 BE the even one.
|
||||
// Note: this can produce false positives (a binary file that uses a 2-byte
|
||||
// encoding of the same format as UTF-16) and false negatives (a UTF-16 file
|
||||
// that is using 4 bytes to encode a character).
|
||||
for (let i = 0; i < bytesRead && i < ZERO_BYTE_DETECTION_BUFFER_MAX_LEN; i++) {
|
||||
const isEndian = (i % 2 === 1); // assume 2-byte sequences typical for UTF-16
|
||||
const isZeroByte = (buffer.readUInt8(i) === 0);
|
||||
|
||||
if (isZeroByte) {
|
||||
containsZeroByte = true;
|
||||
}
|
||||
|
||||
// UTF-16 LE: expect e.g. 0xAA 0x00
|
||||
if (couldBeUTF16LE && (isEndian && !isZeroByte || !isEndian && isZeroByte)) {
|
||||
couldBeUTF16LE = false;
|
||||
}
|
||||
|
||||
// UTF-16 BE: expect e.g. 0x00 0xAA
|
||||
if (couldBeUTF16BE && (isEndian && isZeroByte || !isEndian && !isZeroByte)) {
|
||||
couldBeUTF16BE = false;
|
||||
}
|
||||
|
||||
// Return if this is neither UTF16-LE nor UTF16-BE and thus treat as binary
|
||||
if (isZeroByte && !couldBeUTF16LE && !couldBeUTF16BE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case of 0-byte included
|
||||
if (containsZeroByte) {
|
||||
if (couldBeUTF16LE) {
|
||||
encoding = UTF16le;
|
||||
} else if (couldBeUTF16BE) {
|
||||
encoding = UTF16be;
|
||||
} else {
|
||||
seemsBinary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto guess encoding if configured
|
||||
if (autoGuessEncoding && !seemsBinary && !encoding && buffer) {
|
||||
return guessEncodingByBuffer(buffer.slice(0, bytesRead)).then(guessedEncoding => {
|
||||
return {
|
||||
seemsBinary: false,
|
||||
encoding: guessedEncoding
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return { seemsBinary, encoding };
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small.
|
||||
*/
|
||||
import { exec } from 'child_process';
|
||||
import * as os from 'os';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
const windowsTerminalEncodings = {
|
||||
'437': 'cp437', // United States
|
||||
@@ -39,7 +39,6 @@ const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
|
||||
|
||||
const UTF8 = 'utf8';
|
||||
|
||||
|
||||
export async function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
|
||||
let rawEncodingPromise: Promise<string>;
|
||||
|
||||
@@ -54,7 +53,7 @@ export async function resolveTerminalEncoding(verbose?: boolean): Promise<string
|
||||
}
|
||||
|
||||
// Windows: educated guess
|
||||
else if (os.platform() === 'win32') {
|
||||
else if (isWindows) {
|
||||
rawEncodingPromise = new Promise<string>(resolve => {
|
||||
if (verbose) {
|
||||
console.log('Running "chcp" to detect terminal encoding...');
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
vertical-align: middle;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
min-height: auto;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.quick-input-action {
|
||||
|
||||
@@ -353,10 +353,12 @@ class QuickInput extends Disposable implements IQuickInput {
|
||||
this.ui.inputBox.showDecoration(severity);
|
||||
if (severity === Severity.Error) {
|
||||
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}` : '';
|
||||
this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : '';
|
||||
this.ui.message.style.paddingBottom = '4px';
|
||||
} else {
|
||||
this.ui.message.style.color = '';
|
||||
this.ui.message.style.backgroundColor = '';
|
||||
this.ui.message.style.border = '';
|
||||
this.ui.message.style.paddingBottom = '';
|
||||
|
||||
@@ -21,7 +21,7 @@ export function request(options: IRequestOptions, token: CancellationToken): Pro
|
||||
setRequestHeaders(xhr, options);
|
||||
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onerror = e => reject(new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText)));
|
||||
xhr.onerror = e => reject(new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText) || 'XHR failed'));
|
||||
xhr.onload = (e) => {
|
||||
resolve({
|
||||
res: {
|
||||
|
||||
@@ -247,3 +247,37 @@ export interface FileFilter {
|
||||
extensions: string[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface InputEvent {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/input-event
|
||||
|
||||
/**
|
||||
* An array of modifiers of the event, can be `shift`, `control`, `alt`, `meta`,
|
||||
* `isKeypad`, `isAutoRepeat`, `leftButtonDown`, `middleButtonDown`,
|
||||
* `rightButtonDown`, `capsLock`, `numLock`, `left`, `right`.
|
||||
*/
|
||||
modifiers: Array<'shift' | 'control' | 'alt' | 'meta' | 'isKeypad' | 'isAutoRepeat' | 'leftButtonDown' | 'middleButtonDown' | 'rightButtonDown' | 'capsLock' | 'numLock' | 'left' | 'right'>;
|
||||
}
|
||||
|
||||
export interface MouseInputEvent extends InputEvent {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/mouse-input-event
|
||||
|
||||
/**
|
||||
* The button pressed, can be `left`, `middle`, `right`.
|
||||
*/
|
||||
button?: ('left' | 'middle' | 'right');
|
||||
clickCount?: number;
|
||||
globalX?: number;
|
||||
globalY?: number;
|
||||
movementX?: number;
|
||||
movementY?: number;
|
||||
/**
|
||||
* The type of the event, can be `mouseDown`, `mouseUp`, `mouseEnter`,
|
||||
* `mouseLeave`, `contextMenu`, `mouseWheel` or `mouseMove`.
|
||||
*/
|
||||
type: ('mouseDown' | 'mouseUp' | 'mouseEnter' | 'mouseLeave' | 'contextMenu' | 'mouseWheel' | 'mouseMove');
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
interface IResolvedCompressedTreeElement<T> extends ICompressedTreeElement<T> {
|
||||
readonly element: T;
|
||||
@@ -289,11 +289,12 @@ suite('CompressedObjectTree', function () {
|
||||
});
|
||||
});
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
},
|
||||
updateElementHeight() { }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -305,7 +306,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
@@ -313,7 +314,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{ element: 0 },
|
||||
@@ -340,7 +341,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
@@ -376,7 +377,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('compressed', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new CompressedObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IndexTreeModel, IIndexTreeNode } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { IndexTreeModel, IIndexTreeNode, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
},
|
||||
updateElementHeight() { }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
});
|
||||
|
||||
test('insert', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 0 },
|
||||
@@ -53,7 +53,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('deep insert', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -90,7 +90,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('deep insert collapsed', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -118,7 +118,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('delete', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 0 },
|
||||
@@ -143,7 +143,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('nested delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -177,7 +177,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('deep delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -205,7 +205,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('hidden delete', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -230,7 +230,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('collapse', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -261,7 +261,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('expand', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -301,7 +301,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('collapse should recursively adjust visible count', function () {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -334,7 +334,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('setCollapsible', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -403,7 +403,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -437,7 +437,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -460,7 +460,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -499,7 +499,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -545,7 +545,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -591,7 +591,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -639,7 +639,7 @@ suite('IndexTreeModel', function () {
|
||||
|
||||
test('simple', function () {
|
||||
const list: IIndexTreeNode<number>[] = [];
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1);
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1);
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -669,7 +669,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<number>('test', toSpliceable(list), -1, { filter });
|
||||
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{
|
||||
@@ -701,7 +701,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 'silver' },
|
||||
@@ -735,7 +735,7 @@ suite('IndexTreeModel', function () {
|
||||
}
|
||||
};
|
||||
|
||||
const model = new IndexTreeModel<string>('test', toSpliceable(list), 'root', { filter });
|
||||
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
|
||||
|
||||
model.splice([0], 0, [
|
||||
{ element: 'a', children: [{ element: 'aa' }] },
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
function toSpliceable<T>(arr: T[]): ISpliceable<T> {
|
||||
function toList<T>(arr: T[]): IList<T> {
|
||||
return {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
// console.log(`splice (${start}, ${deleteCount}, ${elements.length} [${elements.join(', ')}] )`); // debugging
|
||||
arr.splice(start, deleteCount, ...elements);
|
||||
}
|
||||
},
|
||||
updateElementHeight() { }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
@@ -33,7 +34,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{ element: 0 },
|
||||
@@ -60,7 +61,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
@@ -96,7 +97,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('setChildren on collapsed node', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{ element: 0, collapsed: true }
|
||||
@@ -117,7 +118,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('setChildren on expanded, unrevealed node', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list));
|
||||
const model = new ObjectTreeModel<number>('test', toList(list));
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
@@ -143,7 +144,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('collapse state is preserved with strict identity', () => {
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { collapseByDefault: true });
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { collapseByDefault: true });
|
||||
const data = [{ element: 'father', children: [{ element: 'child' }] }];
|
||||
|
||||
model.setChildren(null, data);
|
||||
@@ -173,7 +174,7 @@ suite('ObjectTreeModel', function () {
|
||||
let compare: (a: string, b: string) => number = (a, b) => a < b ? -1 : 1;
|
||||
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const data = [
|
||||
{ element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] },
|
||||
{ element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] },
|
||||
@@ -188,7 +189,7 @@ suite('ObjectTreeModel', function () {
|
||||
let compare: (a: string, b: string) => number = () => 0;
|
||||
|
||||
const list: ITreeNode<string>[] = [];
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { sorter: { compare(a, b) { return compare(a, b); } } });
|
||||
const data = [
|
||||
{ element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] },
|
||||
{ element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] },
|
||||
@@ -223,7 +224,7 @@ suite('ObjectTreeModel', function () {
|
||||
|
||||
test('expandTo', () => {
|
||||
const list: ITreeNode<number>[] = [];
|
||||
const model = new ObjectTreeModel<number>('test', toSpliceable(list), { collapseByDefault: true });
|
||||
const model = new ObjectTreeModel<number>('test', toList(list), { collapseByDefault: true });
|
||||
|
||||
model.setChildren(null, [
|
||||
{
|
||||
@@ -254,7 +255,7 @@ suite('ObjectTreeModel', function () {
|
||||
return fn(element) ? TreeVisibility.Visible : parentVisibility;
|
||||
}
|
||||
};
|
||||
const model = new ObjectTreeModel<string>('test', toSpliceable(list), { filter });
|
||||
const model = new ObjectTreeModel<string>('test', toList(list), { filter });
|
||||
|
||||
model.setChildren(null, [{ element: 'file', children: [{ element: 'hello' }] }]);
|
||||
assert.deepEqual(toArray(list), ['file', 'hello']);
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as encoding from 'vs/base/node/encoding';
|
||||
import * as terminalEncoding from 'vs/base/node/terminalEncoding';
|
||||
import * as streams from 'vs/base/common/stream';
|
||||
import * as iconv from 'iconv-lite';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer';
|
||||
|
||||
export async function detectEncodingByBOM(file: string): Promise<typeof encoding.UTF16be | typeof encoding.UTF16le | typeof encoding.UTF8_with_bom | null> {
|
||||
try {
|
||||
const { buffer, bytesRead } = await readExactlyByFile(file, 3);
|
||||
|
||||
return encoding.detectEncodingByBOMFromBuffer(buffer, bytesRead);
|
||||
} catch (error) {
|
||||
return null; // ignore errors (like file not found)
|
||||
}
|
||||
}
|
||||
|
||||
interface ReadResult {
|
||||
buffer: VSBuffer | null;
|
||||
bytesRead: number;
|
||||
}
|
||||
|
||||
function readExactlyByFile(file: string, totalBytes: number): Promise<ReadResult> {
|
||||
return new Promise<ReadResult>((resolve, reject) => {
|
||||
fs.open(file, 'r', null, (err, fd) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
function end(err: Error | null, resultBuffer: Buffer | null, bytesRead: number): void {
|
||||
fs.close(fd, closeError => {
|
||||
if (closeError) {
|
||||
return reject(closeError);
|
||||
}
|
||||
|
||||
if (err && (<any>err).code === 'EISDIR') {
|
||||
return reject(err); // we want to bubble this error up (file is actually a folder)
|
||||
}
|
||||
|
||||
return resolve({ buffer: resultBuffer ? VSBuffer.wrap(resultBuffer) : null, bytesRead });
|
||||
});
|
||||
}
|
||||
|
||||
const buffer = Buffer.allocUnsafe(totalBytes);
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
fs.read(fd, buffer, offset, totalBytes - offset, null, (err, bytesRead) => {
|
||||
if (err) {
|
||||
return end(err, null, 0);
|
||||
}
|
||||
|
||||
if (bytesRead === 0) {
|
||||
return end(null, buffer, offset);
|
||||
}
|
||||
|
||||
offset += bytesRead;
|
||||
|
||||
if (offset === totalBytes) {
|
||||
return end(null, buffer, offset);
|
||||
}
|
||||
|
||||
return readChunk();
|
||||
});
|
||||
}
|
||||
|
||||
readChunk();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suite('Encoding', () => {
|
||||
|
||||
test('detectBOM does not return error for non existing file', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/not-exist.css');
|
||||
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, null);
|
||||
});
|
||||
|
||||
test('detectBOM UTF-8', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_utf8.css');
|
||||
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, 'utf8bom');
|
||||
});
|
||||
|
||||
test('detectBOM UTF-16 LE', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_utf16le.css');
|
||||
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, 'utf16le');
|
||||
});
|
||||
|
||||
test('detectBOM UTF-16 BE', async () => {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
|
||||
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, 'utf16be');
|
||||
});
|
||||
|
||||
test('detectBOM ANSI', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_ansi.css');
|
||||
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, null);
|
||||
});
|
||||
|
||||
test('detectBOM ANSI', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/empty.txt');
|
||||
|
||||
const detectedEncoding = await detectEncodingByBOM(file);
|
||||
assert.equal(detectedEncoding, null);
|
||||
});
|
||||
|
||||
test('resolve terminal encoding (detect)', async function () {
|
||||
const enc = await terminalEncoding.resolveTerminalEncoding();
|
||||
assert.ok(enc.length > 0);
|
||||
});
|
||||
|
||||
test('resolve terminal encoding (environment)', async function () {
|
||||
process.env['VSCODE_CLI_ENCODING'] = 'utf16le';
|
||||
|
||||
const enc = await terminalEncoding.resolveTerminalEncoding();
|
||||
assert.ok(await encoding.encodingExists(enc));
|
||||
assert.equal(enc, 'utf16le');
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (JSON saved as PNG)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.json.png');
|
||||
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.seemsBinary, false);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (PNG saved as TXT)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.png.txt');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.seemsBinary, true);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (XML saved as PNG)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.xml.png');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.seemsBinary, false);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (QWOFF saved as TXT)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.qwoff.txt');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.seemsBinary, true);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (CSS saved as QWOFF)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.css.qwoff');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.seemsBinary, false);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (PDF)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.pdf');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.seemsBinary, true);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (guess UTF-16 LE from content without BOM)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/utf16_le_nobom.txt');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.encoding, encoding.UTF16le);
|
||||
assert.equal(mimes.seemsBinary, false);
|
||||
});
|
||||
|
||||
test('detectEncodingFromBuffer (guess UTF-16 BE from content without BOM)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/utf16_be_nobom.txt');
|
||||
const buffer = await readExactlyByFile(file, 512);
|
||||
const mimes = encoding.detectEncodingFromBuffer(buffer);
|
||||
assert.equal(mimes.encoding, encoding.UTF16be);
|
||||
assert.equal(mimes.seemsBinary, false);
|
||||
});
|
||||
|
||||
test('autoGuessEncoding (UTF8)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_file.css');
|
||||
const buffer = await readExactlyByFile(file, 512 * 8);
|
||||
const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
|
||||
assert.equal(mimes.encoding, 'utf8');
|
||||
});
|
||||
|
||||
test('autoGuessEncoding (ASCII)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some_ansi.css');
|
||||
const buffer = await readExactlyByFile(file, 512 * 8);
|
||||
const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
|
||||
assert.equal(mimes.encoding, null);
|
||||
});
|
||||
|
||||
test('autoGuessEncoding (ShiftJIS)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.shiftjis.txt');
|
||||
const buffer = await readExactlyByFile(file, 512 * 8);
|
||||
const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
|
||||
assert.equal(mimes.encoding, 'shiftjis');
|
||||
});
|
||||
|
||||
test('autoGuessEncoding (CP1252)', async function () {
|
||||
const file = getPathFromAmdModule(require, './fixtures/some.cp1252.txt');
|
||||
const buffer = await readExactlyByFile(file, 512 * 8);
|
||||
const mimes = await encoding.detectEncodingFromBuffer(buffer, true);
|
||||
assert.equal(mimes.encoding, 'windows1252');
|
||||
});
|
||||
|
||||
async function readAndDecodeFromDisk(path: string, fileEncoding: string | null) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
fs.readFile(path, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(iconv.decode(data, encoding.toNodeEncoding(fileEncoding!)));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function newTestReadableStream(buffers: Buffer[]): VSBufferReadableStream {
|
||||
const stream = newWriteableBufferStream();
|
||||
buffers
|
||||
.map(VSBuffer.wrap)
|
||||
.forEach(buffer => {
|
||||
setTimeout(() => {
|
||||
stream.write(buffer);
|
||||
});
|
||||
});
|
||||
setTimeout(() => {
|
||||
stream.end();
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
|
||||
async function readAllAsString(stream: streams.ReadableStream<string>) {
|
||||
return streams.consumeStream(stream, strings => strings.join(''));
|
||||
}
|
||||
|
||||
test('toDecodeStream - some stream', async function () {
|
||||
const source = newTestReadableStream([
|
||||
Buffer.from([65, 66, 67]),
|
||||
Buffer.from([65, 66, 67]),
|
||||
Buffer.from([65, 66, 67]),
|
||||
]);
|
||||
|
||||
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
|
||||
|
||||
assert.ok(detected);
|
||||
assert.ok(stream);
|
||||
|
||||
const content = await readAllAsString(stream);
|
||||
assert.equal(content, 'ABCABCABC');
|
||||
});
|
||||
|
||||
test('toDecodeStream - some stream, expect too much data', async function () {
|
||||
const source = newTestReadableStream([
|
||||
Buffer.from([65, 66, 67]),
|
||||
Buffer.from([65, 66, 67]),
|
||||
Buffer.from([65, 66, 67]),
|
||||
]);
|
||||
|
||||
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
|
||||
|
||||
assert.ok(detected);
|
||||
assert.ok(stream);
|
||||
|
||||
const content = await readAllAsString(stream);
|
||||
assert.equal(content, 'ABCABCABC');
|
||||
});
|
||||
|
||||
test('toDecodeStream - some stream, no data', async function () {
|
||||
const source = newWriteableBufferStream();
|
||||
source.end();
|
||||
|
||||
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
|
||||
|
||||
assert.ok(detected);
|
||||
assert.ok(stream);
|
||||
|
||||
const content = await readAllAsString(stream);
|
||||
assert.equal(content, '');
|
||||
});
|
||||
|
||||
|
||||
test('toDecodeStream - encoding, utf16be', async function () {
|
||||
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
|
||||
const source = streamToBufferReadableStream(fs.createReadStream(path));
|
||||
|
||||
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
|
||||
|
||||
assert.equal(detected.encoding, 'utf16be');
|
||||
assert.equal(detected.seemsBinary, false);
|
||||
|
||||
const expected = await readAndDecodeFromDisk(path, detected.encoding);
|
||||
const actual = await readAllAsString(stream);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
|
||||
test('toDecodeStream - empty file', async function () {
|
||||
const path = getPathFromAmdModule(require, './fixtures/empty.txt');
|
||||
const source = streamToBufferReadableStream(fs.createReadStream(path));
|
||||
const { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
|
||||
|
||||
const expected = await readAndDecodeFromDisk(path, detected.encoding);
|
||||
const actual = await readAllAsString(stream);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
test('toDecodeStream - decodes buffer entirely', async function () {
|
||||
const emojis = Buffer.from('🖥️💻💾');
|
||||
const incompleteEmojis = emojis.slice(0, emojis.length - 1);
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
for (let i = 0; i < incompleteEmojis.length; i++) {
|
||||
buffers.push(incompleteEmojis.slice(i, i + 1));
|
||||
}
|
||||
|
||||
const source = newTestReadableStream(buffers);
|
||||
const { stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
|
||||
|
||||
const expected = incompleteEmojis.toString(encoding.UTF8);
|
||||
const actual = await readAllAsString(stream);
|
||||
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
test('toEncodeReadable - encoding, utf16be', async function () {
|
||||
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
|
||||
const source = await readAndDecodeFromDisk(path, encoding.UTF16be);
|
||||
|
||||
const expected = VSBuffer.wrap(
|
||||
iconv.encode(source, encoding.toNodeEncoding(encoding.UTF16be))
|
||||
).toString();
|
||||
|
||||
const actual = streams.consumeReadable(
|
||||
await encoding.toEncodeReadable(streams.toReadable(source), encoding.UTF16be),
|
||||
VSBuffer.concat
|
||||
).toString();
|
||||
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
test('toEncodeReadable - empty readable to utf8', async function () {
|
||||
const source: streams.Readable<string> = {
|
||||
read() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const actual = streams.consumeReadable(
|
||||
await encoding.toEncodeReadable(source, encoding.UTF8),
|
||||
VSBuffer.concat
|
||||
).toString();
|
||||
|
||||
assert.equal(actual, '');
|
||||
});
|
||||
|
||||
[{
|
||||
utfEncoding: encoding.UTF8,
|
||||
relatedBom: encoding.UTF8_BOM
|
||||
}, {
|
||||
utfEncoding: encoding.UTF8_with_bom,
|
||||
relatedBom: encoding.UTF8_BOM
|
||||
}, {
|
||||
utfEncoding: encoding.UTF16be,
|
||||
relatedBom: encoding.UTF16be_BOM,
|
||||
}, {
|
||||
utfEncoding: encoding.UTF16le,
|
||||
relatedBom: encoding.UTF16le_BOM
|
||||
}].forEach(({ utfEncoding, relatedBom }) => {
|
||||
test(`toEncodeReadable - empty readable to ${utfEncoding} with BOM`, async function () {
|
||||
const source: streams.Readable<string> = {
|
||||
read() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const encodedReadable = encoding.toEncodeReadable(source, utfEncoding, { addBOM: true });
|
||||
|
||||
const expected = VSBuffer.wrap(Buffer.from(relatedBom)).toString();
|
||||
const actual = streams.consumeReadable(await encodedReadable, VSBuffer.concat).toString();
|
||||
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using System.Data;
|
||||
using System.Data.OleDb;
|
||||
using System.Data.Odbc;
|
||||
using System.IO;
|
||||
using System.Net.Mail;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.DirectoryServices;
|
||||
using System.Diagnostics;
|
||||
using System.Resources;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
|
||||
ObjectCount = LoadObjects("Öffentlicher Ordner");
|
||||
|
||||
Private = "Persönliche Information"
|
||||
@@ -1,35 +0,0 @@
|
||||
/*----------------------------------------------------------
|
||||
The base color for this template is #5c87b2. If you'd like
|
||||
to use a different color start by replacing all instances of
|
||||
#5c87b2 with your new color.
|
||||
----------------------------------------------------------*/
|
||||
body
|
||||
{
|
||||
background-color: #5c87b2;
|
||||
font-size: .75em;
|
||||
font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
color: #000;
|
||||
font-size: 40px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
textarea
|
||||
{
|
||||
font-family: Consolas
|
||||
}
|
||||
|
||||
#results
|
||||
{
|
||||
margin-top: 2em;
|
||||
margin-left: 2em;
|
||||
color: black;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"type": "typescript",
|
||||
|
||||
"sources": [
|
||||
"examples/company.ts",
|
||||
"examples/conway.ts",
|
||||
"examples/employee.ts",
|
||||
"examples/large.ts",
|
||||
"examples/small.ts"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 151 B |
Binary file not shown.
@@ -1 +0,0 @@
|
||||
VSCODEは最高のエディタだ。
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml>
|
||||
|
||||
</xml>
|
||||
@@ -1,40 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------
|
||||
The base color for this template is #5c87b2. If you'd like
|
||||
to use a different color start by replacing all instances of
|
||||
#5c87b2 with your new color.
|
||||
----------------------------------------------------------*/
|
||||
body
|
||||
{
|
||||
background-color: #5c87b2;
|
||||
font-size: .75em;
|
||||
font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
color: #000;
|
||||
font-size: 40px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
textarea
|
||||
{
|
||||
font-family: Consolas
|
||||
}
|
||||
|
||||
#results
|
||||
{
|
||||
margin-top: 2em;
|
||||
margin-left: 2em;
|
||||
color: black;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------
|
||||
The base color for this template is #5c87b2. If you'd like
|
||||
to use a different color start by replacing all instances of
|
||||
#5c87b2 with your new color.
|
||||
|
||||
öäüßßß
|
||||
----------------------------------------------------------*/
|
||||
body
|
||||
{
|
||||
background-color: #5c87b2;
|
||||
font-size: .75em;
|
||||
font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
color: #000;
|
||||
font-size: 40px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
textarea
|
||||
{
|
||||
font-family: Consolas
|
||||
}
|
||||
|
||||
#results
|
||||
{
|
||||
margin-top: 2em;
|
||||
margin-left: 2em;
|
||||
color: black;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,42 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------
|
||||
The base color for this template is #5c87b2. If you'd like
|
||||
to use a different color start by replacing all instances of
|
||||
#5c87b2 with your new color.
|
||||
|
||||
öäüßßß
|
||||
----------------------------------------------------------*/
|
||||
body
|
||||
{
|
||||
background-color: #5c87b2;
|
||||
font-size: .75em;
|
||||
font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
color: #000;
|
||||
font-size: 40px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
textarea
|
||||
{
|
||||
font-family: Consolas
|
||||
}
|
||||
|
||||
#results
|
||||
{
|
||||
margin-top: 2em;
|
||||
margin-left: 2em;
|
||||
color: black;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user