Archived
Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568
This commit is contained in:
@@ -10,4 +10,4 @@
|
||||
.monaco-scroll-split-view {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export interface IInsightData {
|
||||
|
||||
export interface IInsightsView {
|
||||
data: IInsightData;
|
||||
setConfig?: (config: { [key: string]: any }) => void;
|
||||
setConfig?: (config: any) => void;
|
||||
init?: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ModelViewInput } from 'sql/workbench/browser/modelComponents/modelViewI
|
||||
import { ModelViewEditor } from 'sql/workbench/browser/modelComponents/modelViewEditor';
|
||||
|
||||
// Model View editor registration
|
||||
const viewModelEditorDescriptor = new EditorDescriptor(
|
||||
const viewModelEditorDescriptor = EditorDescriptor.create(
|
||||
ModelViewEditor,
|
||||
ModelViewEditor.ID,
|
||||
'ViewModel'
|
||||
|
||||
+332
-265
@@ -3,73 +3,69 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { ContextAwareMenuEntryActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { dirname, basename } from 'vs/base/common/resources';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
|
||||
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { ITreeItem, ITreeView } from 'sql/workbench/common/views';
|
||||
import { UserCancelledConnectionError } from 'sql/base/common/errors';
|
||||
import { IOEShimService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerViewTreeShim';
|
||||
import { NodeContextKey } from 'sql/workbench/browser/parts/views/nodeContext';
|
||||
import { UserCancelledConnectionError } from 'sql/base/common/errors';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export class CustomTreeViewPane extends ViewPane {
|
||||
export class TreeViewPane extends ViewPane {
|
||||
|
||||
private treeView: ITreeView;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IOpenerService protected openerService: IOpenerService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
|
||||
this.treeView = treeView as ITreeView;
|
||||
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
|
||||
@@ -86,23 +82,22 @@ export class CustomTreeViewPane extends ViewPane {
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
if (this.treeView instanceof CustomTreeView) {
|
||||
super.renderBody(container);
|
||||
|
||||
if (this.treeView instanceof TreeView) {
|
||||
this.treeView.show(container);
|
||||
}
|
||||
}
|
||||
|
||||
shouldShowWelcome(): boolean {
|
||||
return (this.treeView.dataProvider === undefined) && (this.treeView.message === undefined);
|
||||
return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined);
|
||||
}
|
||||
|
||||
layoutBody(height: number, width: number): void {
|
||||
super.layoutBody(height, width);
|
||||
this.treeView.layout(height, width);
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return this.treeView.getOptimalWidth();
|
||||
}
|
||||
@@ -112,51 +107,6 @@ export class CustomTreeViewPane extends ViewPane {
|
||||
}
|
||||
}
|
||||
|
||||
class TitleMenus extends Disposable {
|
||||
|
||||
private titleActions: IAction[] = [];
|
||||
private readonly titleActionsDisposable = this._register(new MutableDisposable());
|
||||
private titleSecondaryActions: IAction[] = [];
|
||||
|
||||
private _onDidChangeTitle = this._register(new Emitter<void>());
|
||||
readonly onDidChangeTitle: Event<void> = this._onDidChangeTitle.event;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
) {
|
||||
super();
|
||||
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
|
||||
scopedContextKeyService.createKey('view', id);
|
||||
|
||||
const titleMenu = this._register(this.menuService.createMenu(MenuId.ViewTitle, scopedContextKeyService));
|
||||
const updateActions = () => {
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions });
|
||||
this._onDidChangeTitle.fire();
|
||||
};
|
||||
|
||||
this._register(titleMenu.onDidChange(updateActions));
|
||||
updateActions();
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
}));
|
||||
}
|
||||
|
||||
getTitleActions(): IAction[] {
|
||||
return this.titleActions;
|
||||
}
|
||||
|
||||
getTitleSecondaryActions(): IAction[] {
|
||||
return this.titleSecondaryActions;
|
||||
}
|
||||
}
|
||||
|
||||
class Root implements ITreeItem {
|
||||
label = { label: 'root' };
|
||||
handle = '0';
|
||||
@@ -167,29 +117,30 @@ class Root implements ITreeItem {
|
||||
|
||||
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");
|
||||
|
||||
export class CustomTreeView extends Disposable implements ITreeView {
|
||||
class Tree extends WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> { }
|
||||
|
||||
export class TreeView extends Disposable implements ITreeView {
|
||||
|
||||
private isVisible: boolean = false;
|
||||
private activated: boolean = false;
|
||||
private _hasIconForParentNode = false;
|
||||
private _hasIconForLeafNode = false;
|
||||
private _showCollapseAllAction = false;
|
||||
|
||||
private readonly collapseAllContextKey: RawContextKey<boolean>;
|
||||
private readonly collapseAllContext: IContextKey<boolean>;
|
||||
private readonly refreshContextKey: RawContextKey<boolean>;
|
||||
private readonly refreshContext: IContextKey<boolean>;
|
||||
|
||||
private focused: boolean = false;
|
||||
private domNode: HTMLElement;
|
||||
private treeContainer: HTMLElement;
|
||||
private _messageValue: string | IMarkdownString | undefined;
|
||||
private domNode!: HTMLElement;
|
||||
private treeContainer!: HTMLElement;
|
||||
private _messageValue: string | undefined;
|
||||
private _canSelectMany: boolean = false;
|
||||
private messageElement: HTMLDivElement;
|
||||
private tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>;
|
||||
private treeLabels: ResourceLabels;
|
||||
private messageElement!: HTMLDivElement;
|
||||
private tree: Tree | undefined;
|
||||
private treeLabels: ResourceLabels | undefined;
|
||||
|
||||
private root: ITreeItem;
|
||||
private elementsToRefresh: ITreeItem[] = [];
|
||||
private menus: TitleMenus;
|
||||
|
||||
private markdownRenderer: MarkdownRenderer;
|
||||
private markdownResult: IMarkdownRenderResult | null;
|
||||
|
||||
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
|
||||
@@ -212,69 +163,109 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
private readonly _onDidChangeTitle: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidChangeTitle: Event<string> = this._onDidChangeTitle.event;
|
||||
|
||||
private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());
|
||||
|
||||
private nodeContext: NodeContextKey;
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
protected readonly id: string,
|
||||
private _title: string,
|
||||
private viewContainer: ViewContainer,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IProgressService protected readonly progressService: IProgressService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
this.root = new Root();
|
||||
this.menus = this._register(instantiationService.createInstance(TitleMenus, this.id));
|
||||
this._register(this.menus.onDidChangeTitle(() => this._onDidChangeActions.fire()));
|
||||
this.collapseAllContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableCollapseAll`, false);
|
||||
this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService);
|
||||
this.refreshContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableRefresh`, false);
|
||||
this.refreshContext = this.refreshContextKey.bindTo(contextKeyService);
|
||||
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('explorer.decorations')) {
|
||||
this.doRefresh([this.root]).catch(onUnexpectedError); /** soft refresh **/
|
||||
this.doRefresh([this.root]); /** soft refresh **/
|
||||
}
|
||||
}));
|
||||
this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer);
|
||||
this._register(toDisposable(() => {
|
||||
if (this.markdownResult) {
|
||||
this.markdownResult.dispose();
|
||||
}
|
||||
}));
|
||||
this._register(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => {
|
||||
if (from === this.viewContainer && views.some(v => v.id === this.id)) {
|
||||
this.viewContainer = to;
|
||||
this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => {
|
||||
if (views.some(v => v.id === this.id)) {
|
||||
this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } });
|
||||
}
|
||||
}));
|
||||
this.registerActions();
|
||||
|
||||
this.nodeContext = this._register(instantiationService.createInstance(NodeContextKey));
|
||||
this.nodeContext = this._register(instantiationService.createInstance(NodeContextKey)); // tracked change
|
||||
this.create();
|
||||
}
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider | null;
|
||||
get dataProvider(): ITreeViewDataProvider | null {
|
||||
collapse(element: ITreeItem): boolean {
|
||||
if (this.tree) {
|
||||
return this.tree.collapse(element);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get viewContainer(): ViewContainer {
|
||||
return this.viewDescriptorService.getViewContainerByViewId(this.id)!;
|
||||
}
|
||||
|
||||
get viewLocation(): ViewContainerLocation {
|
||||
return this.viewDescriptorService.getViewLocationById(this.id)!;
|
||||
}
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider | undefined;
|
||||
get dataProvider(): ITreeViewDataProvider | undefined {
|
||||
return this._dataProvider;
|
||||
}
|
||||
|
||||
set dataProvider(dataProvider: ITreeViewDataProvider | null) {
|
||||
set dataProvider(dataProvider: ITreeViewDataProvider | undefined) {
|
||||
if (this.tree === undefined) {
|
||||
this.createTree();
|
||||
}
|
||||
|
||||
if (dataProvider) {
|
||||
this._dataProvider = new class implements ITreeViewDataProvider {
|
||||
private _isEmpty: boolean = true;
|
||||
private _onDidChangeEmpty: Emitter<void> = new Emitter();
|
||||
public onDidChangeEmpty: Event<void> = this._onDidChangeEmpty.event;
|
||||
|
||||
get isTreeEmpty(): boolean {
|
||||
return this._isEmpty;
|
||||
}
|
||||
|
||||
async getChildren(node: ITreeItem): Promise<ITreeItem[]> {
|
||||
let children: ITreeItem[];
|
||||
if (node && node.children) {
|
||||
return Promise.resolve(node.children);
|
||||
children = node.children;
|
||||
} else {
|
||||
children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node));
|
||||
node.children = children;
|
||||
}
|
||||
if (node instanceof Root) {
|
||||
const oldEmpty = this._isEmpty;
|
||||
this._isEmpty = children.length === 0;
|
||||
if (oldEmpty !== this._isEmpty) {
|
||||
this._onDidChangeEmpty.fire();
|
||||
}
|
||||
}
|
||||
const children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node));
|
||||
node.children = children;
|
||||
return children;
|
||||
}
|
||||
};
|
||||
if (this._dataProvider.onDidChangeEmpty) {
|
||||
this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire()));
|
||||
}
|
||||
this.updateMessage();
|
||||
this.refresh().catch(onUnexpectedError);
|
||||
this.refresh();
|
||||
} else {
|
||||
this._dataProvider = null;
|
||||
this._dataProvider = undefined;
|
||||
this.updateMessage();
|
||||
}
|
||||
|
||||
@@ -322,27 +313,61 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
|
||||
get showCollapseAllAction(): boolean {
|
||||
return this._showCollapseAllAction;
|
||||
return !!this.collapseAllContext.get();
|
||||
}
|
||||
|
||||
set showCollapseAllAction(showCollapseAllAction: boolean) {
|
||||
if (this._showCollapseAllAction !== !!showCollapseAllAction) {
|
||||
this._showCollapseAllAction = !!showCollapseAllAction;
|
||||
this._onDidChangeActions.fire();
|
||||
}
|
||||
this.collapseAllContext.set(showCollapseAllAction);
|
||||
}
|
||||
|
||||
getPrimaryActions(): IAction[] {
|
||||
if (this.showCollapseAllAction) {
|
||||
const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction<ITreeItem, ITreeItem, FuzzyScore>(this.tree, true).run() : Promise.resolve());
|
||||
return [...this.menus.getTitleActions(), collapseAllAction];
|
||||
} else {
|
||||
return this.menus.getTitleActions();
|
||||
}
|
||||
get showRefreshAction(): boolean {
|
||||
return !!this.refreshContext.get();
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
set showRefreshAction(showRefreshAction: boolean) {
|
||||
this.refreshContext.set(showRefreshAction);
|
||||
}
|
||||
|
||||
private registerActions() {
|
||||
const that = this;
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.treeView.${that.id}.refresh`,
|
||||
title: localize('refresh', "Refresh"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey),
|
||||
group: 'navigation',
|
||||
order: Number.MAX_SAFE_INTEGER - 1,
|
||||
},
|
||||
icon: { id: 'codicon/refresh' }
|
||||
});
|
||||
}
|
||||
async run(): Promise<void> {
|
||||
return that.refresh();
|
||||
}
|
||||
}));
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.treeView.${that.id}.collapseAll`,
|
||||
title: localize('collapseAll', "Collapse All"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey),
|
||||
group: 'navigation',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
icon: { id: 'codicon/collapse-all' }
|
||||
});
|
||||
}
|
||||
async run(): Promise<void> {
|
||||
if (that.tree) {
|
||||
return new CollapseAllAction<ITreeItem, ITreeItem, FuzzyScore>(that.tree, true).run();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
@@ -352,9 +377,6 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
|
||||
this.isVisible = isVisible;
|
||||
if (this.isVisible) {
|
||||
this.activate();
|
||||
}
|
||||
|
||||
if (this.tree) {
|
||||
if (this.isVisible) {
|
||||
@@ -364,7 +386,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
|
||||
if (this.isVisible && this.elementsToRefresh.length) {
|
||||
this.doRefresh(this.elementsToRefresh).catch(onUnexpectedError);
|
||||
this.doRefresh(this.elementsToRefresh);
|
||||
this.elementsToRefresh = [];
|
||||
}
|
||||
}
|
||||
@@ -372,16 +394,18 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
this._onDidChangeVisibility.fire(this.isVisible);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
focus(reveal: boolean = true): void {
|
||||
if (this.tree && this.root.children && this.root.children.length > 0) {
|
||||
// Make sure the current selected element is revealed
|
||||
const selectedElement = this.tree.getSelection()[0];
|
||||
if (selectedElement) {
|
||||
if (selectedElement && reveal) {
|
||||
this.tree.reveal(selectedElement, 0.5);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.domFocus();
|
||||
} else if (this.tree) {
|
||||
this.tree.domFocus();
|
||||
} else {
|
||||
this.domNode.focus();
|
||||
}
|
||||
@@ -406,18 +430,21 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
|
||||
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
|
||||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.id, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.id, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
|
||||
const aligner = new Aligner(this.themeService);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);
|
||||
const widgetAriaLabel = this._title;
|
||||
|
||||
this.tree = this._register(this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'SqlCustomView', this.treeContainer, new CustomTreeDelegate(), [renderer],
|
||||
this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer],
|
||||
dataSource, {
|
||||
identityProvider: new CustomViewIdentityProvider(),
|
||||
identityProvider: new TreeViewIdentityProvider(),
|
||||
accessibilityProvider: {
|
||||
getAriaLabel(element: ITreeItem): string {
|
||||
return element.label ? element.label.label : '';
|
||||
return element.tooltip ? element.tooltip : element.label ? element.label.label : '';
|
||||
},
|
||||
getWidgetAriaLabel: () => this.title
|
||||
getWidgetAriaLabel(): string {
|
||||
return widgetAriaLabel;
|
||||
}
|
||||
},
|
||||
keyboardNavigationLabelProvider: {
|
||||
getKeyboardNavigationLabel: (item: ITreeItem) => {
|
||||
@@ -429,15 +456,22 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
|
||||
},
|
||||
multipleSelectionSupport: this.canSelectMany,
|
||||
overrideStyles: {
|
||||
listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND
|
||||
}
|
||||
}) as WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>);
|
||||
aligner.tree = this.tree;
|
||||
const actionRunner = new MultipleSelectionActionRunner(() => this.tree!.getSelection());
|
||||
const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection());
|
||||
renderer.actionRunner = actionRunner;
|
||||
|
||||
this.tree.contextKeyService.createKey<boolean>(this.id, true);
|
||||
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
|
||||
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
|
||||
this._register(this.tree.onDidChangeCollapseState(e => {
|
||||
if (!e.node.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element;
|
||||
if (e.node.collapsed) {
|
||||
this._onDidCollapseItem.fire(element);
|
||||
@@ -446,18 +480,18 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
}));
|
||||
// Update resource context based on focused element
|
||||
this._register(this.tree.onDidChangeFocus(e => {
|
||||
this._register(this.tree.onDidChangeFocus(e => { // tracked change
|
||||
this.nodeContext.set({ node: e.elements[0], viewId: this.id });
|
||||
}));
|
||||
this.tree.setInput(this.root).then(() => this.updateContentAreas());
|
||||
|
||||
const customTreeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false });
|
||||
this._register(customTreeNavigator);
|
||||
this._register(customTreeNavigator.onDidOpenResource(e => {
|
||||
const treeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false });
|
||||
this._register(treeNavigator);
|
||||
this._register(treeNavigator.onDidOpenResource(e => {
|
||||
if (!e.browserEvent) {
|
||||
return;
|
||||
}
|
||||
const selection = this.tree.getSelection();
|
||||
const selection = this.tree!.getSelection();
|
||||
if ((selection.length === 1) && selection[0].command) {
|
||||
this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || []));
|
||||
}
|
||||
@@ -474,7 +508,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.tree.setFocus([node]);
|
||||
this.tree!.setFocus([node]);
|
||||
const actions = treeMenus.getResourceContextActions(node);
|
||||
if (!actions.length) {
|
||||
return;
|
||||
@@ -494,17 +528,17 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
this.tree.domFocus();
|
||||
this.tree!.domFocus();
|
||||
}
|
||||
},
|
||||
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.id, $treeItemHandle: node.handle, $treeItem: node, $treeContainerId: this.treeContainer.id }),
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.id, $treeItemHandle: node.handle }),
|
||||
|
||||
actionRunner
|
||||
});
|
||||
}
|
||||
|
||||
private updateMessage(): void {
|
||||
protected updateMessage(): void {
|
||||
if (this._message) {
|
||||
this.showMessage(this._message);
|
||||
} else if (!this.dataProvider) {
|
||||
@@ -515,43 +549,36 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
this.updateContentAreas();
|
||||
}
|
||||
|
||||
private showMessage(message: string | IMarkdownString): void {
|
||||
private showMessage(message: string): void {
|
||||
DOM.removeClass(this.messageElement, 'hide');
|
||||
if (this._messageValue !== message) {
|
||||
this.resetMessageElement();
|
||||
this._messageValue = message;
|
||||
if (isString(this._messageValue)) {
|
||||
this.messageElement.textContent = this._messageValue;
|
||||
} else {
|
||||
this.markdownResult = this.markdownRenderer.render(this._messageValue);
|
||||
DOM.append(this.messageElement, this.markdownResult.element);
|
||||
}
|
||||
this.layout(this._size);
|
||||
this.resetMessageElement();
|
||||
this._messageValue = message;
|
||||
if (!isFalsyOrWhitespace(this._message)) {
|
||||
this.messageElement.textContent = this._messageValue;
|
||||
}
|
||||
this.layout(this._height, this._width);
|
||||
}
|
||||
|
||||
private hideMessage(): void {
|
||||
this.resetMessageElement();
|
||||
DOM.addClass(this.messageElement, 'hide');
|
||||
this.layout(this._size);
|
||||
this.layout(this._height, this._width);
|
||||
}
|
||||
|
||||
private resetMessageElement(): void {
|
||||
if (this.markdownResult) {
|
||||
this.markdownResult.dispose();
|
||||
this.markdownResult = null;
|
||||
}
|
||||
DOM.clearNode(this.messageElement);
|
||||
}
|
||||
|
||||
private _size: number;
|
||||
layout(size: number) {
|
||||
if (size) {
|
||||
this._size = size;
|
||||
const treeSize = size - DOM.getTotalHeight(this.messageElement);
|
||||
this.treeContainer.style.height = treeSize + 'px';
|
||||
private _height: number = 0;
|
||||
private _width: number = 0;
|
||||
layout(height: number, width: number) {
|
||||
if (height && width) {
|
||||
this._height = height;
|
||||
this._width = width;
|
||||
const treeHeight = height - DOM.getTotalHeight(this.messageElement);
|
||||
this.treeContainer.style.height = treeHeight + 'px';
|
||||
if (this.tree) {
|
||||
this.tree.layout(treeSize);
|
||||
this.tree.layout(treeHeight, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,8 +592,11 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
return 0;
|
||||
}
|
||||
|
||||
refresh(elements?: ITreeItem[]): Promise<void> {
|
||||
async refresh(elements?: ITreeItem[]): Promise<void> {
|
||||
if (this.dataProvider && this.tree) {
|
||||
if (this.refreshing) {
|
||||
await Event.toPromise(this._onDidCompleteRefresh.event);
|
||||
}
|
||||
if (!elements) {
|
||||
elements = [this.root];
|
||||
// remove all waiting elements to refresh if root is asked to refresh
|
||||
@@ -591,24 +621,17 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
collapse(element: ITreeItem): boolean {
|
||||
if (this.tree) {
|
||||
return this.tree.collapse(element);
|
||||
}
|
||||
return Promise.arguments(null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
|
||||
if (this.tree) {
|
||||
const tree = this.tree;
|
||||
if (tree) {
|
||||
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
||||
await Promise.all(itemOrItems.map(element => {
|
||||
return this.tree.expand(element, false);
|
||||
return tree.expand(element, false);
|
||||
}));
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
setSelection(items: ITreeItem[]): void {
|
||||
@@ -624,34 +647,23 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
}
|
||||
|
||||
reveal(item: ITreeItem): Promise<void> {
|
||||
async reveal(item: ITreeItem): Promise<void> {
|
||||
if (this.tree) {
|
||||
return Promise.resolve(this.tree.reveal(item));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.createTree();
|
||||
this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||
.then(() => timeout(2000))
|
||||
.then(() => {
|
||||
this.updateMessage();
|
||||
});
|
||||
this.activated = true;
|
||||
return this.tree.reveal(item);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshing: boolean = false;
|
||||
private async doRefresh(elements: ITreeItem[]): Promise<void> {
|
||||
if (this.tree) {
|
||||
const tree = this.tree;
|
||||
if (tree && this.visible) {
|
||||
this.refreshing = true;
|
||||
await Promise.all(elements.map(element => this.tree.updateChildren(element, true)));
|
||||
await Promise.all(elements.map(element => tree.updateChildren(element, true, true)));
|
||||
this.refreshing = false;
|
||||
this._onDidCompleteRefresh.fire();
|
||||
this.updateContentAreas();
|
||||
if (this.focused) {
|
||||
this.focus();
|
||||
this.focus(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -669,13 +681,13 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
}
|
||||
}
|
||||
|
||||
class CustomViewIdentityProvider implements IIdentityProvider<ITreeItem> {
|
||||
class TreeViewIdentityProvider implements IIdentityProvider<ITreeItem> {
|
||||
getId(element: ITreeItem): { toString(): string; } {
|
||||
return element.handle;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTreeDelegate implements IListVirtualDelegate<ITreeItem> {
|
||||
class TreeViewDelegate implements IListVirtualDelegate<ITreeItem> {
|
||||
|
||||
getHeight(element: ITreeItem): number {
|
||||
return TreeRenderer.ITEM_HEIGHT;
|
||||
@@ -704,7 +716,7 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
|
||||
}
|
||||
|
||||
async getChildren(node: ITreeItem): Promise<any[]> {
|
||||
if (node.childProvider) {
|
||||
if (node.childProvider) { // tracked change
|
||||
try {
|
||||
return await this.withProgress(this.objectExplorerService.getChildren(node, this.id));
|
||||
} catch (err) {
|
||||
@@ -728,19 +740,18 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// todo@joh,sandy make this proper and contributable from extensions
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight);
|
||||
if (findMatchHighlightColor) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
|
||||
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
|
||||
const matchBackgroundColor = theme.getColor(listFilterMatchHighlight);
|
||||
if (matchBackgroundColor) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
|
||||
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
|
||||
}
|
||||
const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder);
|
||||
if (findMatchHighlightColorBorder) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
|
||||
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
|
||||
const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder);
|
||||
if (matchBorderColor) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
|
||||
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
@@ -776,7 +787,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
private labels: ResourceLabels,
|
||||
private actionViewItemProvider: IActionViewItemProvider,
|
||||
private aligner: Aligner,
|
||||
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILabelService private readonly labelService: ILabelService
|
||||
) {
|
||||
@@ -812,6 +823,23 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined;
|
||||
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined;
|
||||
const label = treeItemLabel ? treeItemLabel.label : undefined;
|
||||
const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => {
|
||||
if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) {
|
||||
return ({ start: 0, end: 0 });
|
||||
}
|
||||
if (start < 0) {
|
||||
start = label.length + start;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = label.length + end;
|
||||
}
|
||||
if (start > end) {
|
||||
const swap = start;
|
||||
start = end;
|
||||
end = swap;
|
||||
}
|
||||
return ({ start, end });
|
||||
}) : undefined;
|
||||
const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
const iconUrl = icon ? URI.revive(icon) : null;
|
||||
const title = node.tooltip ? node.tooltip : resource ? undefined : label;
|
||||
@@ -820,18 +848,30 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
// reset
|
||||
templateData.actionBar.clear();
|
||||
|
||||
if (resource || node.themeIcon) {
|
||||
if (resource || this.isFileKindThemeIcon(node.themeIcon)) {
|
||||
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
||||
templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) });
|
||||
templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
|
||||
} else {
|
||||
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) });
|
||||
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
|
||||
}
|
||||
|
||||
templateData.icon.title = title ? title : '';
|
||||
|
||||
if (iconUrl || sqlIcon) {
|
||||
DOM.toggleClass(templateData.icon, sqlIcon, !!sqlIcon); // tracked change
|
||||
DOM.toggleClass(templateData.icon, 'icon', !!sqlIcon);
|
||||
templateData.icon.className = 'custom-view-tree-node-item-icon';
|
||||
templateData.icon.style.backgroundImage = iconUrl ? DOM.asCSSUrl(iconUrl) : '';
|
||||
|
||||
} else {
|
||||
let iconClass: string | undefined;
|
||||
if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) {
|
||||
iconClass = ThemeIcon.asClassName(node.themeIcon);
|
||||
}
|
||||
templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : '';
|
||||
templateData.icon.style.backgroundImage = '';
|
||||
}
|
||||
|
||||
templateData.icon.className = '';
|
||||
templateData.icon.style.backgroundImage = iconUrl ? `url('${DOM.asDomUri(iconUrl).toString(true)}')` : '';
|
||||
DOM.toggleClass(templateData.icon, sqlIcon, !!sqlIcon);
|
||||
DOM.toggleClass(templateData.icon, 'icon', !!sqlIcon);
|
||||
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl || !!sqlIcon);
|
||||
templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
|
||||
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
||||
if (this._actionRunner) {
|
||||
@@ -845,6 +885,14 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
DOM.toggleClass(container.parentElement!, 'align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
|
||||
}
|
||||
|
||||
private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean {
|
||||
if (icon) {
|
||||
return icon.id === FileThemeIcon.id || icon.id === FolderThemeIcon.id;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private getFileKind(node: ITreeItem): FileKind {
|
||||
if (node.themeIcon) {
|
||||
switch (node.themeIcon.id) {
|
||||
@@ -869,9 +917,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
|
||||
}
|
||||
|
||||
class Aligner extends Disposable {
|
||||
private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>;
|
||||
private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> | undefined;
|
||||
|
||||
constructor(private themeService: IWorkbenchThemeService) {
|
||||
constructor(private themeService: IThemeService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -887,11 +935,15 @@ class Aligner extends Disposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
|
||||
if (this.hasIcon(parent)) {
|
||||
if (this._tree) {
|
||||
const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
|
||||
if (this.hasIcon(parent)) {
|
||||
return false;
|
||||
}
|
||||
return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
|
||||
}
|
||||
|
||||
private hasIcon(node: ITreeItem): boolean {
|
||||
@@ -913,22 +965,32 @@ class Aligner extends Disposable {
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: (() => ITreeItem[])) {
|
||||
constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) {
|
||||
super();
|
||||
this._register(this.onDidRun(e => {
|
||||
if (e.error) {
|
||||
notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: TreeViewItemHandleArg): Promise<any> {
|
||||
runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> {
|
||||
const selection = this.getSelectedResources();
|
||||
let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined;
|
||||
let actionInSelected: boolean = false;
|
||||
if (selection.length > 1) {
|
||||
selectionHandleArgs = [];
|
||||
selection.forEach(selected => {
|
||||
if (selected.handle !== context.$treeItemHandle) {
|
||||
selectionHandleArgs!.push({ $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle });
|
||||
selectionHandleArgs = selection.map(selected => {
|
||||
if (selected.handle === context.$treeItemHandle) {
|
||||
actionInSelected = true;
|
||||
}
|
||||
return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle };
|
||||
});
|
||||
}
|
||||
|
||||
if (!actionInSelected) {
|
||||
selectionHandleArgs = undefined;
|
||||
}
|
||||
|
||||
return action.run(...[context, selectionHandleArgs]);
|
||||
}
|
||||
}
|
||||
@@ -945,14 +1007,14 @@ class TreeMenus extends Disposable implements IDisposable {
|
||||
}
|
||||
|
||||
getResourceActions(element: ITreeItem): IAction[] {
|
||||
return this.mergeActions([
|
||||
return this.mergeActions([ // tracked change
|
||||
this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary,
|
||||
this.getActions(MenuId.DataExplorerContext, { key: 'viewItem', value: element.contextValue }).primary
|
||||
]);
|
||||
}
|
||||
|
||||
getResourceContextActions(element: ITreeItem): IAction[] {
|
||||
return this.mergeActions([
|
||||
return this.mergeActions([ // tracked change
|
||||
this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary,
|
||||
this.getActions(MenuId.DataExplorerContext, { key: 'viewItem', value: element.contextValue }).secondary
|
||||
]);
|
||||
@@ -962,7 +1024,7 @@ class TreeMenus extends Disposable implements IDisposable {
|
||||
return actions.reduce((p, c) => p.concat(...c.filter(a => firstIndex(p, x => x.id === a.id) === -1)), [] as IAction[]);
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
@@ -980,38 +1042,43 @@ class TreeMenus extends Disposable implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
class MarkdownRenderer {
|
||||
export class CustomTreeView extends TreeView {
|
||||
|
||||
private activated: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IOpenerService private readonly _openerService: IOpenerService
|
||||
id: string,
|
||||
title: string,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IProgressService progressService: IProgressService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
) {
|
||||
super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, contextKeyService);
|
||||
}
|
||||
|
||||
private getOptions(disposeables: DisposableStore): MarkdownRenderOptions {
|
||||
return {
|
||||
actionHandler: {
|
||||
callback: (content) => {
|
||||
let uri: URI | undefined;
|
||||
try {
|
||||
uri = URI.parse(content);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (uri && this._openerService) {
|
||||
this._openerService.open(uri).catch(onUnexpectedError);
|
||||
}
|
||||
},
|
||||
disposeables
|
||||
}
|
||||
};
|
||||
setVisibility(isVisible: boolean): void {
|
||||
super.setVisibility(isVisible);
|
||||
if (this.visible) {
|
||||
this.activate();
|
||||
}
|
||||
}
|
||||
|
||||
render(markdown: IMarkdownString): IMarkdownRenderResult {
|
||||
const disposeables = new DisposableStore();
|
||||
const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span');
|
||||
return {
|
||||
element,
|
||||
dispose: () => disposeables.dispose()
|
||||
};
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||
.then(() => timeout(2000))
|
||||
.then(() => {
|
||||
this.updateMessage();
|
||||
});
|
||||
this.activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export interface ITreeItem extends vsITreeItem {
|
||||
|
||||
export interface ITreeView extends vsITreeView {
|
||||
|
||||
collapse(itemOrItems: ITreeItem): boolean;
|
||||
collapse(element: ITreeItem): boolean
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -91,13 +91,13 @@ export class Insight {
|
||||
|
||||
private findctor(type: ChartType | InsightType): IInsightCtor {
|
||||
if (find(Graph.types, x => x === type as ChartType)) {
|
||||
return Graph;
|
||||
return Graph as IInsightCtor;
|
||||
} else if (find(ImageInsight.types, x => x === type as InsightType)) {
|
||||
return ImageInsight;
|
||||
return ImageInsight as IInsightCtor;
|
||||
} else if (find(TableInsight.types, x => x === type as InsightType)) {
|
||||
return TableInsight;
|
||||
return TableInsight as IInsightCtor;
|
||||
} else if (find(CountInsight.types, x => x === type as InsightType)) {
|
||||
return CountInsight;
|
||||
return CountInsight as IInsightCtor;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { mixin } from 'sql/base/common/objects';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IInsightOptions, InsightType, ChartType } from 'sql/workbench/contrib/charts/common/interfaces';
|
||||
import { IInsightData } from 'sql/platform/dashboard/browser/insightRegistry';
|
||||
import { BrandedService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IPointDataSet {
|
||||
data: Array<{ x: number | string, y: number }>;
|
||||
@@ -42,6 +43,6 @@ export interface IInsight {
|
||||
}
|
||||
|
||||
export interface IInsightCtor {
|
||||
new(container: HTMLElement, options: IInsightOptions, ...services: { _serviceBrand: undefined; }[]): IInsight;
|
||||
new <Services extends BrandedService[]>(container: HTMLElement, options: IInsightOptions, ...services: Services): IInsight;
|
||||
readonly types: Array<InsightType | ChartType>;
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
dashboardHelper.filterConfigs
|
||||
];
|
||||
|
||||
private readonly _gridModifiers: Array<(item: Array<WidgetConfig>, originalConfig: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
private readonly _gridModifiers: Array<(item: Array<WidgetConfig>, originalConfig?: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
dashboardHelper.validateGridConfig
|
||||
];
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
|
||||
import { WidgetConfig } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/browser/insightRegistry';
|
||||
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
|
||||
import { DashboardServiceInterface } from 'sql/workbench/contrib/dashboard/browser/services/dashboardServiceInterface.service';
|
||||
import { WIDGETS_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution';
|
||||
import { WEBVIEW_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWebviewContainer.contribution';
|
||||
@@ -111,7 +110,7 @@ export function addProvider<T extends { connectionManagementService: SingleConne
|
||||
* Adds the edition to the passed widgets and returns the new widgets
|
||||
* @param widgets Array of widgets to add edition onto
|
||||
*/
|
||||
export function addEdition<T extends { connectionManagementService: SingleConnectionManagementService }>(config: WidgetConfig[], collection: DashboardServiceInterface): Array<WidgetConfig> {
|
||||
export function addEdition<T extends { connectionManagementService: SingleConnectionManagementService }>(config: WidgetConfig[], collection: T): Array<WidgetConfig> {
|
||||
const connectionInfo: ConnectionManagementInfo = collection.connectionManagementService.connectionInfo;
|
||||
if (connectionInfo.serverInfo) {
|
||||
const edition = connectionInfo.serverInfo.engineEditionId;
|
||||
|
||||
@@ -107,7 +107,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
|
||||
return this.dashboardService.scopedContextKeyService;
|
||||
}
|
||||
|
||||
private readonly _gridModifiers: Array<(item: Array<WidgetConfig>, originalConfig: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
private readonly _gridModifiers: Array<(item: Array<WidgetConfig>, originalConfig?: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
dashboardHelper.validateGridConfig
|
||||
];
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
|
||||
when: ContextKeyExpr.or(ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.NodeType.isEqualTo('Server')), ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.NodeType.isEqualTo('Database')))
|
||||
});
|
||||
|
||||
const dashboardEditorDescriptor = new EditorDescriptor(
|
||||
const dashboardEditorDescriptor = EditorDescriptor.create(
|
||||
DashboardEditor,
|
||||
DashboardEditor.ID,
|
||||
localize('dashboard.editor.label', "Dashboard")
|
||||
|
||||
@@ -7,16 +7,17 @@ import { localize } from 'vs/nls';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { IExtensionPoint, ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
import { CustomTreeViewPane, CustomTreeView } from 'sql/workbench/browser/parts/views/customView';
|
||||
import { CustomTreeView, TreeViewPane } from 'sql/workbench/browser/parts/views/treeView';
|
||||
import { VIEWLET_ID } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ICustomViewDescriptor } from 'vs/workbench/api/browser/viewsExtensionPoint';
|
||||
|
||||
interface IUserFriendlyViewDescriptor {
|
||||
id: string;
|
||||
@@ -103,14 +104,15 @@ export class DataExplorerContainerExtensionHandler implements IWorkbenchContribu
|
||||
return null;
|
||||
}
|
||||
|
||||
const viewDescriptor = <ITreeViewDescriptor>{
|
||||
const viewDescriptor = <ICustomViewDescriptor>{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
ctorDescriptor: new SyncDescriptor(CustomTreeViewPane),
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.deserialize(item.when),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
|
||||
collapsed: this.showCollapsed(container),
|
||||
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name, container),
|
||||
extensionId: extension.description.identifier,
|
||||
originalContainerId: entry.key
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
|
||||
// Editor
|
||||
const editDataEditorDescriptor = new EditorDescriptor(
|
||||
const editDataEditorDescriptor = EditorDescriptor.create(
|
||||
EditDataEditor,
|
||||
EditDataEditor.ID,
|
||||
'EditData'
|
||||
@@ -22,7 +22,7 @@ Registry.as<IEditorRegistry>(Extensions.Editors)
|
||||
.registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]);
|
||||
|
||||
// Editor
|
||||
const editDataResultsEditorDescriptor = new EditorDescriptor(
|
||||
const editDataResultsEditorDescriptor = EditorDescriptor.create(
|
||||
EditDataResultsEditor,
|
||||
EditDataResultsEditor.ID,
|
||||
'EditDataResults'
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
|
||||
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel';
|
||||
import { IEditorDescriptorService } from 'sql/workbench/services/queryEditor/browser/editorDescriptorService';
|
||||
import {
|
||||
@@ -321,7 +321,7 @@ export class EditDataEditor extends BaseEditor {
|
||||
// Create QueryTaskbar
|
||||
this._taskbarContainer = DOM.append(parentElement, DOM.$('div'));
|
||||
this._taskbar = new Taskbar(this._taskbarContainer, {
|
||||
actionViewItemProvider: (action: Action) => this._getChangeMaxRowsAction(action)
|
||||
actionViewItemProvider: (action: IAction) => this._getChangeMaxRowsAction(action)
|
||||
});
|
||||
|
||||
// Create Actions for the toolbar
|
||||
@@ -352,7 +352,7 @@ export class EditDataEditor extends BaseEditor {
|
||||
/**
|
||||
* Gets the IActionItem for the list of row number drop down
|
||||
*/
|
||||
private _getChangeMaxRowsAction(action: Action): IActionViewItem {
|
||||
private _getChangeMaxRowsAction(action: IAction): IActionViewItem {
|
||||
let actionID = ChangeMaxRowsAction.ID;
|
||||
if (action.id === actionID) {
|
||||
if (!this._changeMaxRowsActionItem) {
|
||||
|
||||
@@ -25,8 +25,8 @@ export class ShowRecommendedExtensionsByScenarioAction extends Action {
|
||||
|
||||
run(): Promise<void> {
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer())
|
||||
.then((viewlet: IExtensionsViewPaneContainer) => {
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@' + this.scenarioType);
|
||||
viewlet.focus();
|
||||
});
|
||||
@@ -51,8 +51,8 @@ export class InstallRecommendedExtensionsByScenarioAction extends Action {
|
||||
run(): Promise<any> {
|
||||
if (!this.recommendations.length) { return Promise.resolve(); }
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer())
|
||||
.then((viewlet: IExtensionsViewPaneContainer) => {
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@' + this.scenarioType);
|
||||
viewlet.focus();
|
||||
const names = this.recommendations.map(({ extensionId }) => extensionId);
|
||||
|
||||
@@ -57,7 +57,7 @@ Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.Language
|
||||
.registerLanguageAssociation(NotebookEditorInputAssociation.languages, NotebookEditorInputAssociation);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(new EditorDescriptor(NotebookEditor, NotebookEditor.ID, localize('notebookEditor.name', "Notebook Editor")), [new SyncDescriptor(UntitledNotebookInput), new SyncDescriptor(FileNotebookInput)]);
|
||||
.registerEditor(EditorDescriptor.create(NotebookEditor, NotebookEditor.ID, localize('notebookEditor.name', "Notebook Editor")), [new SyncDescriptor(UntitledNotebookInput), new SyncDescriptor(FileNotebookInput)]);
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(NotebookThemingContribution, LifecyclePhase.Restored);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInp
|
||||
import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor';
|
||||
import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate, EngineType, PROFILER_FILTER_SETTINGS } from 'sql/workbench/services/profiler/browser/interfaces';
|
||||
|
||||
const profilerDescriptor = new EditorDescriptor(
|
||||
const profilerDescriptor = EditorDescriptor.create(
|
||||
ProfilerEditor,
|
||||
ProfilerEditor.ID,
|
||||
'ProfilerEditor'
|
||||
|
||||
@@ -395,7 +395,7 @@ export class ProfilerEditor extends BaseEditor {
|
||||
this._detailTable.updateRowCount();
|
||||
});
|
||||
|
||||
const detailTableCopyKeybind = new CopyKeybind();
|
||||
const detailTableCopyKeybind = new CopyKeybind<IDetailData>();
|
||||
detailTableCopyKeybind.onCopy((ranges: Slick.Range[]) => {
|
||||
// we always only get 1 item in the ranges
|
||||
if (ranges && ranges.length === 1) {
|
||||
|
||||
@@ -323,7 +323,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
|
||||
private table: Table<T>;
|
||||
private actionBar: ActionBar;
|
||||
private container = document.createElement('div');
|
||||
private selectionModel = new CellSelectionModel();
|
||||
private selectionModel = new CellSelectionModel<T>();
|
||||
private styles: ITableStyles;
|
||||
private currentHeight: number;
|
||||
private dataProvider: AsyncDataProvider<T>;
|
||||
@@ -461,7 +461,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
|
||||
this.renderGridDataRowsRange(startIndex, count);
|
||||
});
|
||||
this.rowNumberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
|
||||
let copyHandler = new CopyKeybind();
|
||||
let copyHandler = new CopyKeybind<T>();
|
||||
copyHandler.onCopy(e => {
|
||||
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext());
|
||||
});
|
||||
|
||||
@@ -57,10 +57,10 @@ Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.Language
|
||||
.registerLanguageAssociation(QueryEditorLanguageAssociation.languages, QueryEditorLanguageAssociation, QueryEditorLanguageAssociation.isDefault);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(new EditorDescriptor(QueryResultsEditor, QueryResultsEditor.ID, localize('queryResultsEditor.name', "Query Results")), [new SyncDescriptor(QueryResultsInput)]);
|
||||
.registerEditor(EditorDescriptor.create(QueryResultsEditor, QueryResultsEditor.ID, localize('queryResultsEditor.name', "Query Results")), [new SyncDescriptor(QueryResultsInput)]);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(new EditorDescriptor(QueryEditor, QueryEditor.ID, localize('queryEditor.name', "Query Editor")), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]);
|
||||
.registerEditor(EditorDescriptor.create(QueryEditor, QueryEditor.ID, localize('queryEditor.name', "Query Editor")), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]);
|
||||
|
||||
const actionRegistry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ISelectionData } from 'azdata';
|
||||
import { Action, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { IActionViewItem, IAction } from 'vs/base/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
@@ -171,7 +171,7 @@ export class QueryEditor extends BaseEditor {
|
||||
// Create QueryTaskbar
|
||||
let taskbarContainer = DOM.append(parentElement, DOM.$('div'));
|
||||
this.taskbar = this._register(new Taskbar(taskbarContainer, {
|
||||
actionViewItemProvider: (action: Action) => this._getActionItemForAction(action),
|
||||
actionViewItemProvider: action => this._getActionItemForAction(action),
|
||||
}));
|
||||
|
||||
// Create Actions for the toolbar
|
||||
@@ -236,7 +236,7 @@ export class QueryEditor extends BaseEditor {
|
||||
* Gets the IActionItem for the List Databases dropdown if provided the associated Action.
|
||||
* Otherwise returns null.
|
||||
*/
|
||||
private _getActionItemForAction(action: Action): IActionViewItem {
|
||||
private _getActionItemForAction(action: IAction): IActionViewItem {
|
||||
if (action.id === actions.ListDatabasesAction.ID) {
|
||||
return this.listDatabasesActionItem;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensio
|
||||
|
||||
// Query Plan editor registration
|
||||
|
||||
const queryPlanEditorDescriptor = new EditorDescriptor(
|
||||
const queryPlanEditorDescriptor = EditorDescriptor.create(
|
||||
QueryPlanEditor,
|
||||
QueryPlanEditor.ID,
|
||||
'QueryPlan'
|
||||
|
||||
@@ -48,7 +48,7 @@ export class StatusUpdater extends lifecycle.Disposable implements ext.IWorkbenc
|
||||
lifecycle.dispose(this.badgeHandle);
|
||||
let numOfInProgressTask: number = this.taskService.getNumberOfInProgressTasks();
|
||||
let badge: NumberBadge = new NumberBadge(numOfInProgressTask, n => localize('inProgressTasksChangesBadge', "{0} in progress tasks", n));
|
||||
this.badgeHandle = this.activityBarService.showActivity(TASKS_CONTAINER_ID, badge, 'taskhistory-viewlet-label');
|
||||
this.badgeHandle = this.activityBarService.showViewContainerActivity(TASKS_CONTAINER_ID, { badge, clazz: 'taskhistory-viewlet-label' });
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
|
||||
@@ -46,6 +46,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { addStandardDisposableListener, EventHelper } from 'vs/base/browser/dom';
|
||||
|
||||
const configurationKey = 'workbench.startupEditor';
|
||||
const oldConfigurationKey = 'workbench.welcome.enabled';
|
||||
@@ -321,23 +322,21 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
|
||||
private createWidePreviewToolTip() {
|
||||
const previewLink = document.querySelector('#tool_tip_container_wide');
|
||||
const tooltip = document.querySelector('#tooltip_text_wide');
|
||||
const previewLink = document.querySelector('#tool_tip_container_wide') as HTMLElement;
|
||||
const tooltip = document.querySelector('#tooltip_text_wide') as HTMLElement;
|
||||
const previewModalBody = document.querySelector('.preview_tooltip_body') as HTMLElement;
|
||||
const previewModalHeader = document.querySelector('.preview_tooltip_header') as HTMLElement;
|
||||
|
||||
previewLink.addEventListener('mouseover', () => {
|
||||
addStandardDisposableListener(previewLink, 'mouseover', () => {
|
||||
tooltip.setAttribute('aria-hidden', 'true');
|
||||
tooltip.classList.toggle('show');
|
||||
});
|
||||
previewLink.addEventListener('mouseout', () => {
|
||||
addStandardDisposableListener(previewLink, 'mouseout', () => {
|
||||
tooltip.setAttribute('aria-hidden', 'false');
|
||||
tooltip.classList.remove('show');
|
||||
});
|
||||
|
||||
previewLink.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
|
||||
addStandardDisposableListener(previewLink, 'keydown', event => {
|
||||
if (event.equals(KeyCode.Escape)) {
|
||||
if (tooltip.classList.contains('show')) {
|
||||
tooltip.setAttribute('aria-hidden', 'true');
|
||||
@@ -351,9 +350,7 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
tooltip.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
|
||||
addStandardDisposableListener(tooltip, 'keydown', event => {
|
||||
if (event.equals(KeyCode.Escape)) {
|
||||
if (tooltip.classList.contains('show')) {
|
||||
tooltip.setAttribute('aria-hidden', 'true');
|
||||
@@ -361,8 +358,8 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
}
|
||||
else if (event.equals(KeyCode.Tab)) {
|
||||
e.preventDefault();
|
||||
if (e.target === previewModalBody) {
|
||||
EventHelper.stop(event);
|
||||
if (event.target === previewModalBody) {
|
||||
previewModalHeader.focus();
|
||||
} else {
|
||||
previewModalBody.focus();
|
||||
@@ -381,15 +378,14 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
|
||||
private createDropDown() {
|
||||
const dropdownBtn = document.querySelector('#dropdown_btn');
|
||||
const dropdownBtn = document.querySelector('#dropdown_btn') as HTMLElement;
|
||||
const dropdown = document.querySelector('#dropdown') as HTMLInputElement;
|
||||
|
||||
dropdownBtn.addEventListener('click', () => {
|
||||
addStandardDisposableListener(dropdownBtn, 'click', () => {
|
||||
dropdown.classList.toggle('show');
|
||||
});
|
||||
|
||||
dropdownBtn.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
addStandardDisposableListener(dropdownBtn, 'keydown', event => {
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
const dropdownFirstElement = document.querySelector('#dropdown').firstElementChild.children[0] as HTMLInputElement;
|
||||
dropdown.classList.toggle('show');
|
||||
@@ -397,8 +393,7 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
addStandardDisposableListener(dropdown, 'keydown', event => {
|
||||
if (event.equals(KeyCode.Escape)) {
|
||||
if (dropdown.classList.contains('show')) {
|
||||
dropdown.classList.remove('show');
|
||||
@@ -427,16 +422,15 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.addEventListener('keydown', function (e: KeyboardEvent) {
|
||||
addStandardDisposableListener(dropdown, 'keydown', event => {
|
||||
const dropdownLastElement = document.querySelector('#dropdown').lastElementChild.children[0] as HTMLInputElement;
|
||||
const dropdownFirstElement = document.querySelector('#dropdown').firstElementChild.children[0] as HTMLInputElement;
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Tab)) {
|
||||
e.preventDefault();
|
||||
EventHelper.stop(event);
|
||||
return;
|
||||
}
|
||||
else if (event.equals(KeyCode.UpArrow) || event.equals(KeyCode.LeftArrow)) {
|
||||
if (e.target === dropdownFirstElement) {
|
||||
if (event.target === dropdownFirstElement) {
|
||||
dropdownLastElement.focus();
|
||||
} else {
|
||||
const movePrev = <HTMLElement>document.querySelector('.move:focus').parentElement.previousElementSibling.children[0] as HTMLElement;
|
||||
@@ -444,7 +438,7 @@ class WelcomePage extends Disposable {
|
||||
}
|
||||
}
|
||||
else if (event.equals(KeyCode.DownArrow) || event.equals(KeyCode.RightArrow)) {
|
||||
if (e.target === dropdownLastElement) {
|
||||
if (event.target === dropdownLastElement) {
|
||||
dropdownFirstElement.focus();
|
||||
} else {
|
||||
const moveNext = <HTMLElement>document.querySelector('.move:focus').parentElement.nextElementSibling.children[0] as HTMLElement;
|
||||
|
||||
@@ -52,16 +52,16 @@ const labelDisplay = nls.localize("insights.item", "Item");
|
||||
const valueDisplay = nls.localize("insights.value", "Value");
|
||||
const iconClass = 'codicon';
|
||||
|
||||
class InsightTableView<T> extends ViewPane {
|
||||
private _table: Table<T>;
|
||||
public get table(): Table<T> {
|
||||
class InsightTableView extends ViewPane {
|
||||
private _table: Table<ListResource>;
|
||||
public get table(): Table<ListResource> {
|
||||
return this._table;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private columns: Slick.Column<T>[],
|
||||
private data: IDisposableDataProvider<T> | Array<T>,
|
||||
private tableOptions: Slick.GridOptions<T>,
|
||||
private columns: Slick.Column<ListResource>[],
|
||||
private data: IDisposableDataProvider<ListResource> | Array<ListResource>,
|
||||
private tableOptions: Slick.GridOptions<ListResource>,
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -220,14 +220,14 @@ export class InsightsDialogView extends Modal {
|
||||
const itemsHeaderTitle = nls.localize("insights.dialog.items", "Items");
|
||||
const itemsDetailHeaderTitle = nls.localize("insights.dialog.itemDetails", "Item Details");
|
||||
|
||||
this._topTableData = new TableDataView();
|
||||
this._bottomTableData = new TableDataView();
|
||||
let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle }) as InsightTableView<ListResource>;
|
||||
this._topTableData = new TableDataView<ListResource>();
|
||||
this._bottomTableData = new TableDataView<ListResource>();
|
||||
let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle });
|
||||
topTableView.render();
|
||||
attachPanelStyler(topTableView, this._themeService);
|
||||
this._topTable = topTableView.table;
|
||||
this._topTable.setSelectionModel(new RowSelectionModel<ListResource>());
|
||||
let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle }) as InsightTableView<ListResource>;
|
||||
let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle });
|
||||
bottomTableView.render();
|
||||
attachPanelStyler(bottomTableView, this._themeService);
|
||||
this._bottomTable = bottomTableView.table;
|
||||
@@ -275,8 +275,8 @@ export class InsightsDialogView extends Modal {
|
||||
this._register(attachTableStyler(this._topTable, this._themeService));
|
||||
this._register(attachTableStyler(this._bottomTable, this._themeService));
|
||||
|
||||
this._topTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
this._topTable.grid.onKeyDown.subscribe(e => {
|
||||
let event = new StandardKeyboardEvent(<unknown>e as KeyboardEvent);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
topTableView.focus();
|
||||
e.stopImmediatePropagation();
|
||||
@@ -286,8 +286,8 @@ export class InsightsDialogView extends Modal {
|
||||
}
|
||||
});
|
||||
|
||||
this._bottomTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
this._bottomTable.grid.onKeyDown.subscribe(e => {
|
||||
let event = new StandardKeyboardEvent(<unknown>e as KeyboardEvent);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
bottomTableView.focus();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
@@ -53,7 +53,7 @@ const languageAssociationRegistry = new class implements ILanguageAssociationReg
|
||||
}
|
||||
}
|
||||
|
||||
registerLanguageAssociation<Services extends BrandedService[]>(languages: string[], contribution: ILanguageAssociationSignature<Services>, isDefault?: boolean): IDisposable {
|
||||
registerLanguageAssociation(languages: string[], contribution: ILanguageAssociationSignature<BrandedService[]>, isDefault?: boolean): IDisposable {
|
||||
for (const language of languages) {
|
||||
this.associationContructors.set(language, contribution);
|
||||
}
|
||||
|
||||
@@ -379,8 +379,8 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
});
|
||||
|
||||
this._restorePlanTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
this._restorePlanTable.grid.onKeyDown.subscribe(e => {
|
||||
let event = new StandardKeyboardEvent(<unknown>e as KeyboardEvent);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
this._destinationRestoreToInputBox.isEnabled() ? this._destinationRestoreToInputBox.focus() : this._databaseDropdown.focus();
|
||||
e.stopImmediatePropagation();
|
||||
@@ -390,8 +390,8 @@ export class RestoreDialog extends Modal {
|
||||
}
|
||||
});
|
||||
|
||||
this._fileListTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
this._fileListTable.grid.onKeyDown.subscribe(e => {
|
||||
let event = new StandardKeyboardEvent(<unknown>e as KeyboardEvent);
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
if ((<InputBox>this._optionsMap[this._relocatedLogFileFolderOption]).isEnabled()) {
|
||||
(<InputBox>this._optionsMap[this._relocatedLogFileFolderOption]).focus();
|
||||
|
||||
Vendored
+1
-1
@@ -1554,7 +1554,7 @@ declare namespace Slick {
|
||||
}
|
||||
|
||||
export interface Formatter<T extends SlickData> {
|
||||
(row: number, cell: number, value: any, columnDef: Column<T>, dataContext: SlickData): string | undefined;
|
||||
(row: number, cell: number, value: any, columnDef: Column<T>, dataContext: T): string | undefined;
|
||||
}
|
||||
|
||||
export module Formatters {
|
||||
|
||||
@@ -209,7 +209,10 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
'iframe': ['allowfullscreen', 'frameborder', 'src'],
|
||||
'img': ['src', 'title', 'alt', 'width', 'height'],
|
||||
'div': ['class', 'data-code'],
|
||||
'span': ['class']
|
||||
'span': ['class'],
|
||||
// https://github.com/microsoft/vscode/issues/95937
|
||||
'th': ['align'],
|
||||
'td': ['align']
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'vs/css!./aria';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
|
||||
// Use a max length since we are inserting the whole msg in the DOM and that can cause browsers to freeze for long messages #94233
|
||||
const MAX_MESSAGE_LENGTH = 20000;
|
||||
let ariaContainer: HTMLElement;
|
||||
let alertContainer: HTMLElement;
|
||||
let statusContainer: HTMLElement;
|
||||
@@ -54,6 +56,9 @@ function insertMessage(target: HTMLElement, msg: string): void {
|
||||
}
|
||||
|
||||
dom.clearNode(target);
|
||||
if (msg.length > MAX_MESSAGE_LENGTH) {
|
||||
msg = msg.substr(0, MAX_MESSAGE_LENGTH);
|
||||
}
|
||||
target.textContent = msg;
|
||||
|
||||
// See https://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
.monaco-text-button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
outline-offset: 2px !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.monaco-text-button:hover {
|
||||
@@ -21,3 +23,8 @@
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-button > .codicon {
|
||||
margin: 0 0.2em;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,12 @@ import { mixin } from 'vs/base/common/objects';
|
||||
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Gesture, EventType } from 'vs/base/browser/touch';
|
||||
import { renderCodicons } from 'vs/base/common/codicons';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
title?: boolean | string;
|
||||
readonly title?: boolean | string;
|
||||
readonly supportCodicons?: boolean;
|
||||
}
|
||||
|
||||
export interface IButtonStyles {
|
||||
@@ -150,7 +153,11 @@ export class Button extends Disposable {
|
||||
if (!DOM.hasClass(this._element, 'monaco-text-button')) {
|
||||
DOM.addClass(this._element, 'monaco-text-button');
|
||||
}
|
||||
this._element.textContent = value;
|
||||
if (this.options.supportCodicons) {
|
||||
this._element.innerHTML = renderCodicons(escape(value));
|
||||
} else {
|
||||
this._element.textContent = value;
|
||||
}
|
||||
this._element.setAttribute('aria-label', value); // {{SQL CARBON EDIT}}
|
||||
if (typeof this.options.title === 'string') {
|
||||
this._element.title = this.options.title;
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
}
|
||||
|
||||
.codicon-animation-spin {
|
||||
animation: codicon-spin 1.5s linear infinite;
|
||||
/* Use steps to throttle FPS to reduce CPU usage */
|
||||
animation: codicon-spin 1.5s steps(30) infinite;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface IListViewOptions<T> {
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
}
|
||||
|
||||
const DefaultOptions = {
|
||||
@@ -74,7 +75,8 @@ const DefaultOptions = {
|
||||
onDragOver() { return false; },
|
||||
drop() { }
|
||||
},
|
||||
horizontalScrolling: false
|
||||
horizontalScrolling: false,
|
||||
transformOptimization: true
|
||||
};
|
||||
|
||||
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
|
||||
@@ -275,7 +277,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
this.rowsContainer = document.createElement('div');
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
|
||||
|
||||
if (options.transformOptimization) {
|
||||
this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
|
||||
}
|
||||
|
||||
this.disposables.add(Gesture.addTarget(this.rowsContainer));
|
||||
|
||||
this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
|
||||
|
||||
@@ -838,6 +838,7 @@ export interface IListOptions<T> {
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
|
||||
@@ -84,7 +84,7 @@ export class Menu extends ActionBar {
|
||||
context: options.context,
|
||||
actionRunner: options.actionRunner,
|
||||
ariaLabel: options.ariaLabel,
|
||||
triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh ? [KeyCode.Space] : [])], keyDown: true }
|
||||
triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true }
|
||||
});
|
||||
|
||||
this.menuElement = menuElement;
|
||||
|
||||
@@ -184,13 +184,18 @@ export abstract class Pane extends Disposable implements IView {
|
||||
}
|
||||
|
||||
this._orientation = orientation;
|
||||
|
||||
if (this.header) {
|
||||
this.updateHeader();
|
||||
}
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.header = $('.pane-header');
|
||||
append(this.element, this.header);
|
||||
this.header.setAttribute('tabindex', '0');
|
||||
this.header.setAttribute('role', 'toolbar');
|
||||
// Use role button so the aria-expanded state gets read https://github.com/microsoft/vscode/issues/95996
|
||||
this.header.setAttribute('role', 'button');
|
||||
this.header.setAttribute('aria-label', this.ariaHeaderLabel);
|
||||
this.renderHeader(this.header);
|
||||
|
||||
|
||||
@@ -61,5 +61,6 @@
|
||||
}
|
||||
|
||||
.monaco-tl-twistie.codicon-tree-item-loading::before {
|
||||
animation: codicon-spin 1.25s linear infinite;
|
||||
/* Use steps to throttle FPS to reduce CPU usage */
|
||||
animation: codicon-spin 1.25s steps(30) infinite;
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ export function anyScore(pattern: string, lowPattern: string, _patternPos: numbe
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
|
||||
export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch[] {
|
||||
export function createMatches(score: undefined | FuzzyScore): IMatch[] {
|
||||
if (typeof score === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
@@ -404,10 +404,10 @@ export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch
|
||||
for (let pos = wordStart; pos < _maxLen; pos++) {
|
||||
if (matches[matches.length - (pos + 1)] === '1') {
|
||||
const last = res[res.length - 1];
|
||||
if (last && last.end === pos + offset) {
|
||||
last.end = pos + offset + 1;
|
||||
if (last && last.end === pos) {
|
||||
last.end = pos + 1;
|
||||
} else {
|
||||
res.push({ start: pos + offset, end: pos + offset + 1 });
|
||||
res.push({ start: pos, end: pos + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters';
|
||||
import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches, matchesStrictPrefix } from 'vs/base/common/filters';
|
||||
import { sep } from 'vs/base/common/path';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
@@ -281,24 +281,24 @@ export type FuzzyScore2 = [number | undefined /* score */, IMatch[]];
|
||||
|
||||
const NO_SCORE2: FuzzyScore2 = [undefined, []];
|
||||
|
||||
export function scoreFuzzy2(target: string, query: IPreparedQuery | IPreparedQueryPiece, patternStart = 0, matchOffset = 0): FuzzyScore2 {
|
||||
export function scoreFuzzy2(target: string, query: IPreparedQuery | IPreparedQueryPiece, patternStart = 0, wordStart = 0): FuzzyScore2 {
|
||||
|
||||
// Score: multiple inputs
|
||||
const preparedQuery = query as IPreparedQuery;
|
||||
if (preparedQuery.values && preparedQuery.values.length > 1) {
|
||||
return doScoreFuzzy2Multiple(target, preparedQuery.values, patternStart, matchOffset);
|
||||
return doScoreFuzzy2Multiple(target, preparedQuery.values, patternStart, wordStart);
|
||||
}
|
||||
|
||||
// Score: single input
|
||||
return doScoreFuzzy2Single(target, query, patternStart, matchOffset);
|
||||
return doScoreFuzzy2Single(target, query, patternStart, wordStart);
|
||||
}
|
||||
|
||||
function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, matchOffset: number): FuzzyScore2 {
|
||||
function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, wordStart: number): FuzzyScore2 {
|
||||
let totalScore = 0;
|
||||
const totalMatches: IMatch[] = [];
|
||||
|
||||
for (const queryPiece of query) {
|
||||
const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, matchOffset);
|
||||
const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, wordStart);
|
||||
if (typeof score !== 'number') {
|
||||
// if a single query value does not match, return with
|
||||
// no score entirely, we require all queries to match
|
||||
@@ -314,13 +314,13 @@ function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], pat
|
||||
return [totalScore, normalizeMatches(totalMatches)];
|
||||
}
|
||||
|
||||
function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, matchOffset: number): FuzzyScore2 {
|
||||
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), 0, true);
|
||||
function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 {
|
||||
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, true);
|
||||
if (!score) {
|
||||
return NO_SCORE2;
|
||||
}
|
||||
|
||||
return [score[0], createFuzzyMatches(score, matchOffset)];
|
||||
return [score[0], createFuzzyMatches(score)];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -370,9 +370,10 @@ export interface IItemAccessor<T> {
|
||||
}
|
||||
|
||||
const PATH_IDENTITY_SCORE = 1 << 18;
|
||||
const LABEL_PREFIX_SCORE = 1 << 17;
|
||||
const LABEL_CAMELCASE_SCORE = 1 << 16;
|
||||
const LABEL_SCORE_THRESHOLD = 1 << 15;
|
||||
const LABEL_PREFIX_SCORE_MATCHCASE = 1 << 17;
|
||||
const LABEL_PREFIX_SCORE_IGNORECASE = 1 << 16;
|
||||
const LABEL_CAMELCASE_SCORE = 1 << 15;
|
||||
const LABEL_SCORE_THRESHOLD = 1 << 14;
|
||||
|
||||
export function scoreItemFuzzy<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: FuzzyScorerCache): IItemScore {
|
||||
if (!item || !query.normalized) {
|
||||
@@ -458,13 +459,14 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined,
|
||||
// Prefer label matches if told so
|
||||
if (preferLabelMatches) {
|
||||
|
||||
// Treat prefix matches on the label second highest
|
||||
const prefixLabelMatch = matchesPrefix(query.normalized, label);
|
||||
if (prefixLabelMatch) {
|
||||
return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch };
|
||||
// Treat prefix matches on the label highest
|
||||
const prefixLabelMatchIgnoreCase = matchesPrefix(query.normalized, label);
|
||||
if (prefixLabelMatchIgnoreCase) {
|
||||
const prefixLabelMatchStrictCase = matchesStrictPrefix(query.normalized, label);
|
||||
return { score: prefixLabelMatchStrictCase ? LABEL_PREFIX_SCORE_MATCHCASE : LABEL_PREFIX_SCORE_IGNORECASE, labelMatch: prefixLabelMatchStrictCase || prefixLabelMatchIgnoreCase };
|
||||
}
|
||||
|
||||
// Treat camelcase matches on the label third highest
|
||||
// Treat camelcase matches on the label second highest
|
||||
const camelcaseLabelMatch = matchesCamelCase(query.normalized, label);
|
||||
if (camelcaseLabelMatch) {
|
||||
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
|
||||
@@ -600,10 +602,10 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
}
|
||||
}
|
||||
|
||||
// 2.) prefer label prefix matches
|
||||
if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) {
|
||||
// 2.) prefer label prefix matches (match case)
|
||||
if (scoreA === LABEL_PREFIX_SCORE_MATCHCASE || scoreB === LABEL_PREFIX_SCORE_MATCHCASE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === LABEL_PREFIX_SCORE ? -1 : 1;
|
||||
return scoreA === LABEL_PREFIX_SCORE_MATCHCASE ? -1 : 1;
|
||||
}
|
||||
|
||||
const labelA = accessor.getItemLabel(itemA) || '';
|
||||
@@ -615,7 +617,22 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
}
|
||||
}
|
||||
|
||||
// 3.) prefer camelcase matches
|
||||
// 3.) prefer label prefix matches (ignore case)
|
||||
if (scoreA === LABEL_PREFIX_SCORE_IGNORECASE || scoreB === LABEL_PREFIX_SCORE_IGNORECASE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === LABEL_PREFIX_SCORE_IGNORECASE ? -1 : 1;
|
||||
}
|
||||
|
||||
const labelA = accessor.getItemLabel(itemA) || '';
|
||||
const labelB = accessor.getItemLabel(itemB) || '';
|
||||
|
||||
// prefer shorter names when both match on label prefix
|
||||
if (labelA.length !== labelB.length) {
|
||||
return labelA.length - labelB.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 4.) prefer camelcase matches
|
||||
if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) {
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1;
|
||||
@@ -636,7 +653,7 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
}
|
||||
}
|
||||
|
||||
// 4.) prefer label scores
|
||||
// 5.) prefer label scores
|
||||
if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) {
|
||||
if (scoreB < LABEL_SCORE_THRESHOLD) {
|
||||
return -1;
|
||||
@@ -647,12 +664,12 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
}
|
||||
}
|
||||
|
||||
// 5.) compare by score
|
||||
// 6.) compare by score
|
||||
if (scoreA !== scoreB) {
|
||||
return scoreA > scoreB ? -1 : 1;
|
||||
}
|
||||
|
||||
// 6.) prefer matches in label over non-label matches
|
||||
// 7.) prefer matches in label over non-label matches
|
||||
const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0;
|
||||
const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0;
|
||||
if (itemAHasLabelMatches && !itemBHasLabelMatches) {
|
||||
@@ -661,14 +678,14 @@ export function compareItemsByFuzzyScore<T>(itemA: T, itemB: T, query: IPrepared
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 7.) scores are identical, prefer more compact matches (label and description)
|
||||
// 8.) scores are identical, prefer more compact matches (label and description)
|
||||
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
|
||||
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
|
||||
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
|
||||
return itemBMatchDistance > itemAMatchDistance ? -1 : 1;
|
||||
}
|
||||
|
||||
// 7.) at this point, scores are identical and match compactness as well
|
||||
// 9.) at this point, scores are identical and match compactness as well
|
||||
// for both items so we start to use the fallback compare
|
||||
return fallbackCompare(itemA, itemB, query, accessor);
|
||||
}
|
||||
|
||||
@@ -53,17 +53,18 @@ export interface IJSONSchema {
|
||||
then?: IJSONSchema;
|
||||
else?: IJSONSchema;
|
||||
|
||||
// VSCode extensions
|
||||
defaultSnippets?: IJSONSchemaSnippet[]; // VSCode extension
|
||||
errorMessage?: string; // VSCode extension
|
||||
patternErrorMessage?: string; // VSCode extension
|
||||
deprecationMessage?: string; // VSCode extension
|
||||
enumDescriptions?: string[]; // VSCode extension
|
||||
markdownEnumDescriptions?: string[]; // VSCode extension
|
||||
markdownDescription?: string; // VSCode extension
|
||||
doNotSuggest?: boolean; // VSCode extension
|
||||
allowComments?: boolean; // VSCode extension
|
||||
allowTrailingCommas?: boolean; // VSCode extension
|
||||
// VS Code extensions
|
||||
defaultSnippets?: IJSONSchemaSnippet[];
|
||||
errorMessage?: string;
|
||||
patternErrorMessage?: string;
|
||||
deprecationMessage?: string;
|
||||
markdownDeprecationMessage?: string;
|
||||
enumDescriptions?: string[];
|
||||
markdownEnumDescriptions?: string[];
|
||||
markdownDescription?: string;
|
||||
doNotSuggest?: boolean;
|
||||
allowComments?: boolean;
|
||||
allowTrailingCommas?: boolean;
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
|
||||
@@ -853,7 +853,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
}
|
||||
dom.toggleClass(this.ui.container, 'hidden-input', hideInput);
|
||||
const visibilities: Visibilities = {
|
||||
title: !!this.title || !!this.step,
|
||||
title: !!this.title || !!this.step || !!this.buttons.length,
|
||||
description: !!this.description,
|
||||
checkAll: this.canSelectMany,
|
||||
inputBox: !hideInput,
|
||||
@@ -1048,7 +1048,12 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
this.ui.setVisibilities({ title: !!this.title || !!this.step, description: !!this.description || !!this.step, inputBox: true, message: true });
|
||||
const visibilities: Visibilities = {
|
||||
title: !!this.title || !!this.step || !!this.buttons.length,
|
||||
description: !!this.description || !!this.step,
|
||||
inputBox: true, message: true
|
||||
};
|
||||
this.ui.setVisibilities(visibilities);
|
||||
super.update();
|
||||
if (this.ui.inputBox.value !== this.value) {
|
||||
this.ui.inputBox.value = this.value;
|
||||
|
||||
@@ -912,6 +912,19 @@ suite('Fuzzy Scorer', () => {
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('compareFilesByScore - prefer case match (bug #96122)', function () {
|
||||
const resourceA = URI.file('lists.php');
|
||||
const resourceB = URI.file('lib/Lists.php');
|
||||
|
||||
let query = 'Lists.php';
|
||||
|
||||
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceB);
|
||||
|
||||
res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor));
|
||||
assert.equal(res[0], resourceB);
|
||||
});
|
||||
|
||||
test('prepareQuery', () => {
|
||||
assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa');
|
||||
assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts');
|
||||
@@ -977,14 +990,14 @@ suite('Fuzzy Scorer', () => {
|
||||
const target = 'HeLlo-World';
|
||||
|
||||
for (const offset of [0, 3]) {
|
||||
let [score, matches] = _doScore2(target, 'HeLlo-World', offset);
|
||||
let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HeLlo-World', offset);
|
||||
|
||||
assert.ok(score);
|
||||
assert.equal(matches.length, 1);
|
||||
assert.equal(matches[0].start, 0 + offset);
|
||||
assert.equal(matches[0].end, target.length + offset);
|
||||
|
||||
[score, matches] = _doScore2(target, 'HW', offset);
|
||||
[score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HW', offset);
|
||||
|
||||
assert.ok(score);
|
||||
assert.equal(matches.length, 2);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
baseUrl: `${window.location.origin}/static/out`,
|
||||
paths: {
|
||||
'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`,
|
||||
'onigasm-umd': `${window.location.origin}/static/remote/web/node_modules/onigasm-umd/release/main`,
|
||||
'vscode-oniguruma': `${window.location.origin}/static/remote/web/node_modules/vscode-oniguruma/release/main`,
|
||||
'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
baseUrl: `${window.location.origin}/static/out`,
|
||||
paths: {
|
||||
'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`,
|
||||
'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`,
|
||||
'vscode-oniguruma': `${window.location.origin}/static/node_modules/vscode-oniguruma/release/main`,
|
||||
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `${window.location.origin}/static/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
|
||||
@@ -341,7 +341,7 @@ export class IssueReporter extends Disposable {
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', product.version, configuration.machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
|
||||
const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
|
||||
|
||||
const telemetryService = instantiationService.createInstance(TelemetryService, config);
|
||||
this._register(telemetryService);
|
||||
@@ -716,7 +716,7 @@ export class IssueReporter extends Disposable {
|
||||
type IssueReporterSearchError = {
|
||||
message: string;
|
||||
};
|
||||
this.telemetryService.publicLog2<IssueReporterSearchError, IssueReporterSearchErrorClassification>('issueReporterSearchError', { message: error.message }, true);
|
||||
this.telemetryService.publicLogError2<IssueReporterSearchError, IssueReporterSearchErrorClassification>('issueReporterSearchError', { message: error.message });
|
||||
}
|
||||
|
||||
private setUpTypes(): void {
|
||||
|
||||
@@ -176,6 +176,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender: combinedAppender(appInsightsAppender, new LogAppender(logService)),
|
||||
commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath),
|
||||
sendErrorTelemetry: true,
|
||||
piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot]
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<!-- {{SQL CARBON EDIT}} @anthonydresser add 'unsafe-eval' under script src; since its required by angular -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote-resource:;">
|
||||
</head>
|
||||
<body class="vs-dark" aria-label="">
|
||||
|
||||
@@ -193,7 +193,7 @@ export class CodeApplication extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553
|
||||
delete (webPreferences as { preloadURL: string | undefined }).preloadURL; // https://github.com/electron/electron/issues/21553
|
||||
|
||||
// Otherwise prevent loading
|
||||
this.logService.error('webContents#web-contents-created: Prevented webview attach');
|
||||
@@ -489,7 +489,7 @@ export class CodeApplication extends Disposable {
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
|
||||
const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId };
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId, sendErrorTelemetry: true };
|
||||
|
||||
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
|
||||
} else {
|
||||
|
||||
@@ -746,11 +746,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
// Config (combination of process.argv and window configuration)
|
||||
const environment = parseArgs(process.argv, OPTIONS);
|
||||
const config = Object.assign(environment, windowConfiguration);
|
||||
const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown };
|
||||
for (const key in config) {
|
||||
const configValue = (config as any)[key];
|
||||
const configValue = config[key];
|
||||
if (configValue === undefined || configValue === null || configValue === '' || configValue === false) {
|
||||
delete (config as any)[key]; // only send over properties that have a true value
|
||||
delete config[key]; // only send over properties that have a true value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -345,6 +345,7 @@ export async function main(argv: ParsedArgs): Promise<void> {
|
||||
|
||||
const config: ITelemetryServiceConfig = {
|
||||
appender: combinedAppender(...appenders),
|
||||
sendErrorTelemetry: false,
|
||||
commonProperties: resolveCommonProperties(product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath),
|
||||
piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot]
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -87,19 +87,28 @@ export class EditorState {
|
||||
/**
|
||||
* A cancellation token source that cancels when the editor changes as expressed
|
||||
* by the provided flags
|
||||
* @param range If provided, changes in position and selection within this range will not trigger cancellation
|
||||
*/
|
||||
export class EditorStateCancellationTokenSource extends EditorKeybindingCancellationTokenSource implements IDisposable {
|
||||
|
||||
private readonly _listener = new DisposableStore();
|
||||
|
||||
constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, parent?: CancellationToken) {
|
||||
constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, range?: IRange, parent?: CancellationToken) {
|
||||
super(editor, parent);
|
||||
|
||||
if (flags & CodeEditorStateFlag.Position) {
|
||||
this._listener.add(editor.onDidChangeCursorPosition(_ => this.cancel()));
|
||||
this._listener.add(editor.onDidChangeCursorPosition(e => {
|
||||
if (!range || !Range.containsPosition(range, e.position)) {
|
||||
this.cancel();
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (flags & CodeEditorStateFlag.Selection) {
|
||||
this._listener.add(editor.onDidChangeCursorSelection(_ => this.cancel()));
|
||||
this._listener.add(editor.onDidChangeCursorSelection(e => {
|
||||
if (!range || !Range.containsRange(range, e.selection)) {
|
||||
this.cancel();
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (flags & CodeEditorStateFlag.Scroll) {
|
||||
this._listener.add(editor.onDidScrollChange(_ => this.cancel()));
|
||||
|
||||
@@ -27,11 +27,6 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark.mac .view-lines,
|
||||
.monaco-editor.hc-black.mac .view-lines {
|
||||
cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text;
|
||||
}
|
||||
|
||||
.monaco-editor .view-line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IConfiguration } from 'vs/editor/common/editorCommon';
|
||||
import { TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { editorCursorForeground, editorOverviewRulerBorder } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
|
||||
import { ViewContext, EditorTheme } from 'vs/editor/common/view/viewContext';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
@@ -60,7 +60,10 @@ class Settings {
|
||||
const minimapOpts = options.get(EditorOption.minimap);
|
||||
const minimapEnabled = minimapOpts.enabled;
|
||||
const minimapSide = minimapOpts.side;
|
||||
const backgroundColor = (minimapEnabled ? TokenizationRegistry.getDefaultBackground() : null);
|
||||
const backgroundColor = minimapEnabled
|
||||
? theme.getColor(editorOverviewRulerBackground) || TokenizationRegistry.getDefaultBackground()
|
||||
: null;
|
||||
|
||||
if (backgroundColor === null || minimapSide === 'left') {
|
||||
this.backgroundColor = null;
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffDiagonalFill } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin';
|
||||
@@ -2262,4 +2262,17 @@ registerThemingParticipant((theme, collector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const diffDiagonalFillColor = theme.getColor(diffDiagonalFill);
|
||||
collector.addRule(`
|
||||
.monaco-editor .diagonal-fill {
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
${diffDiagonalFillColor} 12.5%,
|
||||
#0000 12.5%, #0000 50%,
|
||||
${diffDiagonalFillColor} 50%, ${diffDiagonalFillColor} 62.5%,
|
||||
#0000 62.5%, #0000 100%
|
||||
);
|
||||
background-size: 8px 8px;
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -114,6 +114,7 @@ export class DiffReview extends Disposable {
|
||||
|
||||
this._content = createFastDomNode(document.createElement('div'));
|
||||
this._content.setClassName('diff-review-content');
|
||||
this._content.setAttribute('role', 'code');
|
||||
this.scrollbar = this._register(new DomScrollableElement(this._content.domNode, {}));
|
||||
this.domNode.domNode.appendChild(this.scrollbar.getDomNode());
|
||||
|
||||
@@ -748,7 +749,11 @@ export class DiffReview extends Disposable {
|
||||
let ariaLabel: string = '';
|
||||
switch (type) {
|
||||
case DiffEntryType.Equal:
|
||||
ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine);
|
||||
if (originalLine === modifiedLine) {
|
||||
ariaLabel = nls.localize('unchangedLine', "{0} unchanged line {1}", lineContent, originalLine);
|
||||
} else {
|
||||
ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine);
|
||||
}
|
||||
break;
|
||||
case DiffEntryType.Insert:
|
||||
ariaLabel = nls.localize('insertLine', "+ {0} modified line {1}", lineContent, modifiedLine);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 185 B |
@@ -51,16 +51,6 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.monaco-editor .diagonal-fill {
|
||||
background: url('diagonal-fill.png');
|
||||
}
|
||||
.monaco-editor.vs-dark .diagonal-fill {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.monaco-editor.hc-black .diagonal-fill {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* ---------- Inline Diff ---------- */
|
||||
|
||||
.monaco-editor .view-zones .view-lines .view-line span {
|
||||
|
||||
@@ -103,14 +103,14 @@ export class ShiftCommand implements ICommand {
|
||||
const { tabSize, indentSize, insertSpaces } = this._opts;
|
||||
const shouldIndentEmptyLines = (startLine === endLine);
|
||||
|
||||
// if indenting or outdenting on a whitespace only line
|
||||
if (this._selection.isEmpty()) {
|
||||
if (/^\s*$/.test(model.getLineContent(startLine))) {
|
||||
this._useLastEditRangeForCursorEndPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._opts.useTabStops) {
|
||||
// if indenting or outdenting on a whitespace only line
|
||||
if (this._selection.isEmpty()) {
|
||||
if (/^\s*$/.test(model.getLineContent(startLine))) {
|
||||
this._useLastEditRangeForCursorEndPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
// keep track of previous line's "miss-alignment"
|
||||
let previousLineExtraSpaces = 0, extraSpaces = 0;
|
||||
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++, previousLineExtraSpaces = extraSpaces) {
|
||||
@@ -188,6 +188,11 @@ export class ShiftCommand implements ICommand {
|
||||
}
|
||||
} else {
|
||||
|
||||
// if indenting or outdenting on a whitespace only line
|
||||
if (!this._opts.isUnshift && this._selection.isEmpty() && model.getLineLength(startLine) === 0) {
|
||||
this._useLastEditRangeForCursorEndPosition = true;
|
||||
}
|
||||
|
||||
const oneIndent = (insertSpaces ? cachedStringRepeat(' ', indentSize) : '\t');
|
||||
|
||||
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { EditorTheme } from 'vs/editor/common/view/viewContext';
|
||||
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
@@ -172,6 +171,21 @@ const enum StringOffsetValidationType {
|
||||
SurrogatePairs = 1,
|
||||
}
|
||||
|
||||
type ContinueBracketSearchPredicate = null | (() => boolean);
|
||||
|
||||
class BracketSearchCanceled {
|
||||
public static INSTANCE = new BracketSearchCanceled();
|
||||
_searchCanceledBrand = undefined;
|
||||
private constructor() { }
|
||||
}
|
||||
|
||||
function stripBracketSearchCanceled<T>(result: T | null | BracketSearchCanceled): T | null {
|
||||
if (result instanceof BracketSearchCanceled) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
|
||||
@@ -2014,7 +2028,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._findMatchingBracketUp(data, position);
|
||||
return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, null));
|
||||
}
|
||||
|
||||
public matchBracket(position: IPosition): [Range, Range] | null {
|
||||
@@ -2062,8 +2076,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
// check that we didn't hit a bracket too far away from position
|
||||
if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
|
||||
const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
|
||||
const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]);
|
||||
const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], null);
|
||||
if (r) {
|
||||
if (r instanceof BracketSearchCanceled) {
|
||||
return null;
|
||||
}
|
||||
bestResult = r;
|
||||
}
|
||||
}
|
||||
@@ -2100,8 +2117,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
// check that we didn't hit a bracket too far away from position
|
||||
if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
|
||||
const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
|
||||
const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]);
|
||||
const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], null);
|
||||
if (r) {
|
||||
if (r instanceof BracketSearchCanceled) {
|
||||
return null;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -2111,35 +2131,41 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] | null {
|
||||
private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition());
|
||||
if (matched) {
|
||||
return [foundBracket, matched];
|
||||
}
|
||||
} else {
|
||||
let matched = this._findMatchingBracketUp(data, foundBracket.getStartPosition());
|
||||
if (matched) {
|
||||
return [foundBracket, matched];
|
||||
}
|
||||
const matched = (
|
||||
isOpen
|
||||
? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate)
|
||||
: this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate)
|
||||
);
|
||||
|
||||
if (!matched) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (matched instanceof BracketSearchCanceled) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
return [foundBracket, matched];
|
||||
}
|
||||
|
||||
private _findMatchingBracketUp(bracket: RichEditBracket, position: Position): Range | null {
|
||||
private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
|
||||
// console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
|
||||
|
||||
const languageId = bracket.languageIdentifier.id;
|
||||
const reversedBracketRegex = bracket.reversedRegex;
|
||||
let count = -1;
|
||||
|
||||
const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => {
|
||||
let totalCallCount = 0;
|
||||
const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
|
||||
while (true) {
|
||||
if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
|
||||
return BracketSearchCanceled.INSTANCE;
|
||||
}
|
||||
const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!r) {
|
||||
break;
|
||||
@@ -2214,15 +2240,19 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _findMatchingBracketDown(bracket: RichEditBracket, position: Position): Range | null {
|
||||
private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled {
|
||||
// console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position));
|
||||
|
||||
const languageId = bracket.languageIdentifier.id;
|
||||
const bracketRegex = bracket.forwardRegex;
|
||||
let count = 1;
|
||||
|
||||
const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => {
|
||||
let totalCallCount = 0;
|
||||
const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => {
|
||||
while (true) {
|
||||
if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
|
||||
return BracketSearchCanceled.INSTANCE;
|
||||
}
|
||||
const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!r) {
|
||||
break;
|
||||
@@ -2452,7 +2482,16 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
public findEnclosingBrackets(_position: IPosition, maxDuration = Constants.MAX_SAFE_SMALL_INTEGER): [Range, Range] | null {
|
||||
public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null {
|
||||
let continueSearchPredicate: ContinueBracketSearchPredicate;
|
||||
if (typeof maxDuration === 'undefined') {
|
||||
continueSearchPredicate = null;
|
||||
} else {
|
||||
const startTime = Date.now();
|
||||
continueSearchPredicate = () => {
|
||||
return (Date.now() - startTime <= maxDuration);
|
||||
};
|
||||
}
|
||||
const position = this.validatePosition(_position);
|
||||
const lineCount = this.getLineCount();
|
||||
const savedCounts = new Map<number, number[]>();
|
||||
@@ -2468,8 +2507,13 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
counts = savedCounts.get(languageId)!;
|
||||
};
|
||||
const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => {
|
||||
|
||||
let totalCallCount = 0;
|
||||
const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => {
|
||||
while (true) {
|
||||
if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) {
|
||||
return BracketSearchCanceled.INSTANCE;
|
||||
}
|
||||
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!r) {
|
||||
break;
|
||||
@@ -2485,7 +2529,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
|
||||
if (counts[bracket.index] === -1) {
|
||||
return this._matchFoundBracket(r, bracket, false);
|
||||
return this._matchFoundBracket(r, bracket, false, continueSearchPredicate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2496,12 +2540,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
let languageId: LanguageId = -1;
|
||||
let modeBrackets: RichEditBrackets | null = null;
|
||||
const startTime = Date.now();
|
||||
for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
if (elapsedTime > maxDuration) {
|
||||
return null;
|
||||
}
|
||||
const lineTokens = this._getLineTokens(lineNumber);
|
||||
const tokenCount = lineTokens.getCount();
|
||||
const lineText = this._buffer.getLineContent(lineNumber);
|
||||
@@ -2530,7 +2569,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
|
||||
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (r) {
|
||||
return r;
|
||||
return stripBracketSearchCanceled(r);
|
||||
}
|
||||
prevSearchInToken = false;
|
||||
}
|
||||
@@ -2555,7 +2594,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
|
||||
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (r) {
|
||||
return r;
|
||||
return stripBracketSearchCanceled(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2566,7 +2605,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
|
||||
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (r) {
|
||||
return r;
|
||||
return stripBracketSearchCanceled(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1034,10 +1034,12 @@ export class TokensStore2 {
|
||||
aIndex++;
|
||||
}
|
||||
|
||||
if (aIndex < aLen && aTokens.getEndOffset(aIndex) === bEndCharacter) {
|
||||
// `a` ends exactly at the same spot as `b`!
|
||||
emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask));
|
||||
aIndex++;
|
||||
if (aIndex < aLen) {
|
||||
emitToken(bEndCharacter, (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask));
|
||||
if (aTokens.getEndOffset(aIndex) === bEndCharacter) {
|
||||
// `a` ends exactly at the same spot as `b`!
|
||||
aIndex++;
|
||||
}
|
||||
} else {
|
||||
const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1);
|
||||
|
||||
|
||||
@@ -94,13 +94,15 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri
|
||||
// should stop so that subsequent search don't repeat previous searches
|
||||
const regexIndex = pos - config.windowSize * i;
|
||||
wordDefinition.lastIndex = Math.max(0, regexIndex);
|
||||
match = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex);
|
||||
const thisMatch = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex);
|
||||
|
||||
// stop: found something
|
||||
if (match) {
|
||||
if (!thisMatch && match) {
|
||||
// stop: we have something
|
||||
break;
|
||||
}
|
||||
|
||||
match = thisMatch;
|
||||
|
||||
// stop: searched at start
|
||||
if (regexIndex <= 0) {
|
||||
break;
|
||||
@@ -112,7 +114,7 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri
|
||||
let result = {
|
||||
word: match[0],
|
||||
startColumn: textOffset + 1 + match.index!,
|
||||
endColumn: textOffset + 1 + wordDefinition.lastIndex
|
||||
endColumn: textOffset + 1 + match.index! + match[0].length
|
||||
};
|
||||
wordDefinition.lastIndex = 0;
|
||||
return result;
|
||||
|
||||
@@ -1531,6 +1531,21 @@ export interface CommentReaction {
|
||||
readonly canEdit?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CommentOptions {
|
||||
/**
|
||||
* An optional string to show on the comment input box when it's collapsed.
|
||||
*/
|
||||
prompt?: string;
|
||||
|
||||
/**
|
||||
* An optional string to show as placeholder in the comment input box when it's focused.
|
||||
*/
|
||||
placeHolder?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
// Define extension point ids
|
||||
export const Extensions = {
|
||||
@@ -30,9 +31,19 @@ export class EditorModesRegistry {
|
||||
|
||||
// --- languages
|
||||
|
||||
public registerLanguage(def: ILanguageExtensionPoint): void {
|
||||
public registerLanguage(def: ILanguageExtensionPoint): IDisposable {
|
||||
this._languages.push(def);
|
||||
this._onDidChangeLanguages.fire(undefined);
|
||||
return {
|
||||
dispose: () => {
|
||||
for (let i = 0, len = this._languages.length; i < len; i++) {
|
||||
if (this._languages[i] === def) {
|
||||
this._languages.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public setDynamicLanguages(def: ILanguageExtensionPoint[]): void {
|
||||
this._dynamicLanguages = def;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -28,13 +27,9 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { StringSHA1 } from 'vs/base/common/hash';
|
||||
import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling';
|
||||
|
||||
export const MAINTAIN_UNDO_REDO_STACK = true;
|
||||
|
||||
export interface IEditorSemanticHighlightingOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
@@ -143,6 +138,8 @@ function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStac
|
||||
class DisposedModelInfo {
|
||||
constructor(
|
||||
public readonly uri: URI,
|
||||
public readonly time: number,
|
||||
public readonly heapSize: number,
|
||||
public readonly sha1: string,
|
||||
public readonly versionId: number,
|
||||
public readonly alternativeVersionId: number,
|
||||
@@ -151,8 +148,6 @@ class DisposedModelInfo {
|
||||
|
||||
export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
private static _PROMPT_UNDO_REDO_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
|
||||
@@ -171,6 +166,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
*/
|
||||
private readonly _models: { [modelId: string]: ModelData; };
|
||||
private readonly _disposedModels: Map<string, DisposedModelInfo>;
|
||||
private _disposedModelsHeapSize: number;
|
||||
private readonly _semanticStyling: SemanticStyling;
|
||||
|
||||
constructor(
|
||||
@@ -179,12 +175,12 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
) {
|
||||
super();
|
||||
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
|
||||
this._models = {};
|
||||
this._disposedModels = new Map<string, DisposedModelInfo>();
|
||||
this._disposedModelsHeapSize = 0;
|
||||
this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._logService));
|
||||
|
||||
this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions()));
|
||||
@@ -267,6 +263,14 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
return platform.OS === platform.OperatingSystem.Linux || platform.OS === platform.OperatingSystem.Macintosh ? '\n' : '\r\n';
|
||||
}
|
||||
|
||||
private _getMaxMemoryForClosedFilesUndoStack(): number {
|
||||
const result = this._configurationService.getValue<number>('files.maxMemoryForClosedFilesUndoStackMB');
|
||||
if (typeof result === 'number') {
|
||||
return result * 1024 * 1024;
|
||||
}
|
||||
return 20 * 1024 * 1024;
|
||||
}
|
||||
|
||||
public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
|
||||
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
|
||||
if (!creationOptions) {
|
||||
@@ -328,13 +332,40 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
// --- begin IModelService
|
||||
|
||||
private _insertDisposedModel(disposedModelData: DisposedModelInfo): void {
|
||||
this._disposedModels.set(MODEL_ID(disposedModelData.uri), disposedModelData);
|
||||
this._disposedModelsHeapSize += disposedModelData.heapSize;
|
||||
}
|
||||
|
||||
private _removeDisposedModel(resource: URI): DisposedModelInfo | undefined {
|
||||
const disposedModelData = this._disposedModels.get(MODEL_ID(resource));
|
||||
if (disposedModelData) {
|
||||
this._disposedModelsHeapSize -= disposedModelData.heapSize;
|
||||
}
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
return disposedModelData;
|
||||
}
|
||||
|
||||
private _ensureDisposedModelsHeapSize(maxModelsHeapSize: number): void {
|
||||
if (this._disposedModelsHeapSize > maxModelsHeapSize) {
|
||||
// we must remove some old undo stack elements to free up some memory
|
||||
const disposedModels: DisposedModelInfo[] = [];
|
||||
this._disposedModels.forEach(entry => disposedModels.push(entry));
|
||||
disposedModels.sort((a, b) => a.time - b.time);
|
||||
while (disposedModels.length > 0 && this._disposedModelsHeapSize > maxModelsHeapSize) {
|
||||
const disposedModel = disposedModels.shift()!;
|
||||
this._removeDisposedModel(disposedModel.uri);
|
||||
this._undoRedoService.removeElements(disposedModel.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
|
||||
// create & save the model
|
||||
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
|
||||
const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService);
|
||||
if (resource && this._disposedModels.has(MODEL_ID(resource))) {
|
||||
const disposedModelData = this._disposedModels.get(MODEL_ID(resource))!;
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
const disposedModelData = this._removeDisposedModel(resource)!;
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) {
|
||||
for (const element of elements.past) {
|
||||
@@ -473,7 +504,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
const model = modelData.model;
|
||||
let maintainUndoRedoStack = false;
|
||||
let heapSize = 0;
|
||||
if (MAINTAIN_UNDO_REDO_STACK && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) {
|
||||
if (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData) {
|
||||
const elements = this._undoRedoService.getElements(resource);
|
||||
if ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements)) {
|
||||
maintainUndoRedoStack = true;
|
||||
@@ -490,37 +521,27 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
}
|
||||
}
|
||||
|
||||
if (maintainUndoRedoStack) {
|
||||
// We only invalidate the elements, but they remain in the undo-redo service.
|
||||
this._undoRedoService.setElementsIsValid(resource, false);
|
||||
this._disposedModels.set(MODEL_ID(resource), new DisposedModelInfo(resource, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
} else {
|
||||
if (!maintainUndoRedoStack) {
|
||||
this._undoRedoService.removeElements(resource);
|
||||
modelData.model.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMemory = this._getMaxMemoryForClosedFilesUndoStack();
|
||||
if (heapSize > maxMemory) {
|
||||
// the undo stack for this file would never fit in the configured memory, so don't bother with it.
|
||||
this._undoRedoService.removeElements(resource);
|
||||
modelData.model.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
this._ensureDisposedModelsHeapSize(maxMemory - heapSize);
|
||||
|
||||
// We only invalidate the elements, but they remain in the undo-redo service.
|
||||
this._undoRedoService.setElementsIsValid(resource, false);
|
||||
this._insertDisposedModel(new DisposedModelInfo(resource, Date.now(), heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId()));
|
||||
|
||||
modelData.model.dispose();
|
||||
|
||||
// After disposing the model, prompt and ask if we should keep the undo-redo stack
|
||||
if (maintainUndoRedoStack && heapSize > ModelServiceImpl._PROMPT_UNDO_REDO_SIZE_LIMIT) {
|
||||
const mbSize = (heapSize / 1024 / 1024).toFixed(1);
|
||||
this._dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('undoRedoConfirm', "Keep the undo-redo stack for {0} in memory ({1} MB)?", (resource.scheme === Schemas.file ? resource.fsPath : resource.path), mbSize),
|
||||
[
|
||||
nls.localize('nok', "Discard"),
|
||||
nls.localize('ok', "Keep"),
|
||||
],
|
||||
{
|
||||
cancelId: 2
|
||||
}
|
||||
).then((result) => {
|
||||
const discard = (result.choice === 2 || result.choice === 0);
|
||||
if (discard) {
|
||||
this._disposedModels.delete(MODEL_ID(resource));
|
||||
this._undoRedoService.removeElements(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getModels(): ITextModel[] {
|
||||
|
||||
@@ -36,6 +36,7 @@ export const editorBracketMatchBackground = registerColor('editorBracketMatch.ba
|
||||
export const editorBracketMatchBorder = registerColor('editorBracketMatch.border', { dark: '#888', light: '#B9B9B9', hc: contrastBorder }, nls.localize('editorBracketMatchBorder', 'Color for matching brackets boxes'));
|
||||
|
||||
export const editorOverviewRulerBorder = registerColor('editorOverviewRuler.border', { dark: '#7f7f7f4d', light: '#7f7f7f4d', hc: '#7f7f7f4d' }, nls.localize('editorOverviewRulerBorder', 'Color of the overview ruler border.'));
|
||||
export const editorOverviewRulerBackground = registerColor('editorOverviewRuler.background', null, nls.localize('editorOverviewRulerBackground', 'Background color of the editor overview ruler. Only used when the minimap is enabled and placed on the right side of the editor.'));
|
||||
|
||||
export const editorGutter = registerColor('editorGutter.background', { dark: editorBackground, light: editorBackground, hc: editorBackground }, nls.localize('editorGutter', 'Background color of the editor gutter. The gutter contains the glyph margins and the line numbers.'));
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
let breakingOffsets: number[] = arrPool1;
|
||||
let breakingOffsetsVisibleColumn: number[] = arrPool2;
|
||||
let breakingOffsetsCount: number = 0;
|
||||
let lastBreakingOffset = 0;
|
||||
let lastBreakingOffsetVisibleColumn = 0;
|
||||
|
||||
let breakingColumn = firstLineBreakColumn;
|
||||
const prevLen = prevBreakingOffsets.length;
|
||||
@@ -138,8 +140,12 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
|
||||
while (prevIndex < prevLen) {
|
||||
// Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break)
|
||||
const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
|
||||
const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
|
||||
let prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
|
||||
let prevBreakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
|
||||
if (lastBreakingOffset > prevBreakOffset) {
|
||||
prevBreakOffset = lastBreakingOffset;
|
||||
prevBreakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn;
|
||||
}
|
||||
|
||||
let breakOffset = 0;
|
||||
let breakOffsetVisibleColumn = 0;
|
||||
@@ -148,10 +154,10 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
let forcedBreakOffsetVisibleColumn = 0;
|
||||
|
||||
// initially, we search as much as possible to the right (if it fits)
|
||||
if (prevBreakoffsetVisibleColumn <= breakingColumn) {
|
||||
let visibleColumn = prevBreakoffsetVisibleColumn;
|
||||
let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1);
|
||||
let prevCharCodeClass = classifier.get(prevCharCode);
|
||||
if (prevBreakOffsetVisibleColumn <= breakingColumn) {
|
||||
let visibleColumn = prevBreakOffsetVisibleColumn;
|
||||
let prevCharCode = prevBreakOffset === 0 ? CharCode.Null : lineText.charCodeAt(prevBreakOffset - 1);
|
||||
let prevCharCodeClass = prevBreakOffset === 0 ? CharacterClass.NONE : classifier.get(prevCharCode);
|
||||
let entireLineFits = true;
|
||||
for (let i = prevBreakOffset; i < len; i++) {
|
||||
const charStartOffset = i;
|
||||
@@ -169,7 +175,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
|
||||
}
|
||||
|
||||
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
if (charStartOffset > lastBreakingOffset && canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
|
||||
breakOffset = charStartOffset;
|
||||
breakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
@@ -179,8 +185,14 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
// check if adding character at `i` will go over the breaking column
|
||||
if (visibleColumn > breakingColumn) {
|
||||
// We need to break at least before character at `i`:
|
||||
forcedBreakOffset = charStartOffset;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
|
||||
if (charStartOffset > lastBreakingOffset) {
|
||||
forcedBreakOffset = charStartOffset;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
|
||||
} else {
|
||||
// we need to advance at least by one character
|
||||
forcedBreakOffset = i + 1;
|
||||
forcedBreakOffsetVisibleColumn = visibleColumn;
|
||||
}
|
||||
|
||||
if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
|
||||
// Cannot break at `breakOffset` => reset it if it was set
|
||||
@@ -198,7 +210,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
if (entireLineFits) {
|
||||
// there is no more need to break => stop the outer loop!
|
||||
if (breakingOffsetsCount > 0) {
|
||||
// Add last segment
|
||||
// Add last segment, no need to assign to `lastBreakingOffset` and `lastBreakingOffsetVisibleColumn`
|
||||
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
|
||||
breakingOffsetsCount++;
|
||||
@@ -209,11 +221,11 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
|
||||
if (breakOffset === 0) {
|
||||
// must search left
|
||||
let visibleColumn = prevBreakoffsetVisibleColumn;
|
||||
let visibleColumn = prevBreakOffsetVisibleColumn;
|
||||
let charCode = lineText.charCodeAt(prevBreakOffset);
|
||||
let charCodeClass = classifier.get(charCode);
|
||||
let hitATabCharacter = false;
|
||||
for (let i = prevBreakOffset - 1; i >= 0; i--) {
|
||||
for (let i = prevBreakOffset - 1; i >= lastBreakingOffset; i--) {
|
||||
const charStartOffset = i + 1;
|
||||
const prevCharCode = lineText.charCodeAt(i);
|
||||
|
||||
@@ -290,7 +302,9 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla
|
||||
breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn;
|
||||
}
|
||||
|
||||
lastBreakingOffset = breakOffset;
|
||||
breakingOffsets[breakingOffsetsCount] = breakOffset;
|
||||
lastBreakingOffsetVisibleColumn = breakOffsetVisibleColumn;
|
||||
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
|
||||
breakingOffsetsCount++;
|
||||
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-label.hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./clipboard';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
@@ -39,7 +39,6 @@ function contextKeyForSupportedActions(kind: CodeActionKind) {
|
||||
|
||||
const argsSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
required: ['kind'],
|
||||
defaultSnippets: [{ body: { kind: '' } }],
|
||||
properties: {
|
||||
'kind': {
|
||||
|
||||
@@ -153,12 +153,13 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
return this.hide();
|
||||
}
|
||||
|
||||
const { lineNumber, column } = atPosition;
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return this.hide();
|
||||
}
|
||||
|
||||
const { lineNumber, column } = model.validatePosition(atPosition);
|
||||
|
||||
const tabSize = model.getOptions().tabSize;
|
||||
const fontInfo = options.get(EditorOption.fontInfo);
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
|
||||
@@ -150,7 +150,7 @@ export async function formatDocumentRangeWithProvider(
|
||||
let cts: CancellationTokenSource;
|
||||
if (isCodeEditor(editorOrModel)) {
|
||||
model = editorOrModel.getModel();
|
||||
cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, token);
|
||||
cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, undefined, token);
|
||||
} else {
|
||||
model = editorOrModel;
|
||||
cts = new TextModelCancellationTokenSource(editorOrModel, token);
|
||||
@@ -238,7 +238,7 @@ export async function formatDocumentWithProvider(
|
||||
let cts: CancellationTokenSource;
|
||||
if (isCodeEditor(editorOrModel)) {
|
||||
model = editorOrModel.getModel();
|
||||
cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, token);
|
||||
cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, undefined, token);
|
||||
} else {
|
||||
model = editorOrModel;
|
||||
cts = new TextModelCancellationTokenSource(editorOrModel, token);
|
||||
|
||||
@@ -430,7 +430,7 @@ export class NextMarkerAction extends MarkerNavigationAction {
|
||||
id: NextMarkerAction.ID,
|
||||
label: NextMarkerAction.LABEL,
|
||||
alias: 'Go to Next Problem (Error, Warning, Info)',
|
||||
precondition: EditorContextKeys.writable,
|
||||
precondition: undefined,
|
||||
kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
}
|
||||
@@ -444,7 +444,7 @@ class PrevMarkerAction extends MarkerNavigationAction {
|
||||
id: PrevMarkerAction.ID,
|
||||
label: PrevMarkerAction.LABEL,
|
||||
alias: 'Go to Previous Problem (Error, Warning, Info)',
|
||||
precondition: EditorContextKeys.writable,
|
||||
precondition: undefined,
|
||||
kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
}
|
||||
@@ -456,7 +456,7 @@ class NextMarkerInFilesAction extends MarkerNavigationAction {
|
||||
id: 'editor.action.marker.nextInFiles',
|
||||
label: nls.localize('markerAction.nextInFiles.label', "Go to Next Problem in Files (Error, Warning, Info)"),
|
||||
alias: 'Go to Next Problem in Files (Error, Warning, Info)',
|
||||
precondition: EditorContextKeys.writable,
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyCode.F8,
|
||||
@@ -472,7 +472,7 @@ class PrevMarkerInFilesAction extends MarkerNavigationAction {
|
||||
id: 'editor.action.marker.prevInFiles',
|
||||
label: nls.localize('markerAction.previousInFiles.label', "Go to Previous Problem in Files (Error, Warning, Info)"),
|
||||
alias: 'Go to Previous Problem in Files (Error, Warning, Info)',
|
||||
precondition: EditorContextKeys.writable,
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.Shift | KeyCode.F8,
|
||||
|
||||
@@ -960,6 +960,25 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('Indenting on empty line should move cursor', () => {
|
||||
const model = createTextModel(
|
||||
[
|
||||
''
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
withTestCodeEditor(null, { model: model, useTabStops: false }, (editor) => {
|
||||
const indentLinesAction = new IndentLinesAction();
|
||||
editor.setPosition(new Position(1, 1));
|
||||
|
||||
executeAction(indentLinesAction, editor);
|
||||
assert.equal(model.getLineContent(1), ' ');
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 5, 1, 5));
|
||||
});
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('issue #62112: Delete line does not work properly when multiple cursors are on line', () => {
|
||||
const TEXT = [
|
||||
'a',
|
||||
|
||||
@@ -257,7 +257,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
||||
// case we want to skip the container query altogether.
|
||||
let skipContainerQuery = false;
|
||||
if (symbolQuery !== query) {
|
||||
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, { ...query, values: undefined /* disable multi-query support */ }, filterPos, symbolLabelIconOffset);
|
||||
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, { ...query, values: undefined /* disable multi-query support */ }, filterPos, symbolLabelIconOffset);
|
||||
if (typeof symbolScore === 'number') {
|
||||
skipContainerQuery = true; // since we consumed the query, skip any container matching
|
||||
}
|
||||
@@ -265,7 +265,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
|
||||
|
||||
// Otherwise: score on the symbol query and match on the container later
|
||||
if (typeof symbolScore !== 'number') {
|
||||
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelIconOffset);
|
||||
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, filterPos, symbolLabelIconOffset);
|
||||
if (typeof symbolScore !== 'number') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ class RenameController implements IEditorContribution {
|
||||
if (this._cts.token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
this._cts.dispose();
|
||||
this._cts = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range);
|
||||
|
||||
// do rename at location
|
||||
let selection = this.editor.getSelection();
|
||||
@@ -180,7 +182,7 @@ class RenameController implements IEditorContribution {
|
||||
}
|
||||
|
||||
const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
|
||||
const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview);
|
||||
const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token);
|
||||
|
||||
// no result, only hint to focus the editor or not
|
||||
if (typeof inputFieldResult === 'boolean') {
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./renameInputField';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -16,6 +16,7 @@ import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeServic
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { toggleClass } from 'vs/base/browser/dom';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('renameInputVisible', false);
|
||||
|
||||
@@ -149,7 +150,7 @@ export class RenameInputField implements IContentWidget {
|
||||
}
|
||||
}
|
||||
|
||||
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean): Promise<RenameInputFieldResult | boolean> {
|
||||
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise<RenameInputFieldResult | boolean> {
|
||||
|
||||
toggleClass(this._domNode!, 'preview', supportPreview);
|
||||
|
||||
@@ -185,14 +186,7 @@ export class RenameInputField implements IContentWidget {
|
||||
});
|
||||
};
|
||||
|
||||
let onCursorChanged = () => {
|
||||
const editorPosition = this._editor.getPosition();
|
||||
if (!editorPosition || !Range.containsPosition(where, editorPosition)) {
|
||||
this.cancelInput(true);
|
||||
}
|
||||
};
|
||||
|
||||
disposeOnDone.add(this._editor.onDidChangeCursorSelection(onCursorChanged));
|
||||
token.onCancellationRequested(() => this.cancelInput(true));
|
||||
disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(false)));
|
||||
|
||||
this._show();
|
||||
|
||||
@@ -51,7 +51,7 @@ suite('SmartSelect', () => {
|
||||
setup(() => {
|
||||
const configurationService = new TestConfigurationService();
|
||||
const dialogService = new TestDialogService();
|
||||
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService);
|
||||
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()));
|
||||
mode = new MockJSMode();
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IWordAtPosition } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -27,6 +27,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
|
||||
import { activeContrastBorder, editorSelectionHighlight, editorSelectionHighlightBorder, overviewRulerSelectionHighlightForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
|
||||
const editorWordHighlight = registerColor('editor.wordHighlightBackground', { dark: '#575757B8', light: '#57575740', hc: null }, nls.localize('wordHighlight', 'Background color of a symbol during read-access, like reading a variable. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
const editorWordHighlightStrong = registerColor('editor.wordHighlightStrongBackground', { dark: '#004972B8', light: '#0e639c40', hc: null }, nls.localize('wordHighlightStrong', 'Background color of a symbol during write-access, like writing to a variable. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
@@ -245,6 +246,11 @@ class WordHighlighter {
|
||||
this._ignorePositionChangeEvent = true;
|
||||
this.editor.setPosition(dest.getStartPosition());
|
||||
this.editor.revealRangeInCenterIfOutsideViewport(dest);
|
||||
const word = this._getWord();
|
||||
if (word) {
|
||||
const lineContent = this.editor.getModel().getLineContent(dest.startLineNumber);
|
||||
alert(`${lineContent}, ${newIndex + 1} of ${highlights.length} for '${word.word}'`);
|
||||
}
|
||||
} finally {
|
||||
this._ignorePositionChangeEvent = false;
|
||||
}
|
||||
@@ -259,6 +265,11 @@ class WordHighlighter {
|
||||
this._ignorePositionChangeEvent = true;
|
||||
this.editor.setPosition(dest.getStartPosition());
|
||||
this.editor.revealRangeInCenterIfOutsideViewport(dest);
|
||||
const word = this._getWord();
|
||||
if (word) {
|
||||
const lineContent = this.editor.getModel().getLineContent(dest.startLineNumber);
|
||||
alert(`${lineContent}, ${newIndex + 1} of ${highlights.length} for '${word.word}'`);
|
||||
}
|
||||
} finally {
|
||||
this._ignorePositionChangeEvent = false;
|
||||
}
|
||||
@@ -312,6 +323,17 @@ class WordHighlighter {
|
||||
this._run();
|
||||
}
|
||||
|
||||
private _getWord(): IWordAtPosition | null {
|
||||
let editorSelection = this.editor.getSelection();
|
||||
let lineNumber = editorSelection.startLineNumber;
|
||||
let startColumn = editorSelection.startColumn;
|
||||
|
||||
return this.model.getWordAtPosition({
|
||||
lineNumber: lineNumber,
|
||||
column: startColumn
|
||||
});
|
||||
}
|
||||
|
||||
private _run(): void {
|
||||
let editorSelection = this.editor.getSelection();
|
||||
|
||||
@@ -321,14 +343,10 @@ class WordHighlighter {
|
||||
return;
|
||||
}
|
||||
|
||||
let lineNumber = editorSelection.startLineNumber;
|
||||
let startColumn = editorSelection.startColumn;
|
||||
let endColumn = editorSelection.endColumn;
|
||||
|
||||
let word = this.model.getWordAtPosition({
|
||||
lineNumber: lineNumber,
|
||||
column: startColumn
|
||||
});
|
||||
const word = this._getWord();
|
||||
|
||||
// The selection must be inside a word or surround one word at most
|
||||
if (!word || word.startColumn > startColumn || word.endColumn < endColumn) {
|
||||
@@ -465,20 +483,20 @@ class WordHighlighterContribution extends Disposable implements IEditorContribut
|
||||
return editor.getContribution<WordHighlighterContribution>(WordHighlighterContribution.ID);
|
||||
}
|
||||
|
||||
private wordHighligher: WordHighlighter | null;
|
||||
private wordHighlighter: WordHighlighter | null;
|
||||
|
||||
constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService) {
|
||||
super();
|
||||
this.wordHighligher = null;
|
||||
this.wordHighlighter = null;
|
||||
const createWordHighlighterIfPossible = () => {
|
||||
if (editor.hasModel()) {
|
||||
this.wordHighligher = new WordHighlighter(editor, contextKeyService);
|
||||
this.wordHighlighter = new WordHighlighter(editor, contextKeyService);
|
||||
}
|
||||
};
|
||||
this._register(editor.onDidChangeModel((e) => {
|
||||
if (this.wordHighligher) {
|
||||
this.wordHighligher.dispose();
|
||||
this.wordHighligher = null;
|
||||
if (this.wordHighlighter) {
|
||||
this.wordHighlighter.dispose();
|
||||
this.wordHighlighter = null;
|
||||
}
|
||||
createWordHighlighterIfPossible();
|
||||
}));
|
||||
@@ -486,34 +504,34 @@ class WordHighlighterContribution extends Disposable implements IEditorContribut
|
||||
}
|
||||
|
||||
public saveViewState(): boolean {
|
||||
if (this.wordHighligher && this.wordHighligher.hasDecorations()) {
|
||||
if (this.wordHighlighter && this.wordHighlighter.hasDecorations()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public moveNext() {
|
||||
if (this.wordHighligher) {
|
||||
this.wordHighligher.moveNext();
|
||||
if (this.wordHighlighter) {
|
||||
this.wordHighlighter.moveNext();
|
||||
}
|
||||
}
|
||||
|
||||
public moveBack() {
|
||||
if (this.wordHighligher) {
|
||||
this.wordHighligher.moveBack();
|
||||
if (this.wordHighlighter) {
|
||||
this.wordHighlighter.moveBack();
|
||||
}
|
||||
}
|
||||
|
||||
public restoreViewState(state: boolean | undefined): void {
|
||||
if (this.wordHighligher && state) {
|
||||
this.wordHighligher.restore();
|
||||
if (this.wordHighlighter && state) {
|
||||
this.wordHighlighter.restore();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.wordHighligher) {
|
||||
this.wordHighligher.dispose();
|
||||
this.wordHighligher = null;
|
||||
if (this.wordHighlighter) {
|
||||
this.wordHighlighter.dispose();
|
||||
this.wordHighlighter = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ if (typeof global.require !== 'undefined' && typeof global.require.config === 'f
|
||||
ignoreDuplicateModules: [
|
||||
'vscode-languageserver-types',
|
||||
'vscode-languageserver-types/main',
|
||||
'vscode-languageserver-textdocument',
|
||||
'vscode-languageserver-textdocument/main',
|
||||
'vscode-nls',
|
||||
'vscode-nls/vscode-nls',
|
||||
'jsonc-parser',
|
||||
|
||||
@@ -560,6 +560,14 @@ export class StandaloneTelemetryService implements ITelemetryService {
|
||||
return this.publicLog(eventName, data as any);
|
||||
}
|
||||
|
||||
public publicLogError(eventName: string, data?: any): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
publicLogError2<E extends ClassifiedEvent<T> = never, T extends GDPRClassification<T> = never>(eventName: string, data?: StrictPropertyCheck<T, E>) {
|
||||
return this.publicLogError(eventName, data as any);
|
||||
}
|
||||
|
||||
public getTelemetryInfo(): Promise<ITelemetryInfo> {
|
||||
throw new Error(`Not available`);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export module StaticServices {
|
||||
|
||||
export const undoRedoService = define(IUndoRedoService, (o) => new UndoRedoService(dialogService.get(o), notificationService.get(o)));
|
||||
|
||||
export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o), dialogService.get(o)));
|
||||
export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o)));
|
||||
|
||||
export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o)));
|
||||
|
||||
|
||||
@@ -740,6 +740,24 @@ export class MonarchTokenizer implements modes.ITokenizationSupport {
|
||||
throw monarchCommon.createError(this._lexer, 'lexer rule has no well-defined action in rule: ' + this._safeRuleName(rule));
|
||||
}
|
||||
|
||||
const computeNewStateForEmbeddedMode = (enteringEmbeddedMode: string) => {
|
||||
// substitute language alias to known modes to support syntax highlighting
|
||||
let enteringEmbeddedModeId = this._modeService.getModeIdForLanguageName(enteringEmbeddedMode);
|
||||
if (enteringEmbeddedModeId) {
|
||||
enteringEmbeddedMode = enteringEmbeddedModeId;
|
||||
}
|
||||
|
||||
const embeddedModeData = this._getNestedEmbeddedModeData(enteringEmbeddedMode);
|
||||
|
||||
if (pos < lineLength) {
|
||||
// there is content from the embedded mode on this line
|
||||
const restOfLine = line.substr(pos);
|
||||
return this._nestedTokenize(restOfLine, MonarchLineStateFactory.create(stack, embeddedModeData), offsetDelta + pos, tokensCollector);
|
||||
} else {
|
||||
return MonarchLineStateFactory.create(stack, embeddedModeData);
|
||||
}
|
||||
};
|
||||
|
||||
// is the result a group match?
|
||||
if (Array.isArray(result)) {
|
||||
if (groupMatching && groupMatching.groups.length > 0) {
|
||||
@@ -780,6 +798,12 @@ export class MonarchTokenizer implements modes.ITokenizationSupport {
|
||||
matched = ''; // better set the next state too..
|
||||
matches = null;
|
||||
result = '';
|
||||
|
||||
// Even though `@rematch` was specified, if `nextEmbedded` also specified,
|
||||
// a state transition should occur.
|
||||
if (enteringEmbeddedMode !== null) {
|
||||
return computeNewStateForEmbeddedMode(enteringEmbeddedMode);
|
||||
}
|
||||
}
|
||||
|
||||
// check progress
|
||||
@@ -810,21 +834,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport {
|
||||
}
|
||||
|
||||
if (enteringEmbeddedMode !== null) {
|
||||
// substitute language alias to known modes to support syntax highlighting
|
||||
let enteringEmbeddedModeId = this._modeService.getModeIdForLanguageName(enteringEmbeddedMode);
|
||||
if (enteringEmbeddedModeId) {
|
||||
enteringEmbeddedMode = enteringEmbeddedModeId;
|
||||
}
|
||||
|
||||
let embeddedModeData = this._getNestedEmbeddedModeData(enteringEmbeddedMode);
|
||||
|
||||
if (pos < lineLength) {
|
||||
// there is content from the embedded mode on this line
|
||||
let restOfLine = line.substr(pos);
|
||||
return this._nestedTokenize(restOfLine, MonarchLineStateFactory.create(stack, embeddedModeData), offsetDelta + pos, tokensCollector);
|
||||
} else {
|
||||
return MonarchLineStateFactory.create(stack, embeddedModeData);
|
||||
}
|
||||
return computeNewStateForEmbeddedMode(enteringEmbeddedMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ export interface IMonarchLanguage {
|
||||
* shorthands: [reg,act] == { regex: reg, action: act}
|
||||
* and : [reg,act,nxt] == { regex: reg, action: act{ next: nxt }}
|
||||
*/
|
||||
export type IShortMonarchLanguageRule1 = [RegExp, IMonarchLanguageAction];
|
||||
export type IShortMonarchLanguageRule1 = [string | RegExp, IMonarchLanguageAction];
|
||||
|
||||
export type IShortMonarchLanguageRule2 = [RegExp, IMonarchLanguageAction, string];
|
||||
export type IShortMonarchLanguageRule2 = [string | RegExp, IMonarchLanguageAction, string];
|
||||
|
||||
export interface IExpandedMonarchLanguageRule {
|
||||
/**
|
||||
@@ -135,4 +135,4 @@ export interface IMonarchLanguageBracket {
|
||||
* token class
|
||||
*/
|
||||
token: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer';
|
||||
import { compile } from 'vs/editor/standalone/common/monarch/monarchCompile';
|
||||
import { Token } from 'vs/editor/common/core/token';
|
||||
import { TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { IMonarchLanguage } from 'vs/editor/standalone/common/monarch/monarchTypes';
|
||||
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
|
||||
|
||||
suite('Monarch', () => {
|
||||
|
||||
function createMonarchTokenizer(modeService: IModeService, languageId: string, language: IMonarchLanguage): MonarchTokenizer {
|
||||
return new MonarchTokenizer(modeService, null!, languageId, compile(languageId, language));
|
||||
}
|
||||
|
||||
test('Ensure @rematch and nextEmbedded can be used together in Monarch grammar', () => {
|
||||
const modeService = new ModeServiceImpl();
|
||||
const innerModeRegistration = ModesRegistry.registerLanguage({
|
||||
id: 'sql'
|
||||
});
|
||||
const innerModeTokenizationRegistration = TokenizationRegistry.register('sql', createMonarchTokenizer(modeService, 'sql', {
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/./, 'token']
|
||||
]
|
||||
}
|
||||
}));
|
||||
const SQL_QUERY_START = '(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)';
|
||||
const tokenizer = createMonarchTokenizer(modeService, 'test1', {
|
||||
tokenizer: {
|
||||
root: [
|
||||
[`(\"\"\")${SQL_QUERY_START}`, [{ 'token': 'string.quote', }, { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },]],
|
||||
[/(""")$/, [{ token: 'string.quote', next: '@maybeStringIsSQL', },]],
|
||||
],
|
||||
maybeStringIsSQL: [
|
||||
[/(.*)/, {
|
||||
cases: {
|
||||
[`${SQL_QUERY_START}\\b.*`]: { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },
|
||||
'@default': { token: '@rematch', switchTo: '@endDblDocString', },
|
||||
}
|
||||
}],
|
||||
],
|
||||
endDblDocString: [
|
||||
['[^\']+', 'string'],
|
||||
['\\\\\'', 'string'],
|
||||
['\'\'\'', 'string', '@popall'],
|
||||
['\'', 'string']
|
||||
],
|
||||
endStringWithSQL: [[/"""/, { token: 'string.quote', next: '@popall', nextEmbedded: '@pop', },]],
|
||||
}
|
||||
});
|
||||
|
||||
const lines = [
|
||||
`mysql_query("""SELECT * FROM table_name WHERE ds = '<DATEID>'""")`,
|
||||
`mysql_query("""`,
|
||||
`SELECT *`,
|
||||
`FROM table_name`,
|
||||
`WHERE ds = '<DATEID>'`,
|
||||
`""")`,
|
||||
];
|
||||
|
||||
const actualTokens: Token[][] = [];
|
||||
let state = tokenizer.getInitialState();
|
||||
for (const line of lines) {
|
||||
const result = tokenizer.tokenize(line, state, 0);
|
||||
actualTokens.push(result.tokens);
|
||||
state = result.endState;
|
||||
}
|
||||
|
||||
assert.deepEqual(actualTokens, [
|
||||
[
|
||||
{ 'offset': 0, 'type': 'source.test1', 'language': 'test1' },
|
||||
{ 'offset': 12, 'type': 'string.quote.test1', 'language': 'test1' },
|
||||
{ 'offset': 15, 'type': 'token.sql', 'language': 'sql' },
|
||||
{ 'offset': 61, 'type': 'string.quote.test1', 'language': 'test1' },
|
||||
{ 'offset': 64, 'type': 'source.test1', 'language': 'test1' }
|
||||
],
|
||||
[
|
||||
{ 'offset': 0, 'type': 'source.test1', 'language': 'test1' },
|
||||
{ 'offset': 12, 'type': 'string.quote.test1', 'language': 'test1' }
|
||||
],
|
||||
[
|
||||
{ 'offset': 0, 'type': 'token.sql', 'language': 'sql' }
|
||||
],
|
||||
[
|
||||
{ 'offset': 0, 'type': 'token.sql', 'language': 'sql' }
|
||||
],
|
||||
[
|
||||
{ 'offset': 0, 'type': 'token.sql', 'language': 'sql' }
|
||||
],
|
||||
[
|
||||
{ 'offset': 0, 'type': 'string.quote.test1', 'language': 'test1' },
|
||||
{ 'offset': 3, 'type': 'source.test1', 'language': 'test1' }
|
||||
]
|
||||
]);
|
||||
innerModeTokenizationRegistration.dispose();
|
||||
innerModeRegistration.dispose();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1428,6 +1428,28 @@ suite('Editor Controller - Regression tests', () => {
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('issue #95591: Unindenting moves cursor to beginning of line', () => {
|
||||
let model = createTextModel(
|
||||
[
|
||||
' '
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
withTestCodeEditor(null, {
|
||||
model: model,
|
||||
useTabStops: false
|
||||
}, (editor, cursor) => {
|
||||
moveTo(cursor, 1, 9, false);
|
||||
assertCursor(cursor, new Selection(1, 9, 1, 9));
|
||||
|
||||
CoreEditingCommands.Outdent.runEditorCommand(null, editor, null);
|
||||
assert.equal(model.getLineContent(1), ' ');
|
||||
assertCursor(cursor, new Selection(1, 5, 1, 5));
|
||||
});
|
||||
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('Bug #16657: [editor] Tab on empty line of zero indentation moves cursor to position (1,1)', () => {
|
||||
let model = createTextModel(
|
||||
[
|
||||
|
||||
@@ -8,7 +8,7 @@ import { MultilineTokens2, SparseEncodedTokens, TokensStore2 } from 'vs/editor/c
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes';
|
||||
import { MetadataConsts, TokenMetadata, FontStyle } from 'vs/editor/common/modes';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
||||
|
||||
@@ -387,4 +387,50 @@ suite('TokensStore', () => {
|
||||
const lineTokens = store.addSemanticTokens(36451, new LineTokens(new Uint32Array([60, 1]), ` if (flags & ModifierFlags.Ambient) {`));
|
||||
assert.equal(lineTokens.getCount(), 7);
|
||||
});
|
||||
|
||||
|
||||
test('issue #95949: Identifiers are colored in bold when targetting keywords', () => {
|
||||
|
||||
function createTMMetadata(foreground: number, fontStyle: number, languageId: number): number {
|
||||
return (
|
||||
(languageId << MetadataConsts.LANGUAGEID_OFFSET)
|
||||
| (fontStyle << MetadataConsts.FONT_STYLE_OFFSET)
|
||||
| (foreground << MetadataConsts.FOREGROUND_OFFSET)
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
function toArr(lineTokens: LineTokens): number[] {
|
||||
let r: number[] = [];
|
||||
for (let i = 0; i < lineTokens.getCount(); i++) {
|
||||
r.push(lineTokens.getEndOffset(i));
|
||||
r.push(lineTokens.getMetadata(i));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
const store = new TokensStore2();
|
||||
|
||||
store.set([
|
||||
new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array([
|
||||
0, 6, 11, (1 << MetadataConsts.FOREGROUND_OFFSET) | MetadataConsts.SEMANTIC_USE_FOREGROUND,
|
||||
])))
|
||||
], true);
|
||||
|
||||
const lineTokens = store.addSemanticTokens(1, new LineTokens(new Uint32Array([
|
||||
5, createTMMetadata(5, FontStyle.Bold, 53),
|
||||
14, createTMMetadata(1, FontStyle.None, 53),
|
||||
17, createTMMetadata(6, FontStyle.None, 53),
|
||||
18, createTMMetadata(1, FontStyle.None, 53),
|
||||
]), `const hello = 123;`));
|
||||
|
||||
const actual = toArr(lineTokens);
|
||||
assert.deepEqual(actual, [
|
||||
5, createTMMetadata(5, FontStyle.Bold, 53),
|
||||
6, createTMMetadata(1, FontStyle.None, 53),
|
||||
11, createTMMetadata(1, FontStyle.None, 53),
|
||||
14, createTMMetadata(1, FontStyle.None, 53),
|
||||
17, createTMMetadata(6, FontStyle.None, 53),
|
||||
18, createTMMetadata(1, FontStyle.None, 53)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { createStringBuilder } from 'vs/editor/common/core/stringBuilder';
|
||||
import { DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { createTextBuffer } from 'vs/editor/common/model/textModel';
|
||||
import { ModelServiceImpl, MAINTAIN_UNDO_REDO_STACK } from 'vs/editor/common/services/modelServiceImpl';
|
||||
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
@@ -35,7 +35,7 @@ suite('ModelService', () => {
|
||||
configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot'));
|
||||
|
||||
const dialogService = new TestDialogService();
|
||||
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService);
|
||||
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
@@ -310,44 +310,42 @@ suite('ModelService', () => {
|
||||
assertComputeEdits(file1, file2);
|
||||
});
|
||||
|
||||
if (MAINTAIN_UNDO_REDO_STACK) {
|
||||
test('maintains undo for same resource and same content', () => {
|
||||
const resource = URI.parse('file://test.txt');
|
||||
test('maintains undo for same resource and same content', () => {
|
||||
const resource = URI.parse('file://test.txt');
|
||||
|
||||
// create a model
|
||||
const model1 = modelService.createModel('text', null, resource);
|
||||
// make an edit
|
||||
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
|
||||
assert.equal(model1.getValue(), 'text1');
|
||||
// dispose it
|
||||
modelService.destroyModel(resource);
|
||||
// create a model
|
||||
const model1 = modelService.createModel('text', null, resource);
|
||||
// make an edit
|
||||
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
|
||||
assert.equal(model1.getValue(), 'text1');
|
||||
// dispose it
|
||||
modelService.destroyModel(resource);
|
||||
|
||||
// create a new model with the same content
|
||||
const model2 = modelService.createModel('text1', null, resource);
|
||||
// undo
|
||||
model2.undo();
|
||||
assert.equal(model2.getValue(), 'text');
|
||||
});
|
||||
// create a new model with the same content
|
||||
const model2 = modelService.createModel('text1', null, resource);
|
||||
// undo
|
||||
model2.undo();
|
||||
assert.equal(model2.getValue(), 'text');
|
||||
});
|
||||
|
||||
test('maintains version id and alternative version id for same resource and same content', () => {
|
||||
const resource = URI.parse('file://test.txt');
|
||||
test('maintains version id and alternative version id for same resource and same content', () => {
|
||||
const resource = URI.parse('file://test.txt');
|
||||
|
||||
// create a model
|
||||
const model1 = modelService.createModel('text', null, resource);
|
||||
// make an edit
|
||||
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
|
||||
assert.equal(model1.getValue(), 'text1');
|
||||
const versionId = model1.getVersionId();
|
||||
const alternativeVersionId = model1.getAlternativeVersionId();
|
||||
// dispose it
|
||||
modelService.destroyModel(resource);
|
||||
// create a model
|
||||
const model1 = modelService.createModel('text', null, resource);
|
||||
// make an edit
|
||||
model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);
|
||||
assert.equal(model1.getValue(), 'text1');
|
||||
const versionId = model1.getVersionId();
|
||||
const alternativeVersionId = model1.getAlternativeVersionId();
|
||||
// dispose it
|
||||
modelService.destroyModel(resource);
|
||||
|
||||
// create a new model with the same content
|
||||
const model2 = modelService.createModel('text1', null, resource);
|
||||
assert.equal(model2.getVersionId(), versionId);
|
||||
assert.equal(model2.getAlternativeVersionId(), alternativeVersionId);
|
||||
});
|
||||
}
|
||||
// create a new model with the same content
|
||||
const model2 = modelService.createModel('text1', null, resource);
|
||||
assert.equal(model2.getVersionId(), versionId);
|
||||
assert.equal(model2.getAlternativeVersionId(), alternativeVersionId);
|
||||
});
|
||||
|
||||
test('does not maintain undo for same resource and different content', () => {
|
||||
const resource = URI.parse('file://test.txt');
|
||||
|
||||
@@ -147,7 +147,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
|
||||
|
||||
test('MonospaceLineBreaksComputer incremental 1', () => {
|
||||
|
||||
let factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
|
||||
assertIncrementalLineBreaks(
|
||||
factory, 'just some text and more', 4,
|
||||
@@ -203,6 +203,17 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('issue #95686: CRITICAL: loop forever on the monospaceLineBreaksComputer', () => {
|
||||
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
|
||||
assertIncrementalLineBreaks(
|
||||
factory,
|
||||
' <tr dmx-class:table-danger="(alt <= 50)" dmx-class:table-warning="(alt <= 200)" dmx-class:table-primary="(alt <= 400)" dmx-class:table-info="(alt <= 800)" dmx-class:table-success="(alt >= 400)">',
|
||||
4,
|
||||
179, ' <tr dmx-class:table-danger="(alt <= 50)" dmx-class:table-warning="(alt <= 200)" dmx-class:table-primary="(alt <= 400)" dmx-class:table-info="(alt <= 800)" |dmx-class:table-success="(alt >= 400)">',
|
||||
1, ' | | | | | |<|t|r| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|d|a|n|g|e|r|=|"|(|a|l|t| |<|=| |5|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|w|a|r|n|i|n|g|=|"|(|a|l|t| |<|=| |2|0|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|p|r|i|m|a|r|y|=|"|(|a|l|t| |<|=| |4|0|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|i|n|f|o|=|"|(|a|l|t| |<|=| |8|0|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|s|u|c|c|e|s|s|=|"|(|a|l|t| |>|=| |4|0|0|)|"|>',
|
||||
WrappingIndent.Same
|
||||
);
|
||||
});
|
||||
|
||||
test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => {
|
||||
let factory = new MonospaceLineBreaksComputerFactory('(', '\t)');
|
||||
|
||||
@@ -1491,6 +1491,10 @@ var AMDLoader;
|
||||
result.getStats = function () {
|
||||
return _this.getLoaderEvents();
|
||||
};
|
||||
result.config = function (params, shouldOverwrite) {
|
||||
if (shouldOverwrite === void 0) { shouldOverwrite = false; }
|
||||
_this.configure(params, shouldOverwrite);
|
||||
};
|
||||
result.__$__nodeRequire = AMDLoader.global.nodeRequire;
|
||||
return result;
|
||||
};
|
||||
|
||||
Vendored
+2
-2
@@ -6302,9 +6302,9 @@ declare namespace monaco.languages {
|
||||
* shorthands: [reg,act] == { regex: reg, action: act}
|
||||
* and : [reg,act,nxt] == { regex: reg, action: act{ next: nxt }}
|
||||
*/
|
||||
export type IShortMonarchLanguageRule1 = [RegExp, IMonarchLanguageAction];
|
||||
export type IShortMonarchLanguageRule1 = [string | RegExp, IMonarchLanguageAction];
|
||||
|
||||
export type IShortMonarchLanguageRule2 = [RegExp, IMonarchLanguageAction, string];
|
||||
export type IShortMonarchLanguageRule2 = [string | RegExp, IMonarchLanguageAction, string];
|
||||
|
||||
export interface IExpandedMonarchLanguageRule {
|
||||
/**
|
||||
|
||||
@@ -53,6 +53,7 @@ export class AuthenticationTokenService extends Disposable implements IAuthentic
|
||||
}
|
||||
|
||||
sendTokenFailed(): void {
|
||||
this.setToken(undefined);
|
||||
this._onTokenFailed.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +328,11 @@ class ConfigurationRegistry implements IConfigurationRegistry {
|
||||
this.configurationProperties[key] = properties[key];
|
||||
}
|
||||
|
||||
if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {
|
||||
// If not set, default deprecationMessage to the markdown source
|
||||
properties[key].deprecationMessage = properties[key].markdownDeprecationMessage;
|
||||
}
|
||||
|
||||
propertyKeys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface ParsedArgs {
|
||||
log?: string;
|
||||
logExtensionHostCommunication?: boolean;
|
||||
'extensions-dir'?: string;
|
||||
'extensions-download-dir'?: string;
|
||||
'builtin-extensions-dir'?: string;
|
||||
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
|
||||
extensionTestsPath?: string; // either a local path or a URI
|
||||
@@ -54,7 +55,7 @@ export interface ParsedArgs {
|
||||
'locate-extension'?: string[]; // undefined or array of 1 or more
|
||||
'enable-proposed-api'?: string[]; // undefined or array of 1 or more
|
||||
'open-url'?: boolean;
|
||||
'skip-getting-started'?: boolean;
|
||||
'skip-release-notes'?: boolean;
|
||||
'disable-restore-windows'?: boolean;
|
||||
'disable-telemetry'?: boolean;
|
||||
'export-default-configuration'?: string;
|
||||
@@ -143,6 +144,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
|
||||
|
||||
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
'extensions-download-dir': { type: 'string' },
|
||||
'builtin-extensions-dir': { type: 'string' },
|
||||
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
||||
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") },
|
||||
@@ -179,7 +181,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'install-source': { type: 'string' },
|
||||
'driver': { type: 'string' },
|
||||
'logExtensionHostCommunication': { type: 'boolean' },
|
||||
'skip-getting-started': { type: 'boolean' },
|
||||
'skip-release-notes': { type: 'boolean' },
|
||||
'disable-restore-windows': { type: 'boolean' },
|
||||
'disable-telemetry': { type: 'boolean' },
|
||||
'disable-updates': { type: 'boolean' },
|
||||
@@ -265,23 +267,25 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
|
||||
const parsedArgs = minimist(args, { string, boolean, alias });
|
||||
|
||||
const cleanedArgs: any = {};
|
||||
const remainingArgs: any = parsedArgs;
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/58177
|
||||
cleanedArgs._ = parsedArgs._.filter(arg => arg.length > 0);
|
||||
delete parsedArgs._;
|
||||
|
||||
delete remainingArgs._;
|
||||
|
||||
for (let optionId in options) {
|
||||
const o = options[optionId];
|
||||
if (o.alias) {
|
||||
delete parsedArgs[o.alias];
|
||||
delete remainingArgs[o.alias];
|
||||
}
|
||||
|
||||
let val = parsedArgs[optionId];
|
||||
if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates)) {
|
||||
let val = remainingArgs[optionId];
|
||||
if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) {
|
||||
if (!val) {
|
||||
val = parsedArgs[o.deprecates];
|
||||
val = remainingArgs[o.deprecates];
|
||||
}
|
||||
delete parsedArgs[o.deprecates];
|
||||
delete remainingArgs[o.deprecates];
|
||||
}
|
||||
|
||||
if (typeof val !== 'undefined') {
|
||||
@@ -297,10 +301,10 @@ export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, err
|
||||
}
|
||||
cleanedArgs[optionId] = val;
|
||||
}
|
||||
delete parsedArgs[optionId];
|
||||
delete remainingArgs[optionId];
|
||||
}
|
||||
|
||||
for (let key in parsedArgs) {
|
||||
for (let key in remainingArgs) {
|
||||
errorReporter.onUnknownOption(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
||||
installSourcePath: string;
|
||||
|
||||
extensionsPath?: string;
|
||||
extensionsDownloadPath?: string;
|
||||
builtinExtensionsPath: string;
|
||||
|
||||
globalStorageHome: string;
|
||||
@@ -150,6 +151,10 @@ export class EnvironmentService implements INativeEnvironmentService {
|
||||
}
|
||||
}
|
||||
|
||||
get extensionsDownloadPath(): string | undefined {
|
||||
return parsePathArg(this._args['extensions-download-dir'], process);
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionsPath(): string {
|
||||
const fromArgs = parsePathArg(this._args['extensions-dir'], process);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { IRequestService, asJson, asText } from 'vs/platform/request/common/requ
|
||||
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}}
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -21,7 +20,6 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionManifest, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} add imports
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
@@ -704,9 +702,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
});
|
||||
}
|
||||
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<URI> {
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
|
||||
const zip = joinPath(location, generateUuid());
|
||||
const data = getGalleryExtensionTelemetryData(extension);
|
||||
const startTime = new Date().getTime();
|
||||
/* __GDPR__
|
||||
@@ -728,9 +725,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
} : extension.assets.download;
|
||||
|
||||
return this.getAsset(downloadAsset)
|
||||
.then(context => this.fileService.writeFile(zip, context.stream))
|
||||
.then(() => log(new Date().getTime() - startTime))
|
||||
.then(() => zip);
|
||||
.then(context => this.fileService.writeFile(location, context.stream))
|
||||
.then(() => log(new Date().getTime() - startTime));
|
||||
}
|
||||
|
||||
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
|
||||
|
||||
@@ -155,7 +155,7 @@ export interface IExtensionGalleryService {
|
||||
isEnabled(): boolean;
|
||||
query(token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<URI>;
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
|
||||
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
|
||||
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as semver from 'semver-umd';
|
||||
|
||||
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
|
||||
|
||||
export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
private readonly extensionsDownloadDir: URI = URI.file(tmpdir());
|
||||
private readonly cache: number = 0;
|
||||
private readonly cleanUpPromise: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
if (environmentService.extensionsDownloadPath) {
|
||||
this.extensionsDownloadDir = URI.file(environmentService.extensionsDownloadPath);
|
||||
this.cache = 20; // Cache 20 downloads
|
||||
this.cleanUpPromise = this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
await this.cleanUpPromise;
|
||||
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
|
||||
await this.download(extension, location, operation);
|
||||
return location;
|
||||
}
|
||||
|
||||
async delete(location: URI): Promise<void> {
|
||||
// Delete immediately if caching is disabled
|
||||
if (!this.cache) {
|
||||
await this.fileService.del(location);
|
||||
}
|
||||
}
|
||||
|
||||
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
if (!await this.fileService.exists(location)) {
|
||||
await this.extensionGalleryService.download(extension, location, operation);
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanUp(): Promise<void> {
|
||||
try {
|
||||
if (!(await this.fileService.exists(this.extensionsDownloadDir))) {
|
||||
this.logService.trace('Extension VSIX downlads cache dir does not exist');
|
||||
return;
|
||||
}
|
||||
const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true });
|
||||
if (folderStat.children) {
|
||||
const toDelete: URI[] = [];
|
||||
const all: [ExtensionIdentifierWithVersion, IFileStatWithMetadata][] = [];
|
||||
for (const stat of folderStat.children) {
|
||||
const extension = this.parse(stat.name);
|
||||
if (extension) {
|
||||
all.push([extension, stat]);
|
||||
}
|
||||
}
|
||||
const byExtension = groupByExtension(all, ([extension]) => extension.identifier);
|
||||
const distinct: IFileStatWithMetadata[] = [];
|
||||
for (const p of byExtension) {
|
||||
p.sort((a, b) => semver.rcompare(a[0].version, b[0].version));
|
||||
toDelete.push(...p.slice(1).map(e => e[1].resource)); // Delete outdated extensions
|
||||
distinct.push(p[0][1]);
|
||||
}
|
||||
distinct.sort((a, b) => a.mtime - b.mtime); // sort by modified time
|
||||
toDelete.push(...distinct.slice(0, Math.max(0, distinct.length - this.cache)).map(s => s.resource)); // Retain minimum cacheSize and delete the rest
|
||||
await Promise.all(toDelete.map(resource => {
|
||||
this.logService.trace('Deleting vsix from cache', resource.path);
|
||||
return this.fileService.del(resource);
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private getName(extension: IGalleryExtension): string {
|
||||
return this.cache ? new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key().toLowerCase() : generateUuid();
|
||||
}
|
||||
|
||||
private parse(name: string): ExtensionIdentifierWithVersion | null {
|
||||
const matches = ExtensionIdVersionRegex.exec(name);
|
||||
return matches && matches[1] && matches[2] ? new ExtensionIdentifierWithVersion({ id: matches[1] }, matches[2]) : null;
|
||||
}
|
||||
}
|
||||
@@ -40,12 +40,13 @@ import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'
|
||||
import { tmpdir } from 'os';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { optional, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
|
||||
|
||||
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
|
||||
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
|
||||
@@ -113,6 +114,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
private readonly installingExtensions: Map<string, CancelablePromise<ILocalExtension>> = new Map<string, CancelablePromise<ILocalExtension>>();
|
||||
private readonly uninstallingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
|
||||
private readonly manifestCache: ExtensionsManifestCache;
|
||||
private readonly extensionsDownloader: ExtensionsDownloader;
|
||||
private readonly extensionLifecycle: ExtensionsLifecycle;
|
||||
|
||||
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
|
||||
@@ -133,6 +135,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@optional(IDownloadService) private downloadService: IDownloadService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
|
||||
@@ -140,6 +143,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
|
||||
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
|
||||
this.extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService));
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
@@ -347,7 +351,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
this.downloadInstallableExtension(extension, operation)
|
||||
.then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken)
|
||||
.then(local => pfs.rimraf(installableExtension.zipPath).finally(() => { }).then(() => local)))
|
||||
.then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local)))
|
||||
.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
|
||||
.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
|
||||
.then(
|
||||
@@ -425,7 +429,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
};
|
||||
|
||||
this.logService.trace('Started downloading extension:', extension.identifier.id);
|
||||
return this.galleryService.download(extension, URI.file(tmpdir()), operation)
|
||||
return this.extensionsDownloader.downloadExtension(extension, operation)
|
||||
.then(
|
||||
zip => {
|
||||
const zipPath = zip.fsPath;
|
||||
@@ -1003,6 +1007,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
|
||||
this.telemetryService.publicLogError(eventName, assign(extensionData, { success: !error, duration, errorcode }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,8 +320,10 @@ export interface INotificationService {
|
||||
* Shows a prompt in the notification area with the provided choices. The prompt
|
||||
* is non-modal. If you want to show a modal dialog instead, use `IDialogService`.
|
||||
*
|
||||
* @param onCancel will be called if the user closed the notification without picking
|
||||
* any of the provided choices.
|
||||
* @param severity the severity of the notification. Either `Info`, `Warning` or `Error`.
|
||||
* @param message the message to show as status.
|
||||
* @param choices options to be choosen from.
|
||||
* @param options provides some optional configuration options.
|
||||
*
|
||||
* @returns a handle on the notification to e.g. hide it or update message, buttons, etc.
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user