Archived
Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3 (#6516)
* Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3 * fix tests
This commit is contained in:
@@ -31,7 +31,7 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
title: localize('disconnect', "Disconnect")
|
||||
},
|
||||
when: ContextKeyExpr.and(NodeContextKey.IsConnected,
|
||||
new ContextKeyNotEqualsExpr('nodeType', NodeType.Folder))
|
||||
ContextKeyNotEqualsExpr.create('nodeType', NodeType.Folder))
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
@@ -214,7 +214,7 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
},
|
||||
when: ContextKeyExpr.and(MssqlNodeContext.NodeProvider.isEqualTo(mssqlProviderName),
|
||||
MssqlNodeContext.IsWindows,
|
||||
new ContextKeyRegexExpr('nodeType', /^(Database|Table|Column|Index|Statistic|View|ServerLevelLogin|ServerLevelServerRole|ServerLevelCredential|ServerLevelServerAudit|ServerLevelServerAuditSpecification|StoredProcedure|ScalarValuedFunction|TableValuedFunction|AggregateFunction|Synonym|Assembly|UserDefinedDataType|UserDefinedType|UserDefinedTableType|Sequence|User|DatabaseRole|ApplicationRole|Schema|SecurityPolicy|ServerLevelLinkedServer)$/))
|
||||
ContextKeyRegexExpr.create('nodeType', /^(Database|Table|Column|Index|Statistic|View|ServerLevelLogin|ServerLevelServerRole|ServerLevelCredential|ServerLevelServerAudit|ServerLevelServerAuditSpecification|StoredProcedure|ScalarValuedFunction|TableValuedFunction|AggregateFunction|Synonym|Assembly|UserDefinedDataType|UserDefinedType|UserDefinedTableType|Sequence|User|DatabaseRole|ApplicationRole|Schema|SecurityPolicy|ServerLevelLinkedServer)$/))
|
||||
});
|
||||
|
||||
//////////////// Scripting Actions /////////////////
|
||||
@@ -283,4 +283,4 @@ MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
|
||||
title: localize('editData', "Edit Data")
|
||||
},
|
||||
when: MssqlNodeContext.CanEditData
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ if (isMacintosh) {
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: RunQueryKeyboardAction.ID, title: RunQueryKeyboardAction.LABEL },
|
||||
group: 'query',
|
||||
when: new ContextKeyEqualsExpr('activeEditor', 'workbench.editor.queryEditor')
|
||||
when: ContextKeyEqualsExpr.create('activeEditor', 'workbench.editor.queryEditor')
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Vendored
+38
-35
@@ -1,4 +1,4 @@
|
||||
// Type definitions for Electron 4.2.5
|
||||
// Type definitions for Electron 4.2.7
|
||||
// Project: http://electronjs.org/
|
||||
// Definitions by: The Electron Team <https://github.com/electron/electron>
|
||||
// Definitions: https://github.com/electron/electron-typescript-definitions
|
||||
@@ -3479,14 +3479,14 @@ declare namespace Electron {
|
||||
* Creates a new NativeImage instance from the NSImage that maps to the given image
|
||||
* name. See NSImageName for a list of possible values. The hslShift is applied to
|
||||
* the image with the following rules This means that [-1, 0, 1] will make the
|
||||
* image completely white and [-1, 1, 0] will make the image completely black. In
|
||||
* some cases, the NSImageName doesn't match its string representation; one example
|
||||
* of this is NSFolderImageName, whose string representation would actually be
|
||||
* NSFolder. Therefore, you'll need to determine the correct string representation
|
||||
* for your image before passing it in. This can be done with the following: echo
|
||||
* -e '#import <Cocoa/Cocoa.h>\nint main() { NSLog(@"%@", SYSTEM_IMAGE_NAME); }' |
|
||||
* clang -otest -x objective-c -framework Cocoa - && ./test where SYSTEM_IMAGE_NAME
|
||||
* should be replaced with any value from this list.
|
||||
* image completely white and [-1, 1, 0] will make the image completely black. In
|
||||
* some cases, the NSImageName doesn't match its string representation; one example
|
||||
* of this is NSFolderImageName, whose string representation would actually be
|
||||
* NSFolder. Therefore, you'll need to determine the correct string representation
|
||||
* for your image before passing it in. This can be done with the following: echo
|
||||
* -e '#import <Cocoa/Cocoa.h>\nint main() { NSLog(@"%@", SYSTEM_IMAGE_NAME); }' |
|
||||
* clang -otest -x objective-c -framework Cocoa - && ./test where SYSTEM_IMAGE_NAME
|
||||
* should be replaced with any value from this list.
|
||||
*/
|
||||
static createFromNamedImage(imageName: string, hslShift: number[]): NativeImage;
|
||||
/**
|
||||
@@ -3737,6 +3737,17 @@ declare namespace Electron {
|
||||
once(event: 'unlock-screen', listener: Function): this;
|
||||
addListener(event: 'unlock-screen', listener: Function): this;
|
||||
removeListener(event: 'unlock-screen', listener: Function): this;
|
||||
/**
|
||||
* Calculate the system idle state. idleThreshold is the amount of time (in
|
||||
* seconds) before considered idle. callback will be called synchronously on some
|
||||
* systems and with an idleState argument that describes the system's state. locked
|
||||
* is available on supported systems only.
|
||||
*/
|
||||
querySystemIdleState(idleThreshold: number, callback: (idleState: 'active' | 'idle' | 'locked' | 'unknown') => void): void;
|
||||
/**
|
||||
* Calculate system idle time in seconds.
|
||||
*/
|
||||
querySystemIdleTime(callback: (idleTime: number) => void): void;
|
||||
}
|
||||
|
||||
interface PowerSaveBlocker extends EventEmitter {
|
||||
@@ -4418,30 +4429,6 @@ declare namespace Electron {
|
||||
* The new RGBA color the user assigned to be their system accent color.
|
||||
*/
|
||||
newColor: string) => void): this;
|
||||
/**
|
||||
* NOTE: This event is only emitted after you have called
|
||||
* startAppLevelAppearanceTrackingOS
|
||||
*/
|
||||
on(event: 'appearance-changed', listener: (
|
||||
/**
|
||||
* Can be `dark` or `light`
|
||||
*/
|
||||
newAppearance: ('dark' | 'light')) => void): this;
|
||||
once(event: 'appearance-changed', listener: (
|
||||
/**
|
||||
* Can be `dark` or `light`
|
||||
*/
|
||||
newAppearance: ('dark' | 'light')) => void): this;
|
||||
addListener(event: 'appearance-changed', listener: (
|
||||
/**
|
||||
* Can be `dark` or `light`
|
||||
*/
|
||||
newAppearance: ('dark' | 'light')) => void): this;
|
||||
removeListener(event: 'appearance-changed', listener: (
|
||||
/**
|
||||
* Can be `dark` or `light`
|
||||
*/
|
||||
newAppearance: ('dark' | 'light')) => void): this;
|
||||
on(event: 'color-changed', listener: (event: Event) => void): this;
|
||||
once(event: 'color-changed', listener: (event: Event) => void): this;
|
||||
addListener(event: 'color-changed', listener: (event: Event) => void): this;
|
||||
@@ -8789,17 +8776,33 @@ declare namespace Electron {
|
||||
* The type of media access being requested, can be video, audio or unknown
|
||||
*/
|
||||
mediaType: ('video' | 'audio' | 'unknown');
|
||||
/**
|
||||
* The last URL the requesting frame loaded
|
||||
*/
|
||||
requestingUrl: string;
|
||||
/**
|
||||
* Whether the frame making the request is the main frame
|
||||
*/
|
||||
isMainFrame: boolean;
|
||||
}
|
||||
|
||||
interface PermissionRequestHandlerDetails {
|
||||
/**
|
||||
* The url of the openExternal request.
|
||||
*/
|
||||
externalURL: string;
|
||||
externalURL?: string;
|
||||
/**
|
||||
* The types of media access being requested, elements can be video or audio
|
||||
*/
|
||||
mediaTypes: Array<'video' | 'audio'>;
|
||||
mediaTypes?: Array<'video' | 'audio'>;
|
||||
/**
|
||||
* The last URL the requesting frame loaded
|
||||
*/
|
||||
requestingUrl: string;
|
||||
/**
|
||||
* Whether the frame making the request is the main frame
|
||||
*/
|
||||
isMainFrame: boolean;
|
||||
}
|
||||
|
||||
interface PluginCrashedEvent extends Event {
|
||||
|
||||
Vendored
+42
-11
@@ -15,6 +15,11 @@ declare module 'xterm' {
|
||||
*/
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
|
||||
/**
|
||||
* A string representing log level.
|
||||
*/
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
|
||||
/**
|
||||
* A string representing a renderer type.
|
||||
*/
|
||||
@@ -107,6 +112,18 @@ declare module 'xterm' {
|
||||
*/
|
||||
lineHeight?: number;
|
||||
|
||||
/**
|
||||
* What log level to use, this will log for all levels below and including
|
||||
* what is set:
|
||||
*
|
||||
* 1. debug
|
||||
* 2. info (default)
|
||||
* 3. warn
|
||||
* 4. error
|
||||
* 5. off
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
|
||||
/**
|
||||
* Whether to treat option as the meta key.
|
||||
*/
|
||||
@@ -177,6 +194,12 @@ declare module 'xterm' {
|
||||
* not whitespace.
|
||||
*/
|
||||
windowsMode?: boolean;
|
||||
|
||||
/**
|
||||
* A string containing all characters that are considered word separated by the
|
||||
* double click to select work logic.
|
||||
*/
|
||||
wordSeparator?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,7 +214,7 @@ declare module 'xterm' {
|
||||
cursor?: string,
|
||||
/** The accent color of the cursor (fg color for a block cursor) */
|
||||
cursorAccent?: string,
|
||||
/** The selection color (can be transparent) */
|
||||
/** The selection background color (can be transparent) */
|
||||
selection?: string,
|
||||
/** ANSI black (eg. `\x1b[30m`) */
|
||||
black?: string,
|
||||
@@ -483,12 +506,14 @@ declare module 'xterm' {
|
||||
* final character (e.g "m" for SGR) of the CSI sequence.
|
||||
* @param callback The function to handle the escape sequence. The callback
|
||||
* is called with the numerical params, as well as the special characters
|
||||
* (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if
|
||||
* (e.g. "$" for DECSCPP). If the sequence has subparams the array will
|
||||
* contain subarrays with their numercial values.
|
||||
* Return true if the sequence was handled; false if
|
||||
* we should try a previous handler (set by addCsiHandler or setCsiHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
|
||||
addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a handler for OSC escape sequences.
|
||||
@@ -668,12 +693,12 @@ declare module 'xterm' {
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string;
|
||||
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean;
|
||||
getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean;
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
@@ -700,13 +725,19 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void;
|
||||
setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'logLevel', value: LogLevel): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
@@ -724,7 +755,7 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void;
|
||||
setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
@@ -928,16 +959,16 @@ declare module 'xterm' {
|
||||
// Modifications to official .d.ts below
|
||||
declare module 'xterm' {
|
||||
interface TerminalCore {
|
||||
debug: boolean;
|
||||
|
||||
handler(text: string): void;
|
||||
|
||||
_onScroll: IEventEmitter<number>;
|
||||
_onKey: IEventEmitter<{ key: string }>;
|
||||
|
||||
_charSizeService: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
_coreService: {
|
||||
triggerDataEvent(data: string, wasUserInput?: boolean): void;
|
||||
}
|
||||
|
||||
_renderService: {
|
||||
|
||||
@@ -122,7 +122,7 @@ export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0));
|
||||
export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
|
||||
export const isIPad = (userAgent.indexOf('iPad') >= 0);
|
||||
export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
|
||||
export const isStandalone = (window.matchMedia('(display-mode: standalone)').matches);
|
||||
export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);
|
||||
|
||||
export function hasClipboardSupport() {
|
||||
if (isIE) {
|
||||
|
||||
@@ -1200,7 +1200,7 @@ export function asDomUri(uri: URI): URI {
|
||||
if (Schemas.vscodeRemote === uri.scheme) {
|
||||
// rewrite vscode-remote-uris to uris of the window location
|
||||
// so that they can be intercepted by the service worker
|
||||
return _location.with({ path: '/vscode-resources/fetch', query: JSON.stringify({ u: uri.toJSON(), i: 1 }) });
|
||||
return _location.with({ path: '/vscode-resources/fetch', query: `u=${JSON.stringify(uri)}` });
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,8 @@ export class StandardWheelEvent {
|
||||
this.deltaY = e1.wheelDeltaY / 120;
|
||||
} else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) {
|
||||
this.deltaY = -e2.detail / 3;
|
||||
} else {
|
||||
this.deltaY = -e.deltaY / 40;
|
||||
}
|
||||
|
||||
// horizontal delta scroll
|
||||
@@ -171,6 +173,8 @@ export class StandardWheelEvent {
|
||||
}
|
||||
} else if (typeof e2.HORIZONTAL_AXIS !== 'undefined' && e2.axis === e2.HORIZONTAL_AXIS) {
|
||||
this.deltaX = -e.detail / 3;
|
||||
} else {
|
||||
this.deltaX = -e.deltaX / 40;
|
||||
}
|
||||
|
||||
// Assume a vertical scroll if nothing else worked
|
||||
@@ -195,4 +199,4 @@ export class StandardWheelEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IView, IViewSize } from 'vs/base/browser/ui/grid/gridview';
|
||||
import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ import 'vs/css!./gridview';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { tail2 as tail, equals } from 'vs/base/common/arrays';
|
||||
import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize } from './gridview';
|
||||
import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize } from './gridview';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { InvisibleSizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||
|
||||
export { Orientation, Sizing as GridViewSizing } from './gridview';
|
||||
export { Orientation, Sizing as GridViewSizing, IViewSize, orthogonal, LayoutPriority } from './gridview';
|
||||
|
||||
export const enum Direction {
|
||||
Up,
|
||||
@@ -28,9 +29,15 @@ function oppositeDirection(direction: Direction): Direction {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IView extends IGridViewView {
|
||||
readonly preferredHeight?: number;
|
||||
readonly preferredWidth?: number;
|
||||
}
|
||||
|
||||
export interface GridLeafNode<T extends IView> {
|
||||
readonly view: T;
|
||||
readonly box: Box;
|
||||
readonly cachedVisibleSize: number | undefined;
|
||||
}
|
||||
|
||||
export interface GridBranchNode<T extends IView> {
|
||||
@@ -173,16 +180,23 @@ function getGridLocation(element: HTMLElement): number[] {
|
||||
return [...getGridLocation(ancestor), index];
|
||||
}
|
||||
|
||||
export const enum Sizing {
|
||||
Distribute = 'distribute',
|
||||
Split = 'split'
|
||||
export type DistributeSizing = { type: 'distribute' };
|
||||
export type SplitSizing = { type: 'split' };
|
||||
export type InvisibleSizing = { type: 'invisible', cachedVisibleSize: number };
|
||||
export type Sizing = DistributeSizing | SplitSizing | InvisibleSizing;
|
||||
|
||||
export namespace Sizing {
|
||||
export const Distribute: DistributeSizing = { type: 'distribute' };
|
||||
export const Split: SplitSizing = { type: 'split' };
|
||||
export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
|
||||
}
|
||||
|
||||
export interface IGridStyles extends IGridViewStyles { }
|
||||
|
||||
export interface IGridOptions {
|
||||
styles?: IGridStyles;
|
||||
proportionalLayout?: boolean;
|
||||
readonly styles?: IGridStyles;
|
||||
readonly proportionalLayout?: boolean;
|
||||
readonly firstViewVisibleCachedSize?: number;
|
||||
}
|
||||
|
||||
export class Grid<T extends IView = IView> extends Disposable {
|
||||
@@ -208,9 +222,13 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
this.gridview = new GridView(options);
|
||||
this._register(this.gridview);
|
||||
|
||||
this._register(this.gridview.onDidSashReset(this.doResetViewSize, this));
|
||||
this._register(this.gridview.onDidSashReset(this.onDidSashReset, this));
|
||||
|
||||
this._addView(view, 0, [0]);
|
||||
const size: number | GridViewSizing = typeof options.firstViewVisibleCachedSize === 'number'
|
||||
? GridViewSizing.Invisible(options.firstViewVisibleCachedSize)
|
||||
: 0;
|
||||
|
||||
this._addView(view, size, [0]);
|
||||
}
|
||||
|
||||
style(styles: IGridStyles): void {
|
||||
@@ -241,10 +259,12 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
|
||||
let viewSize: number | GridViewSizing;
|
||||
|
||||
if (size === Sizing.Split) {
|
||||
if (typeof size === 'number') {
|
||||
viewSize = size;
|
||||
} else if (size.type === 'split') {
|
||||
const [, index] = tail(referenceLocation);
|
||||
viewSize = GridViewSizing.Split(index);
|
||||
} else if (size === Sizing.Distribute) {
|
||||
} else if (size.type === 'distribute') {
|
||||
viewSize = GridViewSizing.Distribute;
|
||||
} else {
|
||||
viewSize = size;
|
||||
@@ -264,7 +284,7 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
}
|
||||
|
||||
const location = this.getViewLocation(view);
|
||||
this.gridview.removeView(location, sizing === Sizing.Distribute ? GridViewSizing.Distribute : undefined);
|
||||
this.gridview.removeView(location, (sizing && sizing.type === 'distribute') ? GridViewSizing.Distribute : undefined);
|
||||
this.views.delete(view);
|
||||
}
|
||||
|
||||
@@ -320,7 +340,7 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
}
|
||||
|
||||
getViews(): GridBranchNode<T> {
|
||||
return this.gridview.getViews() as GridBranchNode<T>;
|
||||
return this.gridview.getView() as GridBranchNode<T>;
|
||||
}
|
||||
|
||||
getNeighborViews(view: T, direction: Direction, wrap: boolean = false): T[] {
|
||||
@@ -355,8 +375,36 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
return getGridLocation(element);
|
||||
}
|
||||
|
||||
private doResetViewSize(location: number[]): void {
|
||||
const [parentLocation,] = tail(location);
|
||||
private onDidSashReset(location: number[]): void {
|
||||
const resizeToPreferredSize = (location: number[]): boolean => {
|
||||
const node = this.gridview.getView(location) as GridNode<T>;
|
||||
|
||||
if (isGridBranchNode(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const direction = getLocationOrientation(this.orientation, location);
|
||||
const size = direction === Orientation.HORIZONTAL ? node.view.preferredWidth : node.view.preferredHeight;
|
||||
|
||||
if (typeof size !== 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const viewSize = direction === Orientation.HORIZONTAL ? { width: Math.round(size) } : { height: Math.round(size) };
|
||||
this.gridview.resizeView(location, viewSize);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (resizeToPreferredSize(location)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [parentLocation, index] = tail(location);
|
||||
|
||||
if (resizeToPreferredSize([...parentLocation, index + 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.gridview.distributeViewSizes(parentLocation);
|
||||
}
|
||||
}
|
||||
@@ -379,6 +427,7 @@ export interface ISerializedLeafNode {
|
||||
type: 'leaf';
|
||||
data: object | null;
|
||||
size: number;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface ISerializedBranchNode {
|
||||
@@ -402,6 +451,10 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
|
||||
const size = orientation === Orientation.VERTICAL ? node.box.width : node.box.height;
|
||||
|
||||
if (!isGridBranchNode(node)) {
|
||||
if (typeof node.cachedVisibleSize === 'number') {
|
||||
return { type: 'leaf', data: node.view.toJSON(), size: node.cachedVisibleSize, visible: false };
|
||||
}
|
||||
|
||||
return { type: 'leaf', data: node.view.toJSON(), size };
|
||||
}
|
||||
|
||||
@@ -426,25 +479,26 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
|
||||
throw new Error('Invalid JSON: \'size\' property of node must be a number.');
|
||||
}
|
||||
|
||||
const childSize = child.type === 'leaf' && child.visible === false ? 0 : child.size;
|
||||
const childBox: Box = orientation === Orientation.HORIZONTAL
|
||||
? { top: box.top, left: box.left + offset, width: child.size, height: box.height }
|
||||
: { top: box.top + offset, left: box.left, width: box.width, height: child.size };
|
||||
? { top: box.top, left: box.left + offset, width: childSize, height: box.height }
|
||||
: { top: box.top + offset, left: box.left, width: box.width, height: childSize };
|
||||
|
||||
children.push(SerializableGrid.deserializeNode(child, orthogonal(orientation), childBox, deserializer));
|
||||
offset += child.size;
|
||||
offset += childSize;
|
||||
}
|
||||
|
||||
return { children, box };
|
||||
|
||||
} else if (json.type === 'leaf') {
|
||||
const view: T = deserializer.fromJSON(json.data);
|
||||
return { view, box };
|
||||
return { view, box, cachedVisibleSize: json.visible === false ? json.size : undefined };
|
||||
}
|
||||
|
||||
throw new Error('Invalid JSON: \'type\' property must be either \'branch\' or \'leaf\'.');
|
||||
}
|
||||
|
||||
private static getFirstLeaf<T extends IView>(node: GridNode<T>): GridLeafNode<T> | undefined {
|
||||
private static getFirstLeaf<T extends IView>(node: GridNode<T>): GridLeafNode<T> {
|
||||
if (!isGridBranchNode(node)) {
|
||||
return node;
|
||||
}
|
||||
@@ -473,6 +527,10 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
|
||||
throw new Error('Invalid serialized state, first leaf not found');
|
||||
}
|
||||
|
||||
if (typeof firstLeaf.cachedVisibleSize === 'number') {
|
||||
options = { ...options, firstViewVisibleCachedSize: firstLeaf.cachedVisibleSize };
|
||||
}
|
||||
|
||||
const result = new SerializableGrid<T>(firstLeaf.view, options);
|
||||
result.orientation = orientation;
|
||||
result.restoreViews(firstLeaf.view, orientation, root);
|
||||
@@ -522,13 +580,16 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
|
||||
const firstLeaves = node.children.map(c => SerializableGrid.getFirstLeaf(c));
|
||||
|
||||
for (let i = 1; i < firstLeaves.length; i++) {
|
||||
const size = orientation === Orientation.VERTICAL ? firstLeaves[i]!.box.height : firstLeaves[i]!.box.width;
|
||||
this.addView(firstLeaves[i]!.view, size, referenceView, direction);
|
||||
referenceView = firstLeaves[i]!.view;
|
||||
const node = firstLeaves[i];
|
||||
const size: number | InvisibleSizing = typeof node.cachedVisibleSize === 'number'
|
||||
? GridViewSizing.Invisible(node.cachedVisibleSize)
|
||||
: (orientation === Orientation.VERTICAL ? node.box.height : node.box.width);
|
||||
this.addView(node.view, size, referenceView, direction);
|
||||
referenceView = node.view;
|
||||
}
|
||||
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
this.restoreViews(firstLeaves[i]!.view, orthogonal(orientation), node.children[i]);
|
||||
this.restoreViews(firstLeaves[i].view, orthogonal(orientation), node.children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface Box {
|
||||
export interface GridLeafNode {
|
||||
readonly view: IView;
|
||||
readonly box: Box;
|
||||
readonly cachedVisibleSize: number | undefined;
|
||||
}
|
||||
|
||||
export interface GridBranchNode {
|
||||
@@ -343,6 +344,14 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
this.splitview.setViewVisible(index, visible);
|
||||
}
|
||||
|
||||
getChildCachedVisibleSize(index: number): number | undefined {
|
||||
if (index < 0 || index >= this.children.length) {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
return this.splitview.getViewCachedVisibleSize(index);
|
||||
}
|
||||
|
||||
private onDidChildrenChange(): void {
|
||||
const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined);
|
||||
this.childrenChangeDisposable.dispose();
|
||||
@@ -424,6 +433,9 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
private _size: number = 0;
|
||||
get size(): number { return this._size; }
|
||||
|
||||
private _cachedVisibleSize: number | undefined;
|
||||
get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; }
|
||||
|
||||
private _orthogonalSize: number;
|
||||
get orthogonalSize(): number { return this._orthogonalSize; }
|
||||
|
||||
@@ -528,6 +540,12 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
if (visible) {
|
||||
this._cachedVisibleSize = undefined;
|
||||
} else {
|
||||
this._cachedVisibleSize = this._size;
|
||||
}
|
||||
|
||||
if (this.view.setVisible) {
|
||||
this.view.setVisible(visible);
|
||||
}
|
||||
@@ -658,6 +676,14 @@ export class GridView implements IDisposable {
|
||||
} else {
|
||||
const [, grandParent] = tail(pathToParent);
|
||||
const [, parentIndex] = tail(rest);
|
||||
|
||||
let newSiblingSize: number | Sizing = 0;
|
||||
|
||||
const newSiblingCachedVisibleSize = grandParent.getChildCachedVisibleSize(parentIndex);
|
||||
if (typeof newSiblingCachedVisibleSize === 'number') {
|
||||
newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize);
|
||||
}
|
||||
|
||||
grandParent.removeChild(parentIndex);
|
||||
|
||||
const newParent = new BranchNode(parent.orientation, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize);
|
||||
@@ -665,7 +691,7 @@ export class GridView implements IDisposable {
|
||||
newParent.orthogonalLayout(parent.orthogonalSize);
|
||||
|
||||
const newSibling = new LeafNode(parent.view, grandParent.orientation, parent.size);
|
||||
newParent.addChild(newSibling, 0, 0);
|
||||
newParent.addChild(newSibling, newSiblingSize, 0);
|
||||
|
||||
if (typeof size !== 'number' && size.type === 'split') {
|
||||
size = Sizing.Split(0);
|
||||
@@ -877,13 +903,16 @@ export class GridView implements IDisposable {
|
||||
parent.setChildVisible(index, visible);
|
||||
}
|
||||
|
||||
getViews(): GridBranchNode {
|
||||
return this._getViews(this.root, this.orientation, { top: 0, left: 0, width: this.width, height: this.height }) as GridBranchNode;
|
||||
getView(): GridBranchNode;
|
||||
getView(location?: number[]): GridNode;
|
||||
getView(location?: number[]): GridNode {
|
||||
const node = location ? this.getNode(location)[1] : this._root;
|
||||
return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height });
|
||||
}
|
||||
|
||||
private _getViews(node: Node, orientation: Orientation, box: Box): GridNode {
|
||||
if (node instanceof LeafNode) {
|
||||
return { view: node.view, box };
|
||||
return { view: node.view, box, cachedVisibleSize: node.cachedVisibleSize };
|
||||
}
|
||||
|
||||
const children: GridNode[] = [];
|
||||
|
||||
@@ -236,7 +236,7 @@ class KeyboardController<T> implements IDisposable {
|
||||
private view: ListView<T>,
|
||||
options: IListOptions<T>
|
||||
) {
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
const multipleSelectionSupport = options.multipleSelectionSupport !== false;
|
||||
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
|
||||
@@ -190,4 +190,4 @@ export class RangeMap {
|
||||
dispose() {
|
||||
this.groups = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/scrollbars';
|
||||
import { isEdgeOrIE } from 'vs/base/browser/browser';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { IMouseEvent, StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||
@@ -312,7 +313,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
this._onMouseWheel(new StandardWheelEvent(browserEvent));
|
||||
};
|
||||
|
||||
this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, 'mousewheel', onMouseWheel));
|
||||
this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,11 @@ interface ISashEvent {
|
||||
alt: boolean;
|
||||
}
|
||||
|
||||
type ViewItemSize = number | { cachedVisibleSize: number };
|
||||
|
||||
abstract class ViewItem {
|
||||
|
||||
private _size: number;
|
||||
set size(size: number) {
|
||||
this._size = size;
|
||||
}
|
||||
@@ -69,10 +72,11 @@ abstract class ViewItem {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
private cachedSize: number | undefined = undefined;
|
||||
private _cachedVisibleSize: number | undefined = undefined;
|
||||
get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; }
|
||||
|
||||
get visible(): boolean {
|
||||
return typeof this.cachedSize === 'undefined';
|
||||
return typeof this._cachedVisibleSize === 'undefined';
|
||||
}
|
||||
|
||||
set visible(visible: boolean) {
|
||||
@@ -81,10 +85,10 @@ abstract class ViewItem {
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
this.size = this.cachedSize!;
|
||||
this.cachedSize = undefined;
|
||||
this.size = clamp(this._cachedVisibleSize!, this.viewMinimumSize, this.viewMaximumSize);
|
||||
this._cachedVisibleSize = undefined;
|
||||
} else {
|
||||
this.cachedSize = this.size;
|
||||
this._cachedVisibleSize = this.size;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
@@ -104,7 +108,20 @@ abstract class ViewItem {
|
||||
get priority(): LayoutPriority | undefined { return this.view.priority; }
|
||||
get snap(): boolean { return !!this.view.snap; }
|
||||
|
||||
constructor(protected container: HTMLElement, private view: IView, private _size: number, private disposable: IDisposable) {
|
||||
constructor(
|
||||
protected container: HTMLElement,
|
||||
private view: IView,
|
||||
size: ViewItemSize,
|
||||
private disposable: IDisposable
|
||||
) {
|
||||
if (typeof size === 'number') {
|
||||
this._size = size;
|
||||
this._cachedVisibleSize = undefined;
|
||||
} else {
|
||||
this._size = 0;
|
||||
this._cachedVisibleSize = size.cachedVisibleSize;
|
||||
}
|
||||
|
||||
dom.addClass(container, 'visible');
|
||||
}
|
||||
|
||||
@@ -166,11 +183,13 @@ enum State {
|
||||
|
||||
export type DistributeSizing = { type: 'distribute' };
|
||||
export type SplitSizing = { type: 'split', index: number };
|
||||
export type Sizing = DistributeSizing | SplitSizing;
|
||||
export type InvisibleSizing = { type: 'invisible', cachedVisibleSize: number };
|
||||
export type Sizing = DistributeSizing | SplitSizing | InvisibleSizing;
|
||||
|
||||
export namespace Sizing {
|
||||
export const Distribute: DistributeSizing = { type: 'distribute' };
|
||||
export function Split(index: number): SplitSizing { return { type: 'split', index }; }
|
||||
export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
|
||||
}
|
||||
|
||||
export class SplitView extends Disposable {
|
||||
@@ -279,12 +298,14 @@ export class SplitView extends Disposable {
|
||||
const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container));
|
||||
const disposable = combinedDisposable(onChangeDisposable, containerDisposable);
|
||||
|
||||
let viewSize: number;
|
||||
let viewSize: ViewItemSize;
|
||||
|
||||
if (typeof size === 'number') {
|
||||
viewSize = size;
|
||||
} else if (size.type === 'split') {
|
||||
viewSize = this.getViewSize(size.index) / 2;
|
||||
} else if (size.type === 'invisible') {
|
||||
viewSize = { cachedVisibleSize: size.cachedVisibleSize };
|
||||
} else {
|
||||
viewSize = view.minimumSize;
|
||||
}
|
||||
@@ -315,7 +336,24 @@ export class SplitView extends Disposable {
|
||||
const onChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = Event.map(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
|
||||
const onEndDisposable = onEnd(this.onSashEnd, this);
|
||||
const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));
|
||||
|
||||
const onDidResetDisposable = sash.onDidReset(() => {
|
||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||
const upIndexes = range(index, -1);
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
|
||||
const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
|
||||
|
||||
if (typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDidSashReset.fire(index);
|
||||
});
|
||||
|
||||
const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash);
|
||||
const sashItem: ISashItem = { sash, disposable };
|
||||
@@ -420,6 +458,15 @@ export class SplitView extends Disposable {
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
getViewCachedVisibleSize(index: number): number | undefined {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
throw new Error('Index out of bounds');
|
||||
}
|
||||
|
||||
const viewItem = this.viewItems[index];
|
||||
return viewItem.cachedVisibleSize;
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
const previousSize = Math.max(this.size, this.contentSize);
|
||||
this.size = size;
|
||||
@@ -600,10 +647,19 @@ export class SplitView extends Disposable {
|
||||
}
|
||||
|
||||
distributeViewSizes(): void {
|
||||
const size = Math.floor(this.size / this.viewItems.length);
|
||||
const flexibleViewItems: ViewItem[] = [];
|
||||
let flexibleSize = 0;
|
||||
|
||||
for (let i = 0; i < this.viewItems.length; i++) {
|
||||
const item = this.viewItems[i];
|
||||
for (const item of this.viewItems) {
|
||||
if (item.maximumSize - item.minimumSize > 0) {
|
||||
flexibleViewItems.push(item);
|
||||
flexibleSize += item.size;
|
||||
}
|
||||
}
|
||||
|
||||
const size = Math.floor(flexibleSize / flexibleViewItems.length);
|
||||
|
||||
for (const item of flexibleViewItems) {
|
||||
item.size = clamp(size, item.minimumSize, item.maximumSize);
|
||||
}
|
||||
|
||||
|
||||
@@ -1183,7 +1183,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
private _options: IAbstractTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
const treeDelegate = new ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>(delegate);
|
||||
|
||||
@@ -320,7 +320,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
|
||||
get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, TFilterData>> { return this.tree.onDidChangeCollapseState; }
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, TFilterData>> { return this.tree.onDidChangeCollapseState; }
|
||||
|
||||
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
|
||||
|
||||
@@ -340,7 +340,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
private dataSource: IAsyncDataSource<TInput, T>,
|
||||
options: IAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } 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 { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CompressedTreeModel, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export class CompressedObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
|
||||
protected model: CompressedTreeModel<T, TFilterData>;
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<ICompressedTreeNode<T> | null, TFilterData>> { return this.model.onDidChangeCollapseState; }
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<ICompressedTreeNode<T>>,
|
||||
renderers: ITreeRenderer<ICompressedTreeNode<T>, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children?: ISequence<ITreeElement<T>>
|
||||
): Iterator<ITreeElement<T | null>> {
|
||||
return this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
rerender(element?: T): void {
|
||||
if (element === undefined) {
|
||||
this.view.rerender();
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.rerender(element);
|
||||
}
|
||||
|
||||
resort(element: T, recursive = true): void {
|
||||
this.model.resort(element, recursive);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: IObjectTreeOptions<ICompressedTreeNode<T>, TFilterData>): ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
return new CompressedTreeModel(view, options);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
|
||||
@@ -80,9 +80,9 @@ export function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, ch
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
|
||||
export interface ICompressedTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
|
||||
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
export class CompressedTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
|
||||
readonly rootRef = null;
|
||||
|
||||
@@ -95,7 +95,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
constructor(list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}) {
|
||||
constructor(list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>, options: ICompressedTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.model = new ObjectTreeModel(list, options);
|
||||
}
|
||||
|
||||
@@ -289,11 +289,11 @@ function createNodeMapper<T, TFilterData>(elementMapper: ElementMapper<T>): Node
|
||||
return node => mapNode(elementMapper, node);
|
||||
}
|
||||
|
||||
export interface ILinearCompressedObjectTreeModelOptions<T, TFilterData> extends ICompressedObjectTreeModelOptions<T, TFilterData> {
|
||||
export interface ICompressedObjectTreeModelOptions<T, TFilterData> extends ICompressedTreeModelOptions<T, TFilterData> {
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
}
|
||||
|
||||
export class LinearCompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<T | null, TFilterData, T | null> {
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
|
||||
|
||||
readonly rootRef = null;
|
||||
|
||||
@@ -317,15 +317,25 @@ export class LinearCompressedObjectTreeModel<T extends NonNullable<any>, TFilter
|
||||
|
||||
private mapElement: ElementMapper<T | null>;
|
||||
private mapNode: NodeMapper<T | null, TFilterData>;
|
||||
private model: CompressedObjectTreeModel<T, TFilterData>;
|
||||
private model: CompressedTreeModel<T, TFilterData>;
|
||||
|
||||
constructor(
|
||||
list: ISpliceable<ITreeNode<ICompressedTreeNode<T>, TFilterData>>,
|
||||
options: ILinearCompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.mapElement = options.elementMapper || DefaultElementMapper;
|
||||
this.mapNode = createNodeMapper(this.mapElement);
|
||||
this.model = new CompressedObjectTreeModel(list, options);
|
||||
this.model = new CompressedTreeModel(list, options);
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: ISequence<ITreeElement<T>> | undefined
|
||||
): Iterator<ITreeElement<T>> {
|
||||
this.model.setChildren(element, children);
|
||||
|
||||
// TODO
|
||||
return Iterator.empty();
|
||||
}
|
||||
|
||||
getListIndex(location: T | null): number {
|
||||
@@ -401,4 +411,8 @@ export class LinearCompressedObjectTreeModel<T extends NonNullable<any>, TFilter
|
||||
refilter(): void {
|
||||
return this.model.refilter();
|
||||
}
|
||||
|
||||
resort(element: T | null = null, recursive = true): void {
|
||||
return this.model.resort(element, recursive);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
private dataSource: IDataSource<TInput, T>,
|
||||
options: IDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
|
||||
@@ -20,7 +20,7 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
private rootElement: T,
|
||||
options: IIndexTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } 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 } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
@@ -17,14 +17,14 @@ export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTree
|
||||
|
||||
export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
|
||||
|
||||
protected model: ObjectTreeModel<T, TFilterData>;
|
||||
protected model: IObjectTreeModel<T, TFilterData>;
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T, TFilterData>> { return this.model.onDidChangeCollapseState; }
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> { return this.model.onDidChangeCollapseState; }
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
@@ -32,11 +32,9 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children?: ISequence<ITreeElement<T>>,
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
children?: ISequence<ITreeElement<T>>
|
||||
): Iterator<ITreeElement<T | null>> {
|
||||
return this.model.setChildren(element, children, onDidCreateNode, onDidDeleteNode);
|
||||
return this.model.setChildren(element, children);
|
||||
}
|
||||
|
||||
rerender(element?: T): void {
|
||||
|
||||
@@ -10,12 +10,19 @@ import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void;
|
||||
|
||||
export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> {
|
||||
setChildren(element: T | null, children: ISequence<ITreeElement<T>> | undefined): Iterator<ITreeElement<T>>;
|
||||
resort(element?: T | null, recursive?: boolean): void;
|
||||
}
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
}
|
||||
|
||||
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<T | null, TFilterData, T | null> {
|
||||
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements IObjectTreeModel<T, TFilterData> {
|
||||
|
||||
readonly rootRef = null;
|
||||
|
||||
@@ -51,8 +58,8 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: ISequence<ITreeElement<T>> | undefined,
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
): Iterator<ITreeElement<T>> {
|
||||
const location = this.getElementLocation(element);
|
||||
return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode);
|
||||
@@ -61,8 +68,8 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
private _setChildren(
|
||||
location: number[],
|
||||
children: ISequence<ITreeElement<T>> | undefined,
|
||||
onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
onDidCreateNode?: ITreeNodeCallback<T, TFilterData>,
|
||||
onDidDeleteNode?: ITreeNodeCallback<T, TFilterData>
|
||||
): Iterator<ITreeElement<T>> {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const insertedElementIds = new Set<string>();
|
||||
|
||||
@@ -326,6 +326,20 @@ export namespace Event {
|
||||
return result.event;
|
||||
}
|
||||
|
||||
export interface DOMEventEmitter {
|
||||
addEventListener(event: string | symbol, listener: Function): void;
|
||||
removeEventListener(event: string | symbol, listener: Function): void;
|
||||
}
|
||||
|
||||
export function fromDOMEventEmitter<T>(emitter: DOMEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
|
||||
const fn = (...args: any[]) => result.fire(map(...args));
|
||||
const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);
|
||||
const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn);
|
||||
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
|
||||
|
||||
return result.event;
|
||||
}
|
||||
|
||||
export function fromPromise<T = any>(promise: Promise<T>): Event<undefined> {
|
||||
const emitter = new Emitter<undefined>();
|
||||
let shouldEmit = false;
|
||||
|
||||
@@ -414,7 +414,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] {
|
||||
return res;
|
||||
}
|
||||
|
||||
const _maxLen = 53;
|
||||
const _maxLen = 128;
|
||||
|
||||
function initTable() {
|
||||
const table: number[][] = [];
|
||||
|
||||
@@ -334,7 +334,6 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string |
|
||||
if (!extpath.isEqualOrParent(path, arg2.base)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsedPattern(paths.relative(arg2.base, path), basename);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -374,12 +374,12 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
|
||||
let code = text.charCodeAt(pos);
|
||||
// trivia: whitespace
|
||||
if (isWhiteSpace(code)) {
|
||||
if (isWhitespace(code)) {
|
||||
do {
|
||||
pos++;
|
||||
value += String.fromCharCode(code);
|
||||
code = text.charCodeAt(pos);
|
||||
} while (isWhiteSpace(code));
|
||||
} while (isWhitespace(code));
|
||||
|
||||
return token = SyntaxKind.Trivia;
|
||||
}
|
||||
@@ -517,7 +517,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
}
|
||||
|
||||
function isUnknownContentCharacter(code: CharacterCodes) {
|
||||
if (isWhiteSpace(code) || isLineBreak(code)) {
|
||||
if (isWhitespace(code) || isLineBreak(code)) {
|
||||
return false;
|
||||
}
|
||||
switch (code) {
|
||||
@@ -555,7 +555,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
};
|
||||
}
|
||||
|
||||
function isWhiteSpace(ch: number): boolean {
|
||||
function isWhitespace(ch: number): boolean {
|
||||
return ch === CharacterCodes.space || ch === CharacterCodes.tab || ch === CharacterCodes.verticalTab || ch === CharacterCodes.formFeed ||
|
||||
ch === CharacterCodes.nonBreakingSpace || ch === CharacterCodes.ogham || ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace ||
|
||||
ch === CharacterCodes.narrowNoBreakSpace || ch === CharacterCodes.mathematicalSpace || ch === CharacterCodes.ideographicSpace || ch === CharacterCodes.byteOrderMark;
|
||||
|
||||
@@ -1,13 +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';
|
||||
|
||||
suite('Grid', function () {
|
||||
// {{SQL CARBON EDIT}}
|
||||
test('getRelativeLocation', function () {
|
||||
assert.equal(0, 0);
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ suite('Gridview', function () {
|
||||
});
|
||||
|
||||
test('empty gridview is empty', function () {
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), []);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), []);
|
||||
gridview.dispose();
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ suite('Gridview', function () {
|
||||
gridview.addView(views[1], 200, [1]);
|
||||
gridview.addView(views[2], 200, [2]);
|
||||
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), views);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), views);
|
||||
|
||||
gridview.dispose();
|
||||
});
|
||||
@@ -62,7 +62,7 @@ suite('Gridview', function () {
|
||||
gridview.addView((views[1] as TestView[])[0] as IView, 200, [1]);
|
||||
gridview.addView((views[1] as TestView[])[1] as IView, 200, [1, 1]);
|
||||
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), views);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), views);
|
||||
|
||||
gridview.dispose();
|
||||
});
|
||||
@@ -71,35 +71,35 @@ suite('Gridview', function () {
|
||||
|
||||
const view1 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view1 as IView, 200, [0]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1]);
|
||||
|
||||
const view2 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view2 as IView, 200, [1]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, view2]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, view2]);
|
||||
|
||||
const view3 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view3 as IView, 200, [1, 0]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view3, view2]]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view3, view2]]);
|
||||
|
||||
const view4 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view4 as IView, 200, [1, 0, 0]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [[view4, view3], view2]]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [[view4, view3], view2]]);
|
||||
|
||||
const view5 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view5 as IView, 200, [1, 0]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, [view4, view3], view2]]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2]]);
|
||||
|
||||
const view6 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view6 as IView, 200, [2]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, [view4, view3], view2], view6]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2], view6]);
|
||||
|
||||
const view7 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view7 as IView, 200, [1, 1]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, view7, [view4, view3], view2], view6]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, view7, [view4, view3], view2], view6]);
|
||||
|
||||
const view8 = new TestView(20, 20, 20, 20);
|
||||
gridview.addView(view8 as IView, 200, [1, 1, 0]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getViews()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]);
|
||||
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]);
|
||||
|
||||
gridview.dispose();
|
||||
});
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IView, GridNode, isGridBranchNode, } from 'vs/base/browser/ui/grid/gridview';
|
||||
import { GridNode, isGridBranchNode } from 'vs/base/browser/ui/grid/gridview';
|
||||
import { IView } from 'vs/base/browser/ui/grid/grid';
|
||||
|
||||
export class TestView implements IView {
|
||||
|
||||
@@ -78,4 +79,4 @@ export function nodesToArrays(node: GridNode): any {
|
||||
} else {
|
||||
return node.view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
@@ -305,7 +305,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('ctor', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
assert(model);
|
||||
assert.equal(list.length, 0);
|
||||
assert.equal(model.size, 0);
|
||||
@@ -313,7 +313,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('flat', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{ element: 0 },
|
||||
@@ -340,7 +340,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('nested', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{
|
||||
@@ -376,7 +376,7 @@ suite('CompressedObjectTree', function () {
|
||||
|
||||
test('compressed', () => {
|
||||
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
|
||||
const model = new CompressedObjectTreeModel<number>(toSpliceable(list));
|
||||
const model = new CompressedTreeModel<number>(toSpliceable(list));
|
||||
|
||||
model.setChildren(null, Iterator.fromArray([
|
||||
{
|
||||
|
||||
@@ -470,4 +470,19 @@ suite('Filters', () => {
|
||||
test('List highlight filter: Not all characters from match are highlighterd #66923', () => {
|
||||
assertMatches('foo', 'barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo', 'barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_^f^o^o', fuzzyScore);
|
||||
});
|
||||
|
||||
test('Autocompletion is matched against truncated filterText to 54 characters #74133', () => {
|
||||
assertMatches(
|
||||
'foo',
|
||||
'ffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo',
|
||||
'ffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_^f^o^o',
|
||||
fuzzyScore
|
||||
);
|
||||
assertMatches(
|
||||
'foo',
|
||||
'Gffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo',
|
||||
undefined,
|
||||
fuzzyScore
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
|
||||
import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc';
|
||||
import { DialogChannelClient } from 'vs/platform/dialogs/node/dialogIpc';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DownloadService } from 'vs/platform/download/common/downloadService';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
|
||||
@@ -154,7 +154,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
if (!extensionDevelopmentLocationURI && !environmentService.args['disable-telemetry'] && product.enableTelemetry) {
|
||||
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
|
||||
appInsightsAppender = new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, telemetryLogService);
|
||||
disposables.add(appInsightsAppender); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
|
||||
}
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender: combinedAppender(appInsightsAppender, new LogAppender(logService)),
|
||||
|
||||
@@ -77,7 +77,7 @@ import { HistoryMainService } from 'vs/platform/history/electron-main/historyMai
|
||||
import { URLService } from 'vs/platform/url/common/urlService';
|
||||
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
|
||||
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { statSync } from 'fs';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
@@ -167,9 +167,11 @@ export class CodeApplication extends Disposable {
|
||||
event.preventDefault();
|
||||
});
|
||||
app.on('remote-get-current-web-contents', event => {
|
||||
this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);
|
||||
|
||||
event.preventDefault();
|
||||
// The driver needs access to web contents
|
||||
if (!this.environmentService.args.driver) {
|
||||
this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
app.on('web-contents-created', (_event: Electron.Event, contents) => {
|
||||
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
|
||||
@@ -708,7 +710,7 @@ export class CodeApplication extends Disposable {
|
||||
const options: IConnectionOptions = {
|
||||
isBuilt,
|
||||
commit: product.commit,
|
||||
webSocketFactory: nodeWebSocketFactory,
|
||||
socketFactory: nodeSocketFactory,
|
||||
addressProvider: {
|
||||
getAddress: () => {
|
||||
return Promise.resolve({ host, port });
|
||||
|
||||
@@ -86,7 +86,7 @@ export class Main {
|
||||
await this.setInstallSource(argv['install-source']);
|
||||
|
||||
} else if (argv['list-extensions']) {
|
||||
await this.listExtensions(!!argv['show-versions']);
|
||||
await this.listExtensions(!!argv['show-versions'], argv['category']);
|
||||
|
||||
} else if (argv['install-extension']) {
|
||||
const arg = argv['install-extension'];
|
||||
@@ -110,8 +110,17 @@ export class Main {
|
||||
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
|
||||
}
|
||||
|
||||
private async listExtensions(showVersions: boolean): Promise<void> {
|
||||
const extensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
private async listExtensions(showVersions: boolean, category?: string): Promise<void> {
|
||||
let extensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
if (category) {
|
||||
extensions = extensions.filter(e => {
|
||||
if (e.manifest.categories) {
|
||||
const lowerCaseCategories: string[] = e.manifest.categories.map(c => c.toLowerCase());
|
||||
return lowerCaseCategories.indexOf(category.toLowerCase()) > -1;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
|
||||
}
|
||||
|
||||
@@ -345,8 +354,6 @@ export async function main(argv: ParsedArgs): Promise<void> {
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
|
||||
// Dispose the AI adapter so that remaining data gets flushed.
|
||||
disposables.add(combinedAppender(...appenders));
|
||||
} else {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
@@ -356,6 +363,8 @@ export async function main(argv: ParsedArgs): Promise<void> {
|
||||
|
||||
try {
|
||||
await main.run(argv);
|
||||
// Flush the remaining data in AI adapter.
|
||||
await combinedAppender(...appenders).flush();
|
||||
} finally {
|
||||
disposables.dispose();
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ export class MouseHandler extends ViewEventHandler {
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, 'mousewheel', onMouseWheel, true));
|
||||
this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, true));
|
||||
|
||||
this._context.addEventHandler(this);
|
||||
}
|
||||
|
||||
@@ -407,7 +407,12 @@ class HitTestRequest extends BareHitTestRequest {
|
||||
}
|
||||
|
||||
public fulfill(type: MouseTargetType, position: Position | null = null, range: EditorRange | null = null, detail: any = null): MouseTarget {
|
||||
return new MouseTarget(this.target, type, this.mouseColumn, position, range, detail);
|
||||
let mouseColumn = this.mouseColumn;
|
||||
if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) {
|
||||
// Most likely, the line contains foreign decorations...
|
||||
mouseColumn = position.column;
|
||||
}
|
||||
return new MouseTarget(this.target, type, mouseColumn, position, range, detail);
|
||||
}
|
||||
|
||||
public withTarget(target: Element | null): HitTestRequest {
|
||||
|
||||
@@ -24,6 +24,10 @@ import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export type ServicesAccessor = ServicesAccessor;
|
||||
export type IEditorContributionCtor = IConstructorSignature1<ICodeEditor, IEditorContribution>;
|
||||
export type EditorTelemetryDataFragment = {
|
||||
target: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
snippet: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
//#region Command
|
||||
|
||||
@@ -219,16 +223,15 @@ export abstract class EditorAction extends EditorCommand {
|
||||
}
|
||||
|
||||
protected reportTelemetry(accessor: ServicesAccessor, editor: ICodeEditor) {
|
||||
/* __GDPR__
|
||||
"editorActionInvoked" : {
|
||||
"name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"${include}": [
|
||||
"${EditorTelemetryData}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
accessor.get(ITelemetryService).publicLog('editorActionInvoked', { name: this.label, id: this.id, ...editor.getTelemetryData() });
|
||||
type EditorActionInvokedClassification = {
|
||||
name: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
type EditorActionInvokedEvent = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
accessor.get(ITelemetryService).publicLog2<EditorActionInvokedEvent, EditorActionInvokedClassification>('editorActionInvoked', { name: this.label, id: this.id });
|
||||
}
|
||||
|
||||
public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void>;
|
||||
|
||||
@@ -1508,9 +1508,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
return this._codeEditorService.resolveDecorationOptions(typeKey, writable);
|
||||
}
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"EditorTelemetryData" : {}
|
||||
*/
|
||||
public getTelemetryData(): { [key: string]: any; } | undefined {
|
||||
return this._telemetryData;
|
||||
}
|
||||
@@ -1851,4 +1848,6 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (unnecessaryBorder) {
|
||||
collector.addRule(`.${SHOW_UNUSED_ENABLED_CLASS} .monaco-editor .${ClassName.EditorUnnecessaryDecoration} { border-bottom: 2px dashed ${unnecessaryBorder}; }`);
|
||||
}
|
||||
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorDeprecatedInlineDecoration} { text-decoration: line-through; }`);
|
||||
});
|
||||
|
||||
@@ -39,4 +39,10 @@
|
||||
.monaco-editor .view-overlays {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
.monaco-editor .auto-closed-character {
|
||||
opacity: 0.3;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -696,7 +696,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
},
|
||||
'editor.suggest.filteredTypes': {
|
||||
type: 'object',
|
||||
default: { keyword: true },
|
||||
default: { keyword: true, snippet: true },
|
||||
markdownDescription: nls.localize('suggest.filtered', "Controls whether some suggestion types should be filtered from IntelliSense. A list of suggestion types can be found here: https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions."),
|
||||
properties: {
|
||||
method: {
|
||||
@@ -904,7 +904,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'enum': ['none', 'boundary', 'selection', 'all'],
|
||||
'enumDescriptions': [
|
||||
'',
|
||||
nls.localize('renderWhiteSpace.boundary', "Render whitespace characters except for single spaces between words."),
|
||||
nls.localize('renderWhitespace.boundary', "Render whitespace characters except for single spaces between words."),
|
||||
nls.localize('renderWhitespace.selection', "Render whitespace characters only on selected text."),
|
||||
''
|
||||
],
|
||||
|
||||
@@ -10,15 +10,16 @@ import { CursorCollection } from 'vs/editor/common/controller/cursorCollection';
|
||||
import { CursorColumns, CursorConfiguration, CursorContext, CursorState, EditOperationResult, EditOperationType, IColumnSelectData, ICursors, PartialCursorState, RevealTarget } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
|
||||
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations';
|
||||
import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
function containsLineMappingChanged(events: viewEvents.ViewEvent[]): boolean {
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
@@ -83,6 +84,64 @@ export class CursorModelState {
|
||||
}
|
||||
}
|
||||
|
||||
class AutoClosedAction {
|
||||
|
||||
private readonly _model: ITextModel;
|
||||
|
||||
private _autoClosedCharactersDecorations: string[];
|
||||
private _autoClosedEnclosingDecorations: string[];
|
||||
|
||||
constructor(model: ITextModel, autoClosedCharactersDecorations: string[], autoClosedEnclosingDecorations: string[]) {
|
||||
this._model = model;
|
||||
this._autoClosedCharactersDecorations = autoClosedCharactersDecorations;
|
||||
this._autoClosedEnclosingDecorations = autoClosedEnclosingDecorations;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._autoClosedCharactersDecorations = this._model.deltaDecorations(this._autoClosedCharactersDecorations, []);
|
||||
this._autoClosedEnclosingDecorations = this._model.deltaDecorations(this._autoClosedEnclosingDecorations, []);
|
||||
}
|
||||
|
||||
public getAutoClosedCharactersRanges(): Range[] {
|
||||
let result: Range[] = [];
|
||||
for (let i = 0; i < this._autoClosedCharactersDecorations.length; i++) {
|
||||
const decorationRange = this._model.getDecorationRange(this._autoClosedCharactersDecorations[i]);
|
||||
if (decorationRange) {
|
||||
result.push(decorationRange);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public isValid(selections: Range[]): boolean {
|
||||
let enclosingRanges: Range[] = [];
|
||||
for (let i = 0; i < this._autoClosedEnclosingDecorations.length; i++) {
|
||||
const decorationRange = this._model.getDecorationRange(this._autoClosedEnclosingDecorations[i]);
|
||||
if (decorationRange) {
|
||||
enclosingRanges.push(decorationRange);
|
||||
if (decorationRange.startLineNumber !== decorationRange.endLineNumber) {
|
||||
// Stop tracking if the range becomes multiline...
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
enclosingRanges.sort(Range.compareRangesUsingStarts);
|
||||
|
||||
selections.sort(Range.compareRangesUsingStarts);
|
||||
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
if (i >= enclosingRanges.length) {
|
||||
return false;
|
||||
}
|
||||
if (!enclosingRanges[i].strictContainsRange(selections[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|
||||
public static MAX_CURSOR_COUNT = 10000;
|
||||
@@ -106,6 +165,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
private _isHandling: boolean;
|
||||
private _isDoingComposition: boolean;
|
||||
private _columnSelectData: IColumnSelectData | null;
|
||||
private _autoClosedActions: AutoClosedAction[];
|
||||
private _prevEditOperationType: EditOperationType;
|
||||
|
||||
constructor(configuration: editorCommon.IConfiguration, model: ITextModel, viewModel: IViewModel) {
|
||||
@@ -120,6 +180,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
this._isHandling = false;
|
||||
this._isDoingComposition = false;
|
||||
this._columnSelectData = null;
|
||||
this._autoClosedActions = [];
|
||||
this._prevEditOperationType = EditOperationType.Other;
|
||||
|
||||
this._register(this._model.onDidChangeRawContent((e) => {
|
||||
@@ -173,9 +234,24 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|
||||
public dispose(): void {
|
||||
this._cursors.dispose();
|
||||
this._autoClosedActions = dispose(this._autoClosedActions);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _validateAutoClosedActions(): void {
|
||||
if (this._autoClosedActions.length > 0) {
|
||||
let selections: Range[] = this._cursors.getSelections();
|
||||
for (let i = 0; i < this._autoClosedActions.length; i++) {
|
||||
const autoClosedAction = this._autoClosedActions[i];
|
||||
if (!autoClosedAction.isValid(selections)) {
|
||||
autoClosedAction.dispose();
|
||||
this._autoClosedActions.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------ some getters/setters
|
||||
|
||||
public getPrimaryCursor(): CursorState {
|
||||
@@ -202,6 +278,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
this._cursors.normalize();
|
||||
this._columnSelectData = null;
|
||||
|
||||
this._validateAutoClosedActions();
|
||||
|
||||
this._emitStateChangedIfNecessary(source, reason, oldState);
|
||||
}
|
||||
|
||||
@@ -296,7 +374,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
// a model.setValue() was called
|
||||
this._cursors.dispose();
|
||||
this._cursors = new CursorCollection(this.context);
|
||||
|
||||
this._validateAutoClosedActions();
|
||||
this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null);
|
||||
} else {
|
||||
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
|
||||
@@ -367,6 +445,35 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
// The commands were applied correctly
|
||||
this._interpretCommandResult(result);
|
||||
|
||||
// Check for auto-closing closed characters
|
||||
let autoClosedCharactersRanges: IModelDeltaDecoration[] = [];
|
||||
let autoClosedEnclosingRanges: IModelDeltaDecoration[] = [];
|
||||
|
||||
for (let i = 0; i < opResult.commands.length; i++) {
|
||||
const command = opResult.commands[i];
|
||||
if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) {
|
||||
autoClosedCharactersRanges.push({
|
||||
range: command.closeCharacterRange,
|
||||
options: {
|
||||
inlineClassName: 'auto-closed-character',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
|
||||
}
|
||||
});
|
||||
autoClosedEnclosingRanges.push({
|
||||
range: command.enclosingRange,
|
||||
options: {
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (autoClosedCharactersRanges.length > 0) {
|
||||
const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersRanges);
|
||||
const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingRanges);
|
||||
this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations));
|
||||
}
|
||||
|
||||
this._prevEditOperationType = opResult.type;
|
||||
}
|
||||
|
||||
@@ -540,6 +647,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
this._cursors.startTrackingSelections();
|
||||
}
|
||||
|
||||
this._validateAutoClosedActions();
|
||||
|
||||
if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) {
|
||||
this._revealRange(RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
@@ -566,8 +675,15 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
chr = text.charAt(i);
|
||||
}
|
||||
|
||||
let autoClosedCharacters: Range[] = [];
|
||||
if (this._autoClosedActions.length > 0) {
|
||||
for (let i = 0, len = this._autoClosedActions.length; i < len; i++) {
|
||||
autoClosedCharacters = autoClosedCharacters.concat(this._autoClosedActions[i].getAutoClosedCharactersRanges());
|
||||
}
|
||||
}
|
||||
|
||||
// Here we must interpret each typed character individually, that's why we create a new context
|
||||
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), chr));
|
||||
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationT
|
||||
import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ICommand } from 'vs/editor/common/editorCommon';
|
||||
import { ICommand, ICursorStateComputerData } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { EnterAction, IndentAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
@@ -430,7 +430,7 @@ export class TypeOperations {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): boolean {
|
||||
private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): boolean {
|
||||
const autoCloseConfig = isQuote(ch) ? config.autoClosingQuotes : config.autoClosingBrackets;
|
||||
|
||||
if (autoCloseConfig === 'never' || !config.autoClosingPairsClose.hasOwnProperty(ch)) {
|
||||
@@ -461,6 +461,19 @@ export class TypeOperations {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Must over-type a closing character typed by the editor
|
||||
let found = false;
|
||||
for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) {
|
||||
const autoClosedCharacter = autoClosedCharacters[j];
|
||||
if (position.lineNumber === autoClosedCharacter.startLineNumber && position.column === autoClosedCharacter.startColumn) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -573,7 +586,7 @@ export class TypeOperations {
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i];
|
||||
const closeCharacter = config.autoClosingPairsOpen[ch];
|
||||
commands[i] = new ReplaceCommandWithOffsetCursorState(selection, ch + closeCharacter, 0, -closeCharacter.length);
|
||||
commands[i] = new TypeWithAutoClosingCommand(selection, ch, closeCharacter);
|
||||
}
|
||||
return new EditOperationResult(EditOperationType.Typing, commands, {
|
||||
shouldPushStackElementBefore: true,
|
||||
@@ -802,7 +815,7 @@ export class TypeOperations {
|
||||
});
|
||||
}
|
||||
|
||||
public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult {
|
||||
public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult {
|
||||
|
||||
if (ch === '\n') {
|
||||
let commands: ICommand[] = [];
|
||||
@@ -833,7 +846,7 @@ export class TypeOperations {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._isAutoClosingCloseCharType(config, model, selections, ch)) {
|
||||
if (this._isAutoClosingCloseCharType(config, model, selections, autoClosedCharacters, ch)) {
|
||||
return this._runAutoClosingCloseCharType(prevEditOperationType, config, model, selections, ch);
|
||||
}
|
||||
|
||||
@@ -923,3 +936,24 @@ export class TypeOperations {
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState {
|
||||
|
||||
private _closeCharacter: string;
|
||||
public closeCharacterRange: Range | null;
|
||||
public enclosingRange: Range | null;
|
||||
|
||||
constructor(selection: Selection, openCharacter: string, closeCharacter: string) {
|
||||
super(selection, openCharacter + closeCharacter, 0, -closeCharacter.length);
|
||||
this._closeCharacter = closeCharacter;
|
||||
this.closeCharacterRange = null;
|
||||
}
|
||||
|
||||
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
|
||||
let inverseEditOperations = helper.getInverseEditOperations();
|
||||
let range = inverseEditOperations[0].range;
|
||||
this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn);
|
||||
this.enclosingRange = range;
|
||||
return super.computeCursorState(model, helper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,32 @@ export class Range {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if `range` is strictly in this range. `range` must start after and end before this range for the result to be true.
|
||||
*/
|
||||
public strictContainsRange(range: IRange): boolean {
|
||||
return Range.strictContainsRange(this, range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if `otherRange` is strinctly in `range` (must start after, and end before). If the ranges are equal, will return false.
|
||||
*/
|
||||
public static strictContainsRange(range: IRange, otherRange: IRange): boolean {
|
||||
if (otherRange.startLineNumber < range.startLineNumber || otherRange.endLineNumber < range.startLineNumber) {
|
||||
return false;
|
||||
}
|
||||
if (otherRange.startLineNumber > range.endLineNumber || otherRange.endLineNumber > range.endLineNumber) {
|
||||
return false;
|
||||
}
|
||||
if (otherRange.startLineNumber === range.startLineNumber && otherRange.startColumn <= range.startColumn) {
|
||||
return false;
|
||||
}
|
||||
if (otherRange.endLineNumber === range.endLineNumber && otherRange.endColumn >= range.endColumn) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reunion of the two ranges.
|
||||
* The smallest position will be used as the start point, and the largest one as the end point.
|
||||
|
||||
@@ -17,7 +17,8 @@ export const enum ClassName {
|
||||
EditorWarningDecoration = 'squiggly-warning',
|
||||
EditorErrorDecoration = 'squiggly-error',
|
||||
EditorUnnecessaryDecoration = 'squiggly-unnecessary',
|
||||
EditorUnnecessaryInlineDecoration = 'squiggly-inline-unnecessary'
|
||||
EditorUnnecessaryInlineDecoration = 'squiggly-inline-unnecessary',
|
||||
EditorDeprecatedInlineDecoration = 'squiggly-inline-deprecated'
|
||||
}
|
||||
|
||||
export const enum NodeColor {
|
||||
|
||||
@@ -545,6 +545,12 @@ export class Searcher {
|
||||
const matchStartIndex = m.index;
|
||||
const matchLength = m[0].length;
|
||||
if (matchStartIndex === this._prevMatchStartIndex && matchLength === this._prevMatchLength) {
|
||||
if (matchLength === 0) {
|
||||
// the search result is an empty string and won't advance `regex.lastIndex`, so `regex.exec` will stuck here
|
||||
// we attempt to recover from that by advancing by one
|
||||
this._searchRegex.lastIndex += 1;
|
||||
continue;
|
||||
}
|
||||
// Exit early if the regex matches the same range twice
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -221,6 +221,9 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor
|
||||
if (marker.tags.indexOf(MarkerTag.Unnecessary) !== -1) {
|
||||
inlineClassName = ClassName.EditorUnnecessaryInlineDecoration;
|
||||
}
|
||||
if (marker.tags.indexOf(MarkerTag.Deprecated) !== -1) {
|
||||
inlineClassName = ClassName.EditorDeprecatedInlineDecoration;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
|
||||
export enum MarkerTag {
|
||||
Unnecessary = 1
|
||||
Unnecessary = 1,
|
||||
Deprecated = 2
|
||||
}
|
||||
|
||||
export enum MarkerSeverity {
|
||||
|
||||
@@ -148,12 +148,6 @@ class ExecCommandCopyAction extends ExecCommandAction {
|
||||
if (!emptySelectionClipboard && editor.getSelection().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Prevent copying an empty line by accident
|
||||
if (editor.getSelections().length === 1 && editor.getSelection().isEmpty()) {
|
||||
if (editor.getModel().getLineFirstNonWhitespaceColumn(editor.getSelection().positionLineNumber) === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.run(accessor, editor);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'vs/css!./media/outlineTree';
|
||||
import 'vs/css!./media/symbol-icons';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { SymbolKind, symbolKindToCssClass } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -38,7 +38,7 @@ export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelP
|
||||
|
||||
|
||||
export class OutlineIdentityProvider implements IIdentityProvider<OutlineItem> {
|
||||
getId(element: TreeElement): { toString(): string; } {
|
||||
getId(element: OutlineItem): { toString(): string; } {
|
||||
return element.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,8 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
searchScope: null,
|
||||
matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, false),
|
||||
wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, false),
|
||||
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false)
|
||||
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false),
|
||||
preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, false)
|
||||
}, false);
|
||||
|
||||
if (shouldRestartFind) {
|
||||
@@ -170,13 +171,17 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
if (e.matchCase) {
|
||||
this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE);
|
||||
}
|
||||
if (e.preserveCase) {
|
||||
this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private loadQueryState() {
|
||||
this._state.change({
|
||||
matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, this._state.matchCase),
|
||||
wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, this._state.wholeWord),
|
||||
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex)
|
||||
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex),
|
||||
preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, this._state.preserveCase)
|
||||
}, false);
|
||||
}
|
||||
|
||||
@@ -217,6 +222,11 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
}
|
||||
}
|
||||
|
||||
public togglePreserveCase(): void {
|
||||
this._state.change({ preserveCase: !this._state.preserveCase }, false);
|
||||
this.highlightFindOptions();
|
||||
}
|
||||
|
||||
public toggleSearchScope(): void {
|
||||
if (this._state.searchScope) {
|
||||
this._state.change({ searchScope: null }, true);
|
||||
|
||||
@@ -59,6 +59,7 @@ export const FIND_IDS = {
|
||||
ToggleWholeWordCommand: 'toggleFindWholeWord',
|
||||
ToggleRegexCommand: 'toggleFindRegex',
|
||||
ToggleSearchScopeCommand: 'toggleFindInSelection',
|
||||
TogglePreserveCaseCommand: 'togglePreserveCase',
|
||||
ReplaceOneAction: 'editor.action.replaceOne',
|
||||
ReplaceAllAction: 'editor.action.replaceAll',
|
||||
SelectAllMatchesAction: 'editor.action.selectAllMatches'
|
||||
@@ -416,11 +417,11 @@ export class FindModelBoundToEditorModel {
|
||||
|
||||
let replacePattern = this._getReplacePattern();
|
||||
let selection = this._editor.getSelection();
|
||||
let nextMatch = this._getNextMatch(selection.getStartPosition(), replacePattern.hasReplacementPatterns, false);
|
||||
let nextMatch = this._getNextMatch(selection.getStartPosition(), true, false);
|
||||
if (nextMatch) {
|
||||
if (selection.equalsRange(nextMatch.range)) {
|
||||
// selection sits on a find match => replace it!
|
||||
let replaceString = replacePattern.buildReplaceString(nextMatch.matches);
|
||||
let replaceString = replacePattern.buildReplaceString(nextMatch.matches, this._state.preserveCase);
|
||||
|
||||
let command = new ReplaceCommand(selection, replaceString);
|
||||
|
||||
@@ -482,12 +483,14 @@ export class FindModelBoundToEditorModel {
|
||||
|
||||
const replacePattern = this._getReplacePattern();
|
||||
let resultText: string;
|
||||
const preserveCase = this._state.preserveCase;
|
||||
|
||||
if (replacePattern.hasReplacementPatterns) {
|
||||
resultText = modelText.replace(searchRegex, function () {
|
||||
return replacePattern.buildReplaceString(<string[]><any>arguments);
|
||||
return replacePattern.buildReplaceString(<string[]><any>arguments, preserveCase);
|
||||
});
|
||||
} else {
|
||||
resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null));
|
||||
resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null, preserveCase));
|
||||
}
|
||||
|
||||
let command = new ReplaceCommandThatPreservesSelection(fullModelRange, resultText, this._editor.getSelection());
|
||||
@@ -501,7 +504,7 @@ export class FindModelBoundToEditorModel {
|
||||
|
||||
let replaceStrings: string[] = [];
|
||||
for (let i = 0, len = matches.length; i < len; i++) {
|
||||
replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches);
|
||||
replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches, this._state.preserveCase);
|
||||
}
|
||||
|
||||
let command = new ReplaceAllCommand(this._editor.getSelection(), matches.map(m => m.range), replaceStrings);
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface FindReplaceStateChangedEvent {
|
||||
isRegex: boolean;
|
||||
wholeWord: boolean;
|
||||
matchCase: boolean;
|
||||
preserveCase: boolean;
|
||||
searchScope: boolean;
|
||||
matchesPosition: boolean;
|
||||
matchesCount: boolean;
|
||||
@@ -41,6 +42,8 @@ export interface INewFindReplaceState {
|
||||
wholeWordOverride?: FindOptionOverride;
|
||||
matchCase?: boolean;
|
||||
matchCaseOverride?: FindOptionOverride;
|
||||
preserveCase?: boolean;
|
||||
preserveCaseOverride?: FindOptionOverride;
|
||||
searchScope?: Range | null;
|
||||
}
|
||||
|
||||
@@ -65,6 +68,8 @@ export class FindReplaceState implements IDisposable {
|
||||
private _wholeWordOverride: FindOptionOverride;
|
||||
private _matchCase: boolean;
|
||||
private _matchCaseOverride: FindOptionOverride;
|
||||
private _preserveCase: boolean;
|
||||
private _preserveCaseOverride: FindOptionOverride;
|
||||
private _searchScope: Range | null;
|
||||
private _matchesPosition: number;
|
||||
private _matchesCount: number;
|
||||
@@ -78,10 +83,12 @@ export class FindReplaceState implements IDisposable {
|
||||
public get isRegex(): boolean { return effectiveOptionValue(this._isRegexOverride, this._isRegex); }
|
||||
public get wholeWord(): boolean { return effectiveOptionValue(this._wholeWordOverride, this._wholeWord); }
|
||||
public get matchCase(): boolean { return effectiveOptionValue(this._matchCaseOverride, this._matchCase); }
|
||||
public get preserveCase(): boolean { return effectiveOptionValue(this._preserveCaseOverride, this._preserveCase); }
|
||||
|
||||
public get actualIsRegex(): boolean { return this._isRegex; }
|
||||
public get actualWholeWord(): boolean { return this._wholeWord; }
|
||||
public get actualMatchCase(): boolean { return this._matchCase; }
|
||||
public get actualPreserveCase(): boolean { return this._preserveCase; }
|
||||
|
||||
public get searchScope(): Range | null { return this._searchScope; }
|
||||
public get matchesPosition(): number { return this._matchesPosition; }
|
||||
@@ -100,6 +107,8 @@ export class FindReplaceState implements IDisposable {
|
||||
this._wholeWordOverride = FindOptionOverride.NotSet;
|
||||
this._matchCase = false;
|
||||
this._matchCaseOverride = FindOptionOverride.NotSet;
|
||||
this._preserveCase = false;
|
||||
this._preserveCaseOverride = FindOptionOverride.NotSet;
|
||||
this._searchScope = null;
|
||||
this._matchesPosition = 0;
|
||||
this._matchesCount = 0;
|
||||
@@ -120,6 +129,7 @@ export class FindReplaceState implements IDisposable {
|
||||
isRegex: false,
|
||||
wholeWord: false,
|
||||
matchCase: false,
|
||||
preserveCase: false,
|
||||
searchScope: false,
|
||||
matchesPosition: false,
|
||||
matchesCount: false,
|
||||
@@ -169,6 +179,7 @@ export class FindReplaceState implements IDisposable {
|
||||
isRegex: false,
|
||||
wholeWord: false,
|
||||
matchCase: false,
|
||||
preserveCase: false,
|
||||
searchScope: false,
|
||||
matchesPosition: false,
|
||||
matchesCount: false,
|
||||
@@ -179,6 +190,7 @@ export class FindReplaceState implements IDisposable {
|
||||
const oldEffectiveIsRegex = this.isRegex;
|
||||
const oldEffectiveWholeWords = this.wholeWord;
|
||||
const oldEffectiveMatchCase = this.matchCase;
|
||||
const oldEffectivePreserveCase = this.preserveCase;
|
||||
|
||||
if (typeof newState.searchString !== 'undefined') {
|
||||
if (this._searchString !== newState.searchString) {
|
||||
@@ -217,6 +229,9 @@ export class FindReplaceState implements IDisposable {
|
||||
if (typeof newState.matchCase !== 'undefined') {
|
||||
this._matchCase = newState.matchCase;
|
||||
}
|
||||
if (typeof newState.preserveCase !== 'undefined') {
|
||||
this._preserveCase = newState.preserveCase;
|
||||
}
|
||||
if (typeof newState.searchScope !== 'undefined') {
|
||||
if (!Range.equalsRange(this._searchScope, newState.searchScope)) {
|
||||
this._searchScope = newState.searchScope;
|
||||
@@ -229,6 +244,7 @@ export class FindReplaceState implements IDisposable {
|
||||
this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet);
|
||||
this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet);
|
||||
this._matchCaseOverride = (typeof newState.matchCaseOverride !== 'undefined' ? newState.matchCaseOverride : FindOptionOverride.NotSet);
|
||||
this._preserveCaseOverride = (typeof newState.preserveCaseOverride !== 'undefined' ? newState.preserveCaseOverride : FindOptionOverride.NotSet);
|
||||
|
||||
if (oldEffectiveIsRegex !== this.isRegex) {
|
||||
somethingChanged = true;
|
||||
@@ -243,6 +259,11 @@ export class FindReplaceState implements IDisposable {
|
||||
changeEvent.matchCase = true;
|
||||
}
|
||||
|
||||
if (oldEffectivePreserveCase !== this.preserveCase) {
|
||||
somethingChanged = true;
|
||||
changeEvent.preserveCase = true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
this._onFindReplaceStateChange.fire(changeEvent);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
transition: top 200ms linear;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget.hiddenEditor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Find widget when replace is toggled on */
|
||||
.monaco-editor .find-widget.replaceToggled {
|
||||
top: -74px; /* find input height + replace input height + shadow (10px) */
|
||||
@@ -79,6 +84,15 @@
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input {
|
||||
width: 100% !important;
|
||||
padding-right: 66px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input {
|
||||
padding-right: 22px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input,
|
||||
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input {
|
||||
padding-top: 2px;
|
||||
@@ -224,12 +238,19 @@
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .replace-part > .replace-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
vertical-align: middle;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .replace-part > .replace-input > .controls {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
/* REDUCED */
|
||||
.monaco-editor .find-widget.reduced-find-widget .matchesCount,
|
||||
.monaco-editor .find-widget.reduced-find-widget .monaco-checkbox {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findIn
|
||||
import { HistoryInputBox, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
@@ -47,6 +48,7 @@ const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind'
|
||||
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
|
||||
const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");
|
||||
const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace");
|
||||
const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case");
|
||||
const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");
|
||||
const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");
|
||||
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
|
||||
@@ -101,6 +103,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
private _nextBtn: SimpleButton;
|
||||
private _toggleSelectionFind: SimpleCheckbox;
|
||||
private _closeBtn: SimpleButton;
|
||||
private _preserveCase: Checkbox;
|
||||
private _replaceBtn: SimpleButton;
|
||||
private _replaceAllBtn: SimpleButton;
|
||||
|
||||
@@ -590,14 +593,26 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
};
|
||||
this._findInput.style(inputStyles);
|
||||
this._replaceInputBox.style(inputStyles);
|
||||
this._preserveCase.style(inputStyles);
|
||||
}
|
||||
|
||||
private _tryUpdateWidgetWidth() {
|
||||
if (!this._isVisible) {
|
||||
return;
|
||||
}
|
||||
let editorWidth = this._codeEditor.getConfiguration().layoutInfo.width;
|
||||
let minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth;
|
||||
|
||||
const editorContentWidth = this._codeEditor.getConfiguration().layoutInfo.contentWidth;
|
||||
|
||||
if (editorContentWidth <= 0) {
|
||||
// for example, diff view original editor
|
||||
dom.addClass(this._domNode, 'hiddenEditor');
|
||||
return;
|
||||
} else if (dom.hasClass(this._domNode, 'hiddenEditor')) {
|
||||
dom.removeClass(this._domNode, 'hiddenEditor');
|
||||
}
|
||||
|
||||
const editorWidth = this._codeEditor.getConfiguration().layoutInfo.width;
|
||||
const minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth;
|
||||
let collapsedFindWidget = false;
|
||||
let reducedFindWidget = false;
|
||||
let narrowFindWidget = false;
|
||||
@@ -913,6 +928,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
this._state.change({ replaceString: this._replaceInputBox.value }, false);
|
||||
}));
|
||||
|
||||
this._preserveCase = this._register(new Checkbox({
|
||||
actionClassName: 'monaco-case-sensitive',
|
||||
title: NLS_PRESERVE_CASE_LABEL,
|
||||
isChecked: false,
|
||||
}));
|
||||
this._preserveCase.checked = !!this._state.preserveCase;
|
||||
this._register(this._preserveCase.onChange(viaKeyboard => {
|
||||
if (!viaKeyboard) {
|
||||
this._state.change({ preserveCase: !this._state.preserveCase }, false);
|
||||
this._replaceInputBox.focus();
|
||||
}
|
||||
}));
|
||||
|
||||
// Replace one button
|
||||
this._replaceBtn = this._register(new SimpleButton({
|
||||
label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),
|
||||
@@ -937,6 +965,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
}
|
||||
}));
|
||||
|
||||
let controls = document.createElement('div');
|
||||
controls.className = 'controls';
|
||||
controls.style.display = 'block';
|
||||
controls.appendChild(this._preserveCase.domNode);
|
||||
replaceInput.appendChild(controls);
|
||||
|
||||
let replacePart = document.createElement('div');
|
||||
replacePart.className = 'replace-part';
|
||||
replacePart.appendChild(replaceInput);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { containsUppercaseCharacter } from 'vs/base/common/strings';
|
||||
|
||||
const enum ReplacePatternKind {
|
||||
StaticValue = 0,
|
||||
@@ -48,9 +49,22 @@ export class ReplacePattern {
|
||||
}
|
||||
}
|
||||
|
||||
public buildReplaceString(matches: string[] | null): string {
|
||||
public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string {
|
||||
if (this._state.kind === ReplacePatternKind.StaticValue) {
|
||||
return this._state.staticValue;
|
||||
if (preserveCase && matches && (matches[0] !== '')) {
|
||||
if (matches[0].toUpperCase() === matches[0]) {
|
||||
return this._state.staticValue.toUpperCase();
|
||||
} else if (matches[0].toLowerCase() === matches[0]) {
|
||||
return this._state.staticValue.toLowerCase();
|
||||
} else if (containsUppercaseCharacter(matches[0][0])) {
|
||||
return this._state.staticValue[0].toUpperCase() + this._state.staticValue.substr(1);
|
||||
} else {
|
||||
// we don't understand its pattern yet.
|
||||
return this._state.staticValue;
|
||||
}
|
||||
} else {
|
||||
return this._state.staticValue;
|
||||
}
|
||||
}
|
||||
|
||||
let result = '';
|
||||
|
||||
@@ -41,7 +41,8 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
private readonly _state: FindReplaceState = new FindReplaceState(),
|
||||
showOptionButtons?: boolean
|
||||
showOptionButtons?: boolean,
|
||||
private readonly _invertDefaultDirection: boolean = false
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -93,13 +94,13 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
|
||||
this._register(this._findInput.onKeyDown((e) => {
|
||||
if (e.equals(KeyCode.Enter)) {
|
||||
this.find(false);
|
||||
this.find(this._invertDefaultDirection);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
|
||||
this.find(true);
|
||||
this.find(!this._invertDefaultDirection);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
@@ -295,4 +296,4 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (widgetShadowColor) {
|
||||
collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,4 +153,26 @@ suite('Replace Pattern test', () => {
|
||||
let actual = replacePattern.buildReplaceString(matches);
|
||||
assert.equal(actual, 'a{}');
|
||||
});
|
||||
|
||||
test('preserve case', () => {
|
||||
let replacePattern = parseReplaceString('Def');
|
||||
let actual = replacePattern.buildReplaceString(['abc'], true);
|
||||
assert.equal(actual, 'def');
|
||||
actual = replacePattern.buildReplaceString(['Abc'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
actual = replacePattern.buildReplaceString(['ABC'], true);
|
||||
assert.equal(actual, 'DEF');
|
||||
|
||||
actual = replacePattern.buildReplaceString(['abc', 'Abc'], true);
|
||||
assert.equal(actual, 'def');
|
||||
actual = replacePattern.buildReplaceString(['Abc', 'abc'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
actual = replacePattern.buildReplaceString(['ABC', 'abc'], true);
|
||||
assert.equal(actual, 'DEF');
|
||||
|
||||
actual = replacePattern.buildReplaceString(['AbC'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
actual = replacePattern.buildReplaceString(['aBC'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -589,13 +589,13 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
|
||||
private shouldIgnoreLine(model: ITextModel, lineNumber: number): boolean {
|
||||
model.forceTokenization(lineNumber);
|
||||
let nonWhiteSpaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
|
||||
if (nonWhiteSpaceColumn === 0) {
|
||||
let nonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
|
||||
if (nonWhitespaceColumn === 0) {
|
||||
return true;
|
||||
}
|
||||
let tokens = model.getLineTokens(lineNumber);
|
||||
if (tokens.getCount() > 0) {
|
||||
let firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhiteSpaceColumn);
|
||||
let firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhitespaceColumn);
|
||||
if (firstNonWhitespaceTokenIndex >= 0 && tokens.getStandardTokenType(firstNonWhitespaceTokenIndex) === StandardTokenType.Comment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,10 +36,17 @@ export class CommitCharacterController {
|
||||
|
||||
private _onItem(selected: ISelectedSuggestion | undefined): void {
|
||||
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
|
||||
// no item or no commit characters
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._active && this._active.item.item === selected.item) {
|
||||
// still the same item
|
||||
return;
|
||||
}
|
||||
|
||||
// keep item and its commit characters
|
||||
const acceptCharacters = new CharacterSet();
|
||||
for (const ch of selected.item.completion.commitCharacters) {
|
||||
if (ch.length > 0) {
|
||||
|
||||
@@ -50,7 +50,7 @@ interface ISuggestionTemplateData {
|
||||
iconLabel: IconLabel;
|
||||
typeLabel: HTMLElement;
|
||||
readMore: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
disposables: DisposableStore;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,8 +106,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
|
||||
|
||||
renderTemplate(container: HTMLElement): ISuggestionTemplateData {
|
||||
const data = <ISuggestionTemplateData>Object.create(null);
|
||||
const disposables = new DisposableStore();
|
||||
data.disposables = [disposables];
|
||||
data.disposables = new DisposableStore();
|
||||
|
||||
data.root = container;
|
||||
addClass(data.root, 'show-file-icons');
|
||||
@@ -119,7 +118,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
|
||||
const main = append(text, $('.main'));
|
||||
|
||||
data.iconLabel = new IconLabel(main, { supportHighlights: true });
|
||||
disposables.add(data.iconLabel);
|
||||
data.disposables.add(data.iconLabel);
|
||||
|
||||
data.typeLabel = append(main, $('span.type-label'));
|
||||
|
||||
@@ -147,7 +146,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
|
||||
|
||||
configureFont();
|
||||
|
||||
disposables.add(Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
data.disposables.add(Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
.filter(e => e.fontInfo || e.contribInfo)
|
||||
.on(configureFont, null));
|
||||
|
||||
@@ -218,7 +217,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISuggestionTemplateData): void {
|
||||
templateData.disposables = dispose(templateData.disposables);
|
||||
templateData.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +252,7 @@ class SuggestionDetails {
|
||||
private type: HTMLElement;
|
||||
private docs: HTMLElement;
|
||||
private ariaLabel: string | null;
|
||||
private disposables: IDisposable[];
|
||||
private readonly disposables: DisposableStore;
|
||||
private renderDisposeable: IDisposable;
|
||||
private borderWidth: number = 1;
|
||||
|
||||
@@ -264,16 +263,16 @@ class SuggestionDetails {
|
||||
private readonly markdownRenderer: MarkdownRenderer,
|
||||
private readonly triggerKeybindingLabel: string,
|
||||
) {
|
||||
this.disposables = [];
|
||||
this.disposables = new DisposableStore();
|
||||
|
||||
this.el = append(container, $('.details'));
|
||||
this.disposables.push(toDisposable(() => container.removeChild(this.el)));
|
||||
this.disposables.add(toDisposable(() => container.removeChild(this.el)));
|
||||
|
||||
this.body = $('.body');
|
||||
|
||||
this.scrollbar = new DomScrollableElement(this.body, {});
|
||||
append(this.el, this.scrollbar.getDomNode());
|
||||
this.disposables.push(this.scrollbar);
|
||||
this.disposables.add(this.scrollbar);
|
||||
|
||||
this.header = append(this.body, $('.header'));
|
||||
this.close = append(this.header, $('span.close'));
|
||||
@@ -414,7 +413,7 @@ class SuggestionDetails {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
this.disposables.dispose();
|
||||
this.renderDisposeable = dispose(this.renderDisposeable);
|
||||
}
|
||||
}
|
||||
@@ -672,7 +671,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
});
|
||||
|
||||
this.currentSuggestionDetails.then(() => {
|
||||
if (this.list.length < index) {
|
||||
if (index >= this.list.length || item !== this.list.element(index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -689,11 +688,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
}
|
||||
|
||||
this._ariaAlert(this._getSuggestionAriaAlertLabel(item));
|
||||
}).catch(onUnexpectedError).then(() => {
|
||||
if (this.focusedItem === item) {
|
||||
this.currentSuggestionDetails = null;
|
||||
}
|
||||
});
|
||||
}).catch(onUnexpectedError);
|
||||
}
|
||||
|
||||
// emit an event
|
||||
@@ -810,12 +805,11 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
"suggestWidget" : {
|
||||
"wasAutomaticallyTriggered" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"${include}": [
|
||||
"${ICompletionStats}",
|
||||
"${EditorTelemetryData}"
|
||||
"${ICompletionStats}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() });
|
||||
this.telemetryService.publicLog('suggestWidget', { ...stats });
|
||||
}
|
||||
|
||||
this.focusedItem = null;
|
||||
@@ -949,14 +943,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
this.details.element.style.borderColor = this.detailsFocusBorderColor;
|
||||
}
|
||||
}
|
||||
/* __GDPR__
|
||||
"suggestWidget:toggleDetailsFocus" : {
|
||||
"${include}": [
|
||||
"${EditorTelemetryData}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('suggestWidget:toggleDetailsFocus', this.editor.getTelemetryData());
|
||||
this.telemetryService.publicLog2('suggestWidget:toggleDetailsFocus');
|
||||
}
|
||||
|
||||
toggleDetails(): void {
|
||||
@@ -970,14 +957,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
removeClass(this.element, 'docs-side');
|
||||
removeClass(this.element, 'docs-below');
|
||||
this.editor.layoutContentWidget(this);
|
||||
/* __GDPR__
|
||||
"suggestWidget:collapseDetails" : {
|
||||
"${include}": [
|
||||
"${EditorTelemetryData}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('suggestWidget:collapseDetails', this.editor.getTelemetryData());
|
||||
this.telemetryService.publicLog2('suggestWidget:collapseDetails');
|
||||
} else {
|
||||
if (this.state !== State.Open && this.state !== State.Details && this.state !== State.Frozen) {
|
||||
return;
|
||||
@@ -986,14 +966,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
this.updateExpandDocsSetting(true);
|
||||
this.showDetails(false);
|
||||
this._ariaAlert(this.details.getAriaLabel());
|
||||
/* __GDPR__
|
||||
"suggestWidget:expandDetails" : {
|
||||
"${include}": [
|
||||
"${EditorTelemetryData}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('suggestWidget:expandDetails', this.editor.getTelemetryData());
|
||||
this.telemetryService.publicLog2('suggestWidget:expandDetails');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,22 @@ suite('WordOperations', () => {
|
||||
assert.deepEqual(actual, EXPECTED);
|
||||
});
|
||||
|
||||
test('cursorWordLeftSelect - issue #74369: cursorWordLeft and cursorWordLeftSelect do not behave consistently', () => {
|
||||
const EXPECTED = [
|
||||
'|this.|is.|a.|test',
|
||||
].join('\n');
|
||||
const [text,] = deserializePipePositions(EXPECTED);
|
||||
const actualStops = testRepeatedActionAndExtractPositions(
|
||||
text,
|
||||
new Position(1, 15),
|
||||
ed => cursorWordLeft(ed, true),
|
||||
ed => ed.getPosition()!,
|
||||
ed => ed.getPosition()!.equals(new Position(1, 1))
|
||||
);
|
||||
const actual = serializePipePositions(text, actualStops);
|
||||
assert.deepEqual(actual, EXPECTED);
|
||||
});
|
||||
|
||||
test('cursorWordStartLeft', () => {
|
||||
// This is the behaviour observed in Visual Studio, please do not touch test
|
||||
const EXPECTED = ['| |/* |Just |some |more |text |a|+= |3 |+|5|-|3 |+ |7 |*/| '].join('\n');
|
||||
|
||||
@@ -163,7 +163,7 @@ export class CursorWordLeftSelect extends WordLeftCommand {
|
||||
constructor() {
|
||||
super({
|
||||
inSelectionMode: true,
|
||||
wordNavigationType: WordNavigationType.WordStart,
|
||||
wordNavigationType: WordNavigationType.WordStartFast,
|
||||
id: 'cursorWordLeftSelect',
|
||||
precondition: undefined
|
||||
});
|
||||
|
||||
@@ -234,7 +234,9 @@ export class StandaloneCommandService implements ICommandService {
|
||||
private readonly _dynamicCommands: { [id: string]: ICommand; };
|
||||
|
||||
private readonly _onWillExecuteCommand = new Emitter<ICommandEvent>();
|
||||
private readonly _onDidExecuteCommand = new Emitter<ICommandEvent>();
|
||||
public readonly onWillExecuteCommand: Event<ICommandEvent> = this._onWillExecuteCommand.event;
|
||||
public readonly onDidExecuteCommand: Event<ICommandEvent> = this._onDidExecuteCommand.event;
|
||||
|
||||
constructor(instantiationService: IInstantiationService) {
|
||||
this._instantiationService = instantiationService;
|
||||
@@ -256,8 +258,10 @@ export class StandaloneCommandService implements ICommandService {
|
||||
}
|
||||
|
||||
try {
|
||||
this._onWillExecuteCommand.fire({ commandId: id });
|
||||
this._onWillExecuteCommand.fire({ commandId: id, args });
|
||||
const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]) as T;
|
||||
|
||||
this._onDidExecuteCommand.fire({ commandId: id, args });
|
||||
return Promise.resolve(result);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
@@ -352,7 +356,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
|
||||
private _toNormalizedKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] {
|
||||
let result: ResolvedKeybindingItem[] = [], resultLen = 0;
|
||||
for (const item of items) {
|
||||
const when = (item.when ? item.when.normalize() : undefined);
|
||||
const when = item.when || undefined;
|
||||
const keybinding = item.keybinding;
|
||||
|
||||
if (!keybinding) {
|
||||
|
||||
@@ -140,8 +140,6 @@ export module StaticServices {
|
||||
|
||||
export const notificationService = define(INotificationService, () => new SimpleNotificationService());
|
||||
|
||||
export const accessibilityService = define(IAccessibilityService, () => new BrowserAccessibilityService());
|
||||
|
||||
export const markerService = define(IMarkerService, () => new MarkerService());
|
||||
|
||||
export const modeService = define(IModeService, (o) => new ModeServiceImpl());
|
||||
@@ -194,6 +192,8 @@ export class DynamicStandaloneServices extends Disposable {
|
||||
|
||||
let contextKeyService = ensure(IContextKeyService, () => this._register(new ContextKeyService(configurationService)));
|
||||
|
||||
ensure(IAccessibilityService, () => new BrowserAccessibilityService(contextKeyService, configurationService));
|
||||
|
||||
ensure(IListService, () => new ListService(contextKeyService));
|
||||
|
||||
let commandService = ensure(ICommandService, () => new StandaloneCommandService(this._instantiationService));
|
||||
|
||||
@@ -4344,12 +4344,12 @@ suite('autoClosingPairs', () => {
|
||||
let autoClosePositions = [
|
||||
'var a |=| [|]|;|',
|
||||
'var b |=| |`asd`|;|',
|
||||
'var c |=| |\'asd!\'|;|',
|
||||
'var c |=| |\'asd\'|;|',
|
||||
'var d |=| |"asd"|;|',
|
||||
'var e |=| /*3*/| 3;|',
|
||||
'var f |=| /**| 3 */3;|',
|
||||
'var g |=| (3+5)|;|',
|
||||
'var h |=| {| a:| |\'value!\'| |}|;|',
|
||||
'var h |=| {| a:| |\'value\'| |}|;|',
|
||||
];
|
||||
for (let i = 0, len = autoClosePositions.length; i < len; i++) {
|
||||
const lineNumber = i + 1;
|
||||
@@ -4494,6 +4494,107 @@ suite('autoClosingPairs', () => {
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
test('issue #37315 - overtypes only those characters that it inserted', () => {
|
||||
let mode = new AutoClosingMode();
|
||||
usingCursor({
|
||||
text: [
|
||||
'',
|
||||
'y=();'
|
||||
],
|
||||
languageIdentifier: mode.getLanguageIdentifier()
|
||||
}, (model, cursor) => {
|
||||
assertCursor(cursor, new Position(1, 1));
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=()');
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: 'asd' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=(asd)');
|
||||
|
||||
// overtype!
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=(asd)');
|
||||
|
||||
// do not overtype!
|
||||
cursor.setSelections('test', [new Selection(2, 4, 2, 4)]);
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(2), 'y=());');
|
||||
|
||||
});
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
test('issue #37315 - stops overtyping once cursor leaves area', () => {
|
||||
let mode = new AutoClosingMode();
|
||||
usingCursor({
|
||||
text: [
|
||||
'',
|
||||
'y=();'
|
||||
],
|
||||
languageIdentifier: mode.getLanguageIdentifier()
|
||||
}, (model, cursor) => {
|
||||
assertCursor(cursor, new Position(1, 1));
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=()');
|
||||
|
||||
cursor.setSelections('test', [new Selection(1, 5, 1, 5)]);
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=())');
|
||||
});
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
test('issue #37315 - it overtypes only once', () => {
|
||||
let mode = new AutoClosingMode();
|
||||
usingCursor({
|
||||
text: [
|
||||
'',
|
||||
'y=();'
|
||||
],
|
||||
languageIdentifier: mode.getLanguageIdentifier()
|
||||
}, (model, cursor) => {
|
||||
assertCursor(cursor, new Position(1, 1));
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=()');
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=()');
|
||||
|
||||
cursor.setSelections('test', [new Selection(1, 4, 1, 4)]);
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=())');
|
||||
});
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
test('issue #37315 - it can remember multiple auto-closed instances', () => {
|
||||
let mode = new AutoClosingMode();
|
||||
usingCursor({
|
||||
text: [
|
||||
'',
|
||||
'y=();'
|
||||
],
|
||||
languageIdentifier: mode.getLanguageIdentifier()
|
||||
}, (model, cursor) => {
|
||||
assertCursor(cursor, new Position(1, 1));
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: 'x=(' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=()');
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: '(' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=(())');
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=(())');
|
||||
|
||||
cursorCommand(cursor, H.Type, { text: ')' }, 'keyboard');
|
||||
assert.strictEqual(model.getLineContent(1), 'x=(())');
|
||||
});
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
test('issue #15825: accents on mac US intl keyboard', () => {
|
||||
let mode = new AutoClosingMode();
|
||||
usingCursor({
|
||||
|
||||
@@ -132,7 +132,7 @@ suite('Cursor move command test', () => {
|
||||
test('move to first non white space character of line from middle', () => {
|
||||
moveTo(thisCursor, 1, 8);
|
||||
|
||||
moveToLineFirstNonWhiteSpaceCharacter(thisCursor);
|
||||
moveToLineFirstNonWhitespaceCharacter(thisCursor);
|
||||
|
||||
cursorEqual(thisCursor, 1, 6);
|
||||
});
|
||||
@@ -140,7 +140,7 @@ suite('Cursor move command test', () => {
|
||||
test('move to first non white space character of line from first non white space character', () => {
|
||||
moveTo(thisCursor, 1, 6);
|
||||
|
||||
moveToLineFirstNonWhiteSpaceCharacter(thisCursor);
|
||||
moveToLineFirstNonWhitespaceCharacter(thisCursor);
|
||||
|
||||
cursorEqual(thisCursor, 1, 6);
|
||||
});
|
||||
@@ -148,7 +148,7 @@ suite('Cursor move command test', () => {
|
||||
test('move to first non white space character of line from first character', () => {
|
||||
moveTo(thisCursor, 1, 1);
|
||||
|
||||
moveToLineFirstNonWhiteSpaceCharacter(thisCursor);
|
||||
moveToLineFirstNonWhitespaceCharacter(thisCursor);
|
||||
|
||||
cursorEqual(thisCursor, 1, 6);
|
||||
});
|
||||
@@ -180,7 +180,7 @@ suite('Cursor move command test', () => {
|
||||
test('move to last non white space character from middle', () => {
|
||||
moveTo(thisCursor, 1, 8);
|
||||
|
||||
moveToLineLastNonWhiteSpaceCharacter(thisCursor);
|
||||
moveToLineLastNonWhitespaceCharacter(thisCursor);
|
||||
|
||||
cursorEqual(thisCursor, 1, 19);
|
||||
});
|
||||
@@ -188,7 +188,7 @@ suite('Cursor move command test', () => {
|
||||
test('move to last non white space character from last non white space character', () => {
|
||||
moveTo(thisCursor, 1, 19);
|
||||
|
||||
moveToLineLastNonWhiteSpaceCharacter(thisCursor);
|
||||
moveToLineLastNonWhitespaceCharacter(thisCursor);
|
||||
|
||||
cursorEqual(thisCursor, 1, 19);
|
||||
});
|
||||
@@ -196,7 +196,7 @@ suite('Cursor move command test', () => {
|
||||
test('move to last non white space character from line end', () => {
|
||||
moveTo(thisCursor, 1, 21);
|
||||
|
||||
moveToLineLastNonWhiteSpaceCharacter(thisCursor);
|
||||
moveToLineLastNonWhitespaceCharacter(thisCursor);
|
||||
|
||||
cursorEqual(thisCursor, 1, 19);
|
||||
});
|
||||
@@ -415,7 +415,7 @@ function moveToLineStart(cursor: Cursor) {
|
||||
move(cursor, { to: CursorMove.RawDirection.WrappedLineStart });
|
||||
}
|
||||
|
||||
function moveToLineFirstNonWhiteSpaceCharacter(cursor: Cursor) {
|
||||
function moveToLineFirstNonWhitespaceCharacter(cursor: Cursor) {
|
||||
move(cursor, { to: CursorMove.RawDirection.WrappedLineFirstNonWhitespaceCharacter });
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ function moveToLineEnd(cursor: Cursor) {
|
||||
move(cursor, { to: CursorMove.RawDirection.WrappedLineEnd });
|
||||
}
|
||||
|
||||
function moveToLineLastNonWhiteSpaceCharacter(cursor: Cursor) {
|
||||
function moveToLineLastNonWhitespaceCharacter(cursor: Cursor) {
|
||||
move(cursor, { to: CursorMove.RawDirection.WrappedLineLastNonWhitespaceCharacter });
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ export class TestCommandService implements ICommandService {
|
||||
private readonly _onWillExecuteCommand = new Emitter<ICommandEvent>();
|
||||
public readonly onWillExecuteCommand: Event<ICommandEvent> = this._onWillExecuteCommand.event;
|
||||
|
||||
private readonly _onDidExecuteCommand = new Emitter<ICommandEvent>();
|
||||
public readonly onDidExecuteCommand: Event<ICommandEvent> = this._onDidExecuteCommand.event;
|
||||
|
||||
constructor(instantiationService: IInstantiationService) {
|
||||
this._instantiationService = instantiationService;
|
||||
}
|
||||
@@ -43,8 +46,9 @@ export class TestCommandService implements ICommandService {
|
||||
}
|
||||
|
||||
try {
|
||||
this._onWillExecuteCommand.fire({ commandId: id });
|
||||
this._onWillExecuteCommand.fire({ commandId: id, args });
|
||||
const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]) as T;
|
||||
this._onDidExecuteCommand.fire({ commandId: id, args });
|
||||
return Promise.resolve(result);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
|
||||
@@ -17,6 +17,7 @@ suite('OpenerService', function () {
|
||||
const commandService = new class implements ICommandService {
|
||||
_serviceBrand: any;
|
||||
onWillExecuteCommand = () => ({ dispose: () => { } });
|
||||
onDidExecuteCommand = () => ({ dispose: () => { } });
|
||||
executeCommand(id: string, ...args: any[]): Promise<any> {
|
||||
lastCommand = { id, args };
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
@@ -733,4 +733,23 @@ suite('TextModelSearch', () => {
|
||||
assert(isMultilineRegexSource('\\n'));
|
||||
assert(isMultilineRegexSource('foo\\W'));
|
||||
});
|
||||
|
||||
test('issue #74715. \\d* finds empty string and stops searching.', () => {
|
||||
let model = TextModel.createFromString('10.243.30.10');
|
||||
|
||||
let searchParams = new SearchParams('\\d*', true, false, null);
|
||||
|
||||
let actual = TextModelSearch.findMatches(model, searchParams, model.getFullModelRange(), true, 100);
|
||||
assert.deepEqual(actual, [
|
||||
new FindMatch(new Range(1, 1, 1, 3), ['10']),
|
||||
new FindMatch(new Range(1, 3, 1, 3), ['']),
|
||||
new FindMatch(new Range(1, 4, 1, 7), ['243']),
|
||||
new FindMatch(new Range(1, 7, 1, 7), ['']),
|
||||
new FindMatch(new Range(1, 8, 1, 10), ['30']),
|
||||
new FindMatch(new Range(1, 10, 1, 10), ['']),
|
||||
new FindMatch(new Range(1, 11, 1, 13), ['10'])
|
||||
]);
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+12
-1
@@ -5,6 +5,8 @@
|
||||
|
||||
declare namespace monaco {
|
||||
|
||||
// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.
|
||||
|
||||
export type Thenable<T> = PromiseLike<T>;
|
||||
|
||||
export interface IDisposable {
|
||||
@@ -27,7 +29,8 @@ declare namespace monaco {
|
||||
|
||||
|
||||
export enum MarkerTag {
|
||||
Unnecessary = 1
|
||||
Unnecessary = 1,
|
||||
Deprecated = 2
|
||||
}
|
||||
|
||||
export enum MarkerSeverity {
|
||||
@@ -583,6 +586,14 @@ declare namespace monaco {
|
||||
* Test if `otherRange` is in `range`. If the ranges are equal, will return true.
|
||||
*/
|
||||
static containsRange(range: IRange, otherRange: IRange): boolean;
|
||||
/**
|
||||
* Test if `range` is strictly in this range. `range` must start after and end before this range for the result to be true.
|
||||
*/
|
||||
strictContainsRange(range: IRange): boolean;
|
||||
/**
|
||||
* Test if `otherRange` is strinctly in `range` (must start after, and end before). If the ranges are equal, will return false.
|
||||
*/
|
||||
static strictContainsRange(range: IRange, otherRange: IRange): boolean;
|
||||
/**
|
||||
* A reunion of the two ranges.
|
||||
* The smallest position will be used as the start point, and the largest one as the end point.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IAccessibilityService, AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export abstract class AbstractAccessibilityService extends Disposable implements IAccessibilityService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _accessibilityModeEnabledContext: IContextKey<boolean>;
|
||||
protected readonly _onDidChangeAccessibilitySupport = new Emitter<void>();
|
||||
readonly onDidChangeAccessibilitySupport: Event<void> = this._onDidChangeAccessibilitySupport.event;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
this._accessibilityModeEnabledContext = CONTEXT_ACCESSIBILITY_MODE_ENABLED.bindTo(this._contextKeyService);
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('editor.accessibilitySupport')) {
|
||||
this._updateContextKey();
|
||||
}
|
||||
}));
|
||||
this._updateContextKey();
|
||||
this.onDidChangeAccessibilitySupport(() => this._updateContextKey());
|
||||
}
|
||||
|
||||
abstract alwaysUnderlineAccessKeys(): Promise<boolean>;
|
||||
abstract getAccessibilitySupport(): AccessibilitySupport;
|
||||
abstract setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void;
|
||||
|
||||
private _updateContextKey(): void {
|
||||
const detected = this.getAccessibilitySupport() === AccessibilitySupport.Enabled;
|
||||
const config = this._configurationService.getValue('editor.accessibilitySupport');
|
||||
this._accessibilityModeEnabledContext.set(config === 'on' || (config === 'auto' && detected));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const IAccessibilityService = createDecorator<IAccessibilityService>('accessibilityService');
|
||||
|
||||
@@ -28,3 +29,5 @@ export const enum AccessibilitySupport {
|
||||
|
||||
Enabled = 2
|
||||
}
|
||||
|
||||
export const CONTEXT_ACCESSIBILITY_MODE_ENABLED = new RawContextKey<boolean>('accessibilityModeEnabled', false);
|
||||
|
||||
@@ -4,17 +4,34 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IAccessibilityService, AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class BrowserAccessibilityService extends Disposable implements IAccessibilityService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _accessibilitySupport = AccessibilitySupport.Unknown;
|
||||
private _accessibilityModeEnabledContext: IContextKey<boolean>;
|
||||
private readonly _onDidChangeAccessibilitySupport = new Emitter<void>();
|
||||
readonly onDidChangeAccessibilitySupport: Event<void> = this._onDidChangeAccessibilitySupport.event;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
this._accessibilityModeEnabledContext = CONTEXT_ACCESSIBILITY_MODE_ENABLED.bindTo(this._contextKeyService);
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('editor.accessibilitySupport')) {
|
||||
this._updateContextKey();
|
||||
}
|
||||
}));
|
||||
this._updateContextKey();
|
||||
}
|
||||
|
||||
alwaysUnderlineAccessKeys(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
@@ -26,9 +43,16 @@ export class BrowserAccessibilityService extends Disposable implements IAccessib
|
||||
|
||||
this._accessibilitySupport = accessibilitySupport;
|
||||
this._onDidChangeAccessibilitySupport.fire();
|
||||
this._updateContextKey();
|
||||
}
|
||||
|
||||
getAccessibilitySupport(): AccessibilitySupport {
|
||||
return this._accessibilitySupport;
|
||||
}
|
||||
|
||||
private _updateContextKey(): void {
|
||||
const detected = this.getAccessibilitySupport() === AccessibilitySupport.Enabled;
|
||||
const config = this._configurationService.getValue('editor.accessibilitySupport');
|
||||
this._accessibilityModeEnabledContext.set(config === 'on' || (config === 'auto' && detected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IContextKeyService, ContextKeyDefinedExpr, ContextKeyExpr, ContextKeyAndExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
@@ -66,7 +66,7 @@ export class ContextScopedFindInput extends FindInput {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'history.showPrevious',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(new ContextKeyDefinedExpr(HistoryNavigationWidgetContext), new ContextKeyEqualsExpr(HistoryNavigationEnablementContext, true)),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationEnablementContext, true)),
|
||||
primary: KeyCode.UpArrow,
|
||||
secondary: [KeyMod.Alt | KeyCode.UpArrow],
|
||||
handler: (accessor, arg2) => {
|
||||
@@ -81,7 +81,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'history.showNext',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: new ContextKeyAndExpr([new ContextKeyDefinedExpr(HistoryNavigationWidgetContext), new ContextKeyEqualsExpr(HistoryNavigationEnablementContext, true)]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationEnablementContext, true)),
|
||||
primary: KeyCode.DownArrow,
|
||||
secondary: [KeyMod.Alt | KeyCode.DownArrow],
|
||||
handler: (accessor, arg2) => {
|
||||
|
||||
@@ -15,11 +15,13 @@ export const ICommandService = createDecorator<ICommandService>('commandService'
|
||||
|
||||
export interface ICommandEvent {
|
||||
commandId: string;
|
||||
args: any[];
|
||||
}
|
||||
|
||||
export interface ICommandService {
|
||||
_serviceBrand: any;
|
||||
onWillExecuteCommand: Event<ICommandEvent>;
|
||||
onDidExecuteCommand: Event<ICommandEvent>;
|
||||
executeCommand<T = any>(commandId: string, ...args: any[]): Promise<T | undefined>;
|
||||
}
|
||||
|
||||
@@ -135,6 +137,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR
|
||||
export const NullCommandService: ICommandService = {
|
||||
_serviceBrand: undefined,
|
||||
onWillExecuteCommand: () => ({ dispose: () => { } }),
|
||||
onDidExecuteCommand: () => ({ dispose: () => { } }),
|
||||
executeCommand() {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ suite('ConfigurationService - Node', () => {
|
||||
const service = new ConfigurationService(URI.file(res.testFile));
|
||||
await service.initialize();
|
||||
return new Promise((c, e) => {
|
||||
service.onDidChangeConfiguration(() => {
|
||||
const disposable = service.onDidChangeConfiguration(() => {
|
||||
disposable.dispose();
|
||||
assert.equal(service.getValue('foo'), 'bar');
|
||||
service.dispose();
|
||||
c();
|
||||
|
||||
@@ -14,10 +14,10 @@ export const enum ContextKeyExprType {
|
||||
NotEquals = 4,
|
||||
And = 5,
|
||||
Regex = 6,
|
||||
// {{SQL CARBON EDIT}}
|
||||
GreaterThanEquals = 7,
|
||||
LessThanEquals = 8
|
||||
//
|
||||
NotRegex = 7,
|
||||
Or = 8,
|
||||
GreaterThanEquals = 9, // {{SQL CARBON EDIT}} add value
|
||||
LessThanEquals = 10 // {{SQL CARBON EDIT}} add value
|
||||
}
|
||||
|
||||
export interface IContextKeyExprMapper {
|
||||
@@ -31,27 +31,31 @@ export interface IContextKeyExprMapper {
|
||||
export abstract class ContextKeyExpr {
|
||||
|
||||
public static has(key: string): ContextKeyExpr {
|
||||
return new ContextKeyDefinedExpr(key);
|
||||
return ContextKeyDefinedExpr.create(key);
|
||||
}
|
||||
|
||||
public static equals(key: string, value: any): ContextKeyExpr {
|
||||
return new ContextKeyEqualsExpr(key, value);
|
||||
return ContextKeyEqualsExpr.create(key, value);
|
||||
}
|
||||
|
||||
public static notEquals(key: string, value: any): ContextKeyExpr {
|
||||
return new ContextKeyNotEqualsExpr(key, value);
|
||||
return ContextKeyNotEqualsExpr.create(key, value);
|
||||
}
|
||||
|
||||
public static regex(key: string, value: RegExp): ContextKeyExpr {
|
||||
return new ContextKeyRegexExpr(key, value);
|
||||
return ContextKeyRegexExpr.create(key, value);
|
||||
}
|
||||
|
||||
public static not(key: string): ContextKeyExpr {
|
||||
return new ContextKeyNotExpr(key);
|
||||
return ContextKeyNotExpr.create(key);
|
||||
}
|
||||
|
||||
public static and(...expr: Array<ContextKeyExpr | undefined | null>): ContextKeyExpr {
|
||||
return new ContextKeyAndExpr(expr);
|
||||
public static and(...expr: Array<ContextKeyExpr | undefined | null>): ContextKeyExpr | undefined {
|
||||
return ContextKeyAndExpr.create(expr);
|
||||
}
|
||||
|
||||
public static or(...expr: Array<ContextKeyExpr | undefined | null>): ContextKeyExpr | undefined {
|
||||
return ContextKeyOrExpr.create(expr);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -69,9 +73,17 @@ export abstract class ContextKeyExpr {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._deserializeOrExpression(serialized, strict);
|
||||
}
|
||||
|
||||
private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined {
|
||||
let pieces = serialized.split('||');
|
||||
return ContextKeyOrExpr.create(pieces.map(p => this._deserializeAndExpression(p, strict)));
|
||||
}
|
||||
|
||||
private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined {
|
||||
let pieces = serialized.split('&&');
|
||||
let result = new ContextKeyAndExpr(pieces.map(p => this._deserializeOne(p, strict)));
|
||||
return result.normalize();
|
||||
return ContextKeyAndExpr.create(pieces.map(p => this._deserializeOne(p, strict)));
|
||||
}
|
||||
|
||||
private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpr {
|
||||
@@ -79,17 +91,17 @@ export abstract class ContextKeyExpr {
|
||||
|
||||
if (serializedOne.indexOf('!=') >= 0) {
|
||||
let pieces = serializedOne.split('!=');
|
||||
return new ContextKeyNotEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict));
|
||||
return ContextKeyNotEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict));
|
||||
}
|
||||
|
||||
if (serializedOne.indexOf('==') >= 0) {
|
||||
let pieces = serializedOne.split('==');
|
||||
return new ContextKeyEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict));
|
||||
return ContextKeyEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict));
|
||||
}
|
||||
|
||||
if (serializedOne.indexOf('=~') >= 0) {
|
||||
let pieces = serializedOne.split('=~');
|
||||
return new ContextKeyRegexExpr(pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict));
|
||||
return ContextKeyRegexExpr.create(pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict));
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -104,10 +116,10 @@ export abstract class ContextKeyExpr {
|
||||
//
|
||||
|
||||
if (/^\!\s*/.test(serializedOne)) {
|
||||
return new ContextKeyNotExpr(serializedOne.substr(1).trim());
|
||||
return ContextKeyNotExpr.create(serializedOne.substr(1).trim());
|
||||
}
|
||||
|
||||
return new ContextKeyDefinedExpr(serializedOne);
|
||||
return ContextKeyDefinedExpr.create(serializedOne);
|
||||
}
|
||||
|
||||
private static _deserializeValue(serializedValue: string, strict: boolean): any {
|
||||
@@ -168,10 +180,10 @@ export abstract class ContextKeyExpr {
|
||||
public abstract getType(): ContextKeyExprType;
|
||||
public abstract equals(other: ContextKeyExpr): boolean;
|
||||
public abstract evaluate(context: IContext): boolean;
|
||||
public abstract normalize(): ContextKeyExpr | undefined;
|
||||
public abstract serialize(): string;
|
||||
public abstract keys(): string[];
|
||||
public abstract map(mapFnc: IContextKeyExprMapper): ContextKeyExpr;
|
||||
public abstract negate(): ContextKeyExpr;
|
||||
}
|
||||
|
||||
function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number {
|
||||
@@ -191,19 +203,25 @@ function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number {
|
||||
return (<ContextKeyNotEqualsExpr>a).cmp(<ContextKeyNotEqualsExpr>b);
|
||||
case ContextKeyExprType.Regex:
|
||||
return (<ContextKeyRegexExpr>a).cmp(<ContextKeyRegexExpr>b);
|
||||
// {{SQL CARBON EDIT}}
|
||||
case ContextKeyExprType.GreaterThanEquals:
|
||||
case ContextKeyExprType.GreaterThanEquals: // {{SQL CARBON EDIT}} add case
|
||||
return (<ContextKeyGreaterThanEqualsExpr>a).cmp(<ContextKeyGreaterThanEqualsExpr>b);
|
||||
case ContextKeyExprType.LessThanEquals:
|
||||
case ContextKeyExprType.LessThanEquals: // {{SQL CARBON EDIT}} add case
|
||||
return (<ContextKeyLessThanEqualsExpr>a).cmp(<ContextKeyLessThanEqualsExpr>b);
|
||||
//
|
||||
case ContextKeyExprType.NotRegex:
|
||||
return (<ContextKeyNotRegexExpr>a).cmp(<ContextKeyNotRegexExpr>b);
|
||||
case ContextKeyExprType.And:
|
||||
return (<ContextKeyAndExpr>a).cmp(<ContextKeyAndExpr>b);
|
||||
default:
|
||||
throw new Error('Unknown ContextKeyExpr!');
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyDefinedExpr implements ContextKeyExpr {
|
||||
constructor(protected key: string) {
|
||||
public static create(key: string): ContextKeyExpr {
|
||||
return new ContextKeyDefinedExpr(key);
|
||||
}
|
||||
|
||||
protected constructor(protected key: string) {
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
@@ -231,10 +249,6 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr {
|
||||
return (!!context.getValue(this.key));
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr {
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.key;
|
||||
}
|
||||
@@ -246,10 +260,25 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr {
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return mapFnc.mapDefined(this.key);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
return ContextKeyNotExpr.create(this.key);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyEqualsExpr implements ContextKeyExpr {
|
||||
constructor(private readonly key: string, private readonly value: any) {
|
||||
|
||||
public static create(key: string, value: any): ContextKeyExpr {
|
||||
if (typeof value === 'boolean') {
|
||||
if (value) {
|
||||
return ContextKeyDefinedExpr.create(key);
|
||||
}
|
||||
return ContextKeyNotExpr.create(key);
|
||||
}
|
||||
return new ContextKeyEqualsExpr(key, value);
|
||||
}
|
||||
|
||||
private constructor(private readonly key: string, private readonly value: any) {
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
@@ -286,21 +315,7 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr {
|
||||
/* tslint:enable:triple-equals */
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr {
|
||||
if (typeof this.value === 'boolean') {
|
||||
if (this.value) {
|
||||
return new ContextKeyDefinedExpr(this.key);
|
||||
}
|
||||
return new ContextKeyNotExpr(this.key);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
if (typeof this.value === 'boolean') {
|
||||
return this.normalize().serialize();
|
||||
}
|
||||
|
||||
return this.key + ' == \'' + this.value + '\'';
|
||||
}
|
||||
|
||||
@@ -311,10 +326,25 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr {
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return mapFnc.mapEquals(this.key, this.value);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
return ContextKeyNotEqualsExpr.create(this.key, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyNotEqualsExpr implements ContextKeyExpr {
|
||||
constructor(private key: string, private value: any) {
|
||||
|
||||
public static create(key: string, value: any): ContextKeyExpr {
|
||||
if (typeof value === 'boolean') {
|
||||
if (value) {
|
||||
return ContextKeyNotExpr.create(key);
|
||||
}
|
||||
return ContextKeyDefinedExpr.create(key);
|
||||
}
|
||||
return new ContextKeyNotEqualsExpr(key, value);
|
||||
}
|
||||
|
||||
private constructor(private key: string, private value: any) {
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
@@ -351,21 +381,7 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr {
|
||||
/* tslint:enable:triple-equals */
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr {
|
||||
if (typeof this.value === 'boolean') {
|
||||
if (this.value) {
|
||||
return new ContextKeyNotExpr(this.key);
|
||||
}
|
||||
return new ContextKeyDefinedExpr(this.key);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
if (typeof this.value === 'boolean') {
|
||||
return this.normalize().serialize();
|
||||
}
|
||||
|
||||
return this.key + ' != \'' + this.value + '\'';
|
||||
}
|
||||
|
||||
@@ -376,10 +392,19 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr {
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return mapFnc.mapNotEquals(this.key, this.value);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
return ContextKeyEqualsExpr.create(this.key, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyNotExpr implements ContextKeyExpr {
|
||||
constructor(private key: string) {
|
||||
|
||||
public static create(key: string): ContextKeyExpr {
|
||||
return new ContextKeyNotExpr(key);
|
||||
}
|
||||
|
||||
private constructor(private key: string) {
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
@@ -407,10 +432,6 @@ export class ContextKeyNotExpr implements ContextKeyExpr {
|
||||
return (!context.getValue(this.key));
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr {
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return '!' + this.key;
|
||||
}
|
||||
@@ -422,11 +443,19 @@ export class ContextKeyNotExpr implements ContextKeyExpr {
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return mapFnc.mapNot(this.key);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
return ContextKeyDefinedExpr.create(this.key);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyRegexExpr implements ContextKeyExpr {
|
||||
|
||||
constructor(private key: string, private regexp: RegExp | null) {
|
||||
public static create(key: string, regexp: RegExp | null): ContextKeyExpr {
|
||||
return new ContextKeyRegexExpr(key, regexp);
|
||||
}
|
||||
|
||||
private constructor(private key: string, private regexp: RegExp | null) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -466,10 +495,6 @@ export class ContextKeyRegexExpr implements ContextKeyExpr {
|
||||
return this.regexp ? this.regexp.test(value) : false;
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr {
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
const value = this.regexp
|
||||
? `/${this.regexp.source}/${this.regexp.ignoreCase ? 'i' : ''}`
|
||||
@@ -481,22 +506,99 @@ export class ContextKeyRegexExpr implements ContextKeyExpr {
|
||||
return [this.key];
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyRegexExpr {
|
||||
return mapFnc.mapRegex(this.key, this.regexp);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
return ContextKeyNotRegexExpr.create(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyNotRegexExpr implements ContextKeyExpr {
|
||||
|
||||
public static create(actual: ContextKeyRegexExpr): ContextKeyExpr {
|
||||
return new ContextKeyNotRegexExpr(actual);
|
||||
}
|
||||
|
||||
private constructor(private readonly _actual: ContextKeyRegexExpr) {
|
||||
//
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
return ContextKeyExprType.NotRegex;
|
||||
}
|
||||
|
||||
public cmp(other: ContextKeyNotRegexExpr): number {
|
||||
return this._actual.cmp(other._actual);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpr): boolean {
|
||||
if (other instanceof ContextKeyNotRegexExpr) {
|
||||
return this._actual.equals(other._actual);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
return !this._actual.evaluate(context);
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
return this._actual.keys();
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return new ContextKeyNotRegexExpr(this._actual.map(mapFnc));
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
return this._actual;
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyAndExpr implements ContextKeyExpr {
|
||||
public readonly expr: ContextKeyExpr[];
|
||||
|
||||
constructor(expr: Array<ContextKeyExpr | null | undefined>) {
|
||||
this.expr = ContextKeyAndExpr._normalizeArr(expr);
|
||||
public static create(_expr: Array<ContextKeyExpr | null | undefined>): ContextKeyExpr | undefined {
|
||||
const expr = ContextKeyAndExpr._normalizeArr(_expr);
|
||||
if (expr.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (expr.length === 1) {
|
||||
return expr[0];
|
||||
}
|
||||
|
||||
return new ContextKeyAndExpr(expr);
|
||||
}
|
||||
|
||||
private constructor(public readonly expr: ContextKeyExpr[]) {
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
return ContextKeyExprType.And;
|
||||
}
|
||||
|
||||
public cmp(other: ContextKeyAndExpr): number {
|
||||
if (this.expr.length < other.expr.length) {
|
||||
return -1;
|
||||
}
|
||||
if (this.expr.length > other.expr.length) {
|
||||
return 1;
|
||||
}
|
||||
for (let i = 0, len = this.expr.length; i < len; i++) {
|
||||
const r = cmp(this.expr[i], other.expr[i]);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpr): boolean {
|
||||
if (other instanceof ContextKeyAndExpr) {
|
||||
if (this.expr.length !== other.expr.length) {
|
||||
@@ -531,16 +633,16 @@ export class ContextKeyAndExpr implements ContextKeyExpr {
|
||||
continue;
|
||||
}
|
||||
|
||||
e = e.normalize();
|
||||
if (!e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e instanceof ContextKeyAndExpr) {
|
||||
expr = expr.concat(e.expr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e instanceof ContextKeyOrExpr) {
|
||||
// Not allowed, because we don't have parens!
|
||||
throw new Error(`It is not allowed to have an or expression here due to lack of parens!`);
|
||||
}
|
||||
|
||||
expr.push(e);
|
||||
}
|
||||
|
||||
@@ -550,29 +652,7 @@ export class ContextKeyAndExpr implements ContextKeyExpr {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr | undefined {
|
||||
if (this.expr.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.expr.length === 1) {
|
||||
return this.expr[0];
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
if (this.expr.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (this.expr.length === 1) {
|
||||
const normalized = this.normalize();
|
||||
if (!normalized) {
|
||||
return '';
|
||||
}
|
||||
return normalized.serialize();
|
||||
}
|
||||
return this.expr.map(e => e.serialize()).join(' && ');
|
||||
}
|
||||
|
||||
@@ -587,6 +667,132 @@ export class ContextKeyAndExpr implements ContextKeyExpr {
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc)));
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
let result: ContextKeyExpr[] = [];
|
||||
for (let expr of this.expr) {
|
||||
result.push(expr.negate());
|
||||
}
|
||||
return ContextKeyOrExpr.create(result)!;
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyOrExpr implements ContextKeyExpr {
|
||||
|
||||
public static create(_expr: Array<ContextKeyExpr | null | undefined>): ContextKeyExpr | undefined {
|
||||
const expr = ContextKeyOrExpr._normalizeArr(_expr);
|
||||
if (expr.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (expr.length === 1) {
|
||||
return expr[0];
|
||||
}
|
||||
|
||||
return new ContextKeyOrExpr(expr);
|
||||
}
|
||||
|
||||
private constructor(public readonly expr: ContextKeyExpr[]) {
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
return ContextKeyExprType.Or;
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpr): boolean {
|
||||
if (other instanceof ContextKeyOrExpr) {
|
||||
if (this.expr.length !== other.expr.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0, len = this.expr.length; i < len; i++) {
|
||||
if (!this.expr[i].equals(other.expr[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
for (let i = 0, len = this.expr.length; i < len; i++) {
|
||||
if (this.expr[i].evaluate(context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static _normalizeArr(arr: Array<ContextKeyExpr | null | undefined>): ContextKeyExpr[] {
|
||||
let expr: ContextKeyExpr[] = [];
|
||||
|
||||
if (arr) {
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
let e: ContextKeyExpr | null | undefined = arr[i];
|
||||
if (!e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e instanceof ContextKeyOrExpr) {
|
||||
expr = expr.concat(e.expr);
|
||||
continue;
|
||||
}
|
||||
|
||||
expr.push(e);
|
||||
}
|
||||
|
||||
expr.sort(cmp);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.expr.map(e => e.serialize()).join(' || ');
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
const result: string[] = [];
|
||||
for (let expr of this.expr) {
|
||||
result.push(...expr.keys());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr {
|
||||
return new ContextKeyOrExpr(this.expr.map(expr => expr.map(mapFnc)));
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
let result: ContextKeyExpr[] = [];
|
||||
for (let expr of this.expr) {
|
||||
result.push(expr.negate());
|
||||
}
|
||||
|
||||
const terminals = (node: ContextKeyExpr) => {
|
||||
if (node instanceof ContextKeyOrExpr) {
|
||||
return node.expr;
|
||||
}
|
||||
return [node];
|
||||
};
|
||||
|
||||
// We don't support parens, so here we distribute the AND over the OR terminals
|
||||
// We always take the first 2 AND pairs and distribute them
|
||||
while (result.length > 1) {
|
||||
const LEFT = result.shift()!;
|
||||
const RIGHT = result.shift()!;
|
||||
|
||||
const all: ContextKeyExpr[] = [];
|
||||
for (const left of terminals(LEFT)) {
|
||||
for (const right of terminals(RIGHT)) {
|
||||
all.push(ContextKeyExpr.and(left, right)!);
|
||||
}
|
||||
}
|
||||
result.unshift(ContextKeyExpr.or(...all)!);
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -621,6 +827,10 @@ export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr {
|
||||
return false;
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
throw new Error('Method not implemented.'); // @TODO anthonydresser need to figure out what to do in this case
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
const keyVal = context.getValue<string>(this.key);
|
||||
if (!keyVal) {
|
||||
@@ -694,6 +904,10 @@ export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr {
|
||||
return this;
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpr {
|
||||
throw new Error('Method not implemented.'); // @TODO anthonydresser need to figure out what to do in this case
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.key + ' <= \'' + this.value + '\'';
|
||||
}
|
||||
|
||||
@@ -27,31 +27,28 @@ suite('ContextKeyExpr', () => {
|
||||
ContextKeyExpr.notEquals('c2', 'cc2'),
|
||||
ContextKeyExpr.not('d1'),
|
||||
ContextKeyExpr.not('d2'),
|
||||
// {{SQL CARBON EDIT}}
|
||||
ContextKeyExpr.greaterThanEquals('e1', 'ee1'),
|
||||
ContextKeyExpr.greaterThanEquals('e2', 'ee2'),
|
||||
ContextKeyExpr.lessThanEquals('f1', 'ff1'),
|
||||
ContextKeyExpr.lessThanEquals('f2', 'ff2')
|
||||
//
|
||||
);
|
||||
ContextKeyExpr.greaterThanEquals('e1', 'ee1'), // {{SQL CARBON EDIT}} add test case
|
||||
ContextKeyExpr.greaterThanEquals('e2', 'ee2'), // {{SQL CARBON EDIT}} add test case
|
||||
ContextKeyExpr.lessThanEquals('f1', 'ff1'), // {{SQL CARBON EDIT}} add test case
|
||||
ContextKeyExpr.lessThanEquals('f2', 'ff2'), // {{SQL CARBON EDIT}} add test case
|
||||
)!;
|
||||
let b = ContextKeyExpr.and(
|
||||
// {{SQL CARBON EDIT}}
|
||||
ContextKeyExpr.greaterThanEquals('e2', 'ee2'),
|
||||
ContextKeyExpr.lessThanEquals('f1', 'ff1'), // {{SQL CARBON EDIT}}
|
||||
ContextKeyExpr.lessThanEquals('f2', 'ff2'), // {{SQL CARBON EDIT}}
|
||||
ContextKeyExpr.greaterThanEquals('e2', 'ee2'), // {{SQL CARBON EDIT}}
|
||||
ContextKeyExpr.greaterThanEquals('e1', 'ee1'), // {{SQL CARBON EDIT}}
|
||||
ContextKeyExpr.equals('b2', 'bb2'),
|
||||
ContextKeyExpr.notEquals('c1', 'cc1'),
|
||||
ContextKeyExpr.not('d1'),
|
||||
ContextKeyExpr.lessThanEquals('f1', 'ff1'),
|
||||
ContextKeyExpr.regex('d4', /\*\*3*/),
|
||||
ContextKeyExpr.greaterThanEquals('e1', 'ee1'),
|
||||
ContextKeyExpr.notEquals('c2', 'cc2'),
|
||||
ContextKeyExpr.has('a2'),
|
||||
ContextKeyExpr.equals('b1', 'bb1'),
|
||||
ContextKeyExpr.regex('d3', /d.*/),
|
||||
ContextKeyExpr.has('a1'),
|
||||
ContextKeyExpr.lessThanEquals('f2', 'ff2'),
|
||||
ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)),
|
||||
ContextKeyExpr.not('d2')
|
||||
);
|
||||
)!;
|
||||
assert(a.equals(b), 'expressions should be equal');
|
||||
});
|
||||
|
||||
@@ -61,10 +58,10 @@ suite('ContextKeyExpr', () => {
|
||||
let key1IsFalse = ContextKeyExpr.equals('key1', false);
|
||||
let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);
|
||||
|
||||
assert.ok(key1IsTrue.normalize()!.equals(ContextKeyExpr.has('key1')));
|
||||
assert.ok(key1IsNotFalse.normalize()!.equals(ContextKeyExpr.has('key1')));
|
||||
assert.ok(key1IsFalse.normalize()!.equals(ContextKeyExpr.not('key1')));
|
||||
assert.ok(key1IsNotTrue.normalize()!.equals(ContextKeyExpr.not('key1')));
|
||||
assert.ok(key1IsTrue.equals(ContextKeyExpr.has('key1')));
|
||||
assert.ok(key1IsNotFalse.equals(ContextKeyExpr.has('key1')));
|
||||
assert.ok(key1IsFalse.equals(ContextKeyExpr.not('key1')));
|
||||
assert.ok(key1IsNotTrue.equals(ContextKeyExpr.not('key1')));
|
||||
});
|
||||
|
||||
test('evaluate', () => {
|
||||
@@ -108,5 +105,24 @@ suite('ContextKeyExpr', () => {
|
||||
testExpression('a && !b && c == 5', true && !false && '5' == '5');
|
||||
testExpression('d =~ /e.*/', false);
|
||||
/* tslint:enable:triple-equals */
|
||||
|
||||
// precedence test: false && true || true === true because && is evaluated first
|
||||
testExpression('b && a || a', true);
|
||||
|
||||
testExpression('a || b', true);
|
||||
testExpression('b || b', false);
|
||||
testExpression('b && a || a && b', false);
|
||||
});
|
||||
|
||||
test('negate', () => {
|
||||
function testNegate(expr: string, expected: string): void {
|
||||
const actual = ContextKeyExpr.deserialize(expr)!.negate().serialize();
|
||||
assert.strictEqual(actual, expected);
|
||||
}
|
||||
testNegate('a', '!a');
|
||||
testNegate('a && b || c', '!a && !c || !b && !c');
|
||||
testNegate('a && b || c || d', '!a && !c && !d || !b && !c && !d');
|
||||
testNegate('!a && !b || !c && !d', 'a && c || a && d || b && c || b && d');
|
||||
testNegate('!a && !b || !c && !d || !e && !f', 'a && c && e || a && c && f || a && d && e || a && d && f || b && c && e || b && c && f || b && d && e || b && d && f');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,7 +206,7 @@ class WindowDriver implements IWindowDriver {
|
||||
throw new Error(`Xterm not found: ${selector}`);
|
||||
}
|
||||
|
||||
xterm._core.handler(text);
|
||||
xterm._core._coreService.triggerDataEvent(text);
|
||||
}
|
||||
|
||||
async openDevTools(): Promise<void> {
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface ParsedArgs {
|
||||
'disable-extension'?: string | string[];
|
||||
'list-extensions'?: boolean;
|
||||
'show-versions'?: boolean;
|
||||
'category'?: string;
|
||||
'install-extension'?: string | string[];
|
||||
'uninstall-extension'?: string | string[];
|
||||
'locate-extension'?: string | string[];
|
||||
|
||||
@@ -48,6 +48,7 @@ export const options: Option[] = [
|
||||
{ id: 'extensions-dir', type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
{ id: 'list-extensions', type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
||||
{ id: 'show-versions', type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
|
||||
{ id: 'category', type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") },
|
||||
{ id: 'install-extension', type: 'string', cat: 'e', args: 'extension-id | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") },
|
||||
{ id: 'uninstall-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
|
||||
{ id: 'enable-proposed-api', type: 'string', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
|
||||
|
||||
@@ -27,6 +27,7 @@ import { joinPath } from 'vs/base/common/resources';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
interface IRawGalleryExtensionFile {
|
||||
assetType: string;
|
||||
@@ -389,7 +390,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@optional(IStorageService) private readonly storageService: IStorageService,
|
||||
) {
|
||||
const config = productService.extensionsGallery;
|
||||
this.extensionsGalleryUrl = config && config.serviceUrl;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyAndExpr, ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContext, ContextKeyOrExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
import { keys } from 'vs/base/common/map';
|
||||
|
||||
@@ -171,7 +171,6 @@ export class KeybindingResolver {
|
||||
|
||||
/**
|
||||
* Returns true if it is provable `a` implies `b`.
|
||||
* **Precondition**: Assumes `a` and `b` are normalized!
|
||||
*/
|
||||
public static whenIsEntirelyIncluded(a: ContextKeyExpr | null | undefined, b: ContextKeyExpr | null | undefined): boolean {
|
||||
if (!b) {
|
||||
@@ -181,26 +180,35 @@ export class KeybindingResolver {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aExpressions: ContextKeyExpr[] = ((a instanceof ContextKeyAndExpr) ? a.expr : [a]);
|
||||
const bExpressions: ContextKeyExpr[] = ((b instanceof ContextKeyAndExpr) ? b.expr : [b]);
|
||||
return this._implies(a, b);
|
||||
}
|
||||
|
||||
let aIndex = 0;
|
||||
for (const bExpr of bExpressions) {
|
||||
let bExprMatched = false;
|
||||
while (!bExprMatched && aIndex < aExpressions.length) {
|
||||
let aExpr = aExpressions[aIndex];
|
||||
if (aExpr.equals(bExpr)) {
|
||||
bExprMatched = true;
|
||||
}
|
||||
aIndex++;
|
||||
/**
|
||||
* Returns true if it is provable `p` implies `q`.
|
||||
*/
|
||||
private static _implies(p: ContextKeyExpr, q: ContextKeyExpr): boolean {
|
||||
const notP = p.negate();
|
||||
|
||||
const terminals = (node: ContextKeyExpr) => {
|
||||
if (node instanceof ContextKeyOrExpr) {
|
||||
return node.expr;
|
||||
}
|
||||
return [node];
|
||||
};
|
||||
|
||||
if (!bExprMatched) {
|
||||
return false;
|
||||
let expr = terminals(notP).concat(terminals(q));
|
||||
for (let i = 0; i < expr.length; i++) {
|
||||
const a = expr[i];
|
||||
const notA = a.negate();
|
||||
for (let j = i + 1; j < expr.length; j++) {
|
||||
const b = expr[j];
|
||||
if (notA.equals(b)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public getDefaultBoundCommands(): Map<string, boolean> {
|
||||
|
||||
@@ -121,6 +121,7 @@ suite('AbstractKeybindingService', () => {
|
||||
let commandService: ICommandService = {
|
||||
_serviceBrand: undefined,
|
||||
onWillExecuteCommand: () => ({ dispose: () => { } }),
|
||||
onDidExecuteCommand: () => ({ dispose: () => { } }),
|
||||
executeCommand: (commandId: string, ...args: any[]): Promise<any> => {
|
||||
executeCommandCalls.push({
|
||||
commandId: commandId,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { OS } from 'vs/base/common/platform';
|
||||
import { ContextKeyAndExpr, ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
|
||||
@@ -20,13 +20,13 @@ function createContext(ctx: any) {
|
||||
|
||||
suite('KeybindingResolver', () => {
|
||||
|
||||
function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean): ResolvedKeybindingItem {
|
||||
function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean): ResolvedKeybindingItem {
|
||||
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined);
|
||||
return new ResolvedKeybindingItem(
|
||||
resolvedKeybinding,
|
||||
command,
|
||||
commandArgs,
|
||||
when ? when.normalize() : undefined,
|
||||
when,
|
||||
isDefault
|
||||
);
|
||||
}
|
||||
@@ -191,64 +191,41 @@ suite('KeybindingResolver', () => {
|
||||
});
|
||||
|
||||
test('contextIsEntirelyIncluded', () => {
|
||||
let assertIsIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
|
||||
let tmpA = new ContextKeyAndExpr(a).normalize();
|
||||
let tmpB = new ContextKeyAndExpr(b).normalize();
|
||||
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), true);
|
||||
const assertIsIncluded = (a: string | null, b: string | null) => {
|
||||
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), true);
|
||||
};
|
||||
let assertIsNotIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
|
||||
let tmpA = new ContextKeyAndExpr(a).normalize();
|
||||
let tmpB = new ContextKeyAndExpr(b).normalize();
|
||||
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), false);
|
||||
const assertIsNotIncluded = (a: string | null, b: string | null) => {
|
||||
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), false);
|
||||
};
|
||||
let key1IsTrue = ContextKeyExpr.equals('key1', true);
|
||||
let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);
|
||||
let key1IsFalse = ContextKeyExpr.equals('key1', false);
|
||||
let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);
|
||||
let key2IsTrue = ContextKeyExpr.equals('key2', true);
|
||||
let key2IsNotFalse = ContextKeyExpr.notEquals('key2', false);
|
||||
let key3IsTrue = ContextKeyExpr.equals('key3', true);
|
||||
let key4IsTrue = ContextKeyExpr.equals('key4', true);
|
||||
|
||||
assertIsIncluded([key1IsTrue], null!);
|
||||
assertIsIncluded([key1IsTrue], []);
|
||||
assertIsIncluded([key1IsTrue], [key1IsTrue]);
|
||||
assertIsIncluded([key1IsTrue], [key1IsNotFalse]);
|
||||
assertIsIncluded('key1', null);
|
||||
assertIsIncluded('key1', '');
|
||||
assertIsIncluded('key1', 'key1');
|
||||
assertIsIncluded('!key1', '');
|
||||
assertIsIncluded('!key1', '!key1');
|
||||
assertIsIncluded('key2', '');
|
||||
assertIsIncluded('key2', 'key2');
|
||||
assertIsIncluded('key1 && key1 && key2 && key2', 'key2');
|
||||
assertIsIncluded('key1 && key2', 'key2');
|
||||
assertIsIncluded('key1 && key2', 'key1');
|
||||
assertIsIncluded('key1 && key2', '');
|
||||
assertIsIncluded('key1', 'key1 || key2');
|
||||
assertIsIncluded('key1 || !key1', 'key2 || !key2');
|
||||
assertIsIncluded('key1', 'key1 || key2 && key3');
|
||||
|
||||
assertIsIncluded([key1IsFalse], []);
|
||||
assertIsIncluded([key1IsFalse], [key1IsFalse]);
|
||||
assertIsIncluded([key1IsFalse], [key1IsNotTrue]);
|
||||
|
||||
assertIsIncluded([key2IsNotFalse], []);
|
||||
assertIsIncluded([key2IsNotFalse], [key2IsNotFalse]);
|
||||
assertIsIncluded([key2IsNotFalse], [key2IsTrue]);
|
||||
|
||||
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsTrue]);
|
||||
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsNotFalse]);
|
||||
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsTrue]);
|
||||
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsNotFalse]);
|
||||
assertIsIncluded([key1IsTrue, key2IsNotFalse], []);
|
||||
|
||||
assertIsNotIncluded([key1IsTrue], [key1IsFalse]);
|
||||
assertIsNotIncluded([key1IsTrue], [key1IsNotTrue]);
|
||||
assertIsNotIncluded([key1IsNotFalse], [key1IsFalse]);
|
||||
assertIsNotIncluded([key1IsNotFalse], [key1IsNotTrue]);
|
||||
|
||||
assertIsNotIncluded([key1IsFalse], [key1IsTrue]);
|
||||
assertIsNotIncluded([key1IsFalse], [key1IsNotFalse]);
|
||||
assertIsNotIncluded([key1IsNotTrue], [key1IsTrue]);
|
||||
assertIsNotIncluded([key1IsNotTrue], [key1IsNotFalse]);
|
||||
|
||||
assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key3IsTrue]);
|
||||
assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key4IsTrue]);
|
||||
assertIsNotIncluded([key1IsTrue], [key2IsTrue]);
|
||||
assertIsNotIncluded([], [key2IsTrue]);
|
||||
assertIsNotIncluded(null!, [key2IsTrue]);
|
||||
assertIsNotIncluded('key1', '!key1');
|
||||
assertIsNotIncluded('!key1', 'key1');
|
||||
assertIsNotIncluded('key1 && key2', 'key3');
|
||||
assertIsNotIncluded('key1 && key2', 'key4');
|
||||
assertIsNotIncluded('key1', 'key2');
|
||||
assertIsNotIncluded('key1 || key2', 'key2');
|
||||
assertIsNotIncluded('', 'key2');
|
||||
assertIsNotIncluded(null, 'key2');
|
||||
});
|
||||
|
||||
test('resolve command', function () {
|
||||
|
||||
function _kbItem(keybinding: number, command: string, when: ContextKeyExpr): ResolvedKeybindingItem {
|
||||
function _kbItem(keybinding: number, command: string, when: ContextKeyExpr | undefined): ResolvedKeybindingItem {
|
||||
return kbItem(keybinding, command, null, when, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ export class WorkbenchList<T> extends List<T> {
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
renderers: IListRenderer<T, any>[],
|
||||
options: IListOptions<T>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@@ -787,7 +787,7 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
options: IObjectTreeOptions<T, TFilterData>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@@ -813,7 +813,7 @@ export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<T
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
dataSource: IDataSource<TInput, T>,
|
||||
options: IDataTreeOptions<T, TFilterData>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@@ -840,7 +840,7 @@ export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends Async
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
dataSource: IAsyncDataSource<TInput, T>,
|
||||
options: IAsyncDataTreeOptions<T, TFilterData>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface IRelatedInformation {
|
||||
|
||||
export const enum MarkerTag {
|
||||
Unnecessary = 1,
|
||||
Deprecated = 2
|
||||
}
|
||||
|
||||
export enum MarkerSeverity {
|
||||
|
||||
@@ -73,10 +73,9 @@ export interface IProductConfiguration {
|
||||
readonly recommendationsUrl: string;
|
||||
};
|
||||
extensionTips: { [id: string]: string; };
|
||||
// {{SQL CARBON EDIT}}
|
||||
recommendedExtensions: string[];
|
||||
extensionImportantTips: { [id: string]: { name: string; pattern: string; }; };
|
||||
readonly exeBasedExtensionTips: { [id: string]: { friendlyName: string, windowsPath?: string, recommendations: readonly string[] }; };
|
||||
recommendedExtensions: string[]; // {{SQL CARBON EDIT}}
|
||||
extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; };
|
||||
readonly exeBasedExtensionTips: { [id: string]: IExeBasedExtensionTip; };
|
||||
readonly extensionKeywords: { [extension: string]: readonly string[]; };
|
||||
readonly extensionAllowedBadgeProviders: readonly string[];
|
||||
readonly extensionAllowedProposedApi: readonly string[];
|
||||
@@ -125,6 +124,14 @@ export interface IProductConfiguration {
|
||||
readonly uiExtensions?: readonly string[];
|
||||
}
|
||||
|
||||
export interface IExeBasedExtensionTip {
|
||||
friendlyName: string;
|
||||
windowsPath?: string;
|
||||
recommendations: readonly string[];
|
||||
important?: boolean;
|
||||
exeFriendlyName?: string;
|
||||
}
|
||||
|
||||
export interface ISurveyData {
|
||||
surveyId: string;
|
||||
surveyUrl: string;
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IWebSocketFactory {
|
||||
create(url: string): IWebSocket;
|
||||
}
|
||||
|
||||
export interface IWebSocket {
|
||||
readonly onData: Event<ArrayBuffer>;
|
||||
readonly onOpen: Event<void>;
|
||||
readonly onClose: Event<void>;
|
||||
readonly onError: Event<any>;
|
||||
|
||||
send(data: ArrayBuffer | ArrayBufferView): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
class BrowserWebSocket implements IWebSocket {
|
||||
|
||||
private readonly _onData = new Emitter<ArrayBuffer>();
|
||||
public readonly onData = this._onData.event;
|
||||
|
||||
public readonly onOpen: Event<void>;
|
||||
public readonly onClose: Event<void>;
|
||||
public readonly onError: Event<any>;
|
||||
|
||||
private readonly _socket: WebSocket;
|
||||
private readonly _fileReader: FileReader;
|
||||
private readonly _queue: Blob[];
|
||||
private _isReading: boolean;
|
||||
|
||||
private readonly _socketMessageListener: (ev: MessageEvent) => void;
|
||||
|
||||
constructor(socket: WebSocket) {
|
||||
this._socket = socket;
|
||||
this._fileReader = new FileReader();
|
||||
this._queue = [];
|
||||
this._isReading = false;
|
||||
|
||||
this._fileReader.onload = (event) => {
|
||||
this._isReading = false;
|
||||
const buff = <ArrayBuffer>(<any>event.target).result;
|
||||
|
||||
this._onData.fire(buff);
|
||||
|
||||
if (this._queue.length > 0) {
|
||||
enqueue(this._queue.shift()!);
|
||||
}
|
||||
};
|
||||
|
||||
const enqueue = (blob: Blob) => {
|
||||
if (this._isReading) {
|
||||
this._queue.push(blob);
|
||||
return;
|
||||
}
|
||||
this._isReading = true;
|
||||
this._fileReader.readAsArrayBuffer(blob);
|
||||
};
|
||||
|
||||
this._socketMessageListener = (ev: MessageEvent) => {
|
||||
enqueue(<Blob>ev.data);
|
||||
};
|
||||
this._socket.addEventListener('message', this._socketMessageListener);
|
||||
|
||||
this.onOpen = Event.fromDOMEventEmitter(this._socket, 'open');
|
||||
this.onClose = Event.fromDOMEventEmitter(this._socket, 'close');
|
||||
this.onError = Event.fromDOMEventEmitter(this._socket, 'error');
|
||||
}
|
||||
|
||||
send(data: ArrayBuffer | ArrayBufferView): void {
|
||||
this._socket.send(data);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this._socket.close();
|
||||
this._socket.removeEventListener('message', this._socketMessageListener);
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultWebSocketFactory = new class implements IWebSocketFactory {
|
||||
create(url: string): IWebSocket {
|
||||
return new BrowserWebSocket(new WebSocket(url));
|
||||
}
|
||||
};
|
||||
|
||||
class BrowserSocket implements ISocket {
|
||||
public readonly socket: IWebSocket;
|
||||
|
||||
constructor(socket: IWebSocket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
public onData(listener: (e: VSBuffer) => void): IDisposable {
|
||||
return this.socket.onData((data) => listener(VSBuffer.wrap(new Uint8Array(data))));
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
return this.socket.onClose(listener);
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
this.socket.send(buffer.buffer);
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class BrowserSocketFactory implements ISocketFactory {
|
||||
private readonly _webSocketFactory: IWebSocketFactory;
|
||||
|
||||
constructor(webSocketFactory: IWebSocketFactory | null | undefined) {
|
||||
this._webSocketFactory = webSocketFactory || defaultWebSocketFactory;
|
||||
}
|
||||
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
||||
const errorListener = socket.onError((err) => callback(err, undefined));
|
||||
socket.onOpen(() => {
|
||||
errorListener.dispose();
|
||||
callback(undefined, new BrowserSocket(socket));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,89 +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 { IWebSocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
class BrowserSocket implements ISocket {
|
||||
public readonly socket: WebSocket;
|
||||
|
||||
constructor(socket: WebSocket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
public onData(_listener: (e: VSBuffer) => void): IDisposable {
|
||||
const fileReader = new FileReader();
|
||||
const queue: Blob[] = [];
|
||||
let isReading = false;
|
||||
fileReader.onload = function (event) {
|
||||
isReading = false;
|
||||
const buff = <ArrayBuffer>(<any>event.target).result;
|
||||
|
||||
try {
|
||||
_listener(VSBuffer.wrap(new Uint8Array(buff)));
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
|
||||
if (queue.length > 0) {
|
||||
enqueue(queue.shift()!);
|
||||
}
|
||||
};
|
||||
const enqueue = (blob: Blob) => {
|
||||
if (isReading) {
|
||||
queue.push(blob);
|
||||
return;
|
||||
}
|
||||
isReading = true;
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
};
|
||||
const listener = (e: MessageEvent) => {
|
||||
enqueue(<Blob>e.data);
|
||||
};
|
||||
this.socket.addEventListener('message', listener);
|
||||
return {
|
||||
dispose: () => this.socket.removeEventListener('message', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
this.socket.addEventListener('close', listener);
|
||||
return {
|
||||
dispose: () => this.socket.removeEventListener('close', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
this.socket.send(buffer.buffer);
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const browserWebSocketFactory = new class implements IWebSocketFactory {
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
const errorListener = (err: any) => callback(err, undefined);
|
||||
const socket = new WebSocket(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
||||
socket.onopen = function (event) {
|
||||
socket.removeEventListener('error', errorListener);
|
||||
callback(undefined, new BrowserSocket(socket));
|
||||
};
|
||||
socket.addEventListener('error', errorListener);
|
||||
}
|
||||
};
|
||||
@@ -57,7 +57,7 @@ interface ISimpleConnectionOptions {
|
||||
port: number;
|
||||
reconnectionToken: string;
|
||||
reconnectionProtocol: PersistentProtocol | null;
|
||||
webSocketFactory: IWebSocketFactory;
|
||||
socketFactory: ISocketFactory;
|
||||
signService: ISignService;
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ export interface IConnectCallback {
|
||||
(err: any | undefined, socket: ISocket | undefined): void;
|
||||
}
|
||||
|
||||
export interface IWebSocketFactory {
|
||||
export interface ISocketFactory {
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void;
|
||||
}
|
||||
|
||||
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<PersistentProtocol> {
|
||||
const protocol = await new Promise<PersistentProtocol>((c, e) => {
|
||||
options.webSocketFactory.connect(
|
||||
options.socketFactory.connect(
|
||||
options.host,
|
||||
options.port,
|
||||
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
|
||||
@@ -202,7 +202,7 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta
|
||||
export interface IConnectionOptions {
|
||||
isBuilt: boolean;
|
||||
commit: string | undefined;
|
||||
webSocketFactory: IWebSocketFactory;
|
||||
socketFactory: ISocketFactory;
|
||||
addressProvider: IAddressProvider;
|
||||
signService: ISignService;
|
||||
}
|
||||
@@ -216,7 +216,7 @@ async function resolveConnectionOptions(options: IConnectionOptions, reconnectio
|
||||
port: port,
|
||||
reconnectionToken: reconnectionToken,
|
||||
reconnectionProtocol: reconnectionProtocol,
|
||||
webSocketFactory: options.webSocketFactory,
|
||||
socketFactory: options.socketFactory,
|
||||
signService: options.signService
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,4 +10,16 @@ export const REMOTE_HOST_SCHEME = Schemas.vscodeRemote;
|
||||
|
||||
export function getRemoteAuthority(uri: URI): string | undefined {
|
||||
return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined;
|
||||
}
|
||||
|
||||
export function getRemoteName(authority: string | undefined): string | undefined {
|
||||
if (!authority) {
|
||||
return undefined;
|
||||
}
|
||||
const pos = authority.indexOf('+');
|
||||
if (pos < 0) {
|
||||
// funky? bad authority?
|
||||
return authority;
|
||||
}
|
||||
return authority.substr(0, pos);
|
||||
}
|
||||
+2
-2
@@ -5,9 +5,9 @@
|
||||
|
||||
import * as net from 'net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IWebSocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ISocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
|
||||
export const nodeWebSocketFactory = new class implements IWebSocketFactory {
|
||||
export const nodeSocketFactory = new class implements ISocketFactory {
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
const errorListener = (err: any) => callback(err, undefined);
|
||||
|
||||
@@ -90,7 +90,7 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration)
|
||||
'http.proxy': {
|
||||
type: 'string',
|
||||
pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+)(:\\d+)?/?$|^$',
|
||||
description: localize('proxy', "The proxy setting to use. If not set will be taken from the http_proxy and https_proxy environment variables.")
|
||||
markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.")
|
||||
},
|
||||
'http.proxyStrictSSL': {
|
||||
type: 'boolean',
|
||||
@@ -100,7 +100,7 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration)
|
||||
'http.proxyAuthorization': {
|
||||
type: ['null', 'string'],
|
||||
default: null,
|
||||
description: localize('proxyAuthorization', "The value to send as the 'Proxy-Authorization' header for every network request.")
|
||||
markdownDescription: localize('proxyAuthorization', "The value to send as the `Proxy-Authorization` header for every network request.")
|
||||
},
|
||||
'http.proxySupport': {
|
||||
type: 'string',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export interface IPropertyData {
|
||||
classification: 'SystemMetaData' | 'CallstackOrException' | 'CustomerContent' | 'PublicNonPersonalData';
|
||||
classification: 'SystemMetaData' | 'CallstackOrException' | 'CustomerContent' | 'PublicNonPersonalData' | 'EndUserPseudonymizedInformation';
|
||||
purpose: 'PerformanceAndHealth' | 'FeatureInsight' | 'BusinessInsight';
|
||||
endpoint?: string;
|
||||
isMeasurement?: boolean;
|
||||
|
||||
@@ -69,20 +69,16 @@ export class TelemetryService implements ITelemetryService {
|
||||
this._commonProperties.then(values => {
|
||||
const isHashedId = /^[a-f0-9]+$/i.test(values['common.machineId']);
|
||||
|
||||
/* __GDPR__
|
||||
"machineIdFallback" : {
|
||||
"usingFallbackGuid" : { "classification": "SystemMetaData", "purpose": "BusinessInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this.publicLog('machineIdFallback', { usingFallbackGuid: !isHashedId });
|
||||
type MachineIdFallbackClassification = {
|
||||
usingFallbackGuid: { classification: 'SystemMetaData', purpose: 'BusinessInsight', isMeasurement: true };
|
||||
};
|
||||
this.publicLog2<{ usingFallbackGuid: boolean }, MachineIdFallbackClassification>('machineIdFallback', { usingFallbackGuid: !isHashedId });
|
||||
|
||||
if (config.trueMachineId) {
|
||||
/* __GDPR__
|
||||
"machineIdDisambiguation" : {
|
||||
"correctedMachineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.publicLog('machineIdDisambiguation', { correctedMachineId: config.trueMachineId });
|
||||
type MachineIdDisambiguationClassification = {
|
||||
correctedMachineId: { endPoint: 'MacAddressHash', classification: 'EndUserPseudonymizedInformation', purpose: 'FeatureInsight' };
|
||||
};
|
||||
this.publicLog2<{ correctedMachineId: string }, MachineIdDisambiguationClassification>('machineIdDisambiguation', { correctedMachineId: config.trueMachineId });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,17 +33,17 @@ export const NullTelemetryService = new class implements ITelemetryService {
|
||||
|
||||
export interface ITelemetryAppender {
|
||||
log(eventName: string, data: any): void;
|
||||
dispose(): Promise<any> | undefined;
|
||||
flush(): Promise<any>;
|
||||
}
|
||||
|
||||
export function combinedAppender(...appenders: ITelemetryAppender[]): ITelemetryAppender {
|
||||
return {
|
||||
log: (e, d) => appenders.forEach(a => a.log(e, d)),
|
||||
dispose: () => Promise.all(appenders.map(a => a.dispose()))
|
||||
flush: () => Promise.all(appenders.map(a => a.flush()))
|
||||
};
|
||||
}
|
||||
|
||||
export const NullAppender: ITelemetryAppender = { log: () => null, dispose: () => Promise.resolve(null) };
|
||||
export const NullAppender: ITelemetryAppender = { log: () => null, flush: () => Promise.resolve(null) };
|
||||
|
||||
|
||||
export class LogAppender implements ITelemetryAppender {
|
||||
@@ -51,7 +51,7 @@ export class LogAppender implements ITelemetryAppender {
|
||||
private commonPropertiesRegex = /^sessionID$|^version$|^timestamp$|^commitHash$|^common\./;
|
||||
constructor(@ILogService private readonly _logService: ILogService) { }
|
||||
|
||||
dispose(): Promise<any> {
|
||||
flush(): Promise<any> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export class AppInsightsAppender implements ITelemetryAppender {
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): Promise<any> | undefined {
|
||||
flush(): Promise<any> | undefined {
|
||||
if (this._aiClient) {
|
||||
return new Promise(resolve => {
|
||||
this._aiClient!.flush({
|
||||
|
||||
@@ -37,7 +37,7 @@ export class TelemetryAppenderClient implements ITelemetryAppender {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
dispose(): any {
|
||||
flush(): any {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ suite('AIAdapter', () => {
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
adapter.dispose();
|
||||
adapter.flush();
|
||||
});
|
||||
|
||||
test('Simple event', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user