This commit is contained in:
chlafreniere
2020-04-03 00:14:28 -07:00
351 changed files with 7194 additions and 4889 deletions
-2
View File
@@ -142,13 +142,11 @@ function pipeLoggingToParent() {
function handleExceptions() {
// Handle uncaught exceptions
// @ts-ignore
process.on('uncaughtException', function (err) {
console.error('Uncaught Exception: ', err);
});
// Handle unhandled promise rejections
// @ts-ignore
process.on('unhandledRejection', function (reason) {
console.error('Unhandled Promise Rejection: ', reason);
});
-4
View File
@@ -25,7 +25,6 @@ exports.assign = function assign(destination, source) {
*/
exports.load = function (modulePaths, resultCallback, options) {
// @ts-ignore
const webFrame = require('electron').webFrame;
const path = require('path');
@@ -49,7 +48,6 @@ exports.load = function (modulePaths, resultCallback, options) {
}
// Error handler
// @ts-ignore
process.on('uncaughtException', function (error) {
onUnexpectedError(error, enableDeveloperTools);
});
@@ -184,7 +182,6 @@ function parseURLQueryArgs() {
*/
function registerDeveloperKeybindings(disallowReloadKeybinding) {
// @ts-ignore
const ipc = require('electron').ipcRenderer;
const extractKey = function (e) {
@@ -223,7 +220,6 @@ function registerDeveloperKeybindings(disallowReloadKeybinding) {
function onUnexpectedError(error, enableDeveloperTools) {
// @ts-ignore
const ipc = require('electron').ipcRenderer;
if (enableDeveloperTools) {
+1 -1
View File
@@ -323,7 +323,7 @@ function getUserDataPath(cliArgs) {
* @returns {ParsedArgs}
*/
function parseCLIArgs() {
const minimist = require('vscode-minimist');
const minimist = require('minimist');
return minimist(process.argv, {
string: [
+2 -2
View File
@@ -6,7 +6,7 @@
//@ts-check
'use strict';
// @ts-ignore
// @ts-expect-error
// const pkg = require('../package.json');
const path = require('path');
const os = require('os');
@@ -34,4 +34,4 @@ function getDefaultUserDataPath(platform) {
}
exports.getAppDataPath = getAppDataPath;
exports.getDefaultUserDataPath = getDefaultUserDataPath;
exports.getDefaultUserDataPath = getDefaultUserDataPath;
+2 -1
View File
@@ -2212,7 +2212,8 @@ declare module 'azdata' {
Sql = 1,
OssRdbms = 2,
AzureKeyVault = 3,
Graph = 4
Graph = 4,
MicrosoftResourceManagement = 5
}
export interface DidChangeAccountsParams {
+34
View File
@@ -225,6 +225,7 @@ declare module 'azdata' {
*/
export interface TabbedPanelLayout {
orientation: TabOrientation;
showIcon: boolean;
}
/**
@@ -245,6 +246,11 @@ declare module 'azdata' {
* Id of the tab
*/
id: string;
/**
* Icon of the tab
*/
icon?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
}
/**
@@ -287,5 +293,33 @@ declare module 'azdata' {
*/
export const onDidChangeActiveNotebookEditor: vscode.Event<NotebookEditor>;
}
export namespace window {
export interface ModelViewDashboard {
registerTabs(handler: (view: ModelView) => Thenable<(DashboardTab | DashboardTabGroup)[]>): void;
open(): Thenable<void>;
}
export function createModelViewDashboard(title: string): ModelViewDashboard;
}
export interface DashboardTab extends Tab {
/**
* Toolbar of the tab, optional.
*/
toolbar?: ToolbarContainer;
}
export interface DashboardTabGroup {
/**
* * Title of the tab group
*/
title: string;
/**
* children of the tab group
*/
tabs: DashboardTab[];
}
}
+34 -23
View File
@@ -24,9 +24,16 @@ panel {
position: relative;
}
.tabbedPanel.vertical>.title {
flex: 0 0 auto;
flex-direction: column;
height: 100%;
}
.tabbedPanel .tabContainer {
flex: 1 1 auto;
overflow: hidden;
height: 100%;
}
.tabbedPanel .tabList {
@@ -45,31 +52,34 @@ panel {
margin: auto;
}
.tabbedPanel.horizontal .tabList .tab .tabLabel {
font-size: 12px;
font-weight: normal;
}
.tabbedPanel.vertical .tabList .tab .tabLabel {
font-size: 12px;
padding-bottom: 0px;
font-weight: normal;
}
.tabbedPanel .tabList .tab .tabLabel {
font-size: 13px;
padding-bottom: 4px;
font-weight: 600;
}
.tabbedPanel.vertical .tabList .tab .tabLabel {
font-size: 11px;
}
.tabbedPanel .tabList .tab-header {
display: flex;
padding-left: 5px;
padding-right: 5px;
cursor: pointer;
min-width: 65px;
}
.tabbedPanel.vertical .tabList .tab-header {
.tabbedPanel.vertical > .title .tabList .tab-header {
display: block;
text-transform: none;
text-overflow: ellipsis;
overflow: hidden;
width: auto;
height: 50px;
line-height: 45px;
min-width: 150px;
line-height: 35px;
}
.tabbedPanel .tabList .tab .tabIcon.codicon {
@@ -85,8 +95,6 @@ panel {
.tabbedPanel .composite.title .title-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
@@ -110,22 +118,22 @@ panel {
flex-direction: row;
}
.tabbedPanel.vertical > .title {
.tabbedPanel.vertical>.title {
flex: 0 0 auto;
flex-direction: column;
height: 100%;
}
.tabbedPanel > .tab-content {
flex: 1;
.tabbedPanel>.tab-content {
flex: 1 1 auto;
position: relative;
}
.tabbedPanel.vertical > .title > .tabContainer > .monaco-scrollable-element > .tabList {
.tabbedPanel.vertical>.title > .tabContainer .tabList {
flex-flow: column;
}
.tabbedPanel.horizontal > .title > .tabContainer > .monaco-scrollable-element > .tabList {
.tabbedPanel.horizontal > .title > .tabContainer .tabList {
flex-flow: row;
}
@@ -153,9 +161,11 @@ panel {
border-color: rgb(214, 214, 214);
}
.tabbedPanel .action-container {
.tabbedPanel .vertical-tab-action-container {
display: flex;
flex-flow: row-reverse;
height: 35px;
padding: 0px 5px;
}
.tabbedPanel .tab-action {
@@ -164,12 +174,13 @@ panel {
padding: 0px;
border: 0px;
background-color: transparent;
background-position: 2px center;
background-position: center;
background-repeat: no-repeat;
background-size: 11px 11px;
background-size: 9px 9px;
align-self: center;
}
.vs .tabbedPanel .tab-action.collapse{
.vs .tabbedPanel .tab-action.collapse {
background-image: url("collapse.svg");
}
@@ -52,16 +52,16 @@ let idPool = 0;
<div class="tabbedPanel fullsize" [ngClass]="options.layout === NavigationBarLayout.vertical ? 'vertical' : 'horizontal'">
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title">
<div class="tabContainer">
<div *ngIf="options.layout === NavigationBarLayout.vertical" class="action-container">
<div *ngIf="options.layout === NavigationBarLayout.vertical" class="vertical-tab-action-container">
<button [attr.aria-expanded]="_tabExpanded" [title]="toggleTabPanelButtonAriaLabel" [attr.aria-label]="toggleTabPanelButtonAriaLabel" [ngClass]="toggleTabPanelButtonCssClass" tabindex="0" (click)="toggleTabPanel()"></button>
</div>
<div *ngIf="_tabExpanded" class="tabList" role="tablist" scrollable [horizontalScroll]="AutoScrollbarVisibility" [verticalScroll]="HiddenScrollbarVisibility" [scrollYToX]="true" (keydown)="onKey($event)">
<div [style.display]="_tabExpanded ? 'flex': 'none'" [attr.aria-hidden]="_tabExpanded ? 'false': 'true'" class="tabList" role="tablist" scrollable [horizontalScroll]="AutoScrollbarVisibility" [verticalScroll]="HiddenScrollbarVisibility" [scrollYToX]="true" (keydown)="onKey($event)">
<div role="presentation" *ngFor="let tab of _tabs">
<ng-container *ngIf="tab.type!=='group-header'">
<tab-header role="presentation" [active]="_activeTab === tab" [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'></tab-header>
</ng-container>
<ng-container *ngIf="tab.type==='group-header' && options.layout === NavigationBarLayout.vertical">
<div class="tab-group-header" *ngIf="_tabExpanded">
<div class="tab-group-header">
<span>{{tab.title}}</span>
</div>
</ng-container >
@@ -69,7 +69,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
const tabLabelContainer = this._tabLabelRef.nativeElement as HTMLElement;
if (this.showIcon && this.tab.iconClass) {
const tabIconContainer = this._tabIconRef.nativeElement as HTMLElement;
tabIconContainer.className = 'tabIcon codicon';
tabIconContainer.className = 'tabIcon codicon icon';
tabIconContainer.classList.add(this.tab.iconClass);
}
+43 -30
View File
@@ -5,7 +5,7 @@
import 'vs/css!./media/selectBox';
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles, ISelectBoxOptions, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles, ISelectBoxOptions, ISelectOptionItem, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { Color } from 'vs/base/common/color';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import * as dom from 'vs/base/browser/dom';
@@ -65,29 +65,18 @@ export class SelectBox extends vsSelectBox {
private element?: HTMLElement;
constructor(options: SelectOptionItemSQL[] | string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement, selectBoxOptions?: ISelectBoxOptions) {
let optionItems: SelectOptionItemSQL[];
if (Array.isArray<string>(options)) {
optionItems = (options as string[]).map(o => {
return { text: o, value: o } as SelectOptionItemSQL;
});
} else {
optionItems = options;
}
let optionItems: SelectOptionItemSQL[] = SelectBox.createOptions(options);
super(optionItems, 0, contextViewProvider, undefined, selectBoxOptions);
this._optionsDictionary = new Map<string, number>();
for (let i = 0; i < options.length; i++) {
this._optionsDictionary.set(optionItems[i].value, i);
}
this.populateOptionsDictionary(optionItems);
const option = this._optionsDictionary.get(selectedOption);
if (option) {
super.select(option);
}
this._selectedOption = selectedOption;
this._dialogOptions = optionItems;
this._register(this.onDidSelect(newInput => {
const selected = optionItems[newInput.index];
this._selectedOption = selected.value;
this._register(this.onDidSelect(newSelect => {
this.onSelect(newSelect);
}));
this.enabledSelectBackground = this.selectBackground;
@@ -130,6 +119,38 @@ export class SelectBox extends vsSelectBox {
}
}
public onSelect(newInput: ISelectData) {
const selected = this._dialogOptions[newInput.index];
this._selectedOption = selected.value;
}
private static createOptions(options: SelectOptionItemSQL[] | string[] | ISelectOptionItem[]): SelectOptionItemSQL[] {
let selectOptions: SelectOptionItemSQL[];
if (Array.isArray<string>(options) && typeof (options[0]) === 'string') {
selectOptions = options.map(o => {
return { text: o, value: o } as SelectOptionItemSQL;
});
} else { // Handle both SelectOptionItemSql and ISelectOptionItem
const temp = (options as SelectOptionItemSQL[]);
selectOptions = temp.map(opt => {
if (opt.value === undefined) {
opt.value = opt.text;
}
return opt;
});
}
return selectOptions;
}
public populateOptionsDictionary(options: SelectOptionItemSQL[]) {
this._optionsDictionary = new Map<string, number>();
for (let i = 0; i < options.length; i++) {
this._optionsDictionary.set(options[i].value, i);
}
this._dialogOptions = options;
}
public style(styles: ISelectBoxStyles): void {
super.style(styles);
this.enabledSelectBackground = this.selectBackground;
@@ -165,18 +186,10 @@ export class SelectBox extends vsSelectBox {
}
}
public setOptions(options: string[] | ISelectOptionItem[], selected?: number): void {
let selectOptions: SelectOptionItemSQL[];
if (options.length > 0 && typeof options[0] !== 'string') {
selectOptions = options as SelectOptionItemSQL[];
} else {
selectOptions = (options as string[]).map(o => { return { text: o, value: o } as SelectOptionItemSQL; });
}
this._optionsDictionary = new Map<string, number>();
for (let i = 0; i < selectOptions.length; i++) {
this._optionsDictionary.set(selectOptions[i].value, i);
}
this._dialogOptions = selectOptions;
public setOptions(options: string[] | SelectOptionItemSQL[] | ISelectOptionItem[], selected?: number): void {
let selectOptions: SelectOptionItemSQL[] = SelectBox.createOptions(options);
this.populateOptionsDictionary(selectOptions);
super.setOptions(selectOptions, selected);
}
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SelectBox, SelectOptionItemSQL } from 'sql/base/browser/ui/selectBox/selectBox';
import { deepClone, equals } from 'vs/base/common/objects';
const options: SelectOptionItemSQL[] = [
{ text: 't1', value: 'v1' },
{ text: 't2', value: 'v2' }
];
suite('Select Box tests', () => {
test('default value', () => {
const sb = new SelectBox(options, options[1].value, undefined, undefined, undefined);
assert(sb.value === options[1].value);
});
test('values change', () => {
const sb = new SelectBox(options, options[1].value, undefined, undefined, undefined);
const newOptions = deepClone(options);
{
const moreOptions: SelectOptionItemSQL[] = [
{ text: 't3', value: 'v3' },
{ text: 't4', value: 'v4' }
];
newOptions.push(...moreOptions);
}
sb.setOptions(newOptions);
assert(equals(sb.values, newOptions.map(s => s.value)));
});
test('the selected option changes', () => {
const sb = new SelectBox(options, options[1].value, undefined, undefined, undefined);
sb.onSelect({
index: 0,
selected: options[0].value
});
assert(sb.value === options[0].value);
});
test('values get auto populated', () => {
const newOptions = deepClone(options).map(s => { return { text: s.text, value: undefined }; });
const sb = new SelectBox(newOptions, undefined, undefined, undefined, undefined);
assert(equals(sb.values, newOptions.map(s => s.text)));
});
});
@@ -499,22 +499,23 @@ class TabbedPanelComponentBuilder extends ContainerBuilderImpl<azdata.TabbedPane
items.forEach(item => {
if (item && 'tabs' in item) {
item.tabs.forEach(tab => {
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, item.title));
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon));
});
} else {
const tab = <azdata.Tab>item;
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id));
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon));
}
});
this._component.itemConfigs = itemConfigs;
return this;
}
toItemConfig(content: azdata.Component, title: string, id?: string, group?: string): InternalItemConfig {
toItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig {
return new InternalItemConfig(content as ComponentWrapper, {
title: title,
group: group,
id: id
id: id,
icon: icon
});
}
}
@@ -457,6 +457,57 @@ class WizardImpl implements azdata.window.Wizard {
}
}
class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard {
constructor(
private _editor: ModelViewEditorImpl
) {
}
registerTabs(handler: (view: azdata.ModelView) => Thenable<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>): void {
this._editor.registerContent(async (view) => {
const dashboardTabs = await handler(view);
const tabs: (azdata.TabGroup | azdata.Tab)[] = [];
dashboardTabs.forEach((item: azdata.DashboardTab | azdata.DashboardTabGroup) => {
if ('tabs' in item) {
tabs.push(<azdata.TabGroup>{
title: item.title,
tabs: item.tabs.map(tab => {
return this.createTab(tab, view);
})
});
} else {
tabs.push(this.createTab(item, view));
}
});
const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs(tabs).withLayout({
orientation: 'vertical',
showIcon: true
}).component();
return view.initializeModel(tabbedPanel);
});
}
open(): Thenable<void> {
return this._editor.openEditor();
}
createTab(tab: azdata.DashboardTab, view: azdata.ModelView): azdata.Tab {
if (tab.toolbar) {
const flexContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
flexContainer.addItem(tab.toolbar, { flex: '0 0 auto' });
flexContainer.addItem(tab.content, { flex: '1 1 auto' });
return {
title: tab.title,
id: tab.id,
content: flexContainer,
icon: tab.icon
};
} else {
return tab;
}
}
}
export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
private static _currentHandle = 0;
@@ -562,6 +613,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
return editor;
}
public createModelViewDashboard(title: string, extension: IExtensionDescription): azdata.window.ModelViewDashboard {
const editor = this.createModelViewEditor(title, extension, { supportsSave: false }) as ModelViewEditorImpl;
return new ModelViewDashboardImpl(editor);
}
public updateDialogContent(dialog: azdata.window.Dialog): void {
let handle = this.getHandle(dialog);
let tabs = dialog.content;
@@ -418,6 +418,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
createWizard(title: string): azdata.window.Wizard {
return extHostModelViewDialog.createWizard(title);
},
createModelViewDashboard(title: string): azdata.window.ModelViewDashboard {
return extHostModelViewDialog.createModelViewDashboard(title, extension);
},
MessageLevel: sqlExtHostTypes.MessageLevel
};
@@ -397,7 +397,8 @@ export enum AzureResource {
Sql = 1,
OssRdbms = 2,
AzureKeyVault = 3,
Graph = 4
Graph = 4,
MicrosoftResourceManagement = 5
}
export class TreeItem extends vsExtTypes.TreeItem {
@@ -838,4 +839,5 @@ export enum TabOrientation {
export interface TabbedPanelLayout {
orientation: TabOrientation;
showIcon: boolean;
}
@@ -332,6 +332,7 @@ export abstract class ContainerBase<T> extends ComponentBase {
}
}));
this._changeRef.detectChanges();
this.onItemsUpdated();
return;
}
@@ -343,6 +344,7 @@ export abstract class ContainerBase<T> extends ComponentBase {
if (index >= 0) {
this.items.splice(index, 1);
this._changeRef.detectChanges();
this.onItemsUpdated();
return true;
}
return false;
@@ -373,4 +375,7 @@ export abstract class ContainerBase<T> extends ComponentBase {
}
abstract setLayout(layout: any): void;
protected onItemsUpdated(): void {
}
}
@@ -8,25 +8,30 @@
height: 100%;
}
.tabbedpanel-component .tabbedPanel {
border-top-width: 0px;
}
.tabbedpanel-component .tabbedPanel .tabContainer {
border-style: solid;
border-color: rgb(237, 235, 233);
border-width: 0px;
}
.vs-dark .tabbedpanel-component .tabbedPanel .tabContainer, .hc-black .tabbedpanel-component .tabbedPanel .tabContainer {
border-color: rgba(128, 128, 128, 0.5);;
.vs-dark .tabbedpanel-component .tabbedPanel .tabContainer,
.hc-black .tabbedpanel-component .tabbedPanel .tabContainer {
border-color: rgba(128, 128, 128, 0.5);
}
.tabbedpanel-component .tabbedPanel.vertical .tabContainer {
.tabbedpanel-component .tabbedPanel.vertical > .title .tabContainer {
border-right-width: 1px;
}
.tabbedpanel-component .tabbedPanel.horizontal .tabContainer {
.tabbedpanel-component .tabbedPanel.horizontal > .title .tabContainer {
border-bottom-width: 1px;
}
.tabbedpanel-component .tabbedPanel .tab>.tabLabel.active {
.tabbedpanel-component .tabbedPanel .tab > .tabLabel.active {
border-bottom: 0px solid;
}
@@ -45,9 +50,10 @@
}
.tabbedpanel-component .tabList .tab-header.active {
background-color: rgb(237, 235, 233);
background-color: #E1F0FE;
}
.vs-dark .tabbedpanel-component .tabList .tab-header.active, .hc-black .tabbedpanel-component .tabList .tab-header.active {
.vs-dark .tabbedpanel-component .tabList .tab-header.active,
.hc-black .tabbedpanel-component .tabList .tab-header.active {
background-color: rgba(128, 128, 128, 0.5);
}
@@ -7,10 +7,9 @@
<div class="tabbedpanel-component">
<panel (onTabChange)="handleTabChange($event)">
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize"
[identifier]="tab.id" [type]="tab.type">
[identifier]="tab.id" [type]="tab.type" [iconClass]="tab.iconClass">
<ng-template>
<ng-container *ngIf="tab.type === 'tab'">
{{tab.title}}
<model-component-wrapper [descriptor]="tab.content" [modelStore]="modelStore">
</model-component-wrapper>
</ng-container>
@@ -10,11 +10,13 @@ import { TabOrientation, TabbedPanelLayout } from 'sql/workbench/api/common/sqlE
import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase';
import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces';
import 'vs/css!./media/tabbedPanel';
import { IUserFriendlyIcon, createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils';
export interface TabConfig {
title: string;
id?: string;
group: string;
icon?: IUserFriendlyIcon;
}
interface Tab {
@@ -22,6 +24,7 @@ interface Tab {
content?: IComponentDescriptor;
id?: string;
type: TabType;
iconClass?: string;
}
@Component({
@@ -56,7 +59,7 @@ export default class TabbedPanelComponent extends ContainerBase<TabConfig> imple
this._panel.options = {
showTabsWhenOne: true,
layout: layout.orientation === TabOrientation.Horizontal ? NavigationBarLayout.horizontal : NavigationBarLayout.vertical,
showIcon: false
showIcon: layout.showIcon
};
}
@@ -86,6 +89,7 @@ export default class TabbedPanelComponent extends ContainerBase<TabConfig> imple
title: item.config.title,
id: item.config.id,
content: item.descriptor,
iconClass: item.config.icon ? createIconCssClass(item.config.icon) : undefined,
type: 'tab'
});
}
@@ -93,4 +97,11 @@ export default class TabbedPanelComponent extends ContainerBase<TabConfig> imple
}
return this._tabs;
}
onItemsUpdated(): void {
const firstTabIndex = this.tabs.findIndex(tab => tab.type === 'tab');
if (firstTabIndex >= 0) {
this._panel.selectTab(firstTabIndex);
}
}
}
@@ -17,7 +17,6 @@ import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { Event, Emitter } from 'vs/base/common/event';
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
import { values } from 'vs/base/common/collections';
import { fill } from 'vs/base/common/arrays';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
export interface GridCellConfig {
@@ -243,7 +242,7 @@ export class DashboardGridContainer extends DashboardTab implements OnDestroy {
private createIndexes(indexes: number[]) {
const max = Math.max(...indexes) + 1;
return fill(max, 0).map((x, i) => i);
return new Array(max).fill(0).map((x, i) => i);
}
ngOnDestroy() {
@@ -1 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 50 50" height="50px" id="Layer_1" version="1.1" viewBox="0 0 50 50" width="50px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect fill="none" height="50" width="50"/><polyline fill="none" points="44,21 44,49 6,49 6,21 " stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><polyline fill="none" points="19,49 19,28 31,28 31,49 " stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><polygon points="35,5 35,8.016 37,10.094 37,7 39,7 39,12.203 41,14.266 41,5 "/><polyline fill="none" points=" 1.11,25.942 25,1.053 48.89,25.943 " stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/></svg>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;}</style></defs><title>bv</title><path d="M8,1.289l7.352,7.359-.7.7L14,8.711V15H9V10H7v5H2V8.711l-.648.641-.7-.7ZM13,14V7.711l-5-5-5,5V14H6V9h4v5Z"/><rect class="cls-1" width="16" height="16"/></svg>

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 322 B

@@ -1,15 +1 @@
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg">
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<rect id="svg_1" width="50" height="50" fill="none"/>
<polyline id="svg_2" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke="#ffffff" points="44,21 44,49 6,49 6,21 " fill="none"/>
<polyline id="svg_3" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke="#ffffff" points="18.9292186871171,49 18.9292186871171,28 30.9292186871171,28 30.9292186871171,49 " fill="none"/>
<polygon fill="#ffffff" stroke="#000000" stroke-opacity="0" id="svg_4" points="35,5 35,8.016 37,10.094 37,7 39,7 39,12.203 41,14.266 41,5 "/>
<polyline id="svg_5" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round" stroke-linecap="round" stroke="#ffffff" points=" 1.11,25.942 25,1.053 48.89,25.943 " fill="none"/>
</g>
</svg>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:none;}</style></defs><title>bv</title><path class="cls-1" d="M8,1.289l7.352,7.359-.7.7L14,8.711V15H9V10H7v5H2V8.711l-.648.641-.7-.7ZM13,14V7.711l-5-5-5,5V14H6V9h4v5Z"/><rect class="cls-2" width="16" height="16"/></svg>

Before

Width:  |  Height:  |  Size: 975 B

After

Width:  |  Height:  |  Size: 354 B

@@ -84,16 +84,6 @@ const defaultVal = [
sizey: 1
}
},
{
name: nls.localize('databasesWidgetTitle', "Search"),
gridItemConfig: {
sizex: 1,
sizey: 2
},
widget: {
'explorer-widget': {}
}
},
{
widget: {
'backup-history-server-insight': null
@@ -291,6 +291,11 @@ export class EditDataGridPanel extends GridParentComponent {
return;
}
if (this.isNullRow(row)) {
self.addRow(row);
self.refreshGrid();
}
let cellSelectTasks: Promise<void> = this.submitCurrentCellChange(
(result: EditUpdateCellResult) => {
// Cell update was successful, update the flags
@@ -543,15 +548,7 @@ export class EditDataGridPanel extends GridParentComponent {
private submitCurrentCellChange(resultHandler, errorHandler): Promise<void> {
let self = this;
let updateCellPromise: Promise<void> = Promise.resolve();
let refreshGrid = false;
if (this.currentCell && this.currentCell.isEditable && this.currentEditCellValue !== undefined && !this.removingNewRow) {
if (this.isNullRow(this.currentCell.row)) {
refreshGrid = true;
// We've entered the "new row", so we need to add a row and jump to it
updateCellPromise = updateCellPromise.then(() => {
return self.addRow(this.currentCell.row);
});
}
// We're exiting a read/write cell after having changed the value, update the cell value in the service
updateCellPromise = updateCellPromise.then(() => {
// Use the mapped row ID if we're on that row
@@ -564,9 +561,6 @@ export class EditDataGridPanel extends GridParentComponent {
result => {
self.currentEditCellValue = undefined;
let refreshPromise: Thenable<void> = Promise.resolve();
if (refreshGrid) {
refreshPromise = self.refreshGrid();
}
return refreshPromise.then(() => {
return resultHandler(result);
});
@@ -32,7 +32,7 @@ import { tableBackground, cellBackground, cellBorderColor } from 'sql/platform/t
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
import { find, fill } from 'vs/base/common/arrays';
import { find } from 'vs/base/common/arrays';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
import { onUnexpectedError } from 'vs/base/common/errors';
@@ -672,7 +672,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
// if the durations are all 0 secs, show minimal chart
// instead of nothing
if (zeroDurationJobCount === jobHistories.length) {
return fill(jobHistories.length, '5px');
return new Array(jobHistories.length).fill('5px');
} else {
return chartHeights;
}
@@ -33,7 +33,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { find, fill } from 'vs/base/common/arrays';
import { find } from 'vs/base/common/arrays';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
@@ -731,7 +731,7 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit,
// if the durations are all 0 secs, show minimal chart
// instead of nothing
if (zeroDurationJobCount === jobHistories.length) {
return fill(jobHistories.length, '5px');
return new Array(jobHistories.length).fill('5px');
} else {
return chartHeights;
}
@@ -50,6 +50,7 @@ import { find } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/collections';
import { assign } from 'vs/base/common/objects';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class ConnectionManagementService extends Disposable implements IConnectionManagementService {
@@ -90,7 +91,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
@IAccountManagementService private _accountManagementService: IAccountManagementService,
@ILogService private _logService: ILogService,
@IStorageService private _storageService: IStorageService,
@IEnvironmentService private _environmentService: IEnvironmentService
@IEnvironmentService private _environmentService: IEnvironmentService,
@IExtensionService private readonly extensionService: IExtensionService
) {
super();
@@ -819,6 +821,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
options: connection.options
});
await this.extensionService.activateByEvent(`onConnect:${connection.providerName}`);
return this._providers.get(connection.providerName).onReady.then((provider) => {
provider.connect(uri, connectionInfo);
this._onConnectRequestSent.fire();
@@ -698,7 +698,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
if (this.authType === AuthenticationType.AzureMFA || this.authType === AuthenticationType.AzureMFAAndUser) {
this.fillInAzureAccountOptions().then(async () => {
let accountName = (this.authType === AuthenticationType.AzureMFA)
? connectionInfo.userName : connectionInfo.azureAccount;
? connectionInfo.azureAccount : connectionInfo.userName;
this._azureAccountDropdown.selectWithOptionName(this.getModelValue(accountName));
await this.onAzureAccountSelected();
let tenantId = connectionInfo.azureTenantId;
@@ -35,6 +35,7 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { assign } from 'vs/base/common/objects';
import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
suite('SQL ConnectionManagementService tests', () => {
@@ -164,7 +165,8 @@ suite('SQL ConnectionManagementService tests', () => {
accountManagementService.object,
new NullLogService(), // ILogService
undefined, // IStorageService
TestEnvironmentService
TestEnvironmentService,
getBasicExtensionService()
);
return connectionManagementService;
}
@@ -922,7 +924,7 @@ suite('SQL ConnectionManagementService tests', () => {
connectionStoreMock.setup(x => x.getConnectionProfileGroups(TypeMoq.It.isAny(), undefined)).returns(() => {
return [group1];
});
const connectionManagementService = new ConnectionManagementService(connectionStoreMock.object, connectionStatusManagerMock.object, undefined, undefined, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
const connectionManagementService = new ConnectionManagementService(connectionStoreMock.object, connectionStatusManagerMock.object, undefined, undefined, undefined, undefined, undefined, new TestCapabilitiesService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, getBasicExtensionService());
// dupe connections have been seeded the numbers below already reflected the de-duped results
@@ -968,3 +970,9 @@ function createConnectionProfile(id: string): ConnectionProfile {
function createConnectionGroup(id: string): ConnectionProfileGroup {
return new ConnectionProfileGroup(id, undefined, id, undefined, undefined);
}
function getBasicExtensionService(): IExtensionService {
return <any>{
activateByEvent: () => Promise.resolve()
};
}
@@ -5,7 +5,7 @@
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?70edf2c63384951357ed8517386759dd") format("truetype");
src: url("./codicon.ttf?a76e99e42eab7c1a55601640b708d820") format("truetype");
}
.codicon[class*='codicon-'] {
@@ -416,11 +416,12 @@
.codicon-feedback:before { content: "\eb96" }
.codicon-group-by-ref-type:before { content: "\eb97" }
.codicon-ungroup-by-ref-type:before { content: "\eb98" }
.codicon-account:before { content: "\f101" }
.codicon-bell-dot:before { content: "\f102" }
.codicon-debug-alt-2:before { content: "\f103" }
.codicon-debug-alt:before { content: "\f104" }
.codicon-debug-console:before { content: "\f105" }
.codicon-library:before { content: "\f106" }
.codicon-output:before { content: "\f107" }
.codicon-run-all:before { content: "\f108" }
.codicon-account:before { content: "\eb99" }
.codicon-bell-dot:before { content: "\eb9a" }
.codicon-debug-console:before { content: "\eb9b" }
.codicon-library:before { content: "\eb9c" }
.codicon-output:before { content: "\eb9d" }
.codicon-run-all:before { content: "\eb9e" }
.codicon-sync-ignored:before { content: "\eb9f" }
.codicon-debug-alt-2:before { content: "\f101" }
.codicon-debug-alt:before { content: "\f102" }
+2 -2
View File
@@ -105,7 +105,7 @@ export abstract class Pane extends Disposable implements IView {
get minimumSize(): number {
const headerSize = this.headerSize;
const expanded = !this.headerVisible || this.isExpanded();
const minimumBodySize = expanded ? this._minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
const minimumBodySize = expanded ? this.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
return headerSize + minimumBodySize;
}
@@ -113,7 +113,7 @@ export abstract class Pane extends Disposable implements IView {
get maximumSize(): number {
const headerSize = this.headerSize;
const expanded = !this.headerVisible || this.isExpanded();
const maximumBodySize = expanded ? this._maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
const maximumBodySize = expanded ? this.maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
return headerSize + maximumBodySize;
}
-11
View File
@@ -472,17 +472,6 @@ export function range(arg: number, to?: number): number[] {
return result;
}
/**
* @deprecated ES6: use `Array.fill`
*/
export function fill<T>(num: number, value: T, arr: T[] = []): T[] {
for (let i = 0; i < num; i++) {
arr[i] = value;
}
return arr;
}
export function index<T>(array: ReadonlyArray<T>, indexer: (t: T) => string): { [key: string]: T; };
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; };
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } {
+39 -6
View File
@@ -94,8 +94,14 @@ export class VSBuffer {
return new VSBuffer(this.buffer.subarray(start!/*bad lib.d.ts*/, end));
}
set(array: VSBuffer, offset?: number): void {
this.buffer.set(array.buffer, offset);
set(array: VSBuffer, offset?: number): void;
set(array: Uint8Array, offset?: number): void;
set(array: VSBuffer | Uint8Array, offset?: number): void {
if (array instanceof VSBuffer) {
this.buffer.set(array.buffer, offset);
} else {
this.buffer.set(array, offset);
}
}
readUInt32BE(offset: number): number {
@@ -106,6 +112,14 @@ export class VSBuffer {
writeUInt32BE(this.buffer, value, offset);
}
readUInt32LE(offset: number): number {
return readUInt32LE(this.buffer, offset);
}
writeUInt32LE(value: number, offset: number): void {
writeUInt32LE(this.buffer, value, offset);
}
readUInt8(offset: number): number {
return readUInt8(this.buffer, offset);
}
@@ -117,15 +131,15 @@ export class VSBuffer {
export function readUInt16LE(source: Uint8Array, offset: number): number {
return (
source[offset]
+ source[offset + 1] * 2 ** 8
((source[offset + 0] << 0) >>> 0) |
((source[offset + 1] << 8) >>> 0)
);
}
export function writeUInt16LE(destination: Uint8Array, value: number, offset: number): void {
destination[offset] = value;
destination[offset + 0] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 1] = value;
destination[offset + 1] = (value & 0b11111111);
}
export function readUInt32BE(source: Uint8Array, offset: number): number {
@@ -147,6 +161,25 @@ export function writeUInt32BE(destination: Uint8Array, value: number, offset: nu
destination[offset] = value;
}
export function readUInt32LE(source: Uint8Array, offset: number): number {
return (
((source[offset + 0] << 0) >>> 0) |
((source[offset + 1] << 8) >>> 0) |
((source[offset + 2] << 16) >>> 0) |
((source[offset + 3] << 24) >>> 0)
);
}
export function writeUInt32LE(destination: Uint8Array, value: number, offset: number): void {
destination[offset + 0] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 1] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 2] = (value & 0b11111111);
value = value >>> 8;
destination[offset + 3] = (value & 0b11111111);
}
export function readUInt8(source: Uint8Array, offset: number): number {
return source[offset];
}
+4 -5
View File
@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import { sep } from 'vs/base/common/path';
import { IdleValue } from 'vs/base/common/async';
@@ -133,8 +132,8 @@ export function compareAnything(one: string, other: string, lookFor: string): nu
}
// Sort suffix matches over non suffix matches
const elementASuffixMatch = strings.endsWith(elementAName, lookFor);
const elementBSuffixMatch = strings.endsWith(elementBName, lookFor);
const elementASuffixMatch = elementAName.endsWith(lookFor);
const elementBSuffixMatch = elementBName.endsWith(lookFor);
if (elementASuffixMatch !== elementBSuffixMatch) {
return elementASuffixMatch ? -1 : 1;
}
@@ -154,8 +153,8 @@ export function compareByPrefix(one: string, other: string, lookFor: string): nu
const elementBName = other.toLowerCase();
// Sort prefix matches over non prefix matches
const elementAPrefixMatch = strings.startsWith(elementAName, lookFor);
const elementBPrefixMatch = strings.startsWith(elementBName, lookFor);
const elementAPrefixMatch = elementAName.startsWith(lookFor);
const elementBPrefixMatch = elementBName.startsWith(lookFor);
if (elementAPrefixMatch !== elementBPrefixMatch) {
return elementAPrefixMatch ? -1 : 1;
}
+3 -3
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { isWindows } from 'vs/base/common/platform';
import { startsWithIgnoreCase, equalsIgnoreCase, endsWith, rtrim } from 'vs/base/common/strings';
import { startsWithIgnoreCase, equalsIgnoreCase, rtrim } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path';
@@ -235,7 +235,7 @@ export function isWindowsDriveLetter(char0: number): boolean {
export function sanitizeFilePath(candidate: string, cwd: string): string {
// Special case: allow to open a drive letter without trailing backslash
if (isWindows && endsWith(candidate, ':')) {
if (isWindows && candidate.endsWith(':')) {
candidate += sep;
}
@@ -252,7 +252,7 @@ export function sanitizeFilePath(candidate: string, cwd: string): string {
candidate = rtrim(candidate, sep);
// Special case: allow to open drive root ('C:\')
if (endsWith(candidate, ':')) {
if (candidate.endsWith(':')) {
candidate += sep;
}
+53 -27
View File
@@ -25,15 +25,15 @@ export function score(target: string, query: IPreparedQuery, fuzzy: boolean): Sc
return scoreMultiple(target, query.values, fuzzy);
}
return scoreSingle(target, query.value, query.valueLowercase, fuzzy);
return scoreSingle(target, query.normalized, query.normalizedLowercase, fuzzy);
}
function scoreMultiple(target: string, query: IPreparedQueryPiece[], fuzzy: boolean): Score {
let totalScore = NO_MATCH;
const totalPositions: number[] = [];
for (const { value, valueLowercase } of query) {
const [scoreValue, positions] = scoreSingle(target, value, valueLowercase, fuzzy);
for (const { normalized, normalizedLowercase } of query) {
const [scoreValue, positions] = scoreSingle(target, normalized, normalizedLowercase, fuzzy);
if (scoreValue === NO_MATCH) {
// if a single query value does not match, return with
// no score entirely, we require all queries to match
@@ -338,11 +338,26 @@ const LABEL_CAMELCASE_SCORE = 1 << 16;
const LABEL_SCORE_THRESHOLD = 1 << 15;
export interface IPreparedQueryPiece {
/**
* The original query as provided as input.
*/
original: string;
originalLowercase: string;
value: string;
valueLowercase: string;
/**
* Original normalized to platform separators:
* - Windows: \
* - Posix: /
*/
pathNormalized: string;
/**
* In addition to the normalized path, will have
* whitespace and wildcards removed.
*/
normalized: string;
normalizedLowercase: string;
}
export interface IPreparedQuery extends IPreparedQueryPiece {
@@ -364,17 +379,21 @@ export function prepareQuery(original: string): IPreparedQuery {
}
const originalLowercase = original.toLowerCase();
const value = prepareQueryValue(original);
const valueLowercase = value.toLowerCase();
const containsPathSeparator = value.indexOf(sep) >= 0;
const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original);
const containsPathSeparator = pathNormalized.indexOf(sep) >= 0;
let values: IPreparedQueryPiece[] | undefined = undefined;
const originalSplit = original.split(MULTIPL_QUERY_VALUES_SEPARATOR);
if (originalSplit.length > 1) {
for (const originalPiece of originalSplit) {
const valuePiece = prepareQueryValue(originalPiece);
if (valuePiece) {
const {
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
} = normalizeQuery(originalPiece);
if (normalizedPiece) {
if (!values) {
values = [];
}
@@ -382,29 +401,36 @@ export function prepareQuery(original: string): IPreparedQuery {
values.push({
original: originalPiece,
originalLowercase: originalPiece.toLowerCase(),
value: valuePiece,
valueLowercase: valuePiece.toLowerCase()
pathNormalized: pathNormalizedPiece,
normalized: normalizedPiece,
normalizedLowercase: normalizedLowercasePiece
});
}
}
}
return { original, originalLowercase, value, valueLowercase, values, containsPathSeparator };
return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator };
}
function prepareQueryValue(original: string): string {
let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace
function normalizeQuery(original: string): { pathNormalized: string, normalized: string, normalizedLowercase: string } {
let pathNormalized: string;
if (isWindows) {
value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash
pathNormalized = original.replace(/\//g, sep); // Help Windows users to search for paths when using slash
} else {
value = value.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
}
return value;
const normalized = stripWildcards(pathNormalized).replace(/\s/g, '');
return {
pathNormalized,
normalized,
normalizedLowercase: normalized.toLowerCase()
};
}
export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): IItemScore {
if (!item || !query.value) {
if (!item || !query.normalized) {
return NO_ITEM_SCORE; // we need an item and query to score on at least
}
@@ -417,9 +443,9 @@ export function scoreItem<T>(item: T, query: IPreparedQuery, fuzzy: boolean, acc
let cacheHash: string;
if (description) {
cacheHash = `${label}${description}${query.value}${fuzzy}`;
cacheHash = `${label}${description}${query.normalized}${fuzzy}`;
} else {
cacheHash = `${label}${query.value}${fuzzy}`;
cacheHash = `${label}${query.normalized}${fuzzy}`;
}
const cached = cache[cacheHash];
@@ -455,7 +481,7 @@ function createMatches(offsets: undefined | number[]): IMatch[] {
function doScoreItem(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore {
// 1.) treat identity matches on full path highest
if (path && (isLinux ? query.original === path : equalsIgnoreCase(query.original, path))) {
if (path && (isLinux ? query.pathNormalized === path : equalsIgnoreCase(query.pathNormalized, path))) {
return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : undefined };
}
@@ -464,13 +490,13 @@ function doScoreItem(label: string, description: string | undefined, path: strin
if (preferLabelMatches) {
// 2.) treat prefix matches on the label second highest
const prefixLabelMatch = matchesPrefix(query.value, label);
const prefixLabelMatch = matchesPrefix(query.normalized, label);
if (prefixLabelMatch) {
return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch };
}
// 3.) treat camelcase matches on the label third highest
const camelcaseLabelMatch = matchesCamelCase(query.value, label);
const camelcaseLabelMatch = matchesCamelCase(query.normalized, label);
if (camelcaseLabelMatch) {
return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch };
}
@@ -702,17 +728,17 @@ function fallbackCompare<T>(itemA: T, itemB: T, query: IPreparedQuery, accessor:
// compare by label
if (labelA !== labelB) {
return compareAnything(labelA, labelB, query.value);
return compareAnything(labelA, labelB, query.normalized);
}
// compare by description
if (descriptionA && descriptionB && descriptionA !== descriptionB) {
return compareAnything(descriptionA, descriptionB, query.value);
return compareAnything(descriptionA, descriptionB, query.normalized);
}
// compare by path
if (pathA && pathB && pathA !== pathB) {
return compareAnything(pathA, pathB, query.value);
return compareAnything(pathA, pathB, query.normalized);
}
// equal
+4 -4
View File
@@ -302,7 +302,7 @@ function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): P
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
const base = pattern.substr(4); // '**/*'.length === 4
parsedPattern = function (path, basename) {
return typeof path === 'string' && strings.endsWith(path, base) ? pattern : null;
return typeof path === 'string' && path.endsWith(base) ? pattern : null;
};
} else if (match = T2.exec(trimForExclusions(pattern, options))) { // common pattern: **/some.txt just need basename check
parsedPattern = trivia2(match[1], pattern);
@@ -339,7 +339,7 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string |
}
function trimForExclusions(pattern: string, options: IGlobOptions): string {
return options.trimForExclusions && strings.endsWith(pattern, '/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
}
// common pattern: **/some.txt just need basename check
@@ -353,7 +353,7 @@ function trivia2(base: string, originalPattern: string): ParsedStringPattern {
if (basename) {
return basename === base ? originalPattern : null;
}
return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? originalPattern : null;
return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? originalPattern : null;
};
const basenames = [base];
parsedPattern.basenames = basenames;
@@ -398,7 +398,7 @@ function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): Par
const nativePath = paths.sep !== paths.posix.sep ? path.replace(ALL_FORWARD_SLASHES, paths.sep) : path;
const nativePathEnd = paths.sep + nativePath;
const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename) {
return typeof path === 'string' && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null;
return typeof path === 'string' && (path === nativePath || path.endsWith(nativePathEnd)) ? pattern : null;
} : function (path, basename) {
return typeof path === 'string' && path === nativePath ? pattern : null;
};
+4 -4
View File
@@ -5,7 +5,7 @@
import { URI } from 'vs/base/common/uri';
import { posix, normalize, win32, sep } from 'vs/base/common/path';
import { endsWith, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings';
import { startsWithIgnoreCase, rtrim } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
import { isEqual, basename, relativePath } from 'vs/base/common/resources';
@@ -117,7 +117,7 @@ export function tildify(path: string, userHome: string): string {
}
// Linux: case sensitive, macOS: case insensitive
if (isLinux ? startsWith(path, normalizedUserHome) : startsWithIgnoreCase(path, normalizedUserHome)) {
if (isLinux ? path.startsWith(normalizedUserHome) : startsWithIgnoreCase(path, normalizedUserHome)) {
path = `~/${path.substr(normalizedUserHome.length)}`;
}
@@ -210,7 +210,7 @@ export function shorten(paths: string[], pathSeparator: string = sep): string[]
// Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string.
// prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories.
const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(pathSeparator) > -1) ? pathSeparator + subpath : subpath;
const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep);
const isOtherPathEnding: boolean = paths[otherPathIndex].endsWith(subpathWithSep);
match = !isSubpathEnding || isOtherPathEnding;
}
@@ -221,7 +221,7 @@ export function shorten(paths: string[], pathSeparator: string = sep): string[]
let result = '';
// preserve disk drive or root prefix
if (endsWith(segments[0], ':') || prefix !== '') {
if (segments[0].endsWith(':') || prefix !== '') {
if (start === 1) {
// extend subpath to include disk drive prefix
start = 0;
-26
View File
@@ -56,32 +56,6 @@ export function setToString<K>(set: Set<K>): string {
return `Set(${set.size}) {${entries.join(', ')}}`;
}
/**
* @deprecated ES6: use `...Map.entries()`
*/
export function mapToSerializable(map: Map<string, string>): [string, string][] {
const serializable: [string, string][] = [];
map.forEach((value, key) => {
serializable.push([key, value]);
});
return serializable;
}
/**
* @deprecated ES6: use `new Map([[key1, value1],[key2, value2]])`
*/
export function serializableToMap(serializable: [string, string][]): Map<string, string> {
const items = new Map<string, string>();
for (const [key, value] of serializable) {
items.set(key, value);
}
return items;
}
export interface IKeyIterator {
reset(key: string): this;
next(): this;
+4 -4
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { basename, posix, extname } from 'vs/base/common/path';
import { endsWith, startsWithUTF8BOM, startsWith } from 'vs/base/common/strings';
import { startsWithUTF8BOM } from 'vs/base/common/strings';
import { coalesce } from 'vs/base/common/arrays';
import { match } from 'vs/base/common/glob';
import { URI } from 'vs/base/common/uri';
@@ -185,7 +185,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText
// Longest extension match
if (association.extension) {
if (!extensionMatch || association.extension.length > extensionMatch.extension!.length) {
if (endsWith(filename, association.extensionLowercase!)) {
if (filename.endsWith(association.extensionLowercase!)) {
extensionMatch = association;
}
}
@@ -259,11 +259,11 @@ export function suggestFilename(mode: string | undefined, prefix: string): strin
.map(assoc => assoc.extension);
const extensionsWithDotFirst = coalesce(extensions)
.filter(assoc => startsWith(assoc, '.'));
.filter(assoc => assoc.startsWith('.'));
if (extensionsWithDotFirst.length > 0) {
const candidateExtension = extensionsWithDotFirst[0];
if (endsWith(prefix, candidateExtension)) {
if (prefix.endsWith(candidateExtension)) {
// do not add the prefix if it already exists
// https://github.com/microsoft/vscode/issues/83603
return prefix;
+2 -2
View File
@@ -588,11 +588,11 @@ function _makeFsPath(uri: URI, keepDriveLetterCasing: boolean): string {
&& (uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z || uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)
&& uri.path.charCodeAt(2) === CharCode.Colon
) {
// windows drive letter: file:///c:/far/boo
if (!keepDriveLetterCasing) {
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2);
} else {
value = uri.path.substr(1, 2);
value = uri.path.substr(1);
}
} else {
// other path
+1 -2
View File
@@ -9,7 +9,6 @@ import * as fs from 'fs';
import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import { Event } from 'vs/base/common/event';
import { endsWith } from 'vs/base/common/strings';
import { promisify } from 'util';
import { isRootOrDriveLetter } from 'vs/base/common/extpath';
import { generateUuid } from 'vs/base/common/uuid';
@@ -492,7 +491,7 @@ export async function move(source: string, target: string): Promise<void> {
//
// 2.) The user tries to rename a file/folder that ends with a dot. This is not
// really possible to move then, at least on UNC devices.
if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || endsWith(source, '.')) {
if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) {
await copy(source, target);
await rimraf(source, RimRafMode.MOVE);
await updateMtime(target);
@@ -246,8 +246,8 @@
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
margin: 0;
width: 19px;
height: 100%;
padding: 0 2px;
vertical-align: middle;
}
+2 -3
View File
@@ -9,7 +9,6 @@ import { timeout } from 'vs/base/common/async';
import { mapToString, setToString } from 'vs/base/common/map';
import { basename } from 'vs/base/common/path';
import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs';
import { fill } from 'vs/base/common/arrays';
import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage';
interface IDatabaseConnection {
@@ -97,7 +96,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
});
keysValuesChunks.forEach(keysValuesChunk => {
this.prepare(connection, `INSERT INTO ItemTable VALUES ${fill(keysValuesChunk.length / 2, '(?,?)').join(',')}`, stmt => stmt.run(keysValuesChunk), () => {
this.prepare(connection, `INSERT INTO ItemTable VALUES ${new Array(keysValuesChunk.length / 2).fill('(?,?)').join(',')}`, stmt => stmt.run(keysValuesChunk), () => {
const keys: string[] = [];
let length = 0;
toInsert.forEach((value, key) => {
@@ -132,7 +131,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
});
keysChunks.forEach(keysChunk => {
this.prepare(connection, `DELETE FROM ItemTable WHERE key IN (${fill(keysChunk.length, '?').join(',')})`, stmt => stmt.run(keysChunk), () => {
this.prepare(connection, `DELETE FROM ItemTable WHERE key IN (${new Array(keysChunk.length).fill('?').join(',')})`, stmt => stmt.run(keysChunk), () => {
const keys: string[] = [];
toDelete.forEach(key => {
keys.push(key);
+32 -15
View File
@@ -857,41 +857,58 @@ suite('Fuzzy Scorer', () => {
});
test('prepareQuery', () => {
assert.equal(scorer.prepareQuery(' f*a ').value, 'fa');
assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa');
assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts');
assert.equal(scorer.prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase());
assert.equal(scorer.prepareQuery('model Tester.ts').value, 'modelTester.ts');
assert.equal(scorer.prepareQuery('Model Tester.ts').valueLowercase, 'modeltester.ts');
assert.equal(scorer.prepareQuery('model Tester.ts').normalized, 'modelTester.ts');
assert.equal(scorer.prepareQuery('Model Tester.ts').normalizedLowercase, 'modeltester.ts');
assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false);
assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true);
// with spaces
let query = scorer.prepareQuery('He*llo World');
assert.equal(query.original, 'He*llo World');
assert.equal(query.value, 'HelloWorld');
assert.equal(query.valueLowercase, 'HelloWorld'.toLowerCase());
assert.equal(query.normalized, 'HelloWorld');
assert.equal(query.normalizedLowercase, 'HelloWorld'.toLowerCase());
assert.equal(query.values?.length, 2);
assert.equal(query.values?.[0].original, 'He*llo');
assert.equal(query.values?.[0].value, 'Hello');
assert.equal(query.values?.[0].valueLowercase, 'Hello'.toLowerCase());
assert.equal(query.values?.[0].normalized, 'Hello');
assert.equal(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase());
assert.equal(query.values?.[1].original, 'World');
assert.equal(query.values?.[1].value, 'World');
assert.equal(query.values?.[1].valueLowercase, 'World'.toLowerCase());
assert.equal(query.values?.[1].normalized, 'World');
assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase());
// with spaces that are empty
query = scorer.prepareQuery(' Hello World ');
assert.equal(query.original, ' Hello World ');
assert.equal(query.originalLowercase, ' Hello World '.toLowerCase());
assert.equal(query.value, 'HelloWorld');
assert.equal(query.valueLowercase, 'HelloWorld'.toLowerCase());
assert.equal(query.normalized, 'HelloWorld');
assert.equal(query.normalizedLowercase, 'HelloWorld'.toLowerCase());
assert.equal(query.values?.length, 2);
assert.equal(query.values?.[0].original, 'Hello');
assert.equal(query.values?.[0].originalLowercase, 'Hello'.toLowerCase());
assert.equal(query.values?.[0].value, 'Hello');
assert.equal(query.values?.[0].valueLowercase, 'Hello'.toLowerCase());
assert.equal(query.values?.[0].normalized, 'Hello');
assert.equal(query.values?.[0].normalizedLowercase, 'Hello'.toLowerCase());
assert.equal(query.values?.[1].original, 'World');
assert.equal(query.values?.[1].originalLowercase, 'World'.toLowerCase());
assert.equal(query.values?.[1].value, 'World');
assert.equal(query.values?.[1].valueLowercase, 'World'.toLowerCase());
assert.equal(query.values?.[1].normalized, 'World');
assert.equal(query.values?.[1].normalizedLowercase, 'World'.toLowerCase());
// Path related
if (isWindows) {
assert.equal(scorer.prepareQuery('C:\\some\\path').pathNormalized, 'C:\\some\\path');
assert.equal(scorer.prepareQuery('C:\\some\\path').normalized, 'C:\\some\\path');
assert.equal(scorer.prepareQuery('C:\\some\\path').containsPathSeparator, true);
assert.equal(scorer.prepareQuery('C:/some/path').pathNormalized, 'C:\\some\\path');
assert.equal(scorer.prepareQuery('C:/some/path').normalized, 'C:\\some\\path');
assert.equal(scorer.prepareQuery('C:/some/path').containsPathSeparator, true);
} else {
assert.equal(scorer.prepareQuery('/some/path').pathNormalized, '/some/path');
assert.equal(scorer.prepareQuery('/some/path').normalized, '/some/path');
assert.equal(scorer.prepareQuery('/some/path').containsPathSeparator, true);
assert.equal(scorer.prepareQuery('\\some\\path').pathNormalized, '/some/path');
assert.equal(scorer.prepareQuery('\\some\\path').normalized, '/some/path');
assert.equal(scorer.prepareQuery('\\some\\path').containsPathSeparator, true);
}
});
});
+1 -14
View File
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, mapToSerializable, serializableToMap } from 'vs/base/common/map';
import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache } from 'vs/base/common/map';
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
@@ -629,17 +629,4 @@ suite('Map', () => {
// assert.equal(map.get(windowsFile), 'true');
// assert.equal(map.get(uncFile), 'true');
// });
test('mapToSerializable / serializableToMap', function () {
const map = new Map<string, string>();
map.set('1', 'foo');
map.set('2', null!);
map.set('3', 'bar');
const map2 = serializableToMap(mapToSerializable(map));
assert.equal(map2.size, map.size);
assert.equal(map2.get('1'), map.get('1'));
assert.equal(map2.get('2'), map.get('2'));
assert.equal(map2.get('3'), map.get('3'));
});
});
+3
View File
@@ -563,5 +563,8 @@ suite('URI', () => {
assertJoined(('file://ser/foo/'), '../../bazz', 'file://ser/foo/bazz', false);
assertJoined(('file://ser/foo'), '../../bazz', 'file://ser/foo/bazz', false);
//https://github.com/microsoft/vscode/issues/93831
assertJoined('file:///c:/foo/bar', './other/foo.img', 'file:///c:/foo/bar/other/foo.img', false);
});
});
+4 -4
View File
@@ -401,9 +401,9 @@ suite('Paths (Node Implementation)', () => {
];
resolveTests.forEach((test) => {
const resolve = test[0];
//@ts-ignore
//@ts-expect-error
test[1].forEach((test) => {
//@ts-ignore
//@ts-expect-error
const actual = resolve.apply(null, test[0]);
let actualAlt;
const os = resolve === path.win32.resolve ? 'win32' : 'posix';
@@ -579,9 +579,9 @@ suite('Paths (Node Implementation)', () => {
];
relativeTests.forEach((test) => {
const relative = test[0];
//@ts-ignore
//@ts-expect-error
test[1].forEach((test) => {
//@ts-ignore
//@ts-expect-error
const actual = relative(test[0], test[1]);
const expected = test[2];
const os = relative === path.win32.relative ? 'win32' : 'posix';
+1 -2
View File
@@ -61,7 +61,6 @@ import { Schemas } from 'vs/base/common/network';
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
import { startsWith } from 'vs/base/common/strings';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
@@ -178,7 +177,7 @@ export class CodeApplication extends Disposable {
const srcUri = URI.parse(source).fsPath.toLowerCase();
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
return startsWith(srcUri, rootUri + sep);
return srcUri.startsWith(rootUri + sep);
};
// Ensure defaults
+25 -8
View File
@@ -6,7 +6,7 @@
import * as path from 'vs/base/common/path';
import * as objects from 'vs/base/common/objects';
import * as nls from 'vs/nls';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
@@ -26,7 +26,6 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import * as perf from 'vs/base/common/performance';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { endsWith } from 'vs/base/common/strings';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
@@ -69,13 +68,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
private readonly _onClose = this._register(new Emitter<void>());
readonly onClose: CommonEvent<void> = this._onClose.event;
readonly onClose = this._onClose.event;
private readonly _onDestroy = this._register(new Emitter<void>());
readonly onDestroy: CommonEvent<void> = this._onDestroy.event;
readonly onDestroy = this._onDestroy.event;
private readonly _onLoad = this._register(new Emitter<void>());
readonly onLoad: CommonEvent<void> = this._onLoad.event;
readonly onLoad = this._onLoad.event;
private hiddenTitleBarStyle: boolean | undefined;
private showTimeoutHandle: NodeJS.Timeout | undefined;
@@ -83,7 +82,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private _readyState: ReadyState;
private windowState: IWindowState;
private currentMenuBarVisibility: MenuBarVisibility | undefined;
private representedFilename: string | undefined;
private documentEdited: boolean | undefined;
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[];
@@ -271,6 +272,22 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return this.representedFilename;
}
setDocumentEdited(edited: boolean): void {
if (isMacintosh) {
this._win.setDocumentEdited(edited);
}
this.documentEdited = edited;
}
isDocumentEdited(): boolean {
if (isMacintosh) {
return this._win.isDocumentEdited();
}
return !!this.documentEdited;
}
focus(): void {
if (!this._win) {
return;
@@ -349,7 +366,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
if (details.url.indexOf('.svg') > 0) {
const uri = URI.parse(details.url);
if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg')) {
if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) {
return callback({ cancel: true });
}
}
@@ -586,9 +603,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
// Clear Document Edited if needed
if (isMacintosh && this._win.isDocumentEdited()) {
if (this.isDocumentEdited()) {
if (!isReload || !this.backupMainService.isHotExitEnabled()) {
this._win.setDocumentEdited(false);
this.setDocumentEdited(false);
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ var CSSBuildLoaderPlugin;
BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) {
this._pendingLoads++;
var head = document.head || document.getElementsByTagName('head')[0];
var other = head.getElementsByTagName('link') || document.head.getElementsByTagName('script');
var other = head.getElementsByTagName('link') || head.getElementsByTagName('script');
if (other.length > 0) {
head.insertBefore(linkNode, other[other.length - 1]);
}
+1 -1
View File
@@ -51,7 +51,7 @@ var CSSLoaderPlugin;
BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) {
this._pendingLoads++;
var head = document.head || document.getElementsByTagName('head')[0];
var other = head.getElementsByTagName('link') || document.head.getElementsByTagName('script');
var other = head.getElementsByTagName('link') || head.getElementsByTagName('script');
if (other.length > 0) {
head.insertBefore(linkNode, other[other.length - 1]);
}
@@ -3,9 +3,34 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { IDimension } from 'vs/editor/common/editorCommon';
import * as dom from 'vs/base/browser/dom';
interface ResizeObserver {
observe(target: Element): void;
unobserve(target: Element): void;
disconnect(): void;
}
interface ResizeObserverSize {
inlineSize: number;
blockSize: number;
}
interface ResizeObserverEntry {
readonly target: Element;
readonly contentRect: DOMRectReadOnly;
readonly borderBoxSize: ResizeObserverSize;
readonly contentBoxSize: ResizeObserverSize;
}
type ResizeObserverCallback = (entries: ReadonlyArray<ResizeObserverEntry>, observer: ResizeObserver) => void;
declare const ResizeObserver: {
prototype: ResizeObserver;
new(callback: ResizeObserverCallback): ResizeObserver;
};
export class ElementSizeObserver extends Disposable {
@@ -13,8 +38,8 @@ export class ElementSizeObserver extends Disposable {
private readonly changeCallback: () => void;
private width: number;
private height: number;
private mutationObserver: MutationObserver | null;
private windowSizeListener: IDisposable | null;
private resizeObserver: ResizeObserver | null;
private measureReferenceDomElementToken: number;
constructor(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void) {
super();
@@ -22,8 +47,8 @@ export class ElementSizeObserver extends Disposable {
this.changeCallback = changeCallback;
this.width = -1;
this.height = -1;
this.mutationObserver = null;
this.windowSizeListener = null;
this.resizeObserver = null;
this.measureReferenceDomElementToken = -1;
this.measureReferenceDomElement(false, dimension);
}
@@ -41,25 +66,33 @@ export class ElementSizeObserver extends Disposable {
}
public startObserving(): void {
if (!this.mutationObserver && this.referenceDomElement) {
this.mutationObserver = new MutationObserver(() => this._onDidMutate());
this.mutationObserver.observe(this.referenceDomElement, {
attributes: true,
});
}
if (!this.windowSizeListener) {
this.windowSizeListener = dom.addDisposableListener(window, 'resize', () => this._onDidResizeWindow());
if (typeof ResizeObserver !== 'undefined') {
if (!this.resizeObserver && this.referenceDomElement) {
this.resizeObserver = new ResizeObserver((entries) => {
if (entries && entries[0] && entries[0].contentRect) {
this.observe({ width: entries[0].contentRect.width, height: entries[0].contentRect.height });
} else {
this.observe();
}
});
this.resizeObserver.observe(this.referenceDomElement);
}
} else {
if (this.measureReferenceDomElementToken === -1) {
// setInterval type defaults to NodeJS.Timeout instead of number, so specify it as a number
this.measureReferenceDomElementToken = <number><any>setInterval(() => this.observe(), 100);
}
}
}
public stopObserving(): void {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
if (this.windowSizeListener) {
this.windowSizeListener.dispose();
this.windowSizeListener = null;
if (this.measureReferenceDomElementToken !== -1) {
clearInterval(this.measureReferenceDomElementToken);
this.measureReferenceDomElementToken = -1;
}
}
@@ -67,14 +100,6 @@ export class ElementSizeObserver extends Disposable {
this.measureReferenceDomElement(true, dimension);
}
private _onDidMutate(): void {
this.measureReferenceDomElement(true);
}
private _onDidResizeWindow(): void {
this.measureReferenceDomElement(true);
}
private measureReferenceDomElement(callChangeCallback: boolean, dimension?: IDimension): void {
let observedWidth = 0;
let observedHeight = 0;
@@ -2722,10 +2722,6 @@ export interface ISuggestOptions {
* Overwrite word ends on accept. Default to false.
*/
insertMode?: 'insert' | 'replace';
/**
* Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false.
*/
insertHighlight?: boolean;
/**
* Enable graceful matching. Defaults to true.
*/
@@ -2876,7 +2872,6 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
constructor() {
const defaults: InternalSuggestOptions = {
insertMode: 'insert',
insertHighlight: true,
filterGraceful: true,
snippetsPreventQuickSuggestions: true,
localityBonus: false,
@@ -2927,11 +2922,6 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
default: defaults.insertMode,
description: nls.localize('suggest.insertMode', "Controls whether words are overwritten when accepting completions. Note that this depends on extensions opting into this feature.")
},
'editor.suggest.insertHighlight': {
type: 'boolean',
default: defaults.insertHighlight,
description: nls.localize('suggest.insertHighlight', "Controls whether unexpected text modifications while accepting completions should be highlighted, e.g `insertMode` is `replace` but the completion only supports `insert`.")
},
'editor.suggest.filterGraceful': {
type: 'boolean',
default: defaults.filterGraceful,
@@ -3124,7 +3114,6 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
const input = _input as ISuggestOptions;
return {
insertMode: EditorStringEnumOption.stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']),
insertHighlight: EditorBooleanOption.boolean(input.insertHighlight, this.defaultValue.insertHighlight),
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
@@ -238,7 +238,7 @@ export class WordOperations {
const left = lineContent.charCodeAt(column - 2);
const right = lineContent.charCodeAt(column - 1);
if (left !== CharCode.Underline && right === CharCode.Underline) {
if (left === CharCode.Underline && right !== CharCode.Underline) {
// snake_case_variables
return new Position(lineNumber, column);
}
@@ -340,7 +340,7 @@ export class WordOperations {
const left = lineContent.charCodeAt(column - 2);
const right = lineContent.charCodeAt(column - 1);
if (left === CharCode.Underline && right !== CharCode.Underline) {
if (left !== CharCode.Underline && right === CharCode.Underline) {
// snake_case_variables
return new Position(lineNumber, column);
}
+3 -2
View File
@@ -24,17 +24,18 @@ export interface IStringBuilder {
}
let _platformTextDecoder: TextDecoder | null;
function getPlatformTextDecoder(): TextDecoder {
export function getPlatformTextDecoder(): TextDecoder {
if (!_platformTextDecoder) {
_platformTextDecoder = new TextDecoder(platform.isLittleEndian() ? 'UTF-16LE' : 'UTF-16BE');
}
return _platformTextDecoder;
}
export const hasTextDecoder = (typeof TextDecoder !== 'undefined');
export let createStringBuilder: (capacity: number) => IStringBuilder;
export let decodeUTF16LE: (source: Uint8Array, offset: number, len: number) => string;
if (typeof TextDecoder !== 'undefined') {
if (hasTextDecoder) {
createStringBuilder = (capacity) => new StringBuilder(capacity);
decodeUTF16LE = standardDecodeUTF16LE;
} else {
+11 -1
View File
@@ -827,7 +827,17 @@ export interface ITextModel {
/**
* @internal
*/
setSemanticTokens(tokens: MultilineTokens2[] | null): void;
setSemanticTokens(tokens: MultilineTokens2[] | null, isComplete: boolean): void;
/**
* @internal
*/
setPartialSemanticTokens(range: Range, tokens: MultilineTokens2[] | null): void;
/**
* @internal
*/
hasSemanticTokens(): boolean;
/**
* Flush all tokenization state.
+4 -1
View File
@@ -10,10 +10,13 @@ import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation
import { TextModel } from 'vs/editor/common/model/textModel';
import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
import { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/model/textChange';
import * as buffer from 'vs/base/common/buffer';
function uriGetComparisonKey(resource: URI): string {
return resource.toString();
}
class SingleModelEditStackData {
public static create(model: ITextModel, beforeCursorState: Selection[] | null): SingleModelEditStackData {
+19 -2
View File
@@ -1793,8 +1793,8 @@ export class TextModel extends Disposable implements model.ITextModel {
}
}
public setSemanticTokens(tokens: MultilineTokens2[] | null): void {
this._tokens2.set(tokens);
public setSemanticTokens(tokens: MultilineTokens2[] | null, isComplete: boolean): void {
this._tokens2.set(tokens, isComplete);
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
@@ -1803,6 +1803,23 @@ export class TextModel extends Disposable implements model.ITextModel {
});
}
public hasSemanticTokens(): boolean {
return this._tokens2.isComplete();
}
public setPartialSemanticTokens(range: Range, tokens: MultilineTokens2[]): void {
if (this.hasSemanticTokens()) {
return;
}
const changedRange = this._tokens2.setPartial(range, tokens);
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
semanticTokensApplied: true,
ranges: [{ fromLineNumber: changedRange.startLineNumber, toLineNumber: changedRange.endLineNumber }]
});
}
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {
startLineNumber = Math.max(1, startLineNumber);
endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber);
+256 -71
View File
@@ -6,7 +6,7 @@
import * as arrays from 'vs/base/common/arrays';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, TokenMetadata } from 'vs/editor/common/modes';
import { writeUInt32BE, readUInt32BE } from 'vs/base/common/buffer';
import { CharCode } from 'vs/base/common/charCode';
@@ -124,20 +124,7 @@ export class MultilineTokensBuilder {
}
}
export interface IEncodedTokens {
getTokenCount(): number;
getDeltaLine(tokenIndex: number): number;
getMaxDeltaLine(): number;
getStartCharacter(tokenIndex: number): number;
getEndCharacter(tokenIndex: number): number;
getMetadata(tokenIndex: number): number;
clear(): void;
acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void;
acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void;
}
export class SparseEncodedTokens implements IEncodedTokens {
export class SparseEncodedTokens {
/**
* The encoding of tokens is:
* 4*i deltaLine (from `startLineNumber`)
@@ -145,7 +132,7 @@ export class SparseEncodedTokens implements IEncodedTokens {
* 4*i+2 endCharacter (from the line start)
* 4*i+3 metadata
*/
private _tokens: Uint32Array;
private readonly _tokens: Uint32Array;
private _tokenCount: number;
constructor(tokens: Uint32Array) {
@@ -153,38 +140,167 @@ export class SparseEncodedTokens implements IEncodedTokens {
this._tokenCount = tokens.length / 4;
}
public toString(startLineNumber: number): string {
let pieces: string[] = [];
for (let i = 0; i < this._tokenCount; i++) {
pieces.push(`(${this._getDeltaLine(i) + startLineNumber},${this._getStartCharacter(i)}-${this._getEndCharacter(i)})`);
}
return `[${pieces.join(',')}]`;
}
public getMaxDeltaLine(): number {
const tokenCount = this.getTokenCount();
const tokenCount = this._getTokenCount();
if (tokenCount === 0) {
return -1;
}
return this.getDeltaLine(tokenCount - 1);
return this._getDeltaLine(tokenCount - 1);
}
public getTokenCount(): number {
public getRange(): Range | null {
const tokenCount = this._getTokenCount();
if (tokenCount === 0) {
return null;
}
const startChar = this._getStartCharacter(0);
const maxDeltaLine = this._getDeltaLine(tokenCount - 1);
const endChar = this._getEndCharacter(tokenCount - 1);
return new Range(0, startChar + 1, maxDeltaLine, endChar + 1);
}
private _getTokenCount(): number {
return this._tokenCount;
}
public getDeltaLine(tokenIndex: number): number {
private _getDeltaLine(tokenIndex: number): number {
return this._tokens[4 * tokenIndex];
}
public getStartCharacter(tokenIndex: number): number {
private _getStartCharacter(tokenIndex: number): number {
return this._tokens[4 * tokenIndex + 1];
}
public getEndCharacter(tokenIndex: number): number {
private _getEndCharacter(tokenIndex: number): number {
return this._tokens[4 * tokenIndex + 2];
}
public getMetadata(tokenIndex: number): number {
return this._tokens[4 * tokenIndex + 3];
public isEmpty(): boolean {
return (this._getTokenCount() === 0);
}
public getLineTokens(deltaLine: number): LineTokens2 | null {
let low = 0;
let high = this._getTokenCount() - 1;
while (low < high) {
const mid = low + Math.floor((high - low) / 2);
const midDeltaLine = this._getDeltaLine(mid);
if (midDeltaLine < deltaLine) {
low = mid + 1;
} else if (midDeltaLine > deltaLine) {
high = mid - 1;
} else {
let min = mid;
while (min > low && this._getDeltaLine(min - 1) === deltaLine) {
min--;
}
let max = mid;
while (max < high && this._getDeltaLine(max + 1) === deltaLine) {
max++;
}
return new LineTokens2(this._tokens.subarray(4 * min, 4 * max + 4));
}
}
if (this._getDeltaLine(low) === deltaLine) {
return new LineTokens2(this._tokens.subarray(4 * low, 4 * low + 4));
}
return null;
}
public clear(): void {
this._tokenCount = 0;
}
public removeTokens(startDeltaLine: number, startChar: number, endDeltaLine: number, endChar: number): number {
const tokens = this._tokens;
const tokenCount = this._tokenCount;
let newTokenCount = 0;
let hasDeletedTokens = false;
let firstDeltaLine = 0;
for (let i = 0; i < tokenCount; i++) {
const srcOffset = 4 * i;
const tokenDeltaLine = tokens[srcOffset];
const tokenStartCharacter = tokens[srcOffset + 1];
const tokenEndCharacter = tokens[srcOffset + 2];
const tokenMetadata = tokens[srcOffset + 3];
if (
(tokenDeltaLine > startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter >= startChar))
&& (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter <= endChar))
) {
hasDeletedTokens = true;
} else {
if (newTokenCount === 0) {
firstDeltaLine = tokenDeltaLine;
}
if (hasDeletedTokens) {
// must move the token to the left
const destOffset = 4 * newTokenCount;
tokens[destOffset] = tokenDeltaLine - firstDeltaLine;
tokens[destOffset + 1] = tokenStartCharacter;
tokens[destOffset + 2] = tokenEndCharacter;
tokens[destOffset + 3] = tokenMetadata;
}
newTokenCount++;
}
}
this._tokenCount = newTokenCount;
return firstDeltaLine;
}
public split(startDeltaLine: number, startChar: number, endDeltaLine: number, endChar: number): [SparseEncodedTokens, SparseEncodedTokens, number] {
const tokens = this._tokens;
const tokenCount = this._tokenCount;
let aTokens: number[] = [];
let bTokens: number[] = [];
let destTokens: number[] = aTokens;
let destOffset = 0;
let destFirstDeltaLine: number = 0;
for (let i = 0; i < tokenCount; i++) {
const srcOffset = 4 * i;
const tokenDeltaLine = tokens[srcOffset];
const tokenStartCharacter = tokens[srcOffset + 1];
const tokenEndCharacter = tokens[srcOffset + 2];
const tokenMetadata = tokens[srcOffset + 3];
if ((tokenDeltaLine > startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter >= startChar))) {
if ((tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter <= endChar))) {
// this token is touching the range
continue;
} else {
// this token is after the range
if (destTokens !== bTokens) {
// this token is the first token after the range
destTokens = bTokens;
destOffset = 0;
destFirstDeltaLine = tokenDeltaLine;
}
}
}
destTokens[destOffset++] = tokenDeltaLine - destFirstDeltaLine;
destTokens[destOffset++] = tokenStartCharacter;
destTokens[destOffset++] = tokenEndCharacter;
destTokens[destOffset++] = tokenMetadata;
}
return [new SparseEncodedTokens(new Uint32Array(aTokens)), new SparseEncodedTokens(new Uint32Array(bTokens)), destFirstDeltaLine];
}
public acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void {
// This is a bit complex, here are the cases I used to think about this:
//
@@ -414,30 +530,26 @@ export class SparseEncodedTokens implements IEncodedTokens {
export class LineTokens2 {
private readonly _actual: IEncodedTokens;
private readonly _startTokenIndex: number;
private readonly _endTokenIndex: number;
private readonly _tokens: Uint32Array;
constructor(actual: IEncodedTokens, startTokenIndex: number, endTokenIndex: number) {
this._actual = actual;
this._startTokenIndex = startTokenIndex;
this._endTokenIndex = endTokenIndex;
constructor(tokens: Uint32Array) {
this._tokens = tokens;
}
public getCount(): number {
return this._endTokenIndex - this._startTokenIndex + 1;
return this._tokens.length / 4;
}
public getStartCharacter(tokenIndex: number): number {
return this._actual.getStartCharacter(this._startTokenIndex + tokenIndex);
return this._tokens[4 * tokenIndex + 1];
}
public getEndCharacter(tokenIndex: number): number {
return this._actual.getEndCharacter(this._startTokenIndex + tokenIndex);
return this._tokens[4 * tokenIndex + 2];
}
public getMetadata(tokenIndex: number): number {
return this._actual.getMetadata(this._startTokenIndex + tokenIndex);
return this._tokens[4 * tokenIndex + 3];
}
}
@@ -445,59 +557,58 @@ export class MultilineTokens2 {
public startLineNumber: number;
public endLineNumber: number;
public tokens: IEncodedTokens;
public tokens: SparseEncodedTokens;
constructor(startLineNumber: number, tokens: IEncodedTokens) {
constructor(startLineNumber: number, tokens: SparseEncodedTokens) {
this.startLineNumber = startLineNumber;
this.tokens = tokens;
this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine();
}
public toString(): string {
return this.tokens.toString(this.startLineNumber);
}
private _updateEndLineNumber(): void {
this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine();
}
public isEmpty(): boolean {
return this.tokens.isEmpty();
}
public getLineTokens(lineNumber: number): LineTokens2 | null {
if (this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber) {
const findResult = MultilineTokens2._findTokensWithLine(this.tokens, lineNumber - this.startLineNumber);
if (findResult) {
const [startTokenIndex, endTokenIndex] = findResult;
return new LineTokens2(this.tokens, startTokenIndex, endTokenIndex);
}
return this.tokens.getLineTokens(lineNumber - this.startLineNumber);
}
return null;
}
private static _findTokensWithLine(tokens: IEncodedTokens, deltaLine: number): [number, number] | null {
let low = 0;
let high = tokens.getTokenCount() - 1;
while (low < high) {
const mid = low + Math.floor((high - low) / 2);
const midDeltaLine = tokens.getDeltaLine(mid);
if (midDeltaLine < deltaLine) {
low = mid + 1;
} else if (midDeltaLine > deltaLine) {
high = mid - 1;
} else {
let min = mid;
while (min > low && tokens.getDeltaLine(min - 1) === deltaLine) {
min--;
}
let max = mid;
while (max < high && tokens.getDeltaLine(max + 1) === deltaLine) {
max++;
}
return [min, max];
}
public getRange(): Range | null {
const deltaRange = this.tokens.getRange();
if (!deltaRange) {
return deltaRange;
}
return new Range(this.startLineNumber + deltaRange.startLineNumber, deltaRange.startColumn, this.startLineNumber + deltaRange.endLineNumber, deltaRange.endColumn);
}
if (tokens.getDeltaLine(low) === deltaLine) {
return [low, low];
}
public removeTokens(range: Range): void {
const startLineIndex = range.startLineNumber - this.startLineNumber;
const endLineIndex = range.endLineNumber - this.startLineNumber;
return null;
this.startLineNumber += this.tokens.removeTokens(startLineIndex, range.startColumn - 1, endLineIndex, range.endColumn - 1);
this._updateEndLineNumber();
}
public split(range: Range): [MultilineTokens2, MultilineTokens2] {
// split tokens to two:
// a) all the tokens before `range`
// b) all the tokens after `range`
const startLineIndex = range.startLineNumber - this.startLineNumber;
const endLineIndex = range.endLineNumber - this.startLineNumber;
const [a, b, bDeltaLine] = this.tokens.split(startLineIndex, range.startColumn - 1, endLineIndex, range.endColumn - 1);
return [new MultilineTokens2(this.startLineNumber, a), new MultilineTokens2(this.startLineNumber + bDeltaLine, b)];
}
public applyEdit(range: IRange, text: string): void {
@@ -761,17 +872,91 @@ function toUint32Array(arr: Uint32Array | ArrayBuffer): Uint32Array {
export class TokensStore2 {
private _pieces: MultilineTokens2[];
private _isComplete: boolean;
constructor() {
this._pieces = [];
this._isComplete = false;
}
public flush(): void {
this._pieces = [];
this._isComplete = false;
}
public set(pieces: MultilineTokens2[] | null) {
public set(pieces: MultilineTokens2[] | null, isComplete: boolean): void {
this._pieces = pieces || [];
this._isComplete = isComplete;
}
public setPartial(_range: Range, pieces: MultilineTokens2[]): Range {
if (pieces.length === 0) {
return _range;
}
const _firstRange = pieces[0].getRange();
const _lastRange = pieces[pieces.length - 1].getRange();
if (!_firstRange || !_lastRange) {
return _range;
}
const range = _range.plusRange(_firstRange).plusRange(_lastRange);
let insertPosition: { index: number; } | null = null;
for (let i = 0, len = this._pieces.length; i < len; i++) {
const piece = this._pieces[i];
if (piece.endLineNumber < range.startLineNumber) {
// this piece is before the range
continue;
}
if (piece.startLineNumber > range.endLineNumber) {
// this piece is after the range, so mark the spot before this piece
// as a good insertion position and stop looping
insertPosition = insertPosition || { index: i };
break;
}
// this piece might intersect with the range
piece.removeTokens(range);
if (piece.isEmpty()) {
// remove the piece if it became empty
this._pieces.splice(i, 1);
i--;
len--;
continue;
}
if (piece.endLineNumber < range.startLineNumber) {
// after removal, this piece is before the range
continue;
}
if (piece.startLineNumber > range.endLineNumber) {
// after removal, this piece is after the range
insertPosition = insertPosition || { index: i };
continue;
}
// after removal, this piece contains the range
const [a, b] = piece.split(range);
this._pieces.splice(i, 1, a, b);
i++;
len++;
insertPosition = insertPosition || { index: i };
}
insertPosition = insertPosition || { index: this._pieces.length };
this._pieces = arrays.arrayInsert(this._pieces, insertPosition.index, pieces);
// console.log(`I HAVE ${this._pieces.length} pieces`);
// console.log(`${this._pieces.map(p => p.toString()).join(', ')}`);
return range;
}
public isComplete(): boolean {
return this._isComplete;
}
public addSemanticTokens(lineNumber: number, aTokens: LineTokens): LineTokens {
@@ -782,7 +967,7 @@ export class TokensStore2 {
}
const pieceIndex = TokensStore2._findFirstPieceWithLine(pieces, lineNumber);
const bTokens = this._pieces[pieceIndex].getLineTokens(lineNumber);
const bTokens = pieces[pieceIndex].getLineTokens(lineNumber);
if (!bTokens) {
return aTokens;
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import * as stringBuilder from 'vs/editor/common/core/stringBuilder';
import { Range } from 'vs/editor/common/core/range';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration';
@@ -264,14 +265,24 @@ function createBracketOrRegExp(pieces: string[]): RegExp {
return strings.createRegExp(regexStr, true);
}
let toReversedString = (function () {
const toReversedString = (function () {
function reverse(str: string): string {
let reversedStr = '';
for (let i = str.length - 1; i >= 0; i--) {
reversedStr += str.charAt(i);
if (stringBuilder.hasTextDecoder) {
// create a Uint16Array and then use a TextDecoder to create a string
const arr = new Uint16Array(str.length);
let offset = 0;
for (let i = str.length - 1; i >= 0; i--) {
arr[offset++] = str.charCodeAt(i);
}
return stringBuilder.getPlatformTextDecoder().decode(arr);
} else {
let result: string[] = [], resultLen = 0;
for (let i = str.length - 1; i >= 0; i--) {
result[resultLen++] = str.charAt(i);
}
return result.join('');
}
return reversedStr;
}
let lastInput: string | null = null;
@@ -8,9 +8,13 @@ import { URI } from 'vs/base/common/uri';
import { ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/modes';
import { SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling';
export const IModelService = createDecorator<IModelService>('modelService');
export type DocumentTokensProvider = DocumentSemanticTokensProvider | DocumentRangeSemanticTokensProvider;
export interface IModelService {
_serviceBrand: undefined;
@@ -28,6 +32,8 @@ export interface IModelService {
getModel(resource: URI): ITextModel | null;
getSemanticTokensProviderStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling;
onModelAdded: Event<ITextModel>;
onModelRemoved: Event<ITextModel>;
+80 -318
View File
@@ -12,26 +12,26 @@ import { URI } from 'vs/base/common/uri';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions, IValidEditOperation } from 'vs/editor/common/model';
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes';
import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes';
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModelService, DocumentTokensProvider } from 'vs/editor/common/services/modelService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
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;
@@ -171,6 +171,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
*/
private readonly _models: { [modelId: string]: ModelData; };
private readonly _disposedModels: Map<string, DisposedModelInfo>;
private readonly _semanticStyling: SemanticStyling;
constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -184,11 +185,12 @@ export class ModelServiceImpl extends Disposable implements IModelService {
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
this._models = {};
this._disposedModels = new Map<string, DisposedModelInfo>();
this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._logService));
this._register(this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()));
this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions()));
this._updateModelOptions();
this._register(new SemanticColoringFeature(this, this._themeService, this._configurationService, this._logService));
this._register(new SemanticColoringFeature(this, this._themeService, this._configurationService, this._semanticStyling));
}
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
@@ -380,7 +382,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
model.pushEditOperations(
[],
ModelServiceImpl._computeEdits(model, textBuffer),
(inverseEditOperations: IValidEditOperation[]) => []
() => []
);
model.pushStackElement();
}
@@ -542,6 +544,10 @@ export class ModelServiceImpl extends Disposable implements IModelService {
return modelData.model;
}
public getSemanticTokensProviderStyling(provider: DocumentTokensProvider): SemanticTokensProviderStyling {
return this._semanticStyling.get(provider);
}
// --- end IModelService
private _onWillDispose(model: ITextModel): void {
@@ -575,13 +581,13 @@ class SemanticColoringFeature extends Disposable {
private static readonly SETTING_ID = 'editor.semanticHighlighting';
private _watchers: Record<string, ModelSemanticColoring>;
private _semanticStyling: SemanticStyling;
private readonly _watchers: Record<string, ModelSemanticColoring>;
private readonly _semanticStyling: SemanticStyling;
constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) {
constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, semanticStyling: SemanticStyling) {
super();
this._watchers = Object.create(null);
this._semanticStyling = this._register(new SemanticStyling(themeService, logService));
this._semanticStyling = semanticStyling;
const isSemanticColoringEnabled = (model: ITextModel) => {
if (!themeService.getColorTheme().semanticHighlighting) {
@@ -633,204 +639,27 @@ class SemanticColoringFeature extends Disposable {
class SemanticStyling extends Disposable {
private _caches: WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>;
private _caches: WeakMap<DocumentTokensProvider, SemanticTokensProviderStyling>;
constructor(
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
) {
super();
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
this._caches = new WeakMap<DocumentTokensProvider, SemanticTokensProviderStyling>();
this._register(this._themeService.onDidColorThemeChange(() => {
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
this._caches = new WeakMap<DocumentTokensProvider, SemanticTokensProviderStyling>();
}));
}
public get(provider: DocumentSemanticTokensProvider): SemanticColoringProviderStyling {
public get(provider: DocumentTokensProvider): SemanticTokensProviderStyling {
if (!this._caches.has(provider)) {
this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService, this._logService));
this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._logService));
}
return this._caches.get(provider)!;
}
}
const enum Constants {
NO_STYLING = 0b01111111111111111111111111111111
}
class HashTableEntry {
public readonly tokenTypeIndex: number;
public readonly tokenModifierSet: number;
public readonly languageId: number;
public readonly metadata: number;
public next: HashTableEntry | null;
constructor(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number) {
this.tokenTypeIndex = tokenTypeIndex;
this.tokenModifierSet = tokenModifierSet;
this.languageId = languageId;
this.metadata = metadata;
this.next = null;
}
}
class HashTable {
private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143];
private _elementsCount: number;
private _currentLengthIndex: number;
private _currentLength: number;
private _growCount: number;
private _elements: (HashTableEntry | null)[];
constructor() {
this._elementsCount = 0;
this._currentLengthIndex = 0;
this._currentLength = HashTable._SIZES[this._currentLengthIndex];
this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
this._elements = [];
HashTable._nullOutEntries(this._elements, this._currentLength);
}
private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void {
for (let i = 0; i < length; i++) {
entries[i] = null;
}
}
private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): number {
const hash = (n1: number, n2: number) => (((n1 << 5) - n1) + n2) | 0; // n1 * 31 + n2, keep as int32
return hash(hash(tokenTypeIndex, tokenModifierSet), languageId) % this._currentLength;
}
public get(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): HashTableEntry | null {
const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet, languageId);
let p = this._elements[hash];
while (p) {
if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet && p.languageId === languageId) {
return p;
}
p = p.next;
}
return null;
}
public add(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number): void {
this._elementsCount++;
if (this._growCount !== 0 && this._elementsCount >= this._growCount) {
// expand!
const oldElements = this._elements;
this._currentLengthIndex++;
this._currentLength = HashTable._SIZES[this._currentLengthIndex];
this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
this._elements = [];
HashTable._nullOutEntries(this._elements, this._currentLength);
for (const first of oldElements) {
let p = first;
while (p) {
const oldNext = p.next;
p.next = null;
this._add(p);
p = oldNext;
}
}
}
this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, languageId, metadata));
}
private _add(element: HashTableEntry): void {
const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet, element.languageId);
element.next = this._elements[hash];
this._elements[hash] = element;
}
}
class SemanticColoringProviderStyling {
private readonly _hashTable: HashTable;
constructor(
private readonly _legend: SemanticTokensLegend,
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
) {
this._hashTable = new HashTable();
}
public getMetadata(tokenTypeIndex: number, tokenModifierSet: number, languageId: LanguageIdentifier): number {
const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet, languageId.id);
let metadata: number;
if (entry) {
metadata = entry.metadata;
} else {
const tokenType = this._legend.tokenTypes[tokenTypeIndex];
const tokenModifiers: string[] = [];
let modifierSet = tokenModifierSet;
for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
if (modifierSet & 1) {
tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
}
modifierSet = modifierSet >> 1;
}
const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers, languageId.language);
if (typeof tokenStyle === 'undefined') {
metadata = Constants.NO_STYLING;
} else {
metadata = 0;
if (typeof tokenStyle.italic !== 'undefined') {
const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET;
metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC;
}
if (typeof tokenStyle.bold !== 'undefined') {
const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET;
metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD;
}
if (typeof tokenStyle.underline !== 'undefined') {
const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET;
metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE;
}
if (tokenStyle.foreground) {
const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET;
metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND;
}
if (metadata === 0) {
// Nothing!
metadata = Constants.NO_STYLING;
}
}
this._hashTable.add(tokenTypeIndex, tokenModifierSet, languageId.id, metadata);
}
if (this._logService.getLevel() === LogLevel.Trace) {
const type = this._legend.tokenTypes[tokenTypeIndex];
const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : '';
this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`);
}
return metadata;
}
}
const enum SemanticColoringConstants {
/**
* Let's aim at having 8KB buffers if possible...
* So that would be 8192 / (5 * 4) = 409.6 tokens per area
*/
DesiredTokensPerArea = 400,
/**
* Try to keep the total number of areas under 1024 if possible,
* simply compensate by having more tokens per area...
*/
DesiredMaxAreas = 1024,
}
class SemanticTokensResponse {
constructor(
private readonly _provider: DocumentSemanticTokensProvider,
@@ -848,10 +677,10 @@ class ModelSemanticColoring extends Disposable {
private _isDisposed: boolean;
private readonly _model: ITextModel;
private readonly _semanticStyling: SemanticStyling;
private readonly _fetchSemanticTokens: RunOnceScheduler;
private _currentResponse: SemanticTokensResponse | null;
private _currentRequestCancellationTokenSource: CancellationTokenSource | null;
private _providersChangeListeners: IDisposable[];
private readonly _fetchDocumentSemanticTokens: RunOnceScheduler;
private _currentDocumentResponse: SemanticTokensResponse | null;
private _currentDocumentRequestCancellationTokenSource: CancellationTokenSource | null;
private _documentProvidersChangeListeners: IDisposable[];
constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) {
super();
@@ -859,57 +688,57 @@ class ModelSemanticColoring extends Disposable {
this._isDisposed = false;
this._model = model;
this._semanticStyling = stylingProvider;
this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 300));
this._currentResponse = null;
this._currentRequestCancellationTokenSource = null;
this._providersChangeListeners = [];
this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), 300));
this._currentDocumentResponse = null;
this._currentDocumentRequestCancellationTokenSource = null;
this._documentProvidersChangeListeners = [];
this._register(this._model.onDidChangeContent(e => {
if (!this._fetchSemanticTokens.isScheduled()) {
this._fetchSemanticTokens.schedule();
this._register(this._model.onDidChangeContent(() => {
if (!this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule();
}
}));
const bindChangeListeners = () => {
dispose(this._providersChangeListeners);
this._providersChangeListeners = [];
const bindDocumentChangeListeners = () => {
dispose(this._documentProvidersChangeListeners);
this._documentProvidersChangeListeners = [];
for (const provider of DocumentSemanticTokensProviderRegistry.all(model)) {
if (typeof provider.onDidChange === 'function') {
this._providersChangeListeners.push(provider.onDidChange(() => this._fetchSemanticTokens.schedule(0)));
this._documentProvidersChangeListeners.push(provider.onDidChange(() => this._fetchDocumentSemanticTokens.schedule(0)));
}
}
};
bindChangeListeners();
this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => {
bindChangeListeners();
this._fetchSemanticTokens.schedule();
bindDocumentChangeListeners();
this._register(DocumentSemanticTokensProviderRegistry.onDidChange(() => {
bindDocumentChangeListeners();
this._fetchDocumentSemanticTokens.schedule();
}));
this._register(themeService.onDidColorThemeChange(_ => {
// clear out existing tokens
this._setSemanticTokens(null, null, null, []);
this._fetchSemanticTokens.schedule();
this._setDocumentSemanticTokens(null, null, null, []);
this._fetchDocumentSemanticTokens.schedule();
}));
this._fetchSemanticTokens.schedule(0);
this._fetchDocumentSemanticTokens.schedule(0);
}
public dispose(): void {
if (this._currentResponse) {
this._currentResponse.dispose();
this._currentResponse = null;
if (this._currentDocumentResponse) {
this._currentDocumentResponse.dispose();
this._currentDocumentResponse = null;
}
if (this._currentRequestCancellationTokenSource) {
this._currentRequestCancellationTokenSource.cancel();
this._currentRequestCancellationTokenSource = null;
if (this._currentDocumentRequestCancellationTokenSource) {
this._currentDocumentRequestCancellationTokenSource.cancel();
this._currentDocumentRequestCancellationTokenSource = null;
}
this._setSemanticTokens(null, null, null, []);
this._setDocumentSemanticTokens(null, null, null, []);
this._isDisposed = true;
super.dispose();
}
private _fetchSemanticTokensNow(): void {
if (this._currentRequestCancellationTokenSource) {
private _fetchDocumentSemanticTokensNow(): void {
if (this._currentDocumentRequestCancellationTokenSource) {
// there is already a request running, let it finish...
return;
}
@@ -917,7 +746,7 @@ class ModelSemanticColoring extends Disposable {
if (!provider) {
return;
}
this._currentRequestCancellationTokenSource = new CancellationTokenSource();
this._currentDocumentRequestCancellationTokenSource = new CancellationTokenSource();
const pendingChanges: IModelContentChangedEvent[] = [];
const contentChangeListener = this._model.onDidChangeContent((e) => {
@@ -926,13 +755,13 @@ class ModelSemanticColoring extends Disposable {
const styling = this._semanticStyling.get(provider);
const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null;
const request = Promise.resolve(provider.provideDocumentSemanticTokens(this._model, lastResultId, this._currentRequestCancellationTokenSource.token));
const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null;
const request = Promise.resolve(provider.provideDocumentSemanticTokens(this._model, lastResultId, this._currentDocumentRequestCancellationTokenSource.token));
request.then((res) => {
this._currentRequestCancellationTokenSource = null;
this._currentDocumentRequestCancellationTokenSource = null;
contentChangeListener.dispose();
this._setSemanticTokens(provider, res || null, styling, pendingChanges);
this._setDocumentSemanticTokens(provider, res || null, styling, pendingChanges);
}, (err) => {
if (!err || typeof err.message !== 'string' || err.message.indexOf('busy') === -1) {
errors.onUnexpectedError(err);
@@ -940,13 +769,13 @@ class ModelSemanticColoring extends Disposable {
// Semantic tokens eats up all errors and considers errors to mean that the result is temporarily not available
// The API does not have a special error kind to express this...
this._currentRequestCancellationTokenSource = null;
this._currentDocumentRequestCancellationTokenSource = null;
contentChangeListener.dispose();
if (pendingChanges.length > 0) {
// More changes occurred while the request was running
if (!this._fetchSemanticTokens.isScheduled()) {
this._fetchSemanticTokens.schedule();
if (!this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule();
}
}
});
@@ -966,11 +795,11 @@ class ModelSemanticColoring extends Disposable {
}
}
private _setSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void {
const currentResponse = this._currentResponse;
if (this._currentResponse) {
this._currentResponse.dispose();
this._currentResponse = null;
private _setDocumentSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticTokensProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void {
const currentResponse = this._currentDocumentResponse;
if (this._currentDocumentResponse) {
this._currentDocumentResponse.dispose();
this._currentDocumentResponse = null;
}
if (this._isDisposed) {
// disposed!
@@ -979,15 +808,19 @@ class ModelSemanticColoring extends Disposable {
}
return;
}
if (!provider || !tokens || !styling) {
this._model.setSemanticTokens(null);
if (!provider || !styling) {
this._model.setSemanticTokens(null, false);
return;
}
if (!tokens) {
this._model.setSemanticTokens(null, true);
return;
}
if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) {
if (!currentResponse) {
// not possible!
this._model.setSemanticTokens(null);
this._model.setSemanticTokens(null, true);
return;
}
if (tokens.edits.length === 0) {
@@ -1037,80 +870,9 @@ class ModelSemanticColoring extends Disposable {
if (ModelSemanticColoring._isSemanticTokens(tokens)) {
this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data);
this._currentDocumentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data);
const srcData = tokens.data;
const tokenCount = (tokens.data.length / 5) | 0;
const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea);
const result: MultilineTokens2[] = [];
const languageId = this._model.getLanguageIdentifier();
let tokenIndex = 0;
let lastLineNumber = 1;
let lastStartCharacter = 0;
while (tokenIndex < tokenCount) {
const tokenStartIndex = tokenIndex;
let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount);
// Keep tokens on the same line in the same area...
if (tokenEndIndex < tokenCount) {
let smallTokenEndIndex = tokenEndIndex;
while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) {
smallTokenEndIndex--;
}
if (smallTokenEndIndex - 1 === tokenStartIndex) {
// there are so many tokens on this line that our area would be empty, we must now go right
let bigTokenEndIndex = tokenEndIndex;
while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) {
bigTokenEndIndex++;
}
tokenEndIndex = bigTokenEndIndex;
} else {
tokenEndIndex = smallTokenEndIndex;
}
}
let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4);
let destOffset = 0;
let areaLine = 0;
while (tokenIndex < tokenEndIndex) {
const srcOffset = 5 * tokenIndex;
const deltaLine = srcData[srcOffset];
const deltaCharacter = srcData[srcOffset + 1];
const lineNumber = lastLineNumber + deltaLine;
const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter);
const length = srcData[srcOffset + 2];
const tokenTypeIndex = srcData[srcOffset + 3];
const tokenModifierSet = srcData[srcOffset + 4];
const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet, languageId);
if (metadata !== Constants.NO_STYLING) {
if (areaLine === 0) {
areaLine = lineNumber;
}
destData[destOffset] = lineNumber - areaLine;
destData[destOffset + 1] = startCharacter;
destData[destOffset + 2] = startCharacter + length;
destData[destOffset + 3] = metadata;
destOffset += 4;
}
lastLineNumber = lineNumber;
lastStartCharacter = startCharacter;
tokenIndex++;
}
if (destOffset !== destData.length) {
destData = destData.subarray(0, destOffset);
}
const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData));
result.push(tokens);
}
const result = toMultilineTokens2(tokens, styling, this._model.getLanguageIdentifier());
// Adjust incoming semantic tokens
if (pendingChanges.length > 0) {
@@ -1126,16 +888,16 @@ class ModelSemanticColoring extends Disposable {
}
}
if (!this._fetchSemanticTokens.isScheduled()) {
this._fetchSemanticTokens.schedule();
if (!this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule();
}
}
this._model.setSemanticTokens(result);
this._model.setSemanticTokens(result, true);
return;
}
this._model.setSemanticTokens(null);
this._model.setSemanticTokens(null, true);
}
private _getSemanticColoringProvider(): DocumentSemanticTokensProvider | null {
@@ -0,0 +1,261 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SemanticTokensLegend, TokenMetadata, FontStyle, MetadataConsts, SemanticTokens, LanguageIdentifier } from 'vs/editor/common/modes';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore';
export const enum SemanticTokensProviderStylingConstants {
NO_STYLING = 0b01111111111111111111111111111111
}
export class SemanticTokensProviderStyling {
private readonly _hashTable: HashTable;
constructor(
private readonly _legend: SemanticTokensLegend,
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
) {
this._hashTable = new HashTable();
}
public getMetadata(tokenTypeIndex: number, tokenModifierSet: number, languageId: LanguageIdentifier): number {
const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet, languageId.id);
let metadata: number;
if (entry) {
metadata = entry.metadata;
} else {
const tokenType = this._legend.tokenTypes[tokenTypeIndex];
const tokenModifiers: string[] = [];
let modifierSet = tokenModifierSet;
for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
if (modifierSet & 1) {
tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
}
modifierSet = modifierSet >> 1;
}
const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers, languageId.language);
if (typeof tokenStyle === 'undefined') {
metadata = SemanticTokensProviderStylingConstants.NO_STYLING;
} else {
metadata = 0;
if (typeof tokenStyle.italic !== 'undefined') {
const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET;
metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC;
}
if (typeof tokenStyle.bold !== 'undefined') {
const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET;
metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD;
}
if (typeof tokenStyle.underline !== 'undefined') {
const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET;
metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE;
}
if (tokenStyle.foreground) {
const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET;
metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND;
}
if (metadata === 0) {
// Nothing!
metadata = SemanticTokensProviderStylingConstants.NO_STYLING;
}
}
this._hashTable.add(tokenTypeIndex, tokenModifierSet, languageId.id, metadata);
}
if (this._logService.getLevel() === LogLevel.Trace) {
const type = this._legend.tokenTypes[tokenTypeIndex];
const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : '';
this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`);
}
return metadata;
}
}
const enum SemanticColoringConstants {
/**
* Let's aim at having 8KB buffers if possible...
* So that would be 8192 / (5 * 4) = 409.6 tokens per area
*/
DesiredTokensPerArea = 400,
/**
* Try to keep the total number of areas under 1024 if possible,
* simply compensate by having more tokens per area...
*/
DesiredMaxAreas = 1024,
}
export function toMultilineTokens2(tokens: SemanticTokens, styling: SemanticTokensProviderStyling, languageId: LanguageIdentifier): MultilineTokens2[] {
const srcData = tokens.data;
const tokenCount = (tokens.data.length / 5) | 0;
const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea);
const result: MultilineTokens2[] = [];
let tokenIndex = 0;
let lastLineNumber = 1;
let lastStartCharacter = 0;
while (tokenIndex < tokenCount) {
const tokenStartIndex = tokenIndex;
let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount);
// Keep tokens on the same line in the same area...
if (tokenEndIndex < tokenCount) {
let smallTokenEndIndex = tokenEndIndex;
while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) {
smallTokenEndIndex--;
}
if (smallTokenEndIndex - 1 === tokenStartIndex) {
// there are so many tokens on this line that our area would be empty, we must now go right
let bigTokenEndIndex = tokenEndIndex;
while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) {
bigTokenEndIndex++;
}
tokenEndIndex = bigTokenEndIndex;
} else {
tokenEndIndex = smallTokenEndIndex;
}
}
let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4);
let destOffset = 0;
let areaLine = 0;
while (tokenIndex < tokenEndIndex) {
const srcOffset = 5 * tokenIndex;
const deltaLine = srcData[srcOffset];
const deltaCharacter = srcData[srcOffset + 1];
const lineNumber = lastLineNumber + deltaLine;
const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter);
const length = srcData[srcOffset + 2];
const tokenTypeIndex = srcData[srcOffset + 3];
const tokenModifierSet = srcData[srcOffset + 4];
const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet, languageId);
if (metadata !== SemanticTokensProviderStylingConstants.NO_STYLING) {
if (areaLine === 0) {
areaLine = lineNumber;
}
destData[destOffset] = lineNumber - areaLine;
destData[destOffset + 1] = startCharacter;
destData[destOffset + 2] = startCharacter + length;
destData[destOffset + 3] = metadata;
destOffset += 4;
}
lastLineNumber = lineNumber;
lastStartCharacter = startCharacter;
tokenIndex++;
}
if (destOffset !== destData.length) {
destData = destData.subarray(0, destOffset);
}
const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData));
result.push(tokens);
}
return result;
}
class HashTableEntry {
public readonly tokenTypeIndex: number;
public readonly tokenModifierSet: number;
public readonly languageId: number;
public readonly metadata: number;
public next: HashTableEntry | null;
constructor(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number) {
this.tokenTypeIndex = tokenTypeIndex;
this.tokenModifierSet = tokenModifierSet;
this.languageId = languageId;
this.metadata = metadata;
this.next = null;
}
}
class HashTable {
private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143];
private _elementsCount: number;
private _currentLengthIndex: number;
private _currentLength: number;
private _growCount: number;
private _elements: (HashTableEntry | null)[];
constructor() {
this._elementsCount = 0;
this._currentLengthIndex = 0;
this._currentLength = HashTable._SIZES[this._currentLengthIndex];
this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
this._elements = [];
HashTable._nullOutEntries(this._elements, this._currentLength);
}
private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void {
for (let i = 0; i < length; i++) {
entries[i] = null;
}
}
private _hash2(n1: number, n2: number): number {
return (((n1 << 5) - n1) + n2) | 0; // n1 * 31 + n2, keep as int32
}
private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): number {
return this._hash2(this._hash2(tokenTypeIndex, tokenModifierSet), languageId) % this._currentLength;
}
public get(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): HashTableEntry | null {
const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet, languageId);
let p = this._elements[hash];
while (p) {
if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet && p.languageId === languageId) {
return p;
}
p = p.next;
}
return null;
}
public add(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number): void {
this._elementsCount++;
if (this._growCount !== 0 && this._elementsCount >= this._growCount) {
// expand!
const oldElements = this._elements;
this._currentLengthIndex++;
this._currentLength = HashTable._SIZES[this._currentLengthIndex];
this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
this._elements = [];
HashTable._nullOutEntries(this._elements, this._currentLength);
for (const first of oldElements) {
let p = first;
while (p) {
const oldNext = p.next;
p.next = null;
this._add(p);
p = oldNext;
}
}
}
this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, languageId, metadata));
}
private _add(element: HashTableEntry): void {
const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet, element.languageId);
element.next = this._elements[hash];
this._elements[hash] = element;
}
}
@@ -813,7 +813,11 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
let prevPartContentCnt = 0;
let partAbsoluteOffset = 0;
sb.appendASCIIString('<span>');
if (containsRTL) {
sb.appendASCIIString('<span dir="ltr">');
} else {
sb.appendASCIIString('<span>');
}
for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
partAbsoluteOffset += prevPartContentCnt;
@@ -890,9 +894,6 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
let partContentCnt = 0;
if (containsRTL) {
sb.appendASCIIString(' dir="ltr"');
}
sb.appendASCII(CharCode.GreaterThan);
for (; charIndex < partEndIndex; charIndex++) {
@@ -563,8 +563,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
const disposables = new DisposableStore();
const actionsElement = dom.append(hoverElement, $('div.actions'));
if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) {
const peekProblemLabel = nls.localize('peek problem', "Peek Problem");
const peekProblemKeybinding = this._keybindingService.lookupKeybinding(NextMarkerAction.ID);
const peekProblemKeybindingLabel = peekProblemKeybinding && peekProblemKeybinding.getLabel();
disposables.add(this.renderAction(actionsElement, {
label: nls.localize('peek problem', "Peek Problem"),
label: peekProblemKeybindingLabel ? nls.localize('titleAndKb', "{0} ({1})", peekProblemLabel, peekProblemKeybindingLabel) : peekProblemLabel,
commandId: NextMarkerAction.ID,
run: () => {
this.hide();
@@ -581,7 +584,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes...");
disposables.add(toDisposable(() => quickfixPlaceholderElement.remove()));
const codeActionsPromise = this.getCodeActions(markerHover.marker);
disposables.add(toDisposable(() => codeActionsPromise.cancel()));
codeActionsPromise.then(actions => {
@@ -602,8 +604,12 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
}
}));
const quickFixLabel = nls.localize('quick fixes', "Quick Fix...");
const quickFixKeybinding = this._keybindingService.lookupKeybinding(QuickFixAction.Id);
const quickFixKeybindingLabel = quickFixKeybinding && quickFixKeybinding.getLabel();
disposables.add(this.renderAction(actionsElement, {
label: nls.localize('quick fixes', "Quick Fix..."),
label: quickFixKeybindingLabel ? nls.localize('titleAndKb', "{0} ({1})", quickFixLabel, quickFixKeybindingLabel) : quickFixLabel,
commandId: QuickFixAction.Id,
run: (target) => {
showing = true;
@@ -786,11 +786,13 @@ class SelectionHighlighterState {
public readonly searchText: string;
public readonly matchCase: boolean;
public readonly wordSeparators: string | null;
public readonly modelVersionId: number;
constructor(searchText: string, matchCase: boolean, wordSeparators: string | null) {
constructor(searchText: string, matchCase: boolean, wordSeparators: string | null, modelVersionId: number) {
this.searchText = searchText;
this.matchCase = matchCase;
this.wordSeparators = wordSeparators;
this.modelVersionId = modelVersionId;
}
/**
@@ -807,6 +809,7 @@ class SelectionHighlighterState {
a.searchText === b.searchText
&& a.matchCase === b.matchCase
&& a.wordSeparators === b.wordSeparators
&& a.modelVersionId === b.modelVersionId
);
}
}
@@ -857,6 +860,11 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
this._register(editor.onDidChangeModel((e) => {
this._setState(null);
}));
this._register(editor.onDidChangeModelContent((e) => {
if (this._isEnabled) {
this.updateSoon.schedule();
}
}));
this._register(CommonFindController.get(editor).getState().onFindReplaceStateChange((e) => {
this._update();
}));
@@ -939,7 +947,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
}
}
return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getOption(EditorOption.wordSeparators) : null);
return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getOption(EditorOption.wordSeparators) : null, editor.getModel().getVersionId());
}
private _setState(state: SelectionHighlighterState | null): void {
@@ -134,10 +134,10 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
const position = editor.getPosition() || { lineNumber: 1, column: 1 };
const lineCount = this.lineCount(editor);
if (lineCount > 1) {
return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Column: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount);
return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount);
}
return localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column);
return localize('gotoLineLabelEmpty', "Current Line: {0}, Character: {1}. Type a line number to navigate to.", position.lineNumber, position.column);
}
private isValidLineNumber(editor: IEditor, lineNumber: number | undefined): boolean {
@@ -41,7 +41,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
const label = localize('cannotRunGotoSymbolWithoutEditor', "Open a text editor first to go to a symbol.");
const label = localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information.");
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
@@ -70,7 +70,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
const disposables = new DisposableStore();
// Generic pick for not having any symbol information
const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "Open a text editor with symbol information first to go to a symbol.");
const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "The active text editor does not provide symbol information.");
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
@@ -37,7 +37,6 @@ import { IPosition, Position } from 'vs/editor/common/core/position';
import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import * as platform from 'vs/base/common/platform';
import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
// sticky suggest widget which doesn't disappear on focus out and such
@@ -233,9 +232,6 @@ export class SuggestController implements IEditorContribution {
};
this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig()));
updateFromConfig();
// create range highlighter
this._toDispose.add(new SuggestRangeHighlighter(this));
}
dispose(): void {
@@ -1,128 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { CompletionItem } from 'vs/editor/contrib/suggest/suggest';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { domContentLoaded } from 'vs/base/browser/dom';
export class SuggestRangeHighlighter {
private readonly _disposables = new DisposableStore();
private _decorations: string[] = [];
private _widgetListener?: IDisposable;
private _shiftKeyListener?: IDisposable;
private _currentItem?: CompletionItem;
constructor(private readonly _controller: SuggestController) {
this._disposables.add(_controller.model.onDidSuggest(e => {
if (!e.shy) {
const widget = this._controller.widget.getValue();
const focused = widget.getFocusedItem();
if (focused) {
this._highlight(focused.item);
}
if (!this._widgetListener) {
this._widgetListener = widget.onDidFocus(e => this._highlight(e.item));
}
}
}));
this._disposables.add(_controller.model.onDidCancel(() => {
this._reset();
}));
}
dispose(): void {
this._reset();
this._disposables.dispose();
dispose(this._widgetListener);
dispose(this._shiftKeyListener);
}
private _reset(): void {
this._decorations = this._controller.editor.deltaDecorations(this._decorations, []);
if (this._shiftKeyListener) {
this._shiftKeyListener.dispose();
this._shiftKeyListener = undefined;
}
}
private _highlight(item: CompletionItem) {
this._currentItem = item;
const opts = this._controller.editor.getOption(EditorOption.suggest);
let newDeco: IModelDeltaDecoration[] = [];
if (opts.insertHighlight) {
if (!this._shiftKeyListener) {
this._shiftKeyListener = shiftKey.event(() => this._highlight(this._currentItem!));
}
const info = this._controller.getOverwriteInfo(item, shiftKey.isPressed);
const position = this._controller.editor.getPosition()!;
if (opts.insertMode === 'insert' && info.overwriteAfter > 0) {
// wants inserts but got replace-mode -> highlight AFTER range
newDeco = [{
range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + info.overwriteAfter),
options: { inlineClassName: 'suggest-insert-unexpected' }
}];
} else if (opts.insertMode === 'replace' && info.overwriteAfter === 0) {
// want replace but likely got insert -> highlight AFTER range
const wordInfo = this._controller.editor.getModel()?.getWordAtPosition(position);
if (wordInfo && wordInfo.endColumn > position.column) {
newDeco = [{
range: new Range(position.lineNumber, position.column, position.lineNumber, wordInfo.endColumn),
options: { inlineClassName: 'suggest-insert-unexpected' }
}];
}
}
}
// update editor decorations
this._decorations = this._controller.editor.deltaDecorations(this._decorations, newDeco);
}
}
const shiftKey = new class ShiftKey extends Emitter<boolean> {
private readonly _subscriptions = new DisposableStore();
private _isPressed: boolean = false;
constructor() {
super();
domContentLoaded().then(() => {
this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey));
this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false));
});
}
get isPressed(): boolean {
return this._isPressed;
}
set isPressed(value: boolean) {
if (this._isPressed !== value) {
this._isPressed = value;
this.fire(value);
}
}
dispose() {
this._subscriptions.dispose();
super.dispose();
}
};
@@ -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 { RunOnceScheduler, createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { DocumentRangeSemanticTokensProviderRegistry, DocumentRangeSemanticTokensProvider, SemanticTokens } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { toMultilineTokens2, SemanticTokensProviderStyling } from 'vs/editor/common/services/semanticTokensProviderStyling';
class ViewportSemanticTokensContribution extends Disposable implements IEditorContribution {
public static readonly ID = 'editor.contrib.viewportSemanticTokens';
public static get(editor: ICodeEditor): ViewportSemanticTokensContribution {
return editor.getContribution<ViewportSemanticTokensContribution>(ViewportSemanticTokensContribution.ID);
}
private readonly _editor: ICodeEditor;
private readonly _tokenizeViewport: RunOnceScheduler;
private _outstandingRequests: CancelablePromise<SemanticTokens | null | undefined>[];
constructor(
editor: ICodeEditor,
@IModelService private readonly _modelService: IModelService
) {
super();
this._editor = editor;
this._tokenizeViewport = new RunOnceScheduler(() => this._tokenizeViewportNow(), 100);
this._outstandingRequests = [];
this._register(this._editor.onDidScrollChange(() => {
this._tokenizeViewport.schedule();
}));
this._register(this._editor.onDidChangeModel(() => {
this._cancelAll();
this._tokenizeViewport.schedule();
}));
this._register(this._editor.onDidChangeModelContent((e) => {
this._cancelAll();
this._tokenizeViewport.schedule();
}));
this._register(DocumentRangeSemanticTokensProviderRegistry.onDidChange(() => {
this._cancelAll();
this._tokenizeViewport.schedule();
}));
}
private static _getSemanticColoringProvider(model: ITextModel): DocumentRangeSemanticTokensProvider | null {
const result = DocumentRangeSemanticTokensProviderRegistry.ordered(model);
return (result.length > 0 ? result[0] : null);
}
private _cancelAll(): void {
for (const request of this._outstandingRequests) {
request.cancel();
}
this._outstandingRequests = [];
}
private _removeOutstandingRequest(req: CancelablePromise<SemanticTokens | null | undefined>): void {
for (let i = 0, len = this._outstandingRequests.length; i < len; i++) {
if (this._outstandingRequests[i] === req) {
this._outstandingRequests.splice(i, 1);
return;
}
}
}
private _tokenizeViewportNow(): void {
if (!this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
if (model.hasSemanticTokens()) {
return;
}
const provider = ViewportSemanticTokensContribution._getSemanticColoringProvider(model);
if (!provider) {
return;
}
const styling = this._modelService.getSemanticTokensProviderStyling(provider);
const visibleRanges = this._editor.getVisibleRanges();
this._outstandingRequests = this._outstandingRequests.concat(visibleRanges.map(range => this._requestRange(model, range, provider, styling)));
}
private _requestRange(model: ITextModel, range: Range, provider: DocumentRangeSemanticTokensProvider, styling: SemanticTokensProviderStyling): CancelablePromise<SemanticTokens | null | undefined> {
const requestVersionId = model.getVersionId();
const request = createCancelablePromise(token => Promise.resolve(provider.provideDocumentRangeSemanticTokens(model, range, token)));
request.then((r) => {
if (!r || model.isDisposed() || model.getVersionId() !== requestVersionId) {
return;
}
model.setPartialSemanticTokens(range, toMultilineTokens2(r, styling, model.getLanguageIdentifier()));
}).then(() => this._removeOutstandingRequest(request), () => this._removeOutstandingRequest(request));
return request;
}
}
registerEditorContribution(ViewportSemanticTokensContribution.ID, ViewportSemanticTokensContribution);
@@ -37,7 +37,7 @@ suite('WordPartOperations', () => {
test('cursorWordPartLeft - basic', () => {
const EXPECTED = [
'|start| |line|',
'|this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|',
'|this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|',
'|end| |line'
].join('\n');
const [text,] = deserializePipePositions(EXPECTED);
@@ -67,7 +67,7 @@ suite('WordPartOperations', () => {
});
test('cursorWordPartLeft - issue #53899: underscores', () => {
const EXPECTED = '|myvar| |=| |\'|demonstration|_____of| |selection| |with| |space|\'';
const EXPECTED = '|myvar| |=| |\'|demonstration_____|of| |selection| |with| |space|\'';
const [text,] = deserializePipePositions(EXPECTED);
const actualStops = testRepeatedActionAndExtractPositions(
text,
@@ -83,7 +83,7 @@ suite('WordPartOperations', () => {
test('cursorWordPartRight - basic', () => {
const EXPECTED = [
'start| |line|',
'|this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|',
'|this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|',
'|end| |line|'
].join('\n');
const [text,] = deserializePipePositions(EXPECTED);
@@ -113,7 +113,7 @@ suite('WordPartOperations', () => {
});
test('cursorWordPartRight - issue #53899: underscores', () => {
const EXPECTED = 'myvar| |=| |\'|demonstration_____|of| |selection| |with| |space|\'|';
const EXPECTED = 'myvar| |=| |\'|demonstration|_____of| |selection| |with| |space|\'|';
const [text,] = deserializePipePositions(EXPECTED);
const actualStops = testRepeatedActionAndExtractPositions(
text,
@@ -145,8 +145,40 @@ suite('WordPartOperations', () => {
assert.deepEqual(actual, EXPECTED);
});
test('issue #93239 - cursorWordPartRight', () => {
const EXPECTED = [
'foo|_bar|',
].join('\n');
const [text,] = deserializePipePositions(EXPECTED);
const actualStops = testRepeatedActionAndExtractPositions(
text,
new Position(1, 1),
ed => cursorWordPartRight(ed),
ed => ed.getPosition()!,
ed => ed.getPosition()!.equals(new Position(1, 8))
);
const actual = serializePipePositions(text, actualStops);
assert.deepEqual(actual, EXPECTED);
});
test('issue #93239 - cursorWordPartLeft', () => {
const EXPECTED = [
'|foo_|bar',
].join('\n');
const [text,] = deserializePipePositions(EXPECTED);
const actualStops = testRepeatedActionAndExtractPositions(
text,
new Position(1, 8),
ed => cursorWordPartLeft(ed),
ed => ed.getPosition()!,
ed => ed.getPosition()!.equals(new Position(1, 1))
);
const actual = serializePipePositions(text, actualStops);
assert.deepEqual(actual, EXPECTED);
});
test('deleteWordPartLeft - basic', () => {
const EXPECTED = '| |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use';
const EXPECTED = '| |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use';
const [text,] = deserializePipePositions(EXPECTED);
const actualStops = testRepeatedActionAndExtractPositions(
text,
@@ -160,7 +192,7 @@ suite('WordPartOperations', () => {
});
test('deleteWordPartRight - basic', () => {
const EXPECTED = ' |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|';
const EXPECTED = ' |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|';
const [text,] = deserializePipePositions(EXPECTED);
const actualStops = testRepeatedActionAndExtractPositions(
text,
+2
View File
@@ -22,6 +22,7 @@ import 'vs/editor/contrib/find/findController';
import 'vs/editor/contrib/folding/folding';
import 'vs/editor/contrib/fontZoom/fontZoom';
import 'vs/editor/contrib/format/formatActions';
import 'vs/editor/contrib/gotoSymbol/documentSymbols';
import 'vs/editor/contrib/gotoSymbol/goToCommands';
import 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition';
import 'vs/editor/contrib/gotoError/gotoError';
@@ -39,6 +40,7 @@ import 'vs/editor/contrib/snippet/snippetController2';
import 'vs/editor/contrib/suggest/suggestController';
import 'vs/editor/contrib/tokenization/tokenization';
import 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode';
import 'vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens';
import 'vs/editor/contrib/wordHighlighter/wordHighlighter';
import 'vs/editor/contrib/wordOperations/wordOperations';
import 'vs/editor/contrib/wordPartOperations/wordPartOperations';
@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore';
import { MultilineTokens2, SparseEncodedTokens, TokensStore2 } from 'vs/editor/common/model/tokensStore';
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 { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
suite('TokensStore', () => {
@@ -98,7 +99,7 @@ suite('TokensStore', () => {
function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) {
const initialState = parseTokensState(rawInitialState);
const model = createTextModel(initialState.text);
model.setSemanticTokens([initialState.tokens]);
model.setSemanticTokens([initialState.tokens], true);
model.applyEdits(edits);
@@ -183,7 +184,7 @@ suite('TokensStore', () => {
0, 38, 42, 245768,
0, 43, 47, 180232,
])))
]);
], true);
const lineTokens = model.getLineTokens(1);
let decodedTokens: number[] = [];
for (let i = 0, len = lineTokens.getCount(); i < len; i++) {
@@ -212,4 +213,114 @@ suite('TokensStore', () => {
model.dispose();
});
test('partial tokens 1', () => {
const store = new TokensStore2();
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
store.setPartial(new Range(1, 1, 31, 2), [
new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 1,
5, 5, 10, 2,
10, 5, 10, 3,
15, 5, 10, 4,
20, 5, 10, 5,
25, 5, 10, 6,
])))
]);
// setPartial: [18,1 -> 42,1], [(20,5-10),(25,5-10),(30,5-10),(35,5-10),(40,5-10)]
store.setPartial(new Range(18, 1, 42, 1), [
new MultilineTokens2(20, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 4,
5, 5, 10, 5,
10, 5, 10, 6,
15, 5, 10, 7,
20, 5, 10, 8,
])))
]);
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
store.setPartial(new Range(1, 1, 31, 2), [
new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 1,
5, 5, 10, 2,
10, 5, 10, 3,
15, 5, 10, 4,
20, 5, 10, 5,
25, 5, 10, 6,
])))
]);
const lineTokens = store.addSemanticTokens(10, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`));
assert.equal(lineTokens.getCount(), 3);
});
test('partial tokens 2', () => {
const store = new TokensStore2();
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
store.setPartial(new Range(1, 1, 31, 2), [
new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 1,
5, 5, 10, 2,
10, 5, 10, 3,
15, 5, 10, 4,
20, 5, 10, 5,
25, 5, 10, 6,
])))
]);
// setPartial: [6,1 -> 36,2], [(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10),(35,5-10)]
store.setPartial(new Range(6, 1, 36, 2), [
new MultilineTokens2(10, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 2,
5, 5, 10, 3,
10, 5, 10, 4,
15, 5, 10, 5,
20, 5, 10, 6,
])))
]);
// setPartial: [17,1 -> 42,1], [(20,5-10),(25,5-10),(30,5-10),(35,5-10),(40,5-10)]
store.setPartial(new Range(17, 1, 42, 1), [
new MultilineTokens2(20, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 4,
5, 5, 10, 5,
10, 5, 10, 6,
15, 5, 10, 7,
20, 5, 10, 8,
])))
]);
const lineTokens = store.addSemanticTokens(20, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`));
assert.equal(lineTokens.getCount(), 3);
});
test('partial tokens 3', () => {
const store = new TokensStore2();
// setPartial: [1,1 -> 31,2], [(5,5-10),(10,5-10),(15,5-10),(20,5-10),(25,5-10),(30,5-10)]
store.setPartial(new Range(1, 1, 31, 2), [
new MultilineTokens2(5, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 1,
5, 5, 10, 2,
10, 5, 10, 3,
15, 5, 10, 4,
20, 5, 10, 5,
25, 5, 10, 6,
])))
]);
// setPartial: [11,1 -> 16,2], [(15,5-10),(20,5-10)]
store.setPartial(new Range(11, 1, 16, 2), [
new MultilineTokens2(10, new SparseEncodedTokens(new Uint32Array([
0, 5, 10, 3,
5, 5, 10, 4,
])))
]);
const lineTokens = store.addSemanticTokens(5, new LineTokens(new Uint32Array([12, 1]), `enum Enum1 {`));
assert.equal(lineTokens.getCount(), 3);
});
});
@@ -459,10 +459,10 @@ suite('viewLineRenderer.renderLine', () => {
]);
let expectedOutput = [
'<span class="mtk6" dir="ltr">var</span>',
'<span class="mtk1" dir="ltr">\u00a0קודמות\u00a0=\u00a0</span>',
'<span class="mtk20" dir="ltr">"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"</span>',
'<span class="mtk1" dir="ltr">;</span>'
'<span class="mtk6">var</span>',
'<span class="mtk1">\u00a0קודמות\u00a0=\u00a0</span>',
'<span class="mtk20">"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"</span>',
'<span class="mtk1">;</span>'
].join('');
let _actual = renderViewLine(new RenderLineInput(
@@ -487,7 +487,7 @@ suite('viewLineRenderer.renderLine', () => {
null
));
assert.equal(_actual.html, '<span>' + expectedOutput + '</span>');
assert.equal(_actual.html, '<span dir="ltr">' + expectedOutput + '</span>');
assert.equal(_actual.containsRTL, true);
});
@@ -676,7 +676,7 @@ suite('viewLineRenderer.renderLine', () => {
let lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.';
let lineParts = createViewLineTokens([createPart(lineText.length, 1)]);
let expectedOutput = [
'<span class="mtk1" dir="ltr">את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.</span>'
'<span class="mtk1">את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.</span>'
];
let actual = renderViewLine(new RenderLineInput(
false,
@@ -699,7 +699,7 @@ suite('viewLineRenderer.renderLine', () => {
false,
null
));
assert.equal(actual.html, '<span>' + expectedOutput.join('') + '</span>');
assert.equal(actual.html, '<span dir="ltr">' + expectedOutput.join('') + '</span>');
assert.equal(actual.containsRTL, true);
});
@@ -107,7 +107,7 @@ function parseTest(fileName: string): ITest {
return { content, assertions };
}
// @ts-ignore
// @ts-expect-error
function executeTest(fileName: string, parseFunc: IParseFunc): void {
const { content, assertions } = parseTest(fileName);
const actual = parseFunc(content);
-4
View File
@@ -3649,10 +3649,6 @@ declare namespace monaco.editor {
* Overwrite word ends on accept. Default to false.
*/
insertMode?: 'insert' | 'replace';
/**
* Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false.
*/
insertHighlight?: boolean;
/**
* Enable graceful matching. Defaults to true.
*/
+8 -1
View File
@@ -14,6 +14,13 @@
*---------------------------------------------------------------------------------------------
*--------------------------------------------------------------------------------------------*/
'use strict';
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var NLSLoaderPlugin;
(function (NLSLoaderPlugin) {
var Environment = /** @class */ (function () {
@@ -94,7 +101,7 @@ var NLSLoaderPlugin;
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
return localize.apply(void 0, [_this._env, data, message].concat(args));
return localize.apply(void 0, __spreadArrays([_this._env, data, message], args));
};
}
NLSPlugin.prototype.setPseudoTranslation = function (value) {
+15 -1
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
@@ -15,6 +15,12 @@ export interface IWorkspaceBackupInfo {
remoteAuthority?: string;
}
export function isWorkspaceBackupInfo(obj: unknown): obj is IWorkspaceBackupInfo {
const candidate = obj as IWorkspaceBackupInfo;
return candidate && isWorkspaceIdentifier(candidate.workspace);
}
export interface IBackupMainService {
_serviceBrand: undefined;
@@ -31,4 +37,12 @@ export interface IBackupMainService {
unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void;
unregisterFolderBackupSync(folderUri: URI): void;
unregisterEmptyWindowBackupSync(backupFolder: string): void;
/**
* All folders or workspaces that are known to have
* backups stored. This call is long running because
* it checks for each backup location if any backups
* are stored.
*/
getDirtyWorkspaces(): Promise<Array<IWorkspaceIdentifier | URI>>;
}
@@ -8,8 +8,7 @@ import * as crypto from 'crypto';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { writeFileSync, writeFile, readFile, readdir, exists, rimraf, rename, RimRafMode } from 'vs/base/node/pfs';
import * as arrays from 'vs/base/common/arrays';
import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
import { IBackupMainService, IWorkspaceBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -28,9 +27,9 @@ export class BackupMainService implements IBackupMainService {
protected backupHome: string;
protected workspacesJsonPath: string;
private rootWorkspaces: IWorkspaceBackupInfo[] = [];
private folderWorkspaces: URI[] = [];
private emptyWorkspaces: IEmptyWindowBackupInfo[] = [];
private workspaces: IWorkspaceBackupInfo[] = [];
private folders: URI[] = [];
private emptyWindows: IEmptyWindowBackupInfo[] = [];
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@@ -51,31 +50,31 @@ export class BackupMainService implements IBackupMainService {
// read empty workspaces backups first
if (backups.emptyWorkspaceInfos) {
this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos);
this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos);
} else if (Array.isArray(backups.emptyWorkspaces)) {
// read legacy entries
this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(backupFolder => ({ backupFolder })));
this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(emptyWindow => ({ backupFolder: emptyWindow })));
}
// read workspace backups
let rootWorkspaces: IWorkspaceBackupInfo[] = [];
try {
if (Array.isArray(backups.rootURIWorkspaces)) {
rootWorkspaces = backups.rootURIWorkspaces.map(f => ({ workspace: { id: f.id, configPath: URI.parse(f.configURIPath) }, remoteAuthority: f.remoteAuthority }));
rootWorkspaces = backups.rootURIWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) }, remoteAuthority: workspace.remoteAuthority }));
} else if (Array.isArray(backups.rootWorkspaces)) {
rootWorkspaces = backups.rootWorkspaces.map(f => ({ workspace: { id: f.id, configPath: URI.file(f.configPath) } }));
rootWorkspaces = backups.rootWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } }));
}
} catch (e) {
// ignore URI parsing exceptions
}
this.rootWorkspaces = await this.validateWorkspaces(rootWorkspaces);
this.workspaces = await this.validateWorkspaces(rootWorkspaces);
// read folder backups
let workspaceFolders: URI[] = [];
try {
if (Array.isArray(backups.folderURIWorkspaces)) {
workspaceFolders = backups.folderURIWorkspaces.map(f => URI.parse(f));
workspaceFolders = backups.folderURIWorkspaces.map(folder => URI.parse(folder));
} else if (Array.isArray(backups.folderWorkspaces)) {
// migrate legacy folder paths
workspaceFolders = [];
@@ -93,7 +92,7 @@ export class BackupMainService implements IBackupMainService {
// ignore URI parsing exceptions
}
this.folderWorkspaces = await this.validateFolders(workspaceFolders);
this.folders = await this.validateFolders(workspaceFolders);
// save again in case some workspaces or folders have been removed
await this.save();
@@ -106,7 +105,7 @@ export class BackupMainService implements IBackupMainService {
return [];
}
return this.rootWorkspaces.slice(0); // return a copy
return this.workspaces.slice(0); // return a copy
}
getFolderBackupPaths(): URI[] {
@@ -116,7 +115,7 @@ export class BackupMainService implements IBackupMainService {
return [];
}
return this.folderWorkspaces.slice(0); // return a copy
return this.folders.slice(0); // return a copy
}
isHotExitEnabled(): boolean {
@@ -134,12 +133,12 @@ export class BackupMainService implements IBackupMainService {
}
getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[] {
return this.emptyWorkspaces.slice(0); // return a copy
return this.emptyWindows.slice(0); // return a copy
}
registerWorkspaceBackupSync(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string {
if (!this.rootWorkspaces.some(window => workspaceInfo.workspace.id === window.workspace.id)) {
this.rootWorkspaces.push(workspaceInfo);
if (!this.workspaces.some(workspace => workspaceInfo.workspace.id === workspace.workspace.id)) {
this.workspaces.push(workspaceInfo);
this.saveSync();
}
@@ -188,16 +187,16 @@ export class BackupMainService implements IBackupMainService {
unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void {
const id = workspace.id;
let index = arrays.firstIndex(this.rootWorkspaces, w => w.workspace.id === id);
const index = this.workspaces.findIndex(workspace => workspace.workspace.id === id);
if (index !== -1) {
this.rootWorkspaces.splice(index, 1);
this.workspaces.splice(index, 1);
this.saveSync();
}
}
registerFolderBackupSync(folderUri: URI): string {
if (!this.folderWorkspaces.some(uri => areResourcesEquals(folderUri, uri))) {
this.folderWorkspaces.push(folderUri);
if (!this.folders.some(folder => areResourcesEquals(folderUri, folder))) {
this.folders.push(folderUri);
this.saveSync();
}
@@ -205,9 +204,9 @@ export class BackupMainService implements IBackupMainService {
}
unregisterFolderBackupSync(folderUri: URI): void {
let index = arrays.firstIndex(this.folderWorkspaces, uri => areResourcesEquals(folderUri, uri));
const index = this.folders.findIndex(folder => areResourcesEquals(folderUri, folder));
if (index !== -1) {
this.folderWorkspaces.splice(index, 1);
this.folders.splice(index, 1);
this.saveSync();
}
}
@@ -216,8 +215,8 @@ export class BackupMainService implements IBackupMainService {
// Generate a new folder if this is a new empty workspace
const backupFolder = backupFolderCandidate || this.getRandomEmptyWindowId();
if (!this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, backupFolder, !platform.isLinux))) {
this.emptyWorkspaces.push({ backupFolder, remoteAuthority });
if (!this.emptyWindows.some(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, backupFolder, !platform.isLinux))) {
this.emptyWindows.push({ backupFolder, remoteAuthority });
this.saveSync();
}
@@ -225,9 +224,9 @@ export class BackupMainService implements IBackupMainService {
}
unregisterEmptyWindowBackupSync(backupFolder: string): void {
let index = arrays.firstIndex(this.emptyWorkspaces, w => !!w.backupFolder && isEqual(w.backupFolder, backupFolder, !platform.isLinux));
const index = this.emptyWindows.findIndex(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, backupFolder, !platform.isLinux));
if (index !== -1) {
this.emptyWorkspaces.splice(index, 1);
this.emptyWindows.splice(index, 1);
this.saveSync();
}
}
@@ -255,7 +254,7 @@ export class BackupMainService implements IBackupMainService {
seenIds.add(workspace.id);
const backupPath = this.getBackupPath(workspace.id);
const hasBackups = await this.hasBackups(backupPath);
const hasBackups = await this.doHasBackups(backupPath);
// If the workspace has no backups, ignore it
if (hasBackups) {
@@ -287,7 +286,7 @@ export class BackupMainService implements IBackupMainService {
seenIds.add(key);
const backupPath = this.getBackupPath(this.getFolderHash(folderURI));
const hasBackups = await this.hasBackups(backupPath);
const hasBackups = await this.doHasBackups(backupPath);
// If the folder has no backups, ignore it
if (hasBackups) {
@@ -325,7 +324,7 @@ export class BackupMainService implements IBackupMainService {
seenIds.add(backupFolder);
const backupPath = this.getBackupPath(backupFolder);
if (await this.hasBackups(backupPath)) {
if (await this.doHasBackups(backupPath)) {
result.push(backupInfo);
} else {
await this.deleteStaleBackup(backupPath);
@@ -350,7 +349,7 @@ export class BackupMainService implements IBackupMainService {
// New empty window backup
let newBackupFolder = this.getRandomEmptyWindowId();
while (this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, newBackupFolder, platform.isLinux))) {
while (this.emptyWindows.some(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, newBackupFolder, platform.isLinux))) {
newBackupFolder = this.getRandomEmptyWindowId();
}
@@ -362,7 +361,7 @@ export class BackupMainService implements IBackupMainService {
this.logService.error(`Backup: Could not rename backup folder: ${ex.toString()}`);
return false;
}
this.emptyWorkspaces.push({ backupFolder: newBackupFolder });
this.emptyWindows.push({ backupFolder: newBackupFolder });
return true;
}
@@ -371,7 +370,7 @@ export class BackupMainService implements IBackupMainService {
// New empty window backup
let newBackupFolder = this.getRandomEmptyWindowId();
while (this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, newBackupFolder, platform.isLinux))) {
while (this.emptyWindows.some(emptyWindow => !!emptyWindow.backupFolder && isEqual(emptyWindow.backupFolder, newBackupFolder, platform.isLinux))) {
newBackupFolder = this.getRandomEmptyWindowId();
}
@@ -383,12 +382,53 @@ export class BackupMainService implements IBackupMainService {
this.logService.error(`Backup: Could not rename backup folder: ${ex.toString()}`);
return false;
}
this.emptyWorkspaces.push({ backupFolder: newBackupFolder });
this.emptyWindows.push({ backupFolder: newBackupFolder });
return true;
}
private async hasBackups(backupPath: string): Promise<boolean> {
async getDirtyWorkspaces(): Promise<Array<IWorkspaceIdentifier | URI>> {
const dirtyWorkspaces: Array<IWorkspaceIdentifier | URI> = [];
// Workspaces with backups
for (const workspace of this.workspaces) {
if ((await this.hasBackups(workspace))) {
dirtyWorkspaces.push(workspace.workspace);
}
}
// Folders with backups
for (const folder of this.folders) {
if ((await this.hasBackups(folder))) {
dirtyWorkspaces.push(folder);
}
}
return dirtyWorkspaces;
}
private hasBackups(backupLocation: IWorkspaceBackupInfo | IEmptyWindowBackupInfo | URI): Promise<boolean> {
let backupPath: string;
// Folder
if (URI.isUri(backupLocation)) {
backupPath = this.getBackupPath(this.getFolderHash(backupLocation));
}
// Workspace
else if (isWorkspaceBackupInfo(backupLocation)) {
backupPath = this.getBackupPath(backupLocation.workspace.id);
}
// Empty
else {
backupPath = backupLocation.backupFolder;
}
return this.doHasBackups(backupPath);
}
private async doHasBackups(backupPath: string): Promise<boolean> {
try {
const backupSchemas = await readdir(backupPath);
@@ -427,10 +467,10 @@ export class BackupMainService implements IBackupMainService {
private serializeBackups(): IBackupWorkspacesFormat {
return {
rootURIWorkspaces: this.rootWorkspaces.map(f => ({ id: f.workspace.id, configURIPath: f.workspace.configPath.toString(), remoteAuthority: f.remoteAuthority })),
folderURIWorkspaces: this.folderWorkspaces.map(f => f.toString()),
emptyWorkspaceInfos: this.emptyWorkspaces,
emptyWorkspaces: this.emptyWorkspaces.map(info => info.backupFolder)
rootURIWorkspaces: this.workspaces.map(workspace => ({ id: workspace.workspace.id, configURIPath: workspace.workspace.configPath.toString(), remoteAuthority: workspace.remoteAuthority })),
folderURIWorkspaces: this.folders.map(folder => folder.toString()),
emptyWorkspaceInfos: this.emptyWindows,
emptyWorkspaces: this.emptyWindows.map(emptyWindow => emptyWindow.backupFolder)
};
}
@@ -22,6 +22,7 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { createHash } from 'crypto';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources';
suite('BackupMainService', () => {
@@ -731,4 +732,45 @@ suite('BackupMainService', () => {
}
});
});
suite('getDirtyWorkspaces', () => {
test('should report if a workspace or folder has backups', async () => {
const folderBackupPath = service.registerFolderBackupSync(fooFile);
const backupWorkspaceInfo = toWorkspaceBackupInfo(fooFile.fsPath);
const workspaceBackupPath = service.registerWorkspaceBackupSync(backupWorkspaceInfo);
assert.equal(((await service.getDirtyWorkspaces()).length), 0);
try {
await pfs.mkdirp(path.join(folderBackupPath, Schemas.file));
await pfs.mkdirp(path.join(workspaceBackupPath, Schemas.untitled));
} catch (error) {
// ignore - folder might exist already
}
assert.equal(((await service.getDirtyWorkspaces()).length), 0);
fs.writeFileSync(path.join(folderBackupPath, Schemas.file, '594a4a9d82a277a899d4713a5b08f504'), '');
fs.writeFileSync(path.join(workspaceBackupPath, Schemas.untitled, '594a4a9d82a277a899d4713a5b08f504'), '');
const dirtyWorkspaces = await service.getDirtyWorkspaces();
assert.equal(dirtyWorkspaces.length, 2);
let found = 0;
for (const dirtyWorkpspace of dirtyWorkspaces) {
if (URI.isUri(dirtyWorkpspace)) {
if (isEqual(fooFile, dirtyWorkpspace)) {
found++;
}
} else {
if (isEqual(backupWorkspaceInfo.workspace.configPath, dirtyWorkpspace.configPath)) {
found++;
}
}
}
assert.equal(found, 2);
});
});
});
@@ -54,8 +54,7 @@ export class BrowserClipboardService implements IClipboardService {
}
readFindText(): string {
// @ts-ignore
return undefined;
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
}
writeFindText(text: string): void { }
@@ -29,7 +29,6 @@ export class ConfigurationService extends Disposable implements IConfigurationSe
fileService: IFileService
) {
super();
this._register(fileService.watch(settingsResource));
this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, fileService));
this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel());
+2 -2
View File
@@ -53,10 +53,10 @@ export abstract class BaseWindowDriver implements IWindowDriver {
return Promise.reject(new Error(`Element not found: ${selector}`));
}
const inputElement = element as HTMLInputElement;
const inputElement = element as HTMLInputElement | HTMLSelectElement; // {{SQL CARBON EDIT}} handle select element
inputElement.value = text;
const event = new Event('input', { bubbles: true, cancelable: true });
const event = new Event(inputElement.tagName === 'INPUT' ? 'input' : 'change', { bubbles: true, cancelable: true }); // {{SQL CARBON EDIT}} handle select element
inputElement.dispatchEvent(event);
}
@@ -65,7 +65,8 @@ export class ElectronMainService implements IElectronMainService {
workspace: window.openedWorkspace,
folderUri: window.openedFolderUri,
title: window.win.getTitle(),
filename: window.getRepresentedFilename()
filename: window.getRepresentedFilename(),
dirty: window.isDocumentEdited()
}));
}
@@ -271,7 +272,7 @@ export class ElectronMainService implements IElectronMainService {
async setDocumentEdited(windowId: number | undefined, edited: boolean): Promise<void> {
const window = this.windowById(windowId);
if (window) {
window.win.setDocumentEdited(edited);
window.setDocumentEdited(edited);
}
}
@@ -95,7 +95,7 @@ export interface ParsedArgs {
'nolazy'?: boolean;
'force-device-scale-factor'?: string;
'force-renderer-accessibility'?: boolean;
'ignore-certificate-error'?: boolean;
'ignore-certificate-errors'?: boolean;
'allow-insecure-localhost'?: boolean;
}
@@ -154,6 +154,7 @@ export interface IEnvironmentService extends IUserHomeProvider {
extensionsPath?: string;
extensionDevelopmentLocationURI?: URI[];
extensionTestsLocationURI?: URI;
extensionEnabledProposedApi?: string[] | undefined;
logExtensionHostCommunication?: boolean;
debugExtensionHost: IExtensionHostDebugParams;
+2 -2
View File
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as minimist from 'vscode-minimist';
import * as minimist from 'minimist';
import * as os from 'os';
import { localize } from 'vs/nls';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
@@ -128,7 +128,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'nolazy': { type: 'boolean' }, // node inspect
'force-device-scale-factor': { type: 'string' },
'force-renderer-accessibility': { type: 'boolean' },
'ignore-certificate-error': { type: 'boolean' },
'ignore-certificate-errors': { type: 'boolean' },
'allow-insecure-localhost': { type: 'boolean' },
'_urls': { type: 'string[]' },
@@ -233,6 +233,18 @@ export class EnvironmentService implements IEnvironmentService {
return false;
}
get extensionEnabledProposedApi(): string[] | undefined {
if (Array.isArray(this.args['enable-proposed-api'])) {
return this.args['enable-proposed-api'];
}
if ('enable-proposed-api' in this.args) {
return [];
}
return undefined;
}
@memoize
get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); }
@memoize
@@ -26,6 +26,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ReadableStreamEvents, transform } from 'vs/base/common/stream';
import { createReadStream } from 'vs/platform/files/common/io';
import { insert } from 'vs/base/common/arrays';
export interface IWatcherOptions {
pollingInterval?: number;
@@ -524,7 +525,7 @@ export class DiskFileSystemProvider extends Disposable implements
// Add to list of folders to watch recursively
const folderToWatch = { path: this.toFilePath(resource), excludes };
this.recursiveFoldersToWatch.push(folderToWatch);
const remove = insert(this.recursiveFoldersToWatch, folderToWatch);
// Trigger update
this.refreshRecursiveWatchers();
@@ -532,7 +533,7 @@ export class DiskFileSystemProvider extends Disposable implements
return toDisposable(() => {
// Remove from list of folders to watch recursively
this.recursiveFoldersToWatch.splice(this.recursiveFoldersToWatch.indexOf(folderToWatch), 1);
remove();
// Trigger update
this.refreshRecursiveWatchers();
@@ -543,10 +544,8 @@ export class DiskFileSystemProvider extends Disposable implements
// Buffer requests for recursive watching to decide on right watcher
// that supports potentially watching more than one folder at once
this.recursiveWatchRequestDelayer.trigger(() => {
this.recursiveWatchRequestDelayer.trigger(async () => {
this.doRefreshRecursiveWatchers();
return Promise.resolve();
});
}
@@ -6,7 +6,7 @@
import { IDiskFileChange, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { OutOfProcessWin32FolderWatcher } from 'vs/platform/files/node/watcher/win32/csharpWatcherService';
import { posix } from 'vs/base/common/path';
import { rtrim, endsWith } from 'vs/base/common/strings';
import { rtrim } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
export class FileWatcher implements IDisposable {
@@ -22,7 +22,7 @@ export class FileWatcher implements IDisposable {
) {
this.folder = folders[0];
if (this.folder.path.indexOf('\\\\') === 0 && endsWith(this.folder.path, posix.sep)) {
if (this.folder.path.indexOf('\\\\') === 0 && this.folder.path.endsWith(posix.sep)) {
// for some weird reason, node adds a trailing slash to UNC paths
// we never ever want trailing slashes as our base path unless
// someone opens root ("/").
+1 -2
View File
@@ -11,7 +11,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { localize } from 'vs/nls';
import { isEqualOrParent, basename } from 'vs/base/common/resources';
import { endsWith } from 'vs/base/common/strings';
export interface ILabelService {
_serviceBrand: undefined;
@@ -61,7 +60,7 @@ export function getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, w
}
let filename = basename(workspace.configPath);
if (endsWith(filename, WORKSPACE_EXTENSION)) {
if (filename.endsWith(WORKSPACE_EXTENSION)) {
filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1);
}
return localize('workspaceName', "{0} (Workspace)", filename);
@@ -65,7 +65,7 @@ export interface INeverShowAgainOptions {
/**
* Whether to persist the choice in the current workspace or for all workspaces. By
* default it will be persisted for all workspaces.
* default it will be persisted for all workspaces (= `NeverShowAgainScope.GLOBAL`).
*/
readonly scope?: NeverShowAgainScope;
}
@@ -28,7 +28,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
this.quickInputService.quickAccess.show(item.prefix);
this.quickInputService.quickAccess.show(item.prefix, { preserveValue: true });
}
}));
@@ -37,7 +37,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
disposables.add(picker.onDidChangeValue(value => {
const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length));
if (providerDescriptor && providerDescriptor.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) {
this.quickInputService.quickAccess.show(providerDescriptor.prefix);
this.quickInputService.quickAccess.show(providerDescriptor.prefix, { preserveValue: true });
}
}));
@@ -11,14 +11,6 @@ import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cance
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { once } from 'vs/base/common/functional';
interface IInternalQuickAccessOptions extends IQuickAccessOptions {
/**
* Internal option to not rewrite the filter value at all but use it as is.
*/
preserveFilterValue?: boolean;
}
export class QuickAccessController extends Disposable implements IQuickAccessController {
private readonly registry = Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
@@ -39,7 +31,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
super();
}
show(value = '', options?: IInternalQuickAccessOptions): void {
show(value = '', options?: IQuickAccessOptions): void {
// Find provider for the value to show
const [provider, descriptor] = this.getOrInstantiateProvider(value);
@@ -51,7 +43,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
// Apply value only if it is more specific than the prefix
// from the provider and we are not instructed to preserve
if (value !== descriptor.prefix && !options?.preserveFilterValue) {
if (value !== descriptor.prefix && !options?.preserveValue) {
visibleQuickAccess.picker.value = value;
}
@@ -62,7 +54,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
}
// Rewrite the filter value based on certain rules unless disabled
if (descriptor && !options?.preserveFilterValue) {
if (descriptor && !options?.preserveValue) {
let newValue: string | undefined = undefined;
// If we have a visible provider with a value, take it's filter value but
@@ -116,11 +108,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
picker.show();
}
private adjustValueSelection(picker: IQuickPick<IQuickPickItem>, descriptor?: IQuickAccessProviderDescriptor, options?: IInternalQuickAccessOptions): void {
private adjustValueSelection(picker: IQuickPick<IQuickPickItem>, descriptor?: IQuickAccessProviderDescriptor, options?: IQuickAccessOptions): void {
let valueSelection: [number, number];
// Preserve: just always put the cursor at the end
if (options?.preserveFilterValue) {
if (options?.preserveValue) {
valueSelection = [picker.value.length, picker.value.length];
}
@@ -147,7 +139,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
disposables.add(picker.onDidChangeValue(value => {
const [providerForValue] = this.getOrInstantiateProvider(value);
if (providerForValue !== provider) {
this.show(value, { preserveFilterValue: true } /* do not rewrite value from user typing! */);
this.show(value, { preserveValue: true } /* do not rewrite value from user typing! */);
} else {
visibleQuickAccess.value = value; // remember the value in our visible one
}
@@ -6,8 +6,7 @@
import { IQuickPick, IQuickPickItem, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Registry } from 'vs/platform/registry/common/platform';
import { first, coalesce } from 'vs/base/common/arrays';
import { startsWith } from 'vs/base/common/strings';
import { coalesce } from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ItemActivation } from 'vs/base/parts/quickinput/common/quickInput';
@@ -22,7 +21,13 @@ export interface IQuickAccessOptions {
* Allows to configure a different item activation strategy.
* By default the first item in the list will get activated.
*/
itemActivation?: ItemActivation
itemActivation?: ItemActivation;
/**
* Wether to take the input value as is and not restore it
* from any existing value if quick access is visible.
*/
preserveValue?: boolean;
}
export interface IQuickAccessController {
@@ -177,7 +182,7 @@ export class QuickAccessRegistry implements IQuickAccessRegistry {
}
getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined {
const result = prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined;
const result = prefix ? (this.providers.find(provider => prefix.startsWith(provider.prefix)) || undefined) : undefined;
return result || this.defaultProvider;
}
@@ -13,7 +13,6 @@ import { IStorage, Storage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateR
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async';
import { serializableToMap, mapToSerializable } from 'vs/base/common/map';
import { VSBuffer } from 'vs/base/common/buffer';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
@@ -291,7 +290,7 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase
this.ensureWatching(); // now that the file must exist, ensure we watch it for changes
return serializableToMap(JSON.parse(itemsRaw.value.toString()));
return new Map(JSON.parse(itemsRaw.value.toString()));
}
async updateItems(request: IUpdateRequest): Promise<void> {
@@ -311,7 +310,7 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase
try {
this._hasPendingUpdate = true;
await this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(mapToSerializable(items))));
await this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(Array.from(items.entries()))));
this.ensureWatching(); // now that the file must exist, ensure we watch it for changes
} finally {
+9 -7
View File
@@ -7,7 +7,6 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event, Emitter } from 'vs/base/common/event';
import { IStorageChangeEvent, IStorageMainService } from 'vs/platform/storage/node/storageMainService';
import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage';
import { mapToSerializable, serializableToMap, values } from 'vs/base/common/map';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
@@ -117,7 +116,10 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
}
});
return { changed: mapToSerializable(changed), deleted: values(deleted) };
return {
changed: Array.from(changed.entries()),
deleted: Array.from(deleted.values())
};
}
listen(_: unknown, event: string): Event<any> {
@@ -136,7 +138,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
// handle call
switch (command) {
case 'getItems': {
return mapToSerializable(this.storageMainService.items);
return Array.from(this.storageMainService.items.entries());
}
case 'updateItems': {
@@ -182,7 +184,7 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS
private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void {
if (Array.isArray(e.changed) || Array.isArray(e.deleted)) {
this._onDidChangeItemsExternal.fire({
changed: e.changed ? serializableToMap(e.changed) : undefined,
changed: e.changed ? new Map(e.changed) : undefined,
deleted: e.deleted ? new Set<string>(e.deleted) : undefined
});
}
@@ -191,18 +193,18 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS
async getItems(): Promise<Map<string, string>> {
const items: Item[] = await this.channel.call('getItems');
return serializableToMap(items);
return new Map(items);
}
updateItems(request: IUpdateRequest): Promise<void> {
const serializableRequest: ISerializableUpdateRequest = Object.create(null);
if (request.insert) {
serializableRequest.insert = mapToSerializable(request.insert);
serializableRequest.insert = Array.from(request.insert.entries());
}
if (request.delete) {
serializableRequest.delete = values(request.delete);
serializableRequest.delete = Array.from(request.delete.values());
}
return this.channel.call('updateItems', serializableRequest);
@@ -9,6 +9,7 @@ import * as uuid from 'vs/base/common/uuid';
import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils';
import { mixin } from 'vs/base/common/objects';
import { firstSessionDateStorageKey, lastSessionDateStorageKey, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
import product from 'vs/platform/product/common/product'; // {{SQL CARBON EDIT}}
export async function resolveWorkbenchCommonProperties(
storageService: IStorageService,
@@ -54,6 +55,7 @@ export async function resolveWorkbenchCommonProperties(
result['common.product'] = 'web';
// __GDPR__COMMON__ "common.userAgent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.userAgent'] = Platform.userAgent;
result['quality'] = product.quality || 'dev'; // {{SQL CARBON EDIT}} Add quality
// dynamic properties which value differs on each call
let seq = 0;

Some files were not shown because too many files have changed in this diff Show More