Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3 (#6516)

* Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3

* fix tests
This commit is contained in:
Anthony Dresser
2019-07-28 15:15:24 -07:00
committed by GitHub
parent aacf1e7f1c
commit 1d56a17f32
292 changed files with 19784 additions and 1873 deletions
@@ -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')
});
}
+38 -35
View File
@@ -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 {
+42 -11
View File
@@ -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: {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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;
}
+5 -1
View File
@@ -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';
+85 -24
View File
@@ -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]);
}
}
+33 -4
View File
@@ -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[] = [];
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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));
}
}
+68 -12
View File
@@ -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);
}
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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);
}
}
+1 -1
View File
@@ -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> = {}
) {
+1 -1
View File
@@ -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> = {}
) {
+6 -8
View File
@@ -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 {
+12 -5
View File
@@ -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>();
+14
View File
@@ -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;
+1 -1
View File
@@ -414,7 +414,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] {
return res;
}
const _maxLen = 53;
const _maxLen = 128;
function initTable() {
const table: number[][] = [];
-1
View File
@@ -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);
};
}
+4 -4
View File
@@ -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();
});
+3 -2
View File
@@ -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([
{
+15
View File
@@ -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)),
+7 -5
View File
@@ -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 });
+14 -5
View File
@@ -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 {
+13 -10
View File
@@ -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."),
''
],
+120 -4
View File
@@ -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);
}
}
+26
View File
@@ -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.
+2 -1
View File
@@ -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;
}
}
+12 -2
View File
@@ -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);
+8 -5
View File
@@ -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);
+21
View File
@@ -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);
}
+21
View File
@@ -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 {
+36 -2
View File
@@ -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);
+16 -2
View File
@@ -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) {
+17 -44
View File
@@ -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();
});
});
+12 -1
View File
@@ -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();
+313 -99
View File
@@ -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[];
+1
View File
@@ -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);
}
+4 -4
View File
@@ -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 {
+11 -4
View File
@@ -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);
}
@@ -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);
+2 -2
View File
@@ -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